Certbot

Build StatusHex pmHex DocsLicense


Provide certificates for your Phoenix or Plug app using Letsencrypt.

This package should for now be considered a POC. Not everything is implemented at the moment, most notably, certificate renewal.

You can also set your own Certificate Provider for your own functionality, or to provide different certificates for different hostnames.

Installation

The package can be installed by adding certbot to your list of dependencies in mix.exs:

def deps do
  [
    {:certbot, "~> 0.5.0"}
  ]
end

Setting up Letsencrypt with Phoenix

From then on there are a few steps, we need to setup a certbot client, a store for the certificates and a store for Acme challenges. Furthermore we need to setup Certbot.Acme.Plug to verify Acme challenges over http.

Certbot client

First, a certbot client is needed. We use a self generated private key to build into a JWK. If you have a Phoenix project, this can be generated with mix phx.gen.cert

Furthermore, we set an AcmeCertificateProvider

defmodule Myapp.CertbotClient do
  @jwk "priv/cert/selfsigned_key.pem"
       |> File.read!()
       |> JOSE.JWK.from_pem()
       |> JOSE.JWK.to_map()

  use Certbot,
    certificate_provider: Myapp.AcmeCertificateProvider,
    jwk: @jwk,
    email: "mailto:test@example.com"
end

defmodule Myapp.AcmeCertificateProvider do
  use Certbot.Provider.Acme,
    challenge_store: Certbot.Acme.ChallengeStore.Default,
    certificate_store: Certbot.CertificateStore.Default,
    acme_client: Myapp.CertbotClient
end

The Myapp.CertbotClient doubles as an Acme client, and therefore needs to be added to the supervision tree of your application. We use, the default challenge/certificate stores of the package, they also need to be added your application supervision tree. Note, there are downsides to the stores, see their docs for more info.

Your supervision tree will look something like this in a Phoenix project

# application.ex

children = [
  # Start the Ecto repository
  Myapp.Repo,
  # Start the endpoint when the application starts
  MyappWeb.Endpoint,
  Myapp.CertbotClient,
  Certbot.Acme.ChallengeStore.Default,
  Certbot.CertificateStore.Default
]

In your endpoint.ex you should add Certbot.Acme.Plug, with the same challenge store and jwk.

It should be added before the router, and before Plug.SSL if force SSL redirects are turned on.

# endpoint.ex
@jwk "priv/cert/selfsigned_key.pem" |> File.read!() |> JOSE.JWK.from_pem() |> JOSE.JWK.to_map()

plug Certbot.Acme.Plug, challenge_store: Certbot.Acme.ChallengeStore.Default, jwk: @jwk
plug MyappWeb.Router

As a last step we need configure the https endpoint to dynamically return certificates.

config :myapp, MyappWeb.Endpoint,
  http: [port: 6000],
  https: [
    cipher_suite: :strong,
    port: 6001,
    sni_fun: &Myapp.CertbotClient.sni_fun/1 #Set the sni_fun
  ],

This tells cowboy to call sni_fun/1 with the hostname of the request. This function will ask the certificate provider for a certificate. The certificate provider will return one, or first request one from Letsencrypt and then return it.

FAQ

Is this tested in production?

Can I test this against a non-production acme server

Does it do certificate renewal?

How can I test this locally?

Are multiple account keys supported?

How are multiple concurrent requests handled with certification requests?

What happens if I request too many certificates?

I am debugging but don't see errors appearing?

What version of the Acme protocol is used?

Are alternative challenge methods (dns-01, tls-sni-01,tls-alpn-01)?

Errors

[error] %Certbot.Error{detail: "JWS has invalid anti-replay nonce twT0up7DWSrbe163DiRuKnPwd4ZpyXVER0p-COl1vAA", status: 400, type: "urn:acme:error:badNonce"}

[error] Certbot.Acme.Client Certbot.Acme.Client received unexpected message in handle_info/2: {:ssl_closed, {:sslsocket, {:gen_tcp, #Port<0.76>, :tls_connection, :undefined}, [#PID<0.851.0>, #PID<0.850.0>]}}

Documentation

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/certbot.