From a39b3bc88201f664c85c671420993cbe29e05e26 Mon Sep 17 00:00:00 2001 From: Lyas Spiehler Date: Tue, 15 Oct 2024 10:48:47 -0700 Subject: [PATCH] =?UTF-8?q?lookup=20lowercase=20domain=20names=20when=20ve?= =?UTF-8?q?rifying=20authorizations=20to=20preven=E2=80=A6=20(#803)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lookup lowercase domain names when verifying authorizations to prevent failure when CSR has mixed-case names Signed-off-by: Lyas Spiehler * remove .lower() method * make authorizations keys lowercase Signed-off-by: Lyas Spiehler * use lowercase keys for authorizations dict Signed-off-by: Lyas Spiehler * use new normalize_combined_identifier function to normalize identifiers * include two blank lines after functions to pass tests * Update plugins/module_utils/acme/challenges.py Co-authored-by: Felix Fontein * add changelog fragment Signed-off-by: Lyas Spiehler * Update changelogs/fragments/803-fix-authorization-failure-with-mixed-case-sans.yml Co-authored-by: Felix Fontein --------- Signed-off-by: Lyas Spiehler Co-authored-by: Felix Fontein --- ...fix-authorization-failure-with-mixed-case-sans.yml | 2 ++ plugins/module_utils/acme/challenges.py | 7 +++++++ plugins/module_utils/acme/orders.py | 3 ++- plugins/modules/acme_certificate.py | 11 ++++++----- 4 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 changelogs/fragments/803-fix-authorization-failure-with-mixed-case-sans.yml diff --git a/changelogs/fragments/803-fix-authorization-failure-with-mixed-case-sans.yml b/changelogs/fragments/803-fix-authorization-failure-with-mixed-case-sans.yml new file mode 100644 index 00000000..7e4e40e2 --- /dev/null +++ b/changelogs/fragments/803-fix-authorization-failure-with-mixed-case-sans.yml @@ -0,0 +1,2 @@ +bugfixes: + - acme_certificate - fix authorization failure when CSR contains SANs with mixed case (https://github.com/ansible-collections/community.crypto/pull/803). \ No newline at end of file diff --git a/plugins/module_utils/acme/challenges.py b/plugins/module_utils/acme/challenges.py index 116ca420..e37075a6 100644 --- a/plugins/module_utils/acme/challenges.py +++ b/plugins/module_utils/acme/challenges.py @@ -47,6 +47,13 @@ def combine_identifier(identifier_type, identifier): return '{type}:{identifier}'.format(type=identifier_type, identifier=identifier) +def normalize_combined_identifier(identifier): + identifier_type, identifier = split_identifier(identifier) + # Normalize DNS names and IPs + identifier = identifier.lower() + return combine_identifier(identifier_type, identifier) + + def split_identifier(identifier): parts = identifier.split(':', 1) if len(parts) != 2: diff --git a/plugins/module_utils/acme/orders.py b/plugins/module_utils/acme/orders.py index 98c28445..0724615c 100644 --- a/plugins/module_utils/acme/orders.py +++ b/plugins/module_utils/acme/orders.py @@ -21,6 +21,7 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import ( Authorization, + normalize_combined_identifier, ) @@ -93,7 +94,7 @@ class Order(object): def load_authorizations(self, client): for auth_uri in self.authorization_uris: authz = Authorization.from_url(client, auth_uri) - self.authorizations[authz.combined_identifier] = authz + self.authorizations[normalize_combined_identifier(authz.combined_identifier)] = authz def wait_for_finalization(self, client): while True: diff --git a/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py index 8729996c..228090ae 100644 --- a/plugins/modules/acme_certificate.py +++ b/plugins/modules/acme_certificate.py @@ -580,6 +580,7 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.account impo ) from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import ( + normalize_combined_identifier, combine_identifier, split_identifier, wait_for_validation, @@ -721,7 +722,7 @@ class ACMECertificateClient(object): raise ModuleFailException('ACME v1 only supports DNS identifiers!') for identifier_type, identifier in self.identifiers: authz = Authorization.create(self.client, identifier_type, identifier) - self.authorizations[authz.combined_identifier] = authz + self.authorizations[normalize_combined_identifier(authz.combined_identifier)] = authz else: replaces_cert_id = None if ( @@ -755,8 +756,8 @@ class ACMECertificateClient(object): if authz.status == 'valid': continue # We drop the type from the key to preserve backwards compatibility - data[identifier] = authz.get_challenge_data(self.client) - if first_step and self.challenge is not None and self.challenge not in data[identifier]: + data[authz.identifier] = authz.get_challenge_data(self.client) + if first_step and self.challenge is not None and self.challenge not in data[authz.identifier]: raise ModuleFailException("Found no challenge of type '{0}' for identifier {1}!".format( self.challenge, type_identifier)) # Get DNS challenge data @@ -835,7 +836,7 @@ class ACMECertificateClient(object): with an error. ''' for identifier_type, identifier in self.identifiers: - authz = self.authorizations.get(combine_identifier(identifier_type, identifier)) + authz = self.authorizations.get(normalize_combined_identifier(combine_identifier(identifier_type, identifier))) if authz is None: raise ModuleFailException('Found no authorization information for "{identifier}"!'.format( identifier=combine_identifier(identifier_type, identifier))) @@ -965,7 +966,7 @@ def main(): auths = dict() for k, v in client.authorizations.items(): # Remove "type:" from key - auths[split_identifier(k)[1]] = v.to_json() + auths[v.identifier] = v.to_json() module.exit_json( changed=client.changed, authorizations=auths,