Build Status

letsencrypt-erlang

Let's Encrypt client library for Erlang

Overview

Features:

Modes

Validation challenges

Prerequisites

Building

$> ./rebar3 update
$> ./rebar3 compile

Quickstart

You must execute this example on the server targeted by mydomain.tld. Port 80 (http) must be opened and a webserver listening on it (line 1) and serving /path/to/webroot/ content.
Both /path/to/webroot and /path/to/certs MUST be writtable by the erlang process

$> $(cd /path/to/webroot && python -m SimpleHTTPServer 80)&
$> ./rebar3 shell
$erl> application:ensure_all_started(letsencrypt).
$erl> letsencrypt:start([{mode,webroot},{webroot_path,"/path/to/webroot"},{cert_path,"/path/to/certs"}]).
$erl> letsencrypt:make_cert(<<"mydomain.tld">>, #{async => false}).
{ok, #{cert => <<"/path/to/certs/mydomain.tld.crt">>, key => <<"/path/to/certs/mydomain.tld.key">>}}
$erl> ^C
$> ls -1 /path/to/certs
letsencrypt.key
mydomain.tld.crt
mydomain.tld.csr
mydomain.tld.key

Explanations:

During the certification process, letsencrypt server returns a challenge and then tries to query the challenge file from the domain name asked to be certified. So letsencrypt-erlang is writing challenge file under /path/to/webroot directory. Finally, keys and certificates are written in /path/to/certs directory.

API

NOTE: if optional is not written, parameter is required

Action modes

webroot

When you're running a webserver (ie apache or nginx) listening on public http port.

on_complete({State, Data}) ->
io:format("letsencrypt certicate issued: ~p (data: ~p)~n", [State, Data]),
case State of
ok ->
io:format("reloading nginx...~n"),
os:cmd("sudo systemctl reload nginx");
_ -> pass
end.
main() ->
letsencrypt:start([{mode,webroot}, staging, {cert_path,"/path/to/certs"}, {webroot_path, "/var/www/html"]),
letsencrypt:make_cert(<<"mydomain.tld">>, #{callback => fun on_complete/1}),
ok.

### slave

When your erlang application is already running a cowboy server listening on public http port.

on_complete({State, Data}) ->
io:format("letsencrypt certificate issued: ~p (data: ~p)~n", [State, Data]).
main() ->
Dispatch = cowboy_router:compile([
{'_', [
{<<"/.well-known/acme-challenge/:token">>, letsencrypt_cowboy_handler, []}
]}
]),
{ok, _} = cowboy:start_http(my_http_listener, 1, [{port, 80}],
[{env, [{dispatch, Dispatch}]}]
),
letsencrypt:start([{mode,slave}, staging, {cert_path,"/path/to/certs"}]),
letsencrypt:make_cert(<<"mydomain.tld">>, #{callback => fun on_complete/1}),
ok.

standalone

When you have no live http server running on your server.

letsencrypt-erlang will start its own webserver just enough time to validate the challenge, then will stop it immediately after that.

on_complete({State, Data}) ->
io:format("letsencrypt certificate issued: ~p (data: ~p)~n", [State, Data]).
main() ->
letsencrypt:start([{mode,standalone}, staging, {cert_path,"/path/to/certs"}, {port, 80)]),
letsencrypt:make_cert(<<"mydomain.tld">>, #{callback => fun on_complete/1}),
ok.

You can choose to use 'tls-sni-01' challenge instead of default 'http-01'. Thus connection from letsencrypt server will be made on https port (443)

on_complete({State, Data}) ->
io:format("letsencrypt certificate issued: ~p (data: ~p)~n", [State, Data]).
main() ->
letsencrypt:start([{mode,standalone}, staging, {cert_path,"/path/to/certs"}, {port, 443)]),
letsencrypt:make_cert(<<"mydomain.tld">>, #{challenge => 'tls-sni-01', callback => fun on_complete/1}),
ok.

License

letsencrypt-erlang is distributed under APACHE 2.0 license.