acme_certificate and acme_certificate_create_order: add order_creation_error_strategy and order_creation_max_retries options (#842)

* Provide error information.

* Add helper function for order creation retrying.

* Improve existing documentation.

* Document 'replaces' return value.

* Add order_creation_error_strategy and order_creation_max_retries options.

* Add changelog fragment.

* Fix authz deactivation for finalizing step.

* Fix profile handling on order creation.

* Improve existing tests.

* Add ARI and profile tests.

* Warn when 'replaces' is removed when retrying to create an order.
pull/844/head
Felix Fontein 2025-01-18 10:51:10 +01:00 committed by GitHub
parent b9fa5b5193
commit 214794d056
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 632 additions and 56 deletions

View File

@ -0,0 +1,6 @@
minor_changes:
- "acme_certificate - add options ``order_creation_error_strategy`` and ``order_creation_max_retries``
which allow to configure the error handling behavior if creating a new ACME order fails. This is
particularly important when using the ``include_renewal_cert_id`` option, and the default value
``auto`` for ``order_creation_error_strategy`` tries to gracefully handle related errors
(https://github.com/ansible-collections/community.crypto/pull/842)."

View File

@ -19,6 +19,7 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.account impo
) )
from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import ( from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import (
Authorization,
wait_for_validation, wait_for_validation,
) )
@ -63,6 +64,8 @@ class ACMECertificateClient(object):
account = ACMEAccount(self.client) account = ACMEAccount(self.client)
self.account = account self.account = account
self.order_uri = module.params.get('order_uri') self.order_uri = module.params.get('order_uri')
self.order_creation_error_strategy = module.params.get('order_creation_error_strategy', 'auto')
self.order_creation_max_retries = module.params.get('order_creation_max_retries', 3)
# Make sure account exists # Make sure account exists
dummy, account_data = self.account.setup_account(allow_creation=False) dummy, account_data = self.account.setup_account(allow_creation=False)
@ -102,7 +105,15 @@ class ACMECertificateClient(object):
''' '''
if self.identifiers is None: if self.identifiers is None:
raise ModuleFailException('No identifiers have been provided') raise ModuleFailException('No identifiers have been provided')
order = Order.create(self.client, self.identifiers, replaces_cert_id=replaces_cert_id, profile=profile) order = Order.create_with_error_handling(
self.client,
self.identifiers,
error_strategy=self.order_creation_error_strategy,
error_max_retries=self.order_creation_max_retries,
replaces_cert_id=replaces_cert_id,
profile=profile,
message_callback=self.module.warn,
)
self.order_uri = order.url self.order_uri = order.url
order.load_authorizations(self.client) order.load_authorizations(self.client)
return order return order
@ -248,11 +259,22 @@ class ACMECertificateClient(object):
https://community.letsencrypt.org/t/authorization-deactivation/19860/2 https://community.letsencrypt.org/t/authorization-deactivation/19860/2
https://tools.ietf.org/html/rfc8555#section-7.5.2 https://tools.ietf.org/html/rfc8555#section-7.5.2
''' '''
for authz in order.authorizations.values(): if len(order.authorization_uris) > len(order.authorizations):
try: for authz_uri in order.authorization_uris:
authz.deactivate(self.client) authz = None
except Exception: try:
# ignore errors authz = Authorization.deactivate_url(self.client, authz_uri)
pass except Exception:
if authz.status != 'deactivated': # ignore errors
self.module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url)) pass
if authz is None or authz.status != 'deactivated':
self.module.warn(warning='Could not deactivate authz object {0}.'.format(authz_uri))
else:
for authz in order.authorizations.values():
try:
authz.deactivate(self.client)
except Exception:
# ignore errors
pass
if authz.status != 'deactivated':
self.module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url))

View File

@ -322,6 +322,23 @@ class Authorization(object):
return True return True
return False return False
@classmethod
def deactivate_url(cls, client, url):
'''
Deactivates this authorization.
https://community.letsencrypt.org/t/authorization-deactivation/19860/2
https://tools.ietf.org/html/rfc8555#section-7.5.2
'''
authz = cls(url)
authz_deactivate = {
'status': 'deactivated'
}
if client.version == 1:
authz_deactivate['resource'] = 'authz'
result, info = client.send_signed_request(url, authz_deactivate, fail_on_error=True)
authz._setup(client, result)
return authz
def wait_for_validation(authzs, client): def wait_for_validation(authzs, client):
''' '''

View File

@ -84,6 +84,8 @@ class ACMEProtocolException(ModuleFailException):
pass pass
extras = extras or dict() extras = extras or dict()
error_code = None
error_type = None
if msg is None: if msg is None:
msg = 'ACME request failed' msg = 'ACME request failed'
@ -94,7 +96,9 @@ class ACMEProtocolException(ModuleFailException):
code = info['status'] code = info['status']
extras['http_url'] = url extras['http_url'] = url
extras['http_status'] = code extras['http_status'] = code
error_code = code
if code is not None and code >= 400 and content_json is not None and 'type' in content_json: if code is not None and code >= 400 and content_json is not None and 'type' in content_json:
error_type = content_json['type']
if 'status' in content_json and content_json['status'] != code: if 'status' in content_json and content_json['status'] != code:
code_msg = 'status {problem_code} (HTTP status: {http_code})'.format( code_msg = 'status {problem_code} (HTTP status: {http_code})'.format(
http_code=format_http_status(code), problem_code=content_json['status']) http_code=format_http_status(code), problem_code=content_json['status'])
@ -134,6 +138,8 @@ class ACMEProtocolException(ModuleFailException):
) )
self.problem = {} self.problem = {}
self.subproblems = [] self.subproblems = []
self.error_code = error_code
self.error_type = error_type
for k, v in extras.items(): for k, v in extras.items():
setattr(self, k, v) setattr(self, k, v)

View File

@ -87,6 +87,55 @@ class Order(object):
client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201]) client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201])
return cls.from_json(client, result, info['location']) return cls.from_json(client, result, info['location'])
@classmethod
def create_with_error_handling(
cls,
client,
identifiers,
error_strategy='auto',
error_max_retries=3,
replaces_cert_id=None,
profile=None,
message_callback=None,
):
"""
error_strategy can be one of the following strings:
* ``fail``: simply fail. (Same behavior as ``Order.create()``.)
* ``retry_without_replaces_cert_id``: if ``replaces_cert_id`` is not ``None``, set it to ``None`` and retry.
The only exception is an error of type ``urn:ietf:params:acme:error:alreadyReplaced``, that indicates that
the certificate was already replaced.
* ``auto``: try to be clever. Right now this is identical to ``retry_without_replaces_cert_id``, but that can
change at any time in the future.
* ``always``: always retry until ``error_max_retries`` has been reached.
"""
tries = 0
while True:
tries += 1
try:
return cls.create(client, identifiers, replaces_cert_id=replaces_cert_id, profile=profile)
except ACMEProtocolException as exc:
if tries <= error_max_retries + 1 and error_strategy != 'fail':
if error_strategy == 'always':
continue
if (
error_strategy in ('auto', 'retry_without_replaces_cert_id') and
replaces_cert_id is not None and
not (exc.error_code == 409 and exc.error_type == 'urn:ietf:params:acme:error:alreadyReplaced')
):
replaces_cert_id = None
if message_callback:
message_callback(
'Stop passing `replaces` due to error {code} {type} when creating ACME order'.format(
code=exc.error_code,
type=exc.error_type,
)
)
continue
raise
def refresh(self, client): def refresh(self, client):
result, dummy = client.get_request(self.url) result, dummy = client.get_request(self.url)
changed = self.data != result changed = self.data != result

View File

@ -243,8 +243,6 @@ options:
- Determines whether to request renewal of an existing certificate according to L(the ACME ARI draft 3, - Determines whether to request renewal of an existing certificate according to L(the ACME ARI draft 3,
https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-5). https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-5).
- This is only used when the certificate specified in O(dest) or O(fullchain_dest) already exists. - This is only used when the certificate specified in O(dest) or O(fullchain_dest) already exists.
- V(never) never sends the certificate ID of the certificate to renew. V(always) will always send it.
- V(when_ari_supported) only sends the certificate ID if the ARI endpoint is found in the ACME directory.
- Generally you should use V(when_ari_supported) if you know that the ACME service supports a compatible draft (or final - Generally you should use V(when_ari_supported) if you know that the ACME service supports a compatible draft (or final
version, once it is out) of the ARI extension. V(always) should never be necessary. If you are not sure, or if you version, once it is out) of the ARI extension. V(always) should never be necessary. If you are not sure, or if you
receive strange errors on invalid C(replaces) values in order objects, use V(never), which also happens to be the receive strange errors on invalid C(replaces) values in order objects, use V(never), which also happens to be the
@ -252,15 +250,19 @@ options:
- ACME servers might refuse to create new orders with C(replaces) for certificates that already have an existing order. - ACME servers might refuse to create new orders with C(replaces) for certificates that already have an existing order.
This can happen if this module is used to create an order, and then the playbook/role fails in case the challenges This can happen if this module is used to create an order, and then the playbook/role fails in case the challenges
cannot be set up. If the playbook/role does not record the order data to continue with the existing order, but tries cannot be set up. If the playbook/role does not record the order data to continue with the existing order, but tries
to create a new one on the next run, creating the new order might fail. For this reason, this option should only be to create a new one on the next run, creating the new order might fail. If O(order_creation_error_strategy=fail)
set to a value different from V(never) if the role/playbook using it keeps track of order data accross restarts, or this will make the module fail. O(order_creation_error_strategy=auto) and
if it takes care to deactivate orders whose processing is aborted. Orders can be deactivated with the O(order_creation_error_strategy=retry_without_replaces_cert_id) will avoid this by leaving away C(replaces)
on retries.
- If O(order_creation_error_strategy=fail), for the above reason, this option should only be set to a value different
from V(never) if the role/playbook using it keeps track of order data accross restarts, or if it takes care to
deactivate orders whose processing is aborted. Orders can be deactivated with the
M(community.crypto.acme_certificate_deactivate_authz) module. M(community.crypto.acme_certificate_deactivate_authz) module.
type: str type: str
choices: choices:
- never never: Never send the certificate ID of the certificate to renew.
- when_ari_supported when_ari_supported: Only send the certificate ID if the ARI endpoint is found in the ACME directory.
- always always: Will always send the certificate ID of the certificate to renew.
default: never default: never
version_added: 2.20.0 version_added: 2.20.0
profile: profile:
@ -271,6 +273,32 @@ options:
for more information. for more information.
type: str type: str
version_added: 2.24.0 version_added: 2.24.0
order_creation_error_strategy:
description:
- Selects the error handling strategy for ACME protocol errors if creating a new ACME order fails.
type: str
choices:
auto:
- An unspecified algorithm that tries to be clever.
- Right now identical to V(retry_without_replaces_cert_id).
always:
- Always retry, until the limit in O(order_creation_max_retries) has been reached.
fail:
- Simply fail in case of errors. Do not attempt to retry.
- This has been the default before community.crypto 2.24.0.
retry_without_replaces_cert_id:
- If O(include_renewal_cert_id) is present, creating the order will be tried again without C(replaces).
- The only exception is an error of type C(urn:ietf:params:acme:error:alreadyReplaced), that indicates that
the certificate was already replaced. This usually means something went wrong and the user should investigate.
default: auto
version_added: 2.24.0
order_creation_max_retries:
description:
- Depending on the strategy selected in O(order_creation_error_strategy), will retry creating new orders
for at most the specified amount of times.
type: int
default: 3
version_added: 2.24.0
""" """
EXAMPLES = r""" EXAMPLES = r"""
@ -613,6 +641,8 @@ class ACMECertificateClient(object):
self.select_chain_matcher = [] self.select_chain_matcher = []
self.include_renewal_cert_id = module.params['include_renewal_cert_id'] self.include_renewal_cert_id = module.params['include_renewal_cert_id']
self.profile = module.params['profile'] self.profile = module.params['profile']
self.order_creation_error_strategy = module.params['order_creation_error_strategy']
self.order_creation_max_retries = module.params['order_creation_max_retries']
if self.module.params['select_chain']: if self.module.params['select_chain']:
for criterium_idx, criterium in enumerate(self.module.params['select_chain']): for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
@ -712,7 +742,15 @@ class ACMECertificateClient(object):
cert_info=cert_info, cert_info=cert_info,
none_if_required_information_is_missing=True, none_if_required_information_is_missing=True,
) )
self.order = Order.create(self.client, self.identifiers, replaces_cert_id, profile=self.profile) self.order = Order.create_with_error_handling(
self.client,
self.identifiers,
error_strategy=self.order_creation_error_strategy,
error_max_retries=self.order_creation_max_retries,
replaces_cert_id=replaces_cert_id,
profile=self.profile,
message_callback=self.module.warn,
)
self.order_uri = self.order.url self.order_uri = self.order.url
self.order.load_authorizations(self.client) self.order.load_authorizations(self.client)
self.authorizations.update(self.order.authorizations) self.authorizations.update(self.order.authorizations)
@ -899,6 +937,8 @@ def main():
)), )),
include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'), include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
profile=dict(type='str'), profile=dict(type='str'),
order_creation_error_strategy=dict(type='str', default='auto', choices=['auto', 'always', 'fail', 'retry_without_replaces_cert_id']),
order_creation_max_retries=dict(type='int', default=3),
) )
argument_spec.update( argument_spec.update(
required_one_of=[ required_one_of=[

View File

@ -113,15 +113,18 @@ options:
according to L(the ACME ARI draft 3, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-5). according to L(the ACME ARI draft 3, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-5).
- This certificate ID must be computed as specified in - This certificate ID must be computed as specified in
L(the ACME ARI draft 3, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.1). L(the ACME ARI draft 3, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.1).
It is returned as RV(community.crypto.acme_certificate_renewal_info#module:cert_id) of the It is returned as return value RV(community.crypto.acme_certificate_renewal_info#module:cert_id) of the
M(community.crypto.acme_certificate_renewal_info) module. M(community.crypto.acme_certificate_renewal_info) module.
- ACME servers might refuse to create new orders that indicate to replace a certificate for which - ACME servers might refuse to create new orders that indicate to replace a certificate for which
an active replacement order already exists. This can happen if this module is used to create an order, an active replacement order already exists. This can happen if this module is used to create an order,
and then the playbook/role fails in case the challenges cannot be set up. If the playbook/role does not and then the playbook/role fails in case the challenges cannot be set up. If the playbook/role does not
record the order data to continue with the existing order, but tries to create a new one on the next run, record the order data to continue with the existing order, but tries to create a new one on the next run,
creating the new order might fail. For this reason, this option should only be used if the role/playbook creating the new order might fail. If O(order_creation_error_strategy=fail) this will make the module fail.
using it keeps track of order data accross restarts, or if it takes care to deactivate orders whose O(order_creation_error_strategy=auto) and O(order_creation_error_strategy=retry_without_replaces_cert_id)
processing is aborted. Orders can be deactivated with the will avoid this by leaving away C(replaces) on retries.
- If O(order_creation_error_strategy=fail), for the above reason, this option should only be used
if the role/playbook using it keeps track of order data accross restarts, or if it takes care to
deactivate orders whose processing is aborted. Orders can be deactivated with the
M(community.crypto.acme_certificate_deactivate_authz) module. M(community.crypto.acme_certificate_deactivate_authz) module.
type: str type: str
profile: profile:
@ -131,6 +134,29 @@ options:
L(draft-aaron-acme-profiles-00, https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/) L(draft-aaron-acme-profiles-00, https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/)
for more information. for more information.
type: str type: str
order_creation_error_strategy:
description:
- Selects the error handling strategy for ACME protocol errors if creating a new ACME order fails.
type: str
choices:
auto:
- An unspecified algorithm that tries to be clever.
- Right now identical to V(retry_without_replaces_cert_id).
always:
- Always retry, until the limit in O(order_creation_max_retries) has been reached.
fail:
- Simply fail in case of errors. Do not attempt to retry.
retry_without_replaces_cert_id:
- If O(replaces_cert_id) is present, creating the order will be tried again without C(replaces).
- The only exception is an error of type C(urn:ietf:params:acme:error:alreadyReplaced), that indicates that
the certificate was already replaced. This usually means something went wrong and the user should investigate.
default: auto
order_creation_max_retries:
description:
- Depending on the strategy selected in O(order_creation_error_strategy), will retry creating new orders
for at most the specified amount of times.
type: int
default: 3
''' '''
EXAMPLES = r''' EXAMPLES = r'''
@ -370,6 +396,8 @@ def main():
deactivate_authzs=dict(type='bool', default=True), deactivate_authzs=dict(type='bool', default=True),
replaces_cert_id=dict(type='str'), replaces_cert_id=dict(type='str'),
profile=dict(type='str'), profile=dict(type='str'),
order_creation_error_strategy=dict(type='str', default='auto', choices=['auto', 'always', 'fail', 'retry_without_replaces_cert_id']),
order_creation_max_retries=dict(type='int', default=3),
) )
module = argument_spec.create_ansible_module() module = argument_spec.create_ansible_module()
if module.params['acme_version'] == 1: if module.params['acme_version'] == 1:
@ -382,7 +410,7 @@ def main():
profile = module.params['profile'] profile = module.params['profile']
if profile is not None: if profile is not None:
meta_profiles = (client.directory.get('meta') or {}).get('profiles') or {} meta_profiles = (client.client.directory.get('meta') or {}).get('profiles') or {}
if not meta_profiles: if not meta_profiles:
raise ModuleFailException(msg='The ACME CA does not support profiles. Please omit the "profile" option.') raise ModuleFailException(msg='The ACME CA does not support profiles. Please omit the "profile" option.')
if profile not in meta_profiles: if profile not in meta_profiles:

View File

@ -175,6 +175,13 @@ order:
- A URL for the certificate that has been issued in response to this order. - A URL for the certificate that has been issued in response to this order.
type: str type: str
returned: when the certificate has been issued returned: when the certificate has been issued
replaces:
description:
- If the order was created to replace an existing certificate using the C(replaces) mechanism from
L(draft-ietf-acme-ari, https://datatracker.ietf.org/doc/draft-ietf-acme-ari/), this provides the
certificate ID of the certificate that will be replaced by this order.
type: str
returned: when the certificate order is replacing a certificate through draft-ietf-acme-ari
authorizations_by_identifier: authorizations_by_identifier:
description: description:
- A dictionary mapping identifiers to their authorization objects. - A dictionary mapping identifiers to their authorization objects.

View File

@ -3,23 +3,23 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
- name: Generate random domain name - name: "({{ select_crypto_backend }}) Generate random domain name"
set_fact: set_fact:
domain_name: "host{{ '%0x' % ((2**32) | random) }}.example.com" domain_name: "host{{ '%0x' % ((2**32) | random) }}.example.com"
- name: Generate account key - name: "({{ select_crypto_backend }}) Generate account key"
openssl_privatekey: openssl_privatekey:
path: "{{ remote_tmp_dir }}/accountkey.pem" path: "{{ remote_tmp_dir }}/accountkey.pem"
type: ECC type: ECC
curve: secp256r1 curve: secp256r1
force: true force: true
- name: Parse account keys (to ease debugging some test failures) - name: "({{ select_crypto_backend }}) Parse account keys (to ease debugging some test failures)"
openssl_privatekey_info: openssl_privatekey_info:
path: "{{ remote_tmp_dir }}/accountkey.pem" path: "{{ remote_tmp_dir }}/accountkey.pem"
return_private_key_data: true return_private_key_data: true
- name: Create ACME account - name: "({{ select_crypto_backend }}) Create ACME account"
acme_account: acme_account:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -30,14 +30,14 @@
state: present state: present
register: account register: account
- name: Generate certificate key - name: "({{ select_crypto_backend }}) Generate certificate key"
openssl_privatekey: openssl_privatekey:
path: "{{ remote_tmp_dir }}/cert.key" path: "{{ remote_tmp_dir }}/cert.key"
type: ECC type: ECC
curve: secp256r1 curve: secp256r1
force: true force: true
- name: Generate certificate CSR - name: "({{ select_crypto_backend }}) Generate certificate CSR"
openssl_csr: openssl_csr:
path: "{{ remote_tmp_dir }}/cert.csr" path: "{{ remote_tmp_dir }}/cert.csr"
privatekey_path: "{{ remote_tmp_dir }}/cert.key" privatekey_path: "{{ remote_tmp_dir }}/cert.key"
@ -46,7 +46,7 @@
return_content: true return_content: true
register: csr register: csr
- name: Create certificate order - name: "({{ select_crypto_backend }}) Create certificate order"
acme_certificate_order_create: acme_certificate_order_create:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -56,11 +56,11 @@
csr: "{{ remote_tmp_dir }}/cert.csr" csr: "{{ remote_tmp_dir }}/cert.csr"
register: order register: order
- name: Show order information - name: "({{ select_crypto_backend }}) Show order information"
debug: debug:
var: order var: order
- name: Check order - name: "({{ select_crypto_backend }}) Check order"
assert: assert:
that: that:
- order is changed - order is changed
@ -80,7 +80,7 @@
- order.challenge_data_dns['_acme-challenge.' ~ domain_name] | length == 1 - order.challenge_data_dns['_acme-challenge.' ~ domain_name] | length == 1
- order.account_uri == account.account_uri - order.account_uri == account.account_uri
- name: Get order information - name: "({{ select_crypto_backend }}) Get order information"
acme_certificate_order_info: acme_certificate_order_info:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -90,11 +90,11 @@
order_uri: "{{ order.order_uri }}" order_uri: "{{ order.order_uri }}"
register: order_info_1 register: order_info_1
- name: Show order information - name: "({{ select_crypto_backend }}) Show order information"
debug: debug:
var: order_info_1 var: order_info_1
- name: Check order information - name: "({{ select_crypto_backend }}) Check order information"
assert: assert:
that: that:
- order_info_1 is not changed - order_info_1 is not changed
@ -115,10 +115,11 @@
- order_info_1.order.authorizations[0] == order_info_1.authorizations_by_identifier['dns:' ~ domain_name].uri - order_info_1.order.authorizations[0] == order_info_1.authorizations_by_identifier['dns:' ~ domain_name].uri
- "'certificate' not in order_info_1.order" - "'certificate' not in order_info_1.order"
- order_info_1.order.status == 'pending' - order_info_1.order.status == 'pending'
- order_info_1.order.replaces is not defined
- order_info_1.order_uri == order.order_uri - order_info_1.order_uri == order.order_uri
- order_info_1.account_uri == account.account_uri - order_info_1.account_uri == account.account_uri
- name: Create HTTP challenges - name: "({{ select_crypto_backend }}) Create HTTP challenges"
uri: uri:
url: "http://{{ acme_host }}:5000/http/{{ item.identifier }}/{{ item.challenges['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" url: "http://{{ acme_host }}:5000/http/{{ item.identifier }}/{{ item.challenges['http-01'].resource[('.well-known/acme-challenge/'|length):] }}"
method: PUT method: PUT
@ -129,7 +130,7 @@
loop: "{{ order.challenge_data }}" loop: "{{ order.challenge_data }}"
when: "'http-01' in item.challenges" when: "'http-01' in item.challenges"
- name: Let the challenge be validated - name: "({{ select_crypto_backend }}) Let the challenge be validated"
community.crypto.acme_certificate_order_validate: community.crypto.acme_certificate_order_validate:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -140,18 +141,18 @@
challenge: http-01 challenge: http-01
register: validate_1 register: validate_1
- name: Check validation result - name: "({{ select_crypto_backend }}) Check validation result"
assert: assert:
that: that:
- validate_1 is changed - validate_1 is changed
- validate_1.account_uri == account.account_uri - validate_1.account_uri == account.account_uri
- name: Wait until we know that the challenges have been validated for ansible-core <= 2.11 - name: "({{ select_crypto_backend }}) Wait until we know that the challenges have been validated for ansible-core <= 2.11"
pause: pause:
seconds: 5 seconds: 5
when: ansible_version.full is version('2.12', '<') when: ansible_version.full is version('2.12', '<')
- name: Get order information - name: "({{ select_crypto_backend }}) Get order information"
acme_certificate_order_info: acme_certificate_order_info:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -161,11 +162,11 @@
order_uri: "{{ order.order_uri }}" order_uri: "{{ order.order_uri }}"
register: order_info_2 register: order_info_2
- name: Show order information - name: "({{ select_crypto_backend }}) Show order information"
debug: debug:
var: order_info_2 var: order_info_2
- name: Check order information - name: "({{ select_crypto_backend }}) Check order information"
assert: assert:
that: that:
- order_info_2 is not changed - order_info_2 is not changed
@ -186,10 +187,11 @@
- order_info_2.order.authorizations[0] == order_info_2.authorizations_by_identifier['dns:' ~ domain_name].uri - order_info_2.order.authorizations[0] == order_info_2.authorizations_by_identifier['dns:' ~ domain_name].uri
- "'certificate' not in order_info_2.order" - "'certificate' not in order_info_2.order"
- order_info_2.order.status in ['pending', 'ready'] - order_info_2.order.status in ['pending', 'ready']
- order_info_2.order.replaces is not defined
- order_info_2.order_uri == order.order_uri - order_info_2.order_uri == order.order_uri
- order_info_2.account_uri == account.account_uri - order_info_2.account_uri == account.account_uri
- name: Let the challenge be validated (idempotent) - name: "({{ select_crypto_backend }}) Let the challenge be validated (idempotent)"
community.crypto.acme_certificate_order_validate: community.crypto.acme_certificate_order_validate:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -200,13 +202,13 @@
challenge: http-01 challenge: http-01
register: validate_2 register: validate_2
- name: Check validation result - name: "({{ select_crypto_backend }}) Check validation result"
assert: assert:
that: that:
- validate_2 is not changed - validate_2 is not changed
- validate_2.account_uri == account.account_uri - validate_2.account_uri == account.account_uri
- name: Retrieve the cert and intermediate certificate - name: "({{ select_crypto_backend }}) Retrieve the cert and intermediate certificate"
community.crypto.acme_certificate_order_finalize: community.crypto.acme_certificate_order_finalize:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -214,6 +216,7 @@
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem" account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}" select_crypto_backend: "{{ select_crypto_backend }}"
order_uri: "{{ order.order_uri }}" order_uri: "{{ order.order_uri }}"
deactivate_authzs: never
retrieve_all_alternates: true retrieve_all_alternates: true
csr: "{{ remote_tmp_dir }}/cert.csr" csr: "{{ remote_tmp_dir }}/cert.csr"
cert_dest: "{{ remote_tmp_dir }}/cert.pem" cert_dest: "{{ remote_tmp_dir }}/cert.pem"
@ -221,7 +224,7 @@
fullchain_dest: "{{ remote_tmp_dir }}/cert-fullchain.pem" fullchain_dest: "{{ remote_tmp_dir }}/cert-fullchain.pem"
register: finalize_1 register: finalize_1
- name: Check finalization result - name: "({{ select_crypto_backend }}) Check finalization result"
assert: assert:
that: that:
- finalize_1 is changed - finalize_1 is changed
@ -232,7 +235,7 @@
- finalize_1.selected_chain.chain.startswith('-----BEGIN CERTIFICATE-----\nMII') - finalize_1.selected_chain.chain.startswith('-----BEGIN CERTIFICATE-----\nMII')
- finalize_1.selected_chain.full_chain == finalize_1.selected_chain.cert + finalize_1.selected_chain.chain - finalize_1.selected_chain.full_chain == finalize_1.selected_chain.cert + finalize_1.selected_chain.chain
- name: Read files from disk - name: "({{ select_crypto_backend }}) Read files from disk"
slurp: slurp:
src: "{{ remote_tmp_dir }}/{{ item }}.pem" src: "{{ remote_tmp_dir }}/{{ item }}.pem"
loop: loop:
@ -241,14 +244,14 @@
- cert-fullchain - cert-fullchain
register: slurp register: slurp
- name: Compare finalization result with files on disk - name: "({{ select_crypto_backend }}) Compare finalization result with files on disk"
assert: assert:
that: that:
- finalize_1.selected_chain.cert == slurp.results[0].content | b64decode - finalize_1.selected_chain.cert == slurp.results[0].content | b64decode
- finalize_1.selected_chain.chain == slurp.results[1].content | b64decode - finalize_1.selected_chain.chain == slurp.results[1].content | b64decode
- finalize_1.selected_chain.full_chain == slurp.results[2].content | b64decode - finalize_1.selected_chain.full_chain == slurp.results[2].content | b64decode
- name: Get order information - name: "({{ select_crypto_backend }}) Get order information"
acme_certificate_order_info: acme_certificate_order_info:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -258,11 +261,11 @@
order_uri: "{{ order.order_uri }}" order_uri: "{{ order.order_uri }}"
register: order_info_3 register: order_info_3
- name: Show order information - name: "({{ select_crypto_backend }}) Show order information"
debug: debug:
var: order_info_3 var: order_info_3
- name: Check order information - name: "({{ select_crypto_backend }}) Check order information"
assert: assert:
that: that:
- order_info_3 is not changed - order_info_3 is not changed
@ -284,7 +287,7 @@
- order_info_3.order_uri == order.order_uri - order_info_3.order_uri == order.order_uri
- order_info_3.account_uri == account.account_uri - order_info_3.account_uri == account.account_uri
- name: Retrieve the cert and intermediate certificate (idempotent) - name: "({{ select_crypto_backend }}) Retrieve the cert and intermediate certificate (idempotent, but deactivate authzs)"
community.crypto.acme_certificate_order_finalize: community.crypto.acme_certificate_order_finalize:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -300,7 +303,7 @@
fullchain_dest: "{{ remote_tmp_dir }}/cert-fullchain.pem" fullchain_dest: "{{ remote_tmp_dir }}/cert-fullchain.pem"
register: finalize_2 register: finalize_2
- name: Check finalization result - name: "({{ select_crypto_backend }}) Check finalization result"
assert: assert:
that: that:
- finalize_2 is not changed - finalize_2 is not changed
@ -312,7 +315,7 @@
- finalize_2.selected_chain.full_chain == finalize_2.selected_chain.cert + finalize_2.selected_chain.chain - finalize_2.selected_chain.full_chain == finalize_2.selected_chain.cert + finalize_2.selected_chain.chain
- finalize_2.selected_chain == finalize_1.selected_chain - finalize_2.selected_chain == finalize_1.selected_chain
- name: Get order information - name: "({{ select_crypto_backend }}) Get order information"
acme_certificate_order_info: acme_certificate_order_info:
acme_directory: "{{ acme_directory_url }}" acme_directory: "{{ acme_directory_url }}"
acme_version: 2 acme_version: 2
@ -322,11 +325,11 @@
order_uri: "{{ order.order_uri }}" order_uri: "{{ order.order_uri }}"
register: order_info_4 register: order_info_4
- name: Show order information - name: "({{ select_crypto_backend }}) Show order information"
debug: debug:
var: order_info_4 var: order_info_4
- name: Check order information - name: "({{ select_crypto_backend }}) Check order information"
assert: assert:
that: that:
- order_info_4 is not changed - order_info_4 is not changed
@ -347,3 +350,385 @@
- order_info_4.order.status == 'deactivated' - order_info_4.order.status == 'deactivated'
- order_info_4.order_uri == order.order_uri - order_info_4.order_uri == order.order_uri
- order_info_4.account_uri == account.account_uri - order_info_4.account_uri == account.account_uri
# Test ARI support
- when: acme_supports_ari
block:
- name: "({{ select_crypto_backend }}) Get certificate renewal information"
acme_certificate_renewal_info:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
# account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
certificate_path: "{{ remote_tmp_dir }}/cert.pem"
register: cert_info
- name: "({{ select_crypto_backend }}) Verify information"
assert:
that:
- cert_info.supports_ari == true
- cert_info.should_renew == false
- cert_info.cert_id is string
- name: "({{ select_crypto_backend }}) Create replacement order 1"
acme_certificate_order_create:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
csr: "{{ remote_tmp_dir }}/cert.csr"
replaces_cert_id: "{{ cert_info.cert_id }}"
order_creation_error_strategy: fail
register: replacement_order_1
- name: "({{ select_crypto_backend }}) Get replacement order 1 information"
acme_certificate_order_info:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
order_uri: "{{ replacement_order_1.order_uri }}"
register: order_info_5
- name: "({{ select_crypto_backend }}) Check replacement order 1"
assert:
that:
- replacement_order_1 is changed
- replacement_order_1.order_uri.startswith('https://' ~ acme_host ~ ':14000/')
- replacement_order_1.challenge_data | length == 1
- replacement_order_1.challenge_data[0].identifier_type == 'dns'
- replacement_order_1.challenge_data[0].identifier == domain_name
- replacement_order_1.challenge_data[0].challenges | length >= 2
- "'http-01' in replacement_order_1.challenge_data[0].challenges"
- "'dns-01' in replacement_order_1.challenge_data[0].challenges"
- replacement_order_1.challenge_data[0].challenges['http-01'].resource.startswith('.well-known/acme-challenge/')
- replacement_order_1.challenge_data[0].challenges['http-01'].resource_value is string
- replacement_order_1.challenge_data[0].challenges['dns-01'].record == '_acme-challenge.' ~ domain_name
- replacement_order_1.challenge_data[0].challenges['dns-01'].resource == '_acme-challenge'
- replacement_order_1.challenge_data[0].challenges['dns-01'].resource_value is string
- replacement_order_1.challenge_data_dns | length == 1
- replacement_order_1.challenge_data_dns['_acme-challenge.' ~ domain_name] | length == 1
- replacement_order_1.account_uri == account.account_uri
- replacement_order_1.order_uri not in [order.order_uri]
- name: "({{ select_crypto_backend }}) Check replacement order 1 information"
assert:
that:
- order_info_5 is not changed
- order_info_5.authorizations_by_identifier | length == 1
- order_info_5.authorizations_by_identifier['dns:' ~ domain_name].identifier.type == 'dns'
- order_info_5.authorizations_by_identifier['dns:' ~ domain_name].identifier.value == domain_name
- order_info_5.authorizations_by_identifier['dns:' ~ domain_name].status == 'pending'
- (order_info_5.authorizations_by_identifier['dns:' ~ domain_name].challenges | selectattr('type', 'equalto', 'http-01') | first).status == 'pending'
- (order_info_5.authorizations_by_identifier['dns:' ~ domain_name].challenges | selectattr('type', 'equalto', 'dns-01') | first).status == 'pending'
- order_info_5.authorizations_by_status['deactivated'] | length == 0
- order_info_5.authorizations_by_status['expired'] | length == 0
- order_info_5.authorizations_by_status['invalid'] | length == 0
- order_info_5.authorizations_by_status['pending'] | length == 1
- order_info_5.authorizations_by_status['pending'][0] == 'dns:' ~ domain_name
- order_info_5.authorizations_by_status['revoked'] | length == 0
- order_info_5.authorizations_by_status['valid'] | length == 0
- order_info_5.order.authorizations | length == 1
- order_info_5.order.authorizations[0] == order_info_5.authorizations_by_identifier['dns:' ~ domain_name].uri
- "'certificate' not in order_info_5.order"
- order_info_5.order.status == 'pending'
- order_info_5.order.replaces == cert_info.cert_id
- order_info_5.order_uri == replacement_order_1.order_uri
- order_info_5.account_uri == account.account_uri
# Right now Pebble does not reject duplicate replacement orders...
- when: false # TODO get Pebble improved
block:
- name: "({{ select_crypto_backend }}) Create replacement order 2 (should fail)"
acme_certificate_order_create:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
csr: "{{ remote_tmp_dir }}/cert.csr"
replaces_cert_id: "{{ cert_info.cert_id }}"
order_creation_error_strategy: fail
register: replacement_order_2
ignore_errors: true
- name: "({{ select_crypto_backend }}) Check replacement order 2"
assert:
that:
- replacement_order_2 is failed
- >-
replacement_order_2.msg.startswith(
'Failed to start new order for '
~ acme_directory_url
~ '/order-plz with status 409 Conflict. Error urn:ietf:params:acme:error:malformed:'
)
- name: "({{ select_crypto_backend }}) Create replacement order 3 with error handling"
acme_certificate_order_create:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
csr: "{{ remote_tmp_dir }}/cert.csr"
replaces_cert_id: "{{ cert_info.cert_id }}"
order_creation_error_strategy: retry_without_replaces_cert_id
register: replacement_order_3
- name: "({{ select_crypto_backend }}) Get replacement order 3 information"
acme_certificate_order_info:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
order_uri: "{{ replacement_order_3.order_uri }}"
register: order_info_6
- name: "({{ select_crypto_backend }}) Check replacement order 3"
assert:
that:
- replacement_order_3 is changed
- replacement_order_3.order_uri.startswith('https://' ~ acme_host ~ ':14000/')
- replacement_order_3.challenge_data | length == 1
- replacement_order_3.challenge_data[0].identifier_type == 'dns'
- replacement_order_3.challenge_data[0].identifier == domain_name
- replacement_order_3.challenge_data[0].challenges | length >= 2
- "'http-01' in replacement_order_3.challenge_data[0].challenges"
- "'dns-01' in replacement_order_3.challenge_data[0].challenges"
- replacement_order_3.challenge_data[0].challenges['http-01'].resource.startswith('.well-known/acme-challenge/')
- replacement_order_3.challenge_data[0].challenges['http-01'].resource_value is string
- replacement_order_3.challenge_data[0].challenges['dns-01'].record == '_acme-challenge.' ~ domain_name
- replacement_order_3.challenge_data[0].challenges['dns-01'].resource == '_acme-challenge'
- replacement_order_3.challenge_data[0].challenges['dns-01'].resource_value is string
- replacement_order_3.challenge_data_dns | length == 1
- replacement_order_3.challenge_data_dns['_acme-challenge.' ~ domain_name] | length == 1
- replacement_order_3.account_uri == account.account_uri
- replacement_order_3.order_uri not in [order.order_uri, replacement_order_1.order_uri]
- >-
'Stop passing `replaces` due to error 409 urn:ietf:params:acme:error:malformed when creating ACME order' in replacement_order_3.warnings
- name: "({{ select_crypto_backend }}) Check replacement order 3 information"
assert:
that:
- order_info_6 is not changed
- order_info_6.authorizations_by_identifier | length == 1
- order_info_6.authorizations_by_identifier['dns:' ~ domain_name].identifier.type == 'dns'
- order_info_6.authorizations_by_identifier['dns:' ~ domain_name].identifier.value == domain_name
- order_info_6.authorizations_by_identifier['dns:' ~ domain_name].status == 'pending'
- (order_info_6.authorizations_by_identifier['dns:' ~ domain_name].challenges | selectattr('type', 'equalto', 'http-01') | first).status == 'pending'
- (order_info_6.authorizations_by_identifier['dns:' ~ domain_name].challenges | selectattr('type', 'equalto', 'dns-01') | first).status == 'pending'
- order_info_6.authorizations_by_status['deactivated'] | length == 0
- order_info_6.authorizations_by_status['expired'] | length == 0
- order_info_6.authorizations_by_status['invalid'] | length == 0
- order_info_6.authorizations_by_status['pending'] | length == 1
- order_info_6.authorizations_by_status['pending'][0] == 'dns:' ~ domain_name
- order_info_6.authorizations_by_status['revoked'] | length == 0
- order_info_6.authorizations_by_status['valid'] | length == 0
- order_info_6.order.authorizations | length == 1
- order_info_6.order.authorizations[0] == order_info_6.authorizations_by_identifier['dns:' ~ domain_name].uri
- "'certificate' not in order_info_6.order"
- order_info_6.order.status == 'pending'
- order_info_6.order.replaces is not defined
- order_info_6.order_uri == replacement_order_3.order_uri
- order_info_6.account_uri == account.account_uri
- name: "({{ select_crypto_backend }}) Deactivate authzs for replacement order 3"
acme_certificate_deactivate_authz:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
order_uri: "{{ replacement_order_3.order_uri }}"
# Complete replacement order 1
- name: "({{ select_crypto_backend }}) Create HTTP challenges (replacement order 1)"
uri:
url: "http://{{ acme_host }}:5000/http/{{ item.identifier }}/{{ item.challenges['http-01'].resource[('.well-known/acme-challenge/'|length):] }}"
method: PUT
body_format: raw
body: "{{ item.challenges['http-01'].resource_value }}"
headers:
content-type: "application/octet-stream"
loop: "{{ replacement_order_1.challenge_data }}"
when: "'http-01' in item.challenges"
- name: "({{ select_crypto_backend }}) Let the challenge be validated (replacement order 1)"
community.crypto.acme_certificate_order_validate:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
order_uri: "{{ replacement_order_1.order_uri }}"
challenge: http-01
- name: "({{ select_crypto_backend }}) Retrieve the cert and intermediate certificate (replacement order 1)"
community.crypto.acme_certificate_order_finalize:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
order_uri: "{{ replacement_order_1.order_uri }}"
deactivate_authzs: on_success
retrieve_all_alternates: true
csr: "{{ remote_tmp_dir }}/cert.csr"
cert_dest: "{{ remote_tmp_dir }}/cert-repl.pem"
chain_dest: "{{ remote_tmp_dir }}/cert-repl-chain.pem"
fullchain_dest: "{{ remote_tmp_dir }}/cert-repl-fullchain.pem"
# Pebble *does* check against *completed* replacement orders
- when: true
block:
- name: "({{ select_crypto_backend }}) Create replacement order 4 (should fail)"
acme_certificate_order_create:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
csr: "{{ remote_tmp_dir }}/cert.csr"
replaces_cert_id: "{{ cert_info.cert_id }}"
order_creation_error_strategy: fail
register: replacement_order_4
ignore_errors: true
- name: "({{ select_crypto_backend }}) Check replacement order 4"
assert:
that:
- replacement_order_4 is failed
- replacement_order_4.msg.startswith('Failed to start new order for https://' ~ acme_host)
- >-
' with status 409 Conflict. Error urn:ietf:params:acme:error:malformed: ' in replacement_order_4.msg
- name: "({{ select_crypto_backend }}) Create replacement order 5 with error handling"
acme_certificate_order_create:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
csr: "{{ remote_tmp_dir }}/cert.csr"
replaces_cert_id: "{{ cert_info.cert_id }}"
order_creation_error_strategy: retry_without_replaces_cert_id
register: replacement_order_5
- name: "({{ select_crypto_backend }}) Get replacement order 5 information"
acme_certificate_order_info:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
order_uri: "{{ replacement_order_5.order_uri }}"
register: order_info_7
- name: "({{ select_crypto_backend }}) Check replacement order 5"
assert:
that:
- replacement_order_5 is changed
- replacement_order_5.order_uri.startswith('https://' ~ acme_host ~ ':14000/')
- replacement_order_5.challenge_data | length == 1
- replacement_order_5.challenge_data[0].identifier_type == 'dns'
- replacement_order_5.challenge_data[0].identifier == domain_name
- replacement_order_5.challenge_data[0].challenges | length >= 2
- "'http-01' in replacement_order_5.challenge_data[0].challenges"
- "'dns-01' in replacement_order_5.challenge_data[0].challenges"
- replacement_order_5.challenge_data[0].challenges['http-01'].resource.startswith('.well-known/acme-challenge/')
- replacement_order_5.challenge_data[0].challenges['http-01'].resource_value is string
- replacement_order_5.challenge_data[0].challenges['dns-01'].record == '_acme-challenge.' ~ domain_name
- replacement_order_5.challenge_data[0].challenges['dns-01'].resource == '_acme-challenge'
- replacement_order_5.challenge_data[0].challenges['dns-01'].resource_value is string
- replacement_order_5.challenge_data_dns | length == 1
- replacement_order_5.challenge_data_dns['_acme-challenge.' ~ domain_name] | length == 1
- replacement_order_5.account_uri == account.account_uri
- replacement_order_5.order_uri not in [order.order_uri, replacement_order_1.order_uri]
- >-
'Stop passing `replaces` due to error 409 urn:ietf:params:acme:error:malformed when creating ACME order' in replacement_order_5.warnings
- name: "({{ select_crypto_backend }}) Check replacement order 5 information"
assert:
that:
- order_info_7 is not changed
- order_info_7.authorizations_by_identifier | length == 1
- order_info_7.authorizations_by_identifier['dns:' ~ domain_name].identifier.type == 'dns'
- order_info_7.authorizations_by_identifier['dns:' ~ domain_name].identifier.value == domain_name
- order_info_7.authorizations_by_identifier['dns:' ~ domain_name].status == 'pending'
- (order_info_7.authorizations_by_identifier['dns:' ~ domain_name].challenges | selectattr('type', 'equalto', 'http-01') | first).status == 'pending'
- (order_info_7.authorizations_by_identifier['dns:' ~ domain_name].challenges | selectattr('type', 'equalto', 'dns-01') | first).status == 'pending'
- order_info_7.authorizations_by_status['deactivated'] | length == 0
- order_info_7.authorizations_by_status['expired'] | length == 0
- order_info_7.authorizations_by_status['invalid'] | length == 0
- order_info_7.authorizations_by_status['pending'] | length == 1
- order_info_7.authorizations_by_status['pending'][0] == 'dns:' ~ domain_name
- order_info_7.authorizations_by_status['revoked'] | length == 0
- order_info_7.authorizations_by_status['valid'] | length == 0
- order_info_7.order.authorizations | length == 1
- order_info_7.order.authorizations[0] == order_info_7.authorizations_by_identifier['dns:' ~ domain_name].uri
- "'certificate' not in order_info_7.order"
- order_info_7.order.status == 'pending'
- order_info_7.order.replaces is not defined
- order_info_7.order_uri == replacement_order_5.order_uri
- order_info_7.account_uri == account.account_uri
- name: "({{ select_crypto_backend }}) Deactivate authzs for replacement order 5"
acme_certificate_deactivate_authz:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
order_uri: "{{ replacement_order_5.order_uri }}"
# Test invalid profile
- when: acme_supports_profiles
block:
- name: "({{ select_crypto_backend }}) Create order with invalid profile (should fail)"
acme_certificate_order_create:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
csr: "{{ remote_tmp_dir }}/cert.csr"
profile: does-not-exist
order_creation_error_strategy: fail
register: invalid_profile_order
ignore_errors: true
- name: "({{ select_crypto_backend }}) Check invalid profile order"
assert:
that:
- invalid_profile_order is failed
- invalid_profile_order.msg == "The ACME CA does not support selected profile 'does-not-exist'."
# Test profile when server does not support it
- when: not acme_supports_profiles
block:
- name: "({{ select_crypto_backend }}) Create order with profile when server does not support it (should fail)"
acme_certificate_order_create:
acme_directory: "{{ acme_directory_url }}"
acme_version: 2
validate_certs: false
account_key_src: "{{ remote_tmp_dir }}/accountkey.pem"
select_crypto_backend: "{{ select_crypto_backend }}"
csr: "{{ remote_tmp_dir }}/cert.csr"
profile: default
register: profile_without_server_support
ignore_errors: true
- name: "({{ select_crypto_backend }}) Check profile without server support order"
assert:
that:
- profile_without_server_support is failed
- profile_without_server_support.msg == 'The ACME CA does not support profiles. Please omit the "profile" option.'

View File

@ -6,6 +6,8 @@
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate .azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
docs/docsite/rst/guide_selfsigned.rst rstcheck docs/docsite/rst/guide_selfsigned.rst rstcheck
plugins/modules/acme_account_info.py validate-modules:return-syntax-error plugins/modules/acme_account_info.py validate-modules:return-syntax-error
plugins/modules/acme_certificate.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_create.py validate-modules:return-syntax-error
plugins/modules/acme_certificate_order_info.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_info.py validate-modules:return-syntax-error
plugins/modules/acme_certificate_order_validate.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_validate.py validate-modules:return-syntax-error

View File

@ -5,6 +5,8 @@
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate .azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate .azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
plugins/modules/acme_account_info.py validate-modules:return-syntax-error plugins/modules/acme_account_info.py validate-modules:return-syntax-error
plugins/modules/acme_certificate.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_create.py validate-modules:return-syntax-error
plugins/modules/acme_certificate_order_info.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_info.py validate-modules:return-syntax-error
plugins/modules/acme_certificate_order_validate.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_validate.py validate-modules:return-syntax-error

View File

@ -1,5 +1,7 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen .azure-pipelines/scripts/publish-codecov.py replace-urlopen
plugins/modules/acme_account_info.py validate-modules:return-syntax-error plugins/modules/acme_account_info.py validate-modules:return-syntax-error
plugins/modules/acme_certificate.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_create.py validate-modules:return-syntax-error
plugins/modules/acme_certificate_order_info.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_info.py validate-modules:return-syntax-error
plugins/modules/acme_certificate_order_validate.py validate-modules:return-syntax-error plugins/modules/acme_certificate_order_validate.py validate-modules:return-syntax-error

View File

@ -1,5 +1,7 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen .azure-pipelines/scripts/publish-codecov.py replace-urlopen
plugins/lookup/gpg_fingerprint.py validate-modules:invalid-documentation plugins/lookup/gpg_fingerprint.py validate-modules:invalid-documentation
plugins/modules/acme_certificate.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:invalid-documentation
plugins/modules/ecs_certificate.py validate-modules:invalid-documentation plugins/modules/ecs_certificate.py validate-modules:invalid-documentation
plugins/modules/get_certificate.py validate-modules:invalid-documentation plugins/modules/get_certificate.py validate-modules:invalid-documentation
plugins/modules/luks_device.py validate-modules:invalid-documentation plugins/modules/luks_device.py validate-modules:invalid-documentation

View File

@ -1,5 +1,7 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen .azure-pipelines/scripts/publish-codecov.py replace-urlopen
plugins/lookup/gpg_fingerprint.py validate-modules:invalid-documentation plugins/lookup/gpg_fingerprint.py validate-modules:invalid-documentation
plugins/modules/acme_certificate.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:invalid-documentation
plugins/modules/ecs_certificate.py validate-modules:invalid-documentation plugins/modules/ecs_certificate.py validate-modules:invalid-documentation
plugins/modules/get_certificate.py validate-modules:invalid-documentation plugins/modules/get_certificate.py validate-modules:invalid-documentation
plugins/modules/luks_device.py validate-modules:invalid-documentation plugins/modules/luks_device.py validate-modules:invalid-documentation

View File

@ -1,4 +1,6 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen .azure-pipelines/scripts/publish-codecov.py replace-urlopen
plugins/modules/acme_certificate.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:invalid-documentation
plugins/modules/luks_device.py validate-modules:invalid-documentation plugins/modules/luks_device.py validate-modules:invalid-documentation
tests/ee/roles/smoke/library/smoke_ipaddress.py shebang tests/ee/roles/smoke/library/smoke_ipaddress.py shebang
tests/ee/roles/smoke/library/smoke_pyyaml.py shebang tests/ee/roles/smoke/library/smoke_pyyaml.py shebang

View File

@ -1,3 +1,5 @@
plugins/modules/acme_certificate.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:invalid-documentation
plugins/modules/luks_device.py validate-modules:invalid-documentation plugins/modules/luks_device.py validate-modules:invalid-documentation
tests/ee/roles/smoke/library/smoke_ipaddress.py shebang tests/ee/roles/smoke/library/smoke_ipaddress.py shebang
tests/ee/roles/smoke/library/smoke_pyyaml.py shebang tests/ee/roles/smoke/library/smoke_pyyaml.py shebang

View File

@ -5,6 +5,8 @@
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate .azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate .azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
docs/docsite/rst/guide_selfsigned.rst rstcheck docs/docsite/rst/guide_selfsigned.rst rstcheck
plugins/modules/acme_certificate.py validate-modules:invalid-documentation
plugins/modules/acme_certificate_order_create.py validate-modules:invalid-documentation
plugins/modules/acme_challenge_cert_helper.py validate-modules:return-syntax-error plugins/modules/acme_challenge_cert_helper.py validate-modules:return-syntax-error
plugins/modules/ecs_certificate.py validate-modules:invalid-documentation plugins/modules/ecs_certificate.py validate-modules:invalid-documentation
plugins/modules/get_certificate.py validate-modules:invalid-documentation plugins/modules/get_certificate.py validate-modules:invalid-documentation