From c950862b80825acaad24ed88a2ed4b467cb5660c Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Tue, 29 Apr 2025 03:28:29 +0200 Subject: [PATCH] Solved wildcard redirect bug --- library/cert_check_exists.py | 47 +--------------- library/cert_folder_find.py | 71 +++---------------------- module_utils/cert_utils.py | 12 +++++ roles/nginx-redirect-www/tasks/main.yml | 5 -- 4 files changed, 21 insertions(+), 114 deletions(-) diff --git a/library/cert_check_exists.py b/library/cert_check_exists.py index c57fdbd2..e6d34ff0 100644 --- a/library/cert_check_exists.py +++ b/library/cert_check_exists.py @@ -3,49 +3,6 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -DOCUMENTATION = r''' ---- -module: cert_check_exists -short_description: Check if a SSL certificate exists for a domain -description: - - Checks if any certificate covers the given domain. -options: - domain: - description: - - Domain name to check for in the certificates. - required: true - type: str - cert_base_path: - description: - - Path where certificates are stored. - required: false - type: str - default: /etc/letsencrypt/live - debug: - description: - - Enable verbose debug output. - required: false - type: bool - default: false -author: - - Kevin Veen-Birkenbach -''' - -EXAMPLES = r''' -- name: Check if cert exists - cert_check_exists: - domain: "matomo.cymais.cloud" - cert_base_path: "/etc/letsencrypt/live" - register: result -''' - -RETURN = r''' -exists: - description: True if a certificate covering the domain exists, false otherwise. - type: bool - returned: always -''' - import os from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.cert_utils import CertUtils @@ -59,7 +16,7 @@ def cert_exists(domain, cert_files, debug=False): if debug: print(f"Checking {cert_path}: {sans}") for entry in sans: - if entry == domain or (entry.startswith('*.') and domain.endswith('.' + entry[2:])): + if CertUtils.matches(domain, entry): return True return False @@ -89,4 +46,4 @@ def main(): cert_check_exists(module) if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/library/cert_folder_find.py b/library/cert_folder_find.py index 6e306c39..7b621cf1 100644 --- a/library/cert_folder_find.py +++ b/library/cert_folder_find.py @@ -3,58 +3,9 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -DOCUMENTATION = r''' ---- -module: cert_folder_find -short_description: Find SSL certificate folder covering a given domain -description: - - Searches through certificates to find a folder that covers the given domain. -options: - domain: - description: - - Domain name to search for in the certificates. - required: true - type: str - certbot_flavor: - description: - - Certificate type. Either 'san', 'wildcard', or 'dedicated'. - required: true - type: str - cert_base_path: - description: - - Path where certificates are stored. - required: false - type: str - default: /etc/letsencrypt/live - debug: - description: - - Enable verbose debug output. - required: false - type: bool - default: false -author: - - Kevin Veen-Birkenbach -''' - -EXAMPLES = r''' -- name: Find cert folder for matomo.cymais.cloud - cert_folder_find: - domain: "matomo.cymais.cloud" - certbot_flavor: "san" - cert_base_path: "/etc/letsencrypt/live" - register: result -''' - -RETURN = r''' -folder: - description: The name of the folder covering the domain. - type: str - returned: always -''' - import os from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.cert_utils import CertUtils # IMPORT +from ansible.module_utils.cert_utils import CertUtils def find_matching_folders(domain, cert_files, flavor, debug): exact_matches = [] @@ -68,12 +19,12 @@ def find_matching_folders(domain, cert_files, flavor, debug): if debug: print(f"Checking {cert_path}: {sans}") for entry in sans: - if entry == domain: - exact_matches.append(os.path.dirname(cert_path)) - elif entry.startswith('*.'): - base = entry[2:] - if domain.endswith('.' + base): - wildcard_matches.append(os.path.dirname(cert_path)) + if CertUtils.matches(domain, entry): + folder = os.path.dirname(cert_path) + if entry.startswith('*.'): + wildcard_matches.append(folder) + else: + exact_matches.append(folder) if flavor in ('san', 'dedicated'): return exact_matches or wildcard_matches @@ -95,14 +46,6 @@ def cert_folder_find(module): preferred = find_matching_folders(domain, cert_files, certbot_flavor, debug) - if not preferred and certbot_flavor == 'san': - if debug: - print("Fallback: searching SAN matches without SAN structure parsing") - for cert_path in cert_files: - cert_text = CertUtils.run_openssl(cert_path) - if f"DNS:{domain}" in cert_text: - preferred.append(os.path.dirname(cert_path)) - if not preferred: module.fail_json(msg=f"No certificate covering domain {domain} found.") diff --git a/module_utils/cert_utils.py b/module_utils/cert_utils.py index a0569e39..c2d711e8 100644 --- a/module_utils/cert_utils.py +++ b/module_utils/cert_utils.py @@ -37,3 +37,15 @@ class CertUtils: if 'cert.pem' in files: cert_files.append(os.path.join(root, 'cert.pem')) return cert_files + + @staticmethod + def matches(domain, san): + """Check if the SAN entry matches the domain according to wildcard rules.""" + if san.startswith('*.'): + base = san[2:] + # Check if domain is direct subdomain (one label only) + if domain.count('.') == base.count('.') + 1 and domain.endswith('.' + base): + return True + return False + else: + return domain == san diff --git a/roles/nginx-redirect-www/tasks/main.yml b/roles/nginx-redirect-www/tasks/main.yml index 3eb45230..33b6b7f4 100644 --- a/roles/nginx-redirect-www/tasks/main.yml +++ b/roles/nginx-redirect-www/tasks/main.yml @@ -1,9 +1,4 @@ --- -- name: "Debug: all_domains" - debug: - var: all_domains - when: enable_debug - - name: Filter www-prefixed domains from all_domains set_fact: www_domains: "{{ all_domains | select('match', '^www\\.') | list }}"