ACME: improve documentation (#44691)

* Override description for account_key_src and account_key_content to also mention private_key_*.

* Convert generic OpenSSL/cryptography remark from description to note.

This avoids the whole description list to be sorted alphabetically, which will be done by plugin_docs.py in case description is mentioned in both module fragment and module itself.

* Moving more notes to the notes: section.

* Uniformization of first paragraph. Mainly mention ACME supporting CAs, and only then mention Let's Encrypt as one of them.

* Adjusting to current drafts.

* Adjusting to updated drafts.

* Harmonizing short module descriptions.

* Referencing helper modules.

* Move general Let's Encrypt remark to doc fragment.

* Changing some Let's Encrypt references to more generic statements.
This commit is contained in:
Felix Fontein 2018-08-26 22:46:55 +02:00 committed by René Moser
parent 5fecf8baab
commit fadf8a2d09
6 changed files with 86 additions and 56 deletions

View file

@ -466,7 +466,7 @@ class ACMEDirectory(object):
and allows to obtain a Replay-Nonce. The acme_directory URL and allows to obtain a Replay-Nonce. The acme_directory URL
needs to support unauthenticated GET requests; ACME endpoints needs to support unauthenticated GET requests; ACME endpoints
requiring authentication are not supported. requiring authentication are not supported.
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.1 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.1.1
''' '''
def __init__(self, module): def __init__(self, module):
@ -536,7 +536,7 @@ class ACMEAccount(object):
def get_keyauthorization(self, token): def get_keyauthorization(self, token):
''' '''
Returns the key authorization for the given token Returns the key authorization for the given token
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.1 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-8.1
''' '''
accountkey_json = json.dumps(self.jwk, sort_keys=True, separators=(',', ':')) accountkey_json = json.dumps(self.jwk, sort_keys=True, separators=(',', ':'))
thumbprint = nopad_b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) thumbprint = nopad_b64(hashlib.sha256(accountkey_json.encode('utf8')).digest())
@ -570,7 +570,7 @@ class ACMEAccount(object):
''' '''
Sends a JWS signed HTTP POST request to the ACME server and returns Sends a JWS signed HTTP POST request to the ACME server and returns
the response as dictionary the response as dictionary
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-6.2 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-6.2
''' '''
key_data = key_data or self.key_data key_data = key_data or self.key_data
jws_header = jws_header or self.jws_header jws_header = jws_header or self.jws_header
@ -601,7 +601,7 @@ class ACMEAccount(object):
try: try:
result = self.module.from_json(content.decode('utf8')) result = self.module.from_json(content.decode('utf8'))
# In case of badNonce error, try again (up to 5 times) # In case of badNonce error, try again (up to 5 times)
# (https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-6.6) # (https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-6.6)
if (400 <= info['status'] < 600 and if (400 <= info['status'] < 600 and
result.get('type') == 'urn:ietf:params:acme:error:badNonce' and result.get('type') == 'urn:ietf:params:acme:error:badNonce' and
failed_tries <= 5): failed_tries <= 5):
@ -629,7 +629,7 @@ class ACMEAccount(object):
Registers a new ACME account. Returns True if the account was Registers a new ACME account. Returns True if the account was
created and False if it already existed (e.g. it was not newly created and False if it already existed (e.g. it was not newly
created). created).
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.3 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.3
''' '''
contact = [] if contact is None else contact contact = [] if contact is None else contact
@ -711,7 +711,7 @@ class ACMEAccount(object):
will be stored in self.uri; if it is None, the account does not will be stored in self.uri; if it is None, the account does not
exist. exist.
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.3 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.3
''' '''
new_account = True new_account = True

View file

@ -18,16 +18,19 @@ DOCUMENTATION = '''
module: acme_account module: acme_account
author: "Felix Fontein (@felixfontein)" author: "Felix Fontein (@felixfontein)"
version_added: "2.6" version_added: "2.6"
short_description: Create, modify or delete accounts with Let's Encrypt short_description: Create, modify or delete ACME accounts
description: description:
- "Allows to create, modify or delete accounts with Let's Encrypt. - "Allows to create, modify or delete accounts with a CA supporting the
Let's Encrypt is a free, automated, and open certificate authority L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-14),
(CA), run for the public's benefit. For details see U(https://letsencrypt.org)." such as L(Let's Encrypt,https://letsencrypt.org/)."
- "This module only works with the ACME v2 protocol."
notes:
- "Facts about an ACME account can be retrieved with the M(acme_account_facts)
module."
- "The M(acme_certificate) module also allows to do basic account management. - "The M(acme_certificate) module also allows to do basic account management.
When using both modules, it is recommended to disable account management When using both modules, it is recommended to disable account management
for M(acme_certificate). For that, use the C(modify_account) option of for M(acme_certificate). For that, use the C(modify_account) option of
M(acme_certificate)." M(acme_certificate)."
- "This module only works with the ACME v2 protocol."
extends_documentation_fragment: extends_documentation_fragment:
- acme - acme
options: options:
@ -52,7 +55,7 @@ options:
description: description:
- "A list of contact URLs." - "A list of contact URLs."
- "Email addresses must be prefixed with C(mailto:)." - "Email addresses must be prefixed with C(mailto:)."
- "See https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.2 - "See https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.1.2
for what is allowed." for what is allowed."
- "Must be specified when state is C(present). Will be ignored - "Must be specified when state is C(present). Will be ignored
if state is C(absent) or C(changed_key)." if state is C(absent) or C(changed_key)."
@ -65,14 +68,13 @@ options:
type: bool type: bool
new_account_key_src: new_account_key_src:
description: description:
- "Path to a file containing the Let's Encrypt account RSA or Elliptic Curve - "Path to a file containing the ACME account RSA or Elliptic Curve key to change to."
key to change to."
- "Same restrictions apply as to C(account_key_src)." - "Same restrictions apply as to C(account_key_src)."
- "Mutually exclusive with C(new_account_key_content)." - "Mutually exclusive with C(new_account_key_content)."
- "Required if C(new_account_key_content) is not used and state is C(changed_key)." - "Required if C(new_account_key_content) is not used and state is C(changed_key)."
new_account_key_content: new_account_key_content:
description: description:
- "Content of the Let's Encrypt account RSA or Elliptic Curve key to change to." - "Content of the ACME account RSA or Elliptic Curve key to change to."
- "Same restrictions apply as to C(account_key_content)." - "Same restrictions apply as to C(account_key_content)."
- "Mutually exclusive with C(new_account_key_src)." - "Mutually exclusive with C(new_account_key_src)."
- "Required if C(new_account_key_src) is not used and state is C(changed_key)." - "Required if C(new_account_key_src) is not used and state is C(changed_key)."
@ -221,7 +223,7 @@ def main():
# Now we can start the account key rollover # Now we can start the account key rollover
if not module.check_mode: if not module.check_mode:
# Compose inner signed message # Compose inner signed message
# https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.3.6 # https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.3.6
url = account.directory['keyChange'] url = account.directory['keyChange']
protected = { protected = {
"alg": new_key_data['alg'], "alg": new_key_data['alg'],
@ -230,9 +232,8 @@ def main():
} }
payload = { payload = {
"account": account.uri, "account": account.uri,
"newKey": new_key_data['jwk'], # specified in draft 12 "newKey": new_key_data['jwk'], # specified in draft 12 and older
"oldKey": account.jwk, # discussed in https://github.com/ietf-wg-acme/acme/pull/425, "oldKey": account.jwk, # specified in draft 13 and newer
# might be required in draft 13
} }
data = account.sign_request(protected, payload, new_key_data) data = account.sign_request(protected, payload, new_key_data)
# Send request and verify result # Send request and verify result

View file

@ -18,13 +18,14 @@ DOCUMENTATION = '''
module: acme_account_facts module: acme_account_facts
author: "Felix Fontein (@felixfontein)" author: "Felix Fontein (@felixfontein)"
version_added: "2.7" version_added: "2.7"
short_description: Retrieves information on ACME accounts. short_description: Retrieves information on ACME accounts
description: description:
- "Allows to retrieve information on accounts with Let's Encrypt. - "Allows to retrieve information on accounts a CA supporting the
Let's Encrypt is a free, automated, and open certificate authority L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-14),
(CA), run for the public's benefit. For details see U(https://letsencrypt.org)." such as L(Let's Encrypt,https://letsencrypt.org/)."
- "The M(acme_account) module allows to modify, create and delete ACME accounts."
- "This module only works with the ACME v2 protocol." - "This module only works with the ACME v2 protocol."
notes:
- "The M(acme_account) module allows to modify, create and delete ACME accounts."
extends_documentation_fragment: extends_documentation_fragment:
- acme - acme
''' '''

View file

@ -18,10 +18,10 @@ DOCUMENTATION = '''
module: acme_certificate module: acme_certificate
author: "Michael Gruener (@mgruener)" author: "Michael Gruener (@mgruener)"
version_added: "2.2" version_added: "2.2"
short_description: Create SSL certificates with an ACME protocol endpoint short_description: Create SSL/TLS certificates with the ACME protocol
description: description:
- "Create and renew SSL certificates with a CA supporting the - "Create and renew SSL/TLS certificates with a CA supporting the
L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-12), L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-14),
such as L(Let's Encrypt,https://letsencrypt.org/). The current such as L(Let's Encrypt,https://letsencrypt.org/). The current
implementation supports the C(http-01), C(dns-01) and C(tls-alpn-01) implementation supports the C(http-01), C(dns-01) and C(tls-alpn-01)
challenges." challenges."
@ -36,19 +36,21 @@ description:
the necessary certificate has to be created and served. the necessary certificate has to be created and served.
It is I(not) the responsibility of this module to perform these steps." It is I(not) the responsibility of this module to perform these steps."
- "For details on how to fulfill these challenges, you might have to read through - "For details on how to fulfill these challenges, you might have to read through
L(the main ACME specification,https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8) L(the main ACME specification,https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-8)
and the L(TLS-ALPN-01 specification,U(https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-3). and the L(TLS-ALPN-01 specification,https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3).
Also, consider the examples provided for this module." Also, consider the examples provided for this module."
- "Although the defaults are chosen so that the module can be used with notes:
the Let's Encrypt CA, the module can be used with any service using the ACME
v1 or v2 protocol."
- "At least one of C(dest) and C(fullchain_dest) must be specified." - "At least one of C(dest) and C(fullchain_dest) must be specified."
- "Note that this module includes basic account management functionality. - "This module includes basic account management functionality.
If you want to have more control over your ACME account, use the M(acme_account) If you want to have more control over your ACME account, use the M(acme_account)
module and disable account management for this module using the C(modify_account) module and disable account management for this module using the C(modify_account)
option." option."
- "Note: this module was called C(letsencrypt) before Ansible 2.6. The usage - "This module was called C(letsencrypt) before Ansible 2.6. The usage
did not change." did not change."
- "If you want to use the C(tls-alpn-01) challenge, you can use the
M(acme_challenge_cert_helper) module to prepare the challenge certificate."
- "You can use the M(certificate_complet_chain) module to find the root certificate
for the returned fullchain."
extends_documentation_fragment: extends_documentation_fragment:
- acme - acme
options: options:
@ -98,8 +100,8 @@ options:
CSR to be signed." CSR to be signed."
- "I(Note): the private key used to create the CSR I(must not) be the - "I(Note): the private key used to create the CSR I(must not) be the
account key. This is a bad idea from a security point of view, and account key. This is a bad idea from a security point of view, and
the CA should not accept the CSR. Let's Encrypt will return an error the CA should not accept the CSR. The ACME server should return an
in this case." error in this case."
required: true required: true
aliases: ['src'] aliases: ['src']
data: data:
@ -284,7 +286,7 @@ challenge_data:
- "For C(tls-alpn-01) challenges, note that this return value contains a - "For C(tls-alpn-01) challenges, note that this return value contains a
Base64 encoded version of the correct binary blob which has to be put Base64 encoded version of the correct binary blob which has to be put
into the acmeValidation x509 extension; see into the acmeValidation x509 extension; see
U(https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-3) U(https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3)
for details. To do this, you might need the C(b64decode) Jinja filter for details. To do this, you might need the C(b64decode) Jinja filter
to extract the binary blob from this return value." to extract the binary blob from this return value."
returned: changed returned: changed
@ -307,7 +309,7 @@ authorizations:
type: complex type: complex
contains: contains:
authorization: authorization:
description: ACME authorization object. See U(https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.4) description: ACME authorization object. See U(https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.1.4)
returned: success returned: success
type: dict type: dict
order_uri: order_uri:
@ -492,17 +494,17 @@ class ACMEClient(object):
keyauthorization = self.account.get_keyauthorization(token) keyauthorization = self.account.get_keyauthorization(token)
if type == 'http-01': if type == 'http-01':
# https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.3 # https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-8.3
resource = '.well-known/acme-challenge/' + token resource = '.well-known/acme-challenge/' + token
data[type] = {'resource': resource, 'resource_value': keyauthorization} data[type] = {'resource': resource, 'resource_value': keyauthorization}
elif type == 'dns-01': elif type == 'dns-01':
# https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.4 # https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-8.4
resource = '_acme-challenge' resource = '_acme-challenge'
value = nopad_b64(hashlib.sha256(to_bytes(keyauthorization)).digest()) value = nopad_b64(hashlib.sha256(to_bytes(keyauthorization)).digest())
record = (resource + domain[1:]) if domain.startswith('*.') else (resource + '.' + domain) record = (resource + domain[1:]) if domain.startswith('*.') else (resource + '.' + domain)
data[type] = {'resource': resource, 'resource_value': value, 'record': record} data[type] = {'resource': resource, 'resource_value': value, 'record': record}
elif type == 'tls-alpn-01': elif type == 'tls-alpn-01':
# https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-3 # https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3
resource = domain resource = domain
value = base64.b64encode(hashlib.sha256(to_bytes(keyauthorization)).digest()) value = base64.b64encode(hashlib.sha256(to_bytes(keyauthorization)).digest())
data[type] = {'resource': resource, 'resource_value': value} data[type] = {'resource': resource, 'resource_value': value}
@ -573,7 +575,7 @@ class ACMEClient(object):
''' '''
Create a new certificate based on the csr. Create a new certificate based on the csr.
Return the certificate object as dict Return the certificate object as dict
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.4
''' '''
csr = pem_to_der(self.csr) csr = pem_to_der(self.csr)
new_cert = { new_cert = {
@ -607,7 +609,7 @@ class ACMEClient(object):
def _download_cert(self, url): def _download_cert(self, url):
''' '''
Download and parse the certificate chain. Download and parse the certificate chain.
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.4.2
''' '''
resp, info = fetch_url(self.module, url, headers={'Accept': 'application/pem-certificate-chain'}) resp, info = fetch_url(self.module, url, headers={'Accept': 'application/pem-certificate-chain'})
try: try:
@ -679,7 +681,7 @@ class ACMEClient(object):
def _new_order_v2(self): def _new_order_v2(self):
''' '''
Start a new certificate order (ACME v2 protocol). Start a new certificate order (ACME v2 protocol).
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.4
''' '''
identifiers = [] identifiers = []
for domain in self.domains: for domain in self.domains:
@ -841,7 +843,7 @@ class ACMEClient(object):
''' '''
Deactivates all valid authz's. Does not raise exceptions. Deactivates all valid authz's. Does not raise exceptions.
https://community.letsencrypt.org/t/authorization-deactivation/19860/2 https://community.letsencrypt.org/t/authorization-deactivation/19860/2
https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.5.2 https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.5.2
''' '''
authz_deactivate = { authz_deactivate = {
'status': 'deactivated' 'status': 'deactivated'

View file

@ -18,15 +18,15 @@ DOCUMENTATION = '''
module: acme_certificate_revoke module: acme_certificate_revoke
author: "Felix Fontein (@felixfontein)" author: "Felix Fontein (@felixfontein)"
version_added: "2.7" version_added: "2.7"
short_description: Revoke certificates with the ACME protocol. short_description: Revoke certificates with the ACME protocol
description: description:
- "Allows to revoke certificates with the ACME protocol, for example - "Allows to revoke certificates issued by a CA supporting the
for certificates obtained by the M(acme_certificate) module. The L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-14),
ACME protocol is used by some Certificate Authorities such as such as L(Let's Encrypt,https://letsencrypt.org/)."
L(Let's Encrypt,https://letsencrypt.org/)." notes:
- "Note that exactly one of C(account_key_src), C(account_key_content), - "Exactly one of C(account_key_src), C(account_key_content),
C(private_key_src) or C(private_key_content) must be specified." C(private_key_src) or C(private_key_content) must be specified."
- "Also note that trying to revoke an already revoked certificate - "Trying to revoke an already revoked certificate
should result in an unchanged status, even if the revocation reason should result in an unchanged status, even if the revocation reason
was different than the one specified here. Also, depending on the was different than the one specified here. Also, depending on the
server, it can happen that some other error is returned if the server, it can happen that some other error is returned if the
@ -38,6 +38,29 @@ options:
description: description:
- "Path to the certificate to revoke." - "Path to the certificate to revoke."
required: yes required: yes
account_key_src:
description:
- "Path to a file containing the ACME account RSA or Elliptic Curve
key."
- "RSA keys can be created with C(openssl rsa ...). Elliptic curve keys can
be created with C(openssl ecparam -genkey ...). Any other tool creating
private keys in PEM format can be used as well."
- "Mutually exclusive with C(account_key_content)."
- "Required if C(account_key_content) is not used."
account_key_content:
description:
- "Content of the ACME account RSA or Elliptic Curve key."
- "Note that exactly one of C(account_key_src), C(account_key_content),
C(private_key_src) or C(private_key_content) must be specified."
- "I(Warning): the content will be written into a temporary file, which will
be deleted by Ansible when the module completes. Since this is an
important private key it can be used to change the account key,
or to revoke your certificates without knowing their private keys
, this might not be acceptable."
- "In case C(cryptography) is used, the content is not written into a
temporary file. It can still happen that it is written to disk by
Ansible in the process of moving the module with its argument to
the node where it is executed."
private_key_src: private_key_src:
description: description:
- "Path to the certificate's private key." - "Path to the certificate's private key."

View file

@ -8,14 +8,17 @@ class ModuleDocFragment(object):
# Standard files documentation fragment # Standard files documentation fragment
DOCUMENTATION = """ DOCUMENTATION = """
description: notes:
- "Note that if a new enough version of the C(cryptography) library - "If a new enough version of the C(cryptography) library
is available (see Requirements for details), it will be used is available (see Requirements for details), it will be used
instead of the C(openssl) binary. This can be explicitly disabled instead of the C(openssl) binary. This can be explicitly disabled
or enabled with the C(select_crypto_backend) option. Note that using or enabled with the C(select_crypto_backend) option. Note that using
the C(openssl) binary will be slower and less secure, as private key the C(openssl) binary will be slower and less secure, as private key
contents always have to be stored on disk (see contents always have to be stored on disk (see
C(account_key_content))." C(account_key_content))."
- "Although the defaults are chosen so that the module can be used with
the L(Let's Encrypt,https://letsencrypt.org/) CA, the module can in
principle be used with any CA providing an ACME endpoint."
requirements: requirements:
- "python >= 2.6" - "python >= 2.6"
- "either openssl, ..." - "either openssl, ..."
@ -73,8 +76,8 @@ options:
U(https://acme-v01.api.letsencrypt.org/directory), and the production U(https://acme-v01.api.letsencrypt.org/directory), and the production
directory URL for ACME v2 is U(https://acme-v02.api.letsencrypt.org/directory)." directory URL for ACME v2 is U(https://acme-v02.api.letsencrypt.org/directory)."
- "I(Warning): So far, the module has only been tested against Let's Encrypt - "I(Warning): So far, the module has only been tested against Let's Encrypt
(staging and production) and against the Pebble testing server (staging and production) and against the
(U(https://github.com/letsencrypt/Pebble))." L(Pebble testing server,https://github.com/letsencrypt/Pebble)."
default: https://acme-staging.api.letsencrypt.org/directory default: https://acme-staging.api.letsencrypt.org/directory
validate_certs: validate_certs:
description: description: