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
parent
b9fa5b5193
commit
214794d056
|
@ -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)."
|
|
@ -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))
|
||||||
|
|
|
@ -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):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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=[
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue