Skip to content

Commit

Permalink
acme_certificate: allow to request renewal of a certificate according…
Browse files Browse the repository at this point in the history
… to ARI (ansible-collections#739)

* Allow to request renewal of a certificate according to ARI in acme_certificate.

* Improve docs.

* Fix typo and use right object.

* Add warning.
  • Loading branch information
felixfontein authored Apr 30, 2024
1 parent 6d4fc58 commit 33d278a
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- "acme_certificate - add ``include_renewal_cert_id`` option to allow requesting renewal of a specific certificate according to the current ACME Renewal Information specification draft (https://github.com/ansible-collections/community.crypto/pull/739)."
6 changes: 5 additions & 1 deletion plugins/module_utils/acme/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def _setup(self, client, data):
self.identifiers = []
for identifier in data['identifiers']:
self.identifiers.append((identifier['type'], identifier['value']))
self.replaces_cert_id = data.get('replaces')
self.finalize_uri = data.get('finalize')
self.certificate_uri = data.get('certificate')
self.authorization_uris = data['authorizations']
Expand All @@ -44,6 +45,7 @@ def __init__(self, url):

self.status = None
self.identifiers = []
self.replaces_cert_id = None
self.finalize_uri = None
self.certificate_uri = None
self.authorization_uris = []
Expand All @@ -62,7 +64,7 @@ def from_url(cls, client, url):
return result

@classmethod
def create(cls, client, identifiers):
def create(cls, client, identifiers, replaces_cert_id=None):
'''
Start a new certificate order (ACME v2 protocol).
https://tools.ietf.org/html/rfc8555#section-7.4
Expand All @@ -76,6 +78,8 @@ def create(cls, client, identifiers):
new_order = {
"identifiers": acme_identifiers
}
if replaces_cert_id is not None:
new_order["replaces"] = replaces_cert_id
result, info = client.send_signed_request(
client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201])
return cls.from_json(client, result, info['location'])
Expand Down
46 changes: 45 additions & 1 deletion plugins/modules/acme_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,30 @@
- "The identifier must be of the form
V(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)."
type: str
include_renewal_cert_id:
description:
- 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).
- 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 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 default.
- 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 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 set to a value different from V(never) if the
role/playbook using it keeps track of order data accross restarts.
type: str
choices:
- never
- when_ari_supported
- always
default: never
version_added: 2.20.0
'''

EXAMPLES = r'''
Expand Down Expand Up @@ -586,6 +610,7 @@
)

from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
compute_cert_id,
pem_to_der,
)

Expand Down Expand Up @@ -622,6 +647,7 @@ def __init__(self, module, backend):
self.order_uri = self.data.get('order_uri') if self.data else None
self.all_chains = None
self.select_chain_matcher = []
self.include_renewal_cert_id = module.params['include_renewal_cert_id']

if self.module.params['select_chain']:
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
Expand Down Expand Up @@ -679,6 +705,15 @@ def is_first_step(self):
# stored in self.order_uri by the constructor).
return self.order_uri is None

def _get_cert_info_or_none(self):
if self.module.params.get('dest'):
filename = self.module.params['dest']
else:
filename = self.module.params['fullchain_dest']
if not os.path.exists(filename):
return None
return self.client.backend.get_cert_information(cert_filename=filename)

def start_challenges(self):
'''
Create new authorizations for all identifiers of the CSR,
Expand All @@ -693,7 +728,15 @@ def start_challenges(self):
authz = Authorization.create(self.client, identifier_type, identifier)
self.authorizations[authz.combined_identifier] = authz
else:
self.order = Order.create(self.client, self.identifiers)
replaces_cert_id = None
if (
self.include_renewal_cert_id == 'always' or
(self.include_renewal_cert_id == 'when_ari_supported' and self.client.directory.has_renewal_info_endpoint())
):
cert_info = self._get_cert_info_or_none()
if cert_info is not None:
replaces_cert_id = compute_cert_id(self.client.backend, cert_info=cert_info)
self.order = Order.create(self.client, self.identifiers, replaces_cert_id)
self.order_uri = self.order.url
self.order.load_authorizations(self.client)
self.authorizations.update(self.order.authorizations)
Expand Down Expand Up @@ -879,6 +922,7 @@ def main():
subject_key_identifier=dict(type='str'),
authority_key_identifier=dict(type='str'),
)),
include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
))
module = AnsibleModule(
argument_spec=argument_spec,
Expand Down

0 comments on commit 33d278a

Please sign in to comment.