From 6b6c017dd15482fab1819472bf159bf73badcfc3 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Wed, 4 Jul 2018 15:22:11 +0200 Subject: [PATCH] ACME modules: documentation improvements (#42165) * Always using current draft when referring to ACME v2. * Adding URL for ACME v1 protocol. * Improve cross-referencing of acme_* modules. * General improvements. * Fixing syntax error. --- lib/ansible/module_utils/acme.py | 12 +++--- .../web_infrastructure/acme_account.py | 6 ++- .../web_infrastructure/acme_certificate.py | 38 +++++++++++-------- .../acme_certificate_revoke.py | 8 ++-- .../utils/module_docs_fragments/acme.py | 9 +++-- 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/lib/ansible/module_utils/acme.py b/lib/ansible/module_utils/acme.py index 9ab192a328e..131fd10b649 100644 --- a/lib/ansible/module_utils/acme.py +++ b/lib/ansible/module_utils/acme.py @@ -147,7 +147,7 @@ class ACMEDirectory(object): and allows to obtain a Replay-Nonce. The acme_directory URL needs to support unauthenticated GET requests; ACME endpoints requiring authentication are not supported. - https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-7.1.1 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.1 ''' def __init__(self, module): @@ -228,7 +228,7 @@ class ACMEAccount(object): def get_keyauthorization(self, token): ''' Returns the key authorization for the given token - https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-8.1 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.1 ''' accountkey_json = json.dumps(self.jwk, sort_keys=True, separators=(',', ':')) thumbprint = nopad_b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) @@ -360,7 +360,7 @@ class ACMEAccount(object): ''' Sends a JWS signed HTTP POST request to the ACME server and returns the response as dictionary - https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-6.2 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-6.2 ''' key_data = key_data or self.key_data key = key or self.key @@ -392,7 +392,7 @@ class ACMEAccount(object): try: result = self.module.from_json(content.decode('utf8')) # In case of badNonce error, try again (up to 5 times) - # (https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-6.6) + # (https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-6.6) if (400 <= info['status'] < 600 and result.get('type') == 'urn:ietf:params:acme:error:badNonce' and failed_tries <= 5): @@ -420,7 +420,7 @@ class ACMEAccount(object): 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). - https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.3 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.3 ''' contact = [] if contact is None else contact @@ -498,7 +498,7 @@ class ACMEAccount(object): will be stored in self.uri; if it is None, the account does not exist. - https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.3 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.3 ''' new_account = True diff --git a/lib/ansible/modules/web_infrastructure/acme_account.py b/lib/ansible/modules/web_infrastructure/acme_account.py index 700bf8af4d9..944a30823c0 100644 --- a/lib/ansible/modules/web_infrastructure/acme_account.py +++ b/lib/ansible/modules/web_infrastructure/acme_account.py @@ -23,6 +23,10 @@ description: - "Allows to create, modify or delete accounts with Let's Encrypt. Let's Encrypt is a free, automated, and open certificate authority (CA), run for the public's benefit. For details see U(https://letsencrypt.org)." + - "The M(acme_certificate) module also allows to do basic 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 + M(acme_certificate)." - "This module only works with the ACME v2 protocol." extends_documentation_fragment: - acme @@ -48,7 +52,7 @@ options: description: - "A list of contact URLs." - "Email addresses must be prefixed with C(mailto:)." - - "See https://tools.ietf.org/html/draft-ietf-acme-acme-10#section-7.1.2 + - "See https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.1.2 for what is allowed." - "Must be specified when state is C(present). Will be ignored if state is C(absent) or C(changed_key)." diff --git a/lib/ansible/modules/web_infrastructure/acme_certificate.py b/lib/ansible/modules/web_infrastructure/acme_certificate.py index 406af0314d6..d6abc39e511 100644 --- a/lib/ansible/modules/web_infrastructure/acme_certificate.py +++ b/lib/ansible/modules/web_infrastructure/acme_certificate.py @@ -20,10 +20,10 @@ author: "Michael Gruener (@mgruener)" version_added: "2.2" short_description: Create SSL certificates with an ACME protocol endpoint description: - - "Create and renew SSL certificates with a CA supporting the ACME protocol, - such as Let's Encrypt (U(https://letsencrypt.org)). For details see - U(https://letsencrypt.org). The current implementation supports the - C(http-01) and C(dns-01) challenges." + - "Create and renew SSL certificates with a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-12), + such as L(Let's Encrypt,https://letsencrypt.org/). The current + implementation supports the C(http-01) and C(dns-01) challenges." - "To use this module, it has to be executed twice. Either as two different tasks in the same run or during two runs. Note that the output of the first run needs to be recorded and passed to the second run as the @@ -34,12 +34,16 @@ description: C(dns-01) the necessary dns record has to be created. 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 - U(https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8). + L(the specification,https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8). Also, consider the examples provided for this module." - "Although the defaults are chosen so that the module can be used with 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." + - "Note that this module includes basic account management functionality. + 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) + option." - "Note: this module was called C(letsencrypt) before Ansible 2.6. The usage did not change." extends_documentation_fragment: @@ -49,6 +53,10 @@ options: description: - "The email address associated with this account." - "It will be used for certificate expiration warnings." + - "Note that when C(modify_account) is not set to C(no) and you also + used the M(acme_account) module to specify more than one contact + for your account, this module will update your account and restrict + it to the (at most one) contact email address specified here." agreement: description: - "URI to a terms of service document you agree to when using the @@ -67,9 +75,9 @@ options: description: - "Boolean indicating whether the module should create the account if necessary, and update its contact data." - - "Set to C(no) if you want to use C(acme_account) to manage your - account instead, and to avoid accidental creation of a new account - using an old key if you changed the account key with C(acme_account)." + - "Set to C(no) if you want to use the M(acme_account) module to manage + your account instead, and to avoid accidental creation of a new account + using an old key if you changed the account key with M(acme_account)." - "If set to C(no), C(terms_agreed) and C(account_email) are ignored." type: bool default: 'yes' @@ -465,11 +473,11 @@ class ACMEClient(object): keyauthorization = self.account.get_keyauthorization(token) if type == 'http-01': - # https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-8.3 + # https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.3 resource = '.well-known/acme-challenge/' + token data[type] = {'resource': resource, 'resource_value': keyauthorization} elif type == 'dns-01': - # https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-8.5 + # https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8.4 resource = '_acme-challenge' value = nopad_b64(hashlib.sha256(to_bytes(keyauthorization)).digest()) record = (resource + domain[1:]) if domain.startswith('*.') else (resource + '.' + domain) @@ -523,7 +531,7 @@ class ACMEClient(object): result['uri'] = auth['uri'] if self._add_or_update_auth(domain, result): self.changed = True - # draft-ietf-acme-acme-02 + # https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.1.2 # "status (required, string): ... # If this field is missing, then the default value is "pending"." if self.version == 1 and 'status' not in result: @@ -541,7 +549,7 @@ class ACMEClient(object): ''' Create a new certificate based on the csr. Return the certificate object as dict - https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-7.4 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4 ''' openssl_csr_cmd = [self._openssl_bin, "req", "-in", self.csr, "-outform", "DER"] dummy, out, dummy = self.module.run_command(openssl_csr_cmd, check_rc=True) @@ -577,7 +585,7 @@ class ACMEClient(object): def _download_cert(self, url): ''' Download and parse the certificate chain. - https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-7.4.2 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2 ''' resp, info = fetch_url(self.module, url, headers={'Accept': 'application/pem-certificate-chain'}) try: @@ -651,7 +659,7 @@ class ACMEClient(object): def _new_order_v2(self): ''' Start a new certificate order (ACME v2 protocol). - https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-7.4 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4 ''' identifiers = [] for domain in self.domains: @@ -813,7 +821,7 @@ class ACMEClient(object): ''' Deactivates all valid authz's. Does not raise exceptions. https://community.letsencrypt.org/t/authorization-deactivation/19860/2 - https://tools.ietf.org/html/draft-ietf-acme-acme-09#section-7.5.2 + https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.5.2 ''' authz_deactivate = { 'status': 'deactivated' diff --git a/lib/ansible/modules/web_infrastructure/acme_certificate_revoke.py b/lib/ansible/modules/web_infrastructure/acme_certificate_revoke.py index 518a3b3823b..20336ee7a30 100644 --- a/lib/ansible/modules/web_infrastructure/acme_certificate_revoke.py +++ b/lib/ansible/modules/web_infrastructure/acme_certificate_revoke.py @@ -20,8 +20,10 @@ author: "Felix Fontein (@felixfontein)" version_added: "2.7" short_description: Revoke certificates with the ACME protocol. description: - - "Allows to revoke certificates with the ACME protocol. This protocol - is, for example, used by Let's Encrypt." + - "Allows to revoke certificates with the ACME protocol, for example + for certificates obtained by the M(acme_certificate) module. The + ACME protocol is used by some Certificate Authorities such as + L(Let's Encrypt,https://letsencrypt.org/)." - "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." - "Also note that in general, trying to revoke an already revoked @@ -48,7 +50,7 @@ options: - "Content of the certificate's private 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." - - "Warning: the content will be written into a temporary file, which will + - "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 diff --git a/lib/ansible/utils/module_docs_fragments/acme.py b/lib/ansible/utils/module_docs_fragments/acme.py index 52c928dd45d..feadc0764d4 100644 --- a/lib/ansible/utils/module_docs_fragments/acme.py +++ b/lib/ansible/utils/module_docs_fragments/acme.py @@ -17,7 +17,8 @@ options: - "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 ...)." + 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." aliases: [ account_key ] @@ -26,7 +27,7 @@ options: - "Content of the ACME account RSA or Elliptic Curve key." - "Mutually exclusive with C(account_key_src)." - "Required if C(account_key_src) is not used." - - "Warning: the content will be written into a temporary file, which will + - "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 @@ -59,8 +60,8 @@ options: validate_certs: description: - Whether calls to the ACME directory will validate TLS certificates. - - I(Warning:) Should I(only ever) be set to C(no) for testing purposes, - for example when testing against a local Pebble server. + - "I(Warning): Should I(only ever) be set to C(no) for testing purposes, + for example when testing against a local Pebble server." type: bool default: 'yes' version_added: 2.5