From 2419e6c6adf3175f8d117e1bd8af0da3042b5a3c Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 12 Jan 2025 10:24:24 +0100 Subject: [PATCH] Implement profile option. (#835) --- changelogs/fragments/835-acme-profiles.yml | 4 ++++ plugins/module_utils/acme/orders.py | 4 +++- plugins/modules/acme_certificate.py | 19 ++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/835-acme-profiles.yml diff --git a/changelogs/fragments/835-acme-profiles.yml b/changelogs/fragments/835-acme-profiles.yml new file mode 100644 index 00000000..ee40ae29 --- /dev/null +++ b/changelogs/fragments/835-acme-profiles.yml @@ -0,0 +1,4 @@ +minor_changes: + - "acme_certificate - allow to chose a profile for certificate generation, in case the CA supports this using + Internet-Draft `draft-aaron-acme-profiles `__ + (https://github.com/ansible-collections/community.crypto/pull/835)." \ No newline at end of file diff --git a/plugins/module_utils/acme/orders.py b/plugins/module_utils/acme/orders.py index 0724615c..5734abf6 100644 --- a/plugins/module_utils/acme/orders.py +++ b/plugins/module_utils/acme/orders.py @@ -65,7 +65,7 @@ class Order(object): return result @classmethod - def create(cls, client, identifiers, replaces_cert_id=None): + def create(cls, client, identifiers, replaces_cert_id=None, profile=None): ''' Start a new certificate order (ACME v2 protocol). https://tools.ietf.org/html/rfc8555#section-7.4 @@ -81,6 +81,8 @@ class Order(object): } if replaces_cert_id is not None: new_order["replaces"] = replaces_cert_id + if profile is not None: + new_order["profile"] = profile 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']) diff --git a/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py index 57048322..90b150ce 100644 --- a/plugins/modules/acme_certificate.py +++ b/plugins/modules/acme_certificate.py @@ -263,6 +263,14 @@ options: - always default: never version_added: 2.20.0 + profile: + description: + - Chose a specific profile for certificate selection. The available profiles depend on the CA. + - See L(a blog post by Let's Encrypt, https://letsencrypt.org/2025/01/09/acme-profiles/) and + L(draft-aaron-acme-profiles-00, https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/) + for more information. + type: str + version_added: 2.24.0 """ EXAMPLES = r""" @@ -604,6 +612,7 @@ class ACMECertificateClient(object): self.all_chains = None self.select_chain_matcher = [] self.include_renewal_cert_id = module.params['include_renewal_cert_id'] + self.profile = module.params['profile'] if self.module.params['select_chain']: for criterium_idx, criterium in enumerate(self.module.params['select_chain']): @@ -614,6 +623,13 @@ class ACMECertificateClient(object): except ValueError as exc: self.module.warn('Error while parsing criterium: {error}. Ignoring criterium.'.format(error=exc)) + if self.profile is not None: + meta_profiles = (self.directory.get('meta') or {}).get('profiles') or {} + if not meta_profiles: + raise ModuleFailException(msg='The ACME CA does not support profiles.') + if self.profile not in meta_profiles: + raise ModuleFailException(msg='The ACME CA does not support selected profile {0!r}.'.format(self.profile)) + # Make sure account exists modify_account = module.params['modify_account'] if modify_account or self.version > 1: @@ -696,7 +712,7 @@ class ACMECertificateClient(object): cert_info=cert_info, none_if_required_information_is_missing=True, ) - self.order = Order.create(self.client, self.identifiers, replaces_cert_id) + self.order = Order.create(self.client, self.identifiers, replaces_cert_id, profile=self.profile) self.order_uri = self.order.url self.order.load_authorizations(self.client) self.authorizations.update(self.order.authorizations) @@ -882,6 +898,7 @@ def main(): authority_key_identifier=dict(type='str'), )), include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'), + profile=dict(type='str'), ) argument_spec.update( required_one_of=[