diff --git a/lib/ansible/module_utils/acme.py b/lib/ansible/module_utils/acme.py index 68f95e3b1c2..fcf91513bfd 100644 --- a/lib/ansible/module_utils/acme.py +++ b/lib/ansible/module_utils/acme.py @@ -466,7 +466,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-12#section-7.1.1 + https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.1.1 ''' def __init__(self, module): @@ -536,7 +536,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-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=(',', ':')) 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 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 jws_header = jws_header or self.jws_header @@ -601,7 +601,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-12#section-6.6) + # (https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-6.6) if (400 <= info['status'] < 600 and result.get('type') == 'urn:ietf:params:acme:error:badNonce' and failed_tries <= 5): @@ -629,7 +629,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-12#section-7.3 + https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.3 ''' 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 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 diff --git a/lib/ansible/modules/crypto/acme/acme_account.py b/lib/ansible/modules/crypto/acme/acme_account.py index 5d779ee59f0..1fbe8165012 100644 --- a/lib/ansible/modules/crypto/acme/acme_account.py +++ b/lib/ansible/modules/crypto/acme/acme_account.py @@ -18,16 +18,19 @@ DOCUMENTATION = ''' module: acme_account author: "Felix Fontein (@felixfontein)" version_added: "2.6" -short_description: Create, modify or delete accounts with Let's Encrypt +short_description: Create, modify or delete ACME accounts 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)." + - "Allows to create, modify or delete accounts with a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-14), + 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. 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 options: @@ -52,7 +55,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-12#section-7.1.2 + - "See https://tools.ietf.org/html/draft-ietf-acme-acme-14#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)." @@ -65,14 +68,13 @@ options: type: bool new_account_key_src: description: - - "Path to a file containing the Let's Encrypt account RSA or Elliptic Curve - key to change to." + - "Path to a file containing the ACME account RSA or Elliptic Curve key to change to." - "Same restrictions apply as to C(account_key_src)." - "Mutually exclusive with C(new_account_key_content)." - "Required if C(new_account_key_content) is not used and state is C(changed_key)." new_account_key_content: 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)." - "Mutually exclusive with C(new_account_key_src)." - "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 if not module.check_mode: # 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'] protected = { "alg": new_key_data['alg'], @@ -230,9 +232,8 @@ def main(): } payload = { "account": account.uri, - "newKey": new_key_data['jwk'], # specified in draft 12 - "oldKey": account.jwk, # discussed in https://github.com/ietf-wg-acme/acme/pull/425, - # might be required in draft 13 + "newKey": new_key_data['jwk'], # specified in draft 12 and older + "oldKey": account.jwk, # specified in draft 13 and newer } data = account.sign_request(protected, payload, new_key_data) # Send request and verify result diff --git a/lib/ansible/modules/crypto/acme/acme_account_facts.py b/lib/ansible/modules/crypto/acme/acme_account_facts.py index 34b369c3bed..12c77fb0b79 100644 --- a/lib/ansible/modules/crypto/acme/acme_account_facts.py +++ b/lib/ansible/modules/crypto/acme/acme_account_facts.py @@ -18,13 +18,14 @@ DOCUMENTATION = ''' module: acme_account_facts author: "Felix Fontein (@felixfontein)" version_added: "2.7" -short_description: Retrieves information on ACME accounts. +short_description: Retrieves information on ACME accounts description: - - "Allows to retrieve information on 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_account) module allows to modify, create and delete ACME accounts." + - "Allows to retrieve information on accounts a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-14), + such as L(Let's Encrypt,https://letsencrypt.org/)." - "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: - acme ''' diff --git a/lib/ansible/modules/crypto/acme/acme_certificate.py b/lib/ansible/modules/crypto/acme/acme_certificate.py index dcdea4000e3..54e17fdbb1c 100644 --- a/lib/ansible/modules/crypto/acme/acme_certificate.py +++ b/lib/ansible/modules/crypto/acme/acme_certificate.py @@ -18,10 +18,10 @@ DOCUMENTATION = ''' module: acme_certificate author: "Michael Gruener (@mgruener)" 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: - - "Create and renew SSL certificates with a CA supporting the - L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-12), + - "Create and renew SSL/TLS certificates with a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-14), 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) challenges." @@ -36,19 +36,21 @@ description: the necessary certificate has to be created and served. 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 - L(the main ACME specification,https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-8) - and the L(TLS-ALPN-01 specification,U(https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-3). + L(the main ACME specification,https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-8) + 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." - - "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." +notes: - "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) 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 + - "This module was called C(letsencrypt) before Ansible 2.6. The usage 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: - acme options: @@ -98,8 +100,8 @@ options: CSR to be signed." - "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 - the CA should not accept the CSR. Let's Encrypt will return an error - in this case." + the CA should not accept the CSR. The ACME server should return an + error in this case." required: true aliases: ['src'] data: @@ -284,7 +286,7 @@ challenge_data: - "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 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 to extract the binary blob from this return value." returned: changed @@ -307,7 +309,7 @@ authorizations: type: complex contains: 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 type: dict order_uri: @@ -492,17 +494,17 @@ class ACMEClient(object): keyauthorization = self.account.get_keyauthorization(token) 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 data[type] = {'resource': resource, 'resource_value': keyauthorization} 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' value = nopad_b64(hashlib.sha256(to_bytes(keyauthorization)).digest()) record = (resource + domain[1:]) if domain.startswith('*.') else (resource + '.' + domain) data[type] = {'resource': resource, 'resource_value': value, 'record': record} 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 value = base64.b64encode(hashlib.sha256(to_bytes(keyauthorization)).digest()) data[type] = {'resource': resource, 'resource_value': value} @@ -573,7 +575,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-12#section-7.4 + https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.4 ''' csr = pem_to_der(self.csr) new_cert = { @@ -607,7 +609,7 @@ class ACMEClient(object): def _download_cert(self, url): ''' 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'}) try: @@ -679,7 +681,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-12#section-7.4 + https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.4 ''' identifiers = [] for domain in self.domains: @@ -841,7 +843,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-12#section-7.5.2 + https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.5.2 ''' authz_deactivate = { 'status': 'deactivated' diff --git a/lib/ansible/modules/crypto/acme/acme_certificate_revoke.py b/lib/ansible/modules/crypto/acme/acme_certificate_revoke.py index 524034f0d02..dfc8c63bfa2 100644 --- a/lib/ansible/modules/crypto/acme/acme_certificate_revoke.py +++ b/lib/ansible/modules/crypto/acme/acme_certificate_revoke.py @@ -18,15 +18,15 @@ DOCUMENTATION = ''' module: acme_certificate_revoke author: "Felix Fontein (@felixfontein)" version_added: "2.7" -short_description: Revoke certificates with the ACME protocol. +short_description: Revoke certificates with the ACME protocol description: - - "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), + - "Allows to revoke certificates issued by a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/draft-ietf-acme-acme-14), + such as L(Let's Encrypt,https://letsencrypt.org/)." +notes: + - "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 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 was different than the one specified here. Also, depending on the server, it can happen that some other error is returned if the @@ -38,6 +38,29 @@ options: description: - "Path to the certificate to revoke." 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: description: - "Path to the certificate's private key." diff --git a/lib/ansible/utils/module_docs_fragments/acme.py b/lib/ansible/utils/module_docs_fragments/acme.py index f0e67213b84..c058435b576 100644 --- a/lib/ansible/utils/module_docs_fragments/acme.py +++ b/lib/ansible/utils/module_docs_fragments/acme.py @@ -8,14 +8,17 @@ class ModuleDocFragment(object): # Standard files documentation fragment DOCUMENTATION = """ -description: - - "Note that if a new enough version of the C(cryptography) library +notes: + - "If a new enough version of the C(cryptography) library is available (see Requirements for details), it will be used instead of the C(openssl) binary. This can be explicitly disabled 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 contents always have to be stored on disk (see 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: - "python >= 2.6" - "either openssl, ..." @@ -73,8 +76,8 @@ options: 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)." - "I(Warning): So far, the module has only been tested against Let's Encrypt - (staging and production) and against the Pebble testing server - (U(https://github.com/letsencrypt/Pebble))." + (staging and production) and against the + L(Pebble testing server,https://github.com/letsencrypt/Pebble)." default: https://acme-staging.api.letsencrypt.org/directory validate_certs: description: