Module leec

Main module of LEEC, the Ceylan Let's Encrypt Erlang fork; see http://leec.esperide.org for more information.

Behaviours: application, gen_statem.

Description

Main module of LEEC, the Ceylan Let's Encrypt Erlang fork; see http://leec.esperide.org for more information.

Original 'Let's Encrypt Erlang' application: https://github.com/gbour/letsencrypt-erlang.

Data Types

acme_operation()

acme_operation() = bin_string()

ACME operations that may be triggered.

Known operations: - <<"newAccount">> - <<"newNonce">> - <<"newOrder">> - <<"revokeCert">>

agent_key_file_info()

agent_key_file_info() = {new, bin_file_path()} | bin_file_path()

Information regarding the private key of the LEEC agent.

(if 'new' is used, the path is supposed to be either absolute, or relative to the certificate directory)

any_directory_path()

any_directory_path() = file_utils:any_directory_path()

any_file_path()

any_file_path() = file_utils:any_file_path()

any_san()

any_san() = san() | bin_san()

application_name()

application_name() = otp_utils:application_name()

bin_certificate()

bin_certificate() = binary()

A certificate, as a binary.

bin_challenge_type()

bin_challenge_type() = bin_string()

Challenge type, as a binary string.

bin_csr_key()

bin_csr_key() = bin_key()

A CSR key, as a binary.

bin_domain()

bin_domain() = net_utils:bin_fqdn()

bin_file_path()

bin_file_path() = file_utils:bin_file_path()

bin_key()

bin_key() = binary()

A key, as a binary.

bin_san()

bin_san() = bin_string()

bin_string()

bin_string() = text_utils:bin_string()

bin_uri()

bin_uri() = web_utils:bin_uri()

binary_b64()

binary_b64() = binary()

A binary that is encoded in base 64.

bridge_spec()

bridge_spec() = trace_bridge:bridge_spec()

cert_file_path()

cert_file_path() = pem_file_path()

A PEM-encoded file containing the actual certificate of interest.

Typical names for such files may be "fullchain.pem", "cert.pem" or "MYDOMAIN.crt".

cert_priv_key_file_path()

cert_priv_key_file_path() = pem_file_path()

A PEM-encoded file containing the private key corresponding to a certificate, as securely sent back by an ACME server.

Such a key must be strongly secured.

Typical names for such files may be "privkey.pem" or "MYDOMAIN.key".

cert_req_option_id()

cert_req_option_id() = async | callback | netopts | challenge_type | sans | json

Certificate request options.

cert_req_option_map()

cert_req_option_map() = table(cert_req_option_id(), term())

Storing certificate request options.

Known (atom) keys:

- options common for all certificate requests:

- async :: boolean(): - if true (the default), immediately returns, and a callback will be triggered once the certificate is obtained - if false, blocks until completed, and returns the path to the generated certificate

- callback :: creation_callback() -> void(): the function executed when Async is true, once the domain certificate has been successfully obtained

- options for http-01 certificate requests: - netopts :: map() => #{ timeout => milliseconds(), ssl => [ssl:client_option()] }: to specify an HTTP timeout or SSL client options

- sans :: [any_san()]: a list of the Subject Alternative Names for that certificate (if single-domain, not wildcard)

- options for dns-01 certificate requests:

- email: email address used for registration and recovery contact (otherwise specifying leec-certificates@DOMAIN)

- not to be set by the user:

- json :: boolean()

- challenge_type :: challenge_type() is the type of challenge to rely on when interacting with the ACME server

certificate_provider()

certificate_provider() = letsencrypt

These are CA (Certificate Authorities).

Other certificate providers (e.g. ZeroSSL) may be added in the future.

certificate_type()

certificate_type() = single_domain | wildcard_domain

challenge()

challenge() = table(bin_string(), bin_string())

All known information regarding a challenge.

As Key => example of associated value:

- <<"status">> => <<"pending">>

- <<"token">> => <<"qVTx6gQWZO4Dt4gUmnaTQdwTRkpaSnMiRx8L7Grzhl8">>

- <<"type">> => <<"http-01">>

- <<"url">> => <<"ACME_BASE/acme/chall-v3/132509381/-Axkdw">>

challenge_type()

challenge_type() = 'http-01' | 'dns-01' | 'tls-sni-01'

creation_callback()

creation_callback() = fun((obtain_outcome()) -> void())

A function executed when a domain certificate has been successfully obtained asynchronously.

creation_outcome()

creation_outcome() = {certificate_ready, cert_file_path(), cert_priv_key_file_path()} | tagged_error()

That is: {'error', term()}

credentials_path()

credentials_path() = any_file_path()

A path to a file containing LEEC-related credentials. See https://leec.esperide.org/#credentials-file.

directory_map()

directory_map() = table(acme_operation(), bin_uri())

ACME directory, converting operations to trigger into the URIs to access for them.

dns_provider()

dns_provider() = ovh

The known and supported DNS providers.

domain_name()

domain_name() = net_utils:string_fqdn() | bin_domain()

environment()

environment() = staging | production

The ACME environments.

Production is 'default' in ACME parlance.

error_reason()

error_reason() = basic_utils:error_reason()

event_content()

event_content() = term()

event_type()

event_type() = gen_statem:event_type()

file_name()

file_name() = file_utils:file_name()

fsm_pid()

fsm_pid() = pid()

The PID of a LEEC FSM.

json()

json() = json_utils:json()

json_map_decoded()

json_map_decoded() = map()

JSON element decoded as a map.

jws()

jws() = #jws{alg = leec:jws_algorithm(), url = maybe(leec:bin_uri()), kid = maybe(leec:bin_uri()), jwk = maybe(leec:tls_public_key()), nonce = maybe(leec:nonce())}

jws_algorithm()

jws_algorithm() = 'RS256'

key_auth()

key_auth() = binary()

key_integer()

key_integer() = integer() | binary()

Element of a key.

leec_caller_state()

leec_caller_state() = {challenge_type(), fsm_pid()}

The minimal LEEC state returned to the caller.

Not to be mixed up with the internal state of LEEC FSMs.

leec_dns_state()

leec_dns_state() = #leec_dns_state{environment = leec:environment(), state_dir_path = file_utils:bin_directory_path(), work_dir_path = file_utils:bin_directory_path(), certbot_path = file_utils:bin_executable_path(), credentials_dir_path = file_utils:bin_directory_path(), cert_dir_path = file_utils:bin_directory_path(), cert_key_file = maybe(file_utils:bin_file_path()), domain = maybe(net_utils:bin_fqdn())}

LEEC state for the dns-01 challenge.

Needed by other LEEC modules.

leec_http_state()

leec_http_state() = #leec_http_state{environment = leec:environment(), directory_map = maybe(leec:directory_map()), cert_dir_path = file_utils:bin_directory_path(), cert_key_file = maybe(file_utils:bin_file_path()), cert_priv_key_path = maybe(leec:cert_priv_key_file_path()), interfacing_mode = maybe(leec:web_interfacing_mode()), webroot_dir_path = maybe(file_utils:bin_directory_path()), port = net_utils:tcp_port(), nonce = maybe(leec:nonce()), domain = maybe(net_utils:bin_fqdn()), sans = [leec:san()], agent_key_file_info = maybe(leec:agent_key_file_info()), agent_private_key = maybe(leec:tls_private_key()), jws = maybe(leec:jws()), account_key = maybe(leec:tls_public_key()), order = maybe(leec:order_map()), challenges = leec:uri_challenge_map(), cert_req_option_map = leec:cert_req_option_map(), json_parser_state = json_utils:parser_state(), tcp_connection_cache = leec:tcp_connection_cache()}

LEEC state for the http-01 challenge.

Needed by other LEEC modules.

leec_state()

leec_state() = leec_http_state() | leec_dns_state()

Any type of LEEC state.

maybe()

maybe(T) = basic_utils:maybe(T)

milliseconds()

milliseconds() = unit_utils:milliseconds()

nonce()

nonce() = binary()

An arbitrary binary that can be used just once in a cryptographic communication.

obtain_outcome()

obtain_outcome() = async | obtained_outcome()

Returned value once requesting a certificate.

obtained_outcome()

obtained_outcome() = {certificate_generation_success, cert_file_path(), cert_priv_key_file_path()} | {certificate_generation_failure, error_reason()}

Returned user-targeted value (either as a message or as the argument of a callback) after having sent a request to obtain a certificate.

Defined to differentiate from creation_outcome() and gather all possible error terms.

order_map()

order_map() = table(bin_string(), bin_uri())

pem_file_path()

pem_file_path() = bin_file_path()

A path to a PEM-encoded ("Privacy Enhanced Mail", a rather misleading naming) file may contain just a public certificate, or an entire certificate chain including the public key, private key, and root certificates (which is preferrable), or even just a certificate request.

This is a text file containing sections like "-----BEGIN CERTIFICATE-----", "-----BEGIN PRIVATE KEY-----", etc.

Its extension may be ".pem".

request()

request() = atom()

rsa_private_key()

rsa_private_key() = [key_integer()]

Description of a RSA private key.

(as crypto:rsa_private() is not exported)

san()

san() = ustring()

Subject Alternative Name, i.e. values to be associated with a security certificate using a subjectAltName field.

See https://en.wikipedia.org/wiki/Subject_Alternative_Name.

start_common_option()

start_common_option() = {environment, environment()} | {work_dir_path, any_directory_path()} | {agent_key_file_path, any_file_path()} | {cert_dir_path, any_directory_path()} | {http_timeout, milliseconds()}

start_dns_01_option()

start_dns_01_option() = {dns_provider, dns_provider()} | {cred_dir_path, any_directory_path()}

start_http_01_option()

start_http_01_option() = {interfacing_mode, web_interfacing_mode()} | {webroot_dir_path, any_directory_path()} | {port, tcp_port()}

Options associated to the http-01 challenge, for single-domain certificates.

start_option()

start_option() = start_common_option() | start_http_01_option() | start_dns_01_option()

start_outcome()

start_outcome() = {ok, leec_caller_state()} | basic_utils:tagged_error()

'ok' | gen_statem:start_ret().

state_callback_result()

state_callback_result() = fsm_utils:state_callback_result(gen_statem:action())

status()

status() = pending | processing | valid | invalid | revoked

LEEC FSM status (corresponding to state names).

table()

table(K, V) = map_hashtable:map_hashtable(K, V)

tagged_error()

tagged_error() = basic_utils:tagged_error()

tcp_connection_cache()

tcp_connection_cache() = table({web_utils:protocol_type(), net_utils:string_host_name(), tcp_port()}, shotgun:connection())

For the reuse of TCP connections to the ACME server.

tcp_port()

tcp_port() = net_utils:tcp_port()

thumbprint()

thumbprint() = json()

A JSON-encoded key.

thumbprint_map()

thumbprint_map() = table(token(), thumbprint())

Associating tokens with keys.

tls_csr()

tls_csr() = binary_b64()

tls_private_key()

tls_private_key() = #tls_private_key{raw = leec:rsa_private_key(), b64_pair = {leec:binary_b64(), leec:binary_b64()}, file_path = file_utils:bin_file_path()}

The TLS private key (locally generated and never sent) of the target certificate.

tls_public_key()

tls_public_key() = #tls_public_key{kty = 'RSA', n = text_utils:bin_string(), e = text_utils:bin_string()}

The TLS public key (locally generated) of the target certificate.

token()

token() = ustring()

type_challenge_map()

type_challenge_map() = table(challenge_type(), challenge())

uri_challenge_map()

uri_challenge_map() = table(bin_uri(), challenge())

ustring()

ustring() = text_utils:ustring()

void()

void() = basic_utils:void()

web_interfacing_mode()

web_interfacing_mode() = webroot | slave | standalone

Function Index

callback_mode/0Tells about the retained mode regarding callback.
caller_state_to_string/1Returns a textual description of the specified LEEC caller state.
can_perform_dns_challenges/0Tells whether LEEC has a chance to run successfully a dns-01 challenge.
code_change/4Standard "code change" callback.
dns_provider_to_string/1Returns a textual representation of the specified DNS provider.
finalize/3Manages the 'finalize' state.
get_agent_key_path/1Returns the (absolute, binary) path of the current private key of the LEEC agent.
get_certificate_priv_key_filename/1Returns the filename of the private key of the certificate for the specified domain.
get_credentials_path_for/3Returns a file path corresponding to the specified domain name managed by the specified DNS provider, in the specified credentials directory, made based on the proposed LEEC conventions.
get_default_cert_request_options/1Returns the default options for certificate requests for the specified challenge type, here enabling the async (non-blocking) mode.
get_default_cert_request_options/2Returns the default optionsfor certificate requests, with specified async mode.
get_ongoing_challenges/1Returns the ongoing challenges with pre-computed thumbprints.
get_ordered_prerequisites/0Returns an (ordered) list of the LEEC prerequisite OTP applications, to be started in that order.
idle/3Manages the 'idle' state, the initial state, typically used when awaiting for certificate requests to be triggered.
init/1Initializes the LEEC state machine.
invalid/3Manages the 'invalid' state.
is_known_challenge_type/1Tells whether the specified atom is a known challenge type.
is_supported_dns_provider/1Tells whether LEEC supports the specified DNS provider.
maybe_caller_state_to_string/1Returns a textual description of the specified maybe-LEEC caller state.
obtain_certificate_for/2Generates, once started, asynchronously (in a non-blocking manner), a new certificate for the specified domain (FQDN).
obtain_certificate_for/3Generates, once started, synchronously (in a blocking manner) or not, a new certificate for the specified domain (FQDN).
pending/3Manages the 'pending' state, when challenges are on-the-go, that is being processed with the ACME server.
reset_state/2Resets the LEEC state, typically prior to starting any (first) LEEC instance.
send_ongoing_challenges/2Sends the ongoing challenges to the specified process.
start/2Starts a (non-bridged) instance of the LEEC service FSM, meant to rely on the specified type of challenge.
start/3Starts an instance of the LEEC service FSM, possibly with a trace bridge, meant to rely on the specified type of challenge.
state_to_string/1Returns a textual description of the specified LEEC (internal) state.
stop/1Stops the specified instance of LEEC service; switches it to idle (does not terminate it for good).
terminate/1Terminates the specified instance of LEEC service: stops it properly, and terminates the corresponding FSM process.
terminate/3Standard termination callback.
valid/3Manages the 'valid' state.

Function Details

callback_mode/0

callback_mode() -> fsm_utils:callback_mode_ret()

Tells about the retained mode regarding callback. Here, one callback function per state, akin to gen_fsm.

caller_state_to_string/1

caller_state_to_string(LEECCallerState::leec_caller_state()) -> ustring()

Returns a textual description of the specified LEEC caller state.

can_perform_dns_challenges/0

can_perform_dns_challenges() -> boolean()

Tells whether LEEC has a chance to run successfully a dns-01 challenge.

code_change/4

code_change(X1, StateName, LHState, X4) -> any()

Standard "code change" callback.

dns_provider_to_string/1

dns_provider_to_string(DNSProvider::dns_provider()) -> ustring()

Returns a textual representation of the specified DNS provider.

finalize/3

finalize(EventType, PreviousState, Data) -> any()

Manages the 'finalize' state.

When order is being finalized, and certificate generation is ongoing.

Waits for certificate generation being complete (order status becoming 'valid').

Returns the order status.

Transitions to: state 'processing': still ongoing state 'valid' : certificate is ready

get_agent_key_path/1

get_agent_key_path(CallerState::leec_caller_state()) -> error | maybe(bin_file_path())

Returns the (absolute, binary) path of the current private key of the LEEC agent.

Useful so that the same key can be used for multiple ACME orders (possibly in parallel) rather than multiplying the keys.

(exported API helper)

get_certificate_priv_key_filename/1

get_certificate_priv_key_filename(DomainName::domain_name()) -> file_name()

Returns the filename of the private key of the certificate for the specified domain.

get_credentials_path_for/3

get_credentials_path_for(DNSProvider::dns_provider(), DomainName::domain_name(), AnyCredBasePath::any_directory_path()) -> credentials_path()

Returns a file path corresponding to the specified domain name managed by the specified DNS provider, in the specified credentials directory, made based on the proposed LEEC conventions.

get_default_cert_request_options/1

get_default_cert_request_options(ChallengeType::challenge_type()) -> cert_req_option_map()

Returns the default options for certificate requests for the specified challenge type, here enabling the async (non-blocking) mode.

get_default_cert_request_options/2

get_default_cert_request_options(ChallengeType::challenge_type(), Async::boolean()) -> cert_req_option_map()

Returns the default optionsfor certificate requests, with specified async mode.

get_ongoing_challenges/1

get_ongoing_challenges(FsmPid::fsm_pid()) -> error | no_challenge | thumbprint_map()

Returns the ongoing challenges with pre-computed thumbprints.

Returns #{Challenge => Thumbrint} if ok, 'error' if fails.

(exported API helper)

get_ordered_prerequisites/0

get_ordered_prerequisites() -> [application_name()]

Returns an (ordered) list of the LEEC prerequisite OTP applications, to be started in that order.

Notes:

- not listed here (not relevant for that use case): elli, getopt, yamerl, erlang_color

- jsx preferred over jiffy; yet neither needs to be initialised as an application

- no need to start Myriad either (library application)

idle/3

idle(EventType::event_type(), PreviousState::event_content(), Data::leec_http_state()) -> state_callback_result()

Manages the 'idle' state, the initial state, typically used when awaiting for certificate requests to be triggered.

idle(get_ongoing_challenges | send_ongoing_challenges): nothing done

init/1

init(X1::{challenge_type(), [start_option()], json_utils:parser_state(), maybe(bridge_spec())}) -> {ok, InitialStateName::idle, InitialData::leec_http_state()}

Initializes the LEEC state machine.

Parameters:

- init TLS private key and its JWS

- fetch ACME directory

- get valid nonce

Will make use of any trace bridge transmitted.

Transitions to the 'idle' initial state.

invalid/3

invalid(EventType, PreviousState, Data) -> any()

Manages the 'invalid' state.

When order is being finalized, and certificate generation is ongoing.

Waits for certificate generation being complete (order status == 'valid').

Returns the order status.

Transitions to: state 'processing': still ongoing state 'valid' : certificate is ready

is_known_challenge_type/1

is_known_challenge_type(ChalType::atom()) -> boolean()

Tells whether the specified atom is a known challenge type.

Does not guarantee that this LEEC instance is able to handle it.

is_supported_dns_provider/1

is_supported_dns_provider(DNSProvider::atom()) -> boolean()

Tells whether LEEC supports the specified DNS provider.

It is a necessary yet not sufficient condition (e.g. proper provider-specific credentials will be needed as well).

maybe_caller_state_to_string/1

maybe_caller_state_to_string(MaybeLEECCallerState::maybe(leec_caller_state())) -> ustring()

Returns a textual description of the specified maybe-LEEC caller state.

obtain_certificate_for/2

obtain_certificate_for(Domain::domain_name(), LeecCallerState::leec_caller_state()) -> obtain_outcome()

Generates, once started, asynchronously (in a non-blocking manner), a new certificate for the specified domain (FQDN).

Parameters: - Domain is the domain name to generate an ACME certificate for - LeecCallerState is the caller state obtained when starting LEEC

See obtain_certificate_for/3 for the return type.

Belongs to the user-facing API; requires the LEEC service to be already started.

obtain_certificate_for/3

obtain_certificate_for(Domain::domain_name(), LeecCallerState::leec_caller_state(), CertReqOptionMap::cert_req_option_map()) -> obtain_outcome()

Generates, once started, synchronously (in a blocking manner) or not, a new certificate for the specified domain (FQDN).

Parameters: - Domain is the domain name to generate an ACME certificate for - LeecCallerState is the caller state obtained when starting LEEC - CertReqOptionMap is a map listing the options applying to this certificate request, whose key (as atom) / value pairs may depend on the challenge type

Belongs to the user-facing API; requires the LEEC service to be already started.

pending/3

pending(EventType, PreviousState, Data) -> any()

Manages the 'pending' state, when challenges are on-the-go, that is being processed with the ACME server.

reset_state/2

reset_state(ChallengeType::challenge_type(), AnyCertDir::any_directory_path()) -> boolean()

Resets the LEEC state, typically prior to starting any (first) LEEC instance.

This may be useful for example with a certbot-based dns-01 challenge, in order to wipe out the state of certbot to ensure that new certificates are obtained (as opposed to former ones being reused, whereas their expiration timestamp will not be specifically read).

Otherwise tries to preserve state (e.g. any former certificate obtained through http-01), as during the (potentially lengthy) renewal process, no functional certificate (hence HTTPS access) would exist.

Returns whether a state deletion was done.

Not integrated to start/{2,3} as multiple LEEC instances can be used, and one should not interfere with the others, through their common state (e.g. regarding the one of certbot). Also, this may be useful only at the first LEEC initialisation, not for the next automatic renewals.

send_ongoing_challenges/2

send_ongoing_challenges(LCS::leec_caller_state(), TargetPid::pid()) -> void()

Sends the ongoing challenges to the specified process.

Typically useful in a slave interfacing mode, when the web handler cannot access directly the PID of the LEEC FSM: this code is then called by a third-party process (e.g. a certificate manager one, statically known of the web handler, and triggered by it), and returns the requested challenged to the specified target PID (most probably the one of the web handler itself).

(exported API helper)

start/2

start(ChallengeType::challenge_type(), StartOptions::[start_option()]) -> start_outcome()

Starts a (non-bridged) instance of the LEEC service FSM, meant to rely on the specified type of challenge.

See start/3 for more details.

start/3

start(ChallengeType::challenge_type(), StartOptions::[start_option()], MaybeBridgeSpec::maybe(bridge_spec())) -> start_outcome()

Starts an instance of the LEEC service FSM, possibly with a trace bridge, meant to rely on the specified type of challenge.

Note that for most challenges to succeed, LEEC must be started from the domain of interest, as a webserver there must be controlled (for the http-01 challenge) or its DNS zone must be updated, generally from one of the authorised IP addresses (for the dns-01 challenge).

Note also that some challenges (especially the dns-01 one) will take significant time to succeed (typically as a few minutes will have to be waited for DNS changes to propagate).

state_to_string/1

state_to_string(LHS::leec_state()) -> ustring()

Returns a textual description of the specified LEEC (internal) state.

stop/1

stop(LCS::leec_caller_state()) -> void()

Stops the specified instance of LEEC service; switches it to idle (does not terminate it for good).

terminate/1

terminate(LEECCallerState::leec_caller_state()) -> void()

Terminates the specified instance of LEEC service: stops it properly, and terminates the corresponding FSM process.

Not to be mixed up with the terminate/3 function known as a gen_statem callback.

terminate/3

terminate(Reason, State, Data) -> any()

Standard termination callback.

valid/3

valid(EventType, PreviousState, Data) -> any()

Manages the 'valid' state.

When challenges have been successfully completed, finalizes the ACME order and generates the TLS certificate.

Returns Status, the order status.

Transitions to 'finalize' state.


Generated by EDoc