From 41d06e4e6d3d43372e65a05d3ae642a9525d6fc6 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Mon, 7 Sep 2026 15:02:55 +0200 Subject: [PATCH] feat!: apply subject alternative names to root and intermediate CA The variables `certificate_authority_root_ca_subject_alternative_names` and `certificate_authority_intermediate_ca_subject_alternative_names` were documented but never referenced by any task, so both CA certificates were always issued without SANs. Wire them into the corresponding CSR tasks and fall back to `omit` when the list is empty. SAN entries are now passed to `openssl_csr` unchanged instead of being prefixed with `DNS:` by the role. This allows other types such as `IP:` or `email:`, which the previous rewrite would have corrupted into values like `DNS:IP:...`. The client tasks additionally dropped `join(',') | quote`, because `quote` performs shell escaping and `openssl_csr` expects a list. Since both client CSR tasks only differed in `subject_alt_name`, they collapse into a single task per file. BREAKING CHANGE: Entries of all `*_subject_alternative_names` variables must now carry their type prefix, for example `DNS:example.local` instead of `example.local`. Co-authored-by: Copilot --- README.md | 11 ++++---- defaults/main.yaml | 6 ++--- meta/argument_specs.yaml | 6 ++--- tasks/client_certificate_protected.yaml | 25 ++----------------- tasks/client_certificate_unprotected.yaml | 24 ++---------------- ...diate_certificate_authority_protected.yaml | 1 + ...ate_certificate_authority_unprotected.yaml | 1 + .../root_certificate_authority_protected.yaml | 1 + ...oot_certificate_authority_unprotected.yaml | 1 + 9 files changed, 20 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index d624375..12e794a 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,9 @@ the intermediate certificate authority. certificate_authority_client_skip: false certificate_authority_client_common_name: "{{ inventory_hostname }}" certificate_authority_client_subject_alternative_names: -- "{{ inventory_hostname }}" -- san.example.local +- "DNS:{{ inventory_hostname }}" +- "DNS:san.example.local" +- "IP:10.11.12.13" ``` ## Parameters @@ -33,7 +34,7 @@ certificate_authority_client_subject_alternative_names: | `certificate_authority_root_ca_organization_name` | Organization name of the root certificate authority owner. | `""` | | `certificate_authority_root_ca_organizational_unit_name` | Organizational unit name of the root certificate authority. | `""` | | `certificate_authority_root_ca_state_or_province_name` | State or province name where the owner of the root certificate authority is located. | `""` | -| `certificate_authority_root_ca_subject_alternative_names` | Subject Alternative Names (SAN) of the root certificate authority. | `[]` | +| `certificate_authority_root_ca_subject_alternative_names` | Subject Alternative Names (SAN) of the root certificate authority. Each entry must be prefixed by its type, for example `DNS:example.local` or `IP:10.11.12.13`. | `[]` | | `certificate_authority_root_ca_not_after` | Time in the future from now when the TLS certificate should expire | `+3650d` | | `certificate_authority_root_ca_not_before` | Time in the past from now when the TLS certificate should be valid. | `+0s` | | `certificate_authority_root_ca_tls_key_content` | Content of a custom used root certificate authority. Will only be imported, when `certificate_authority_root_ca_create: false`. | `""` | @@ -54,7 +55,7 @@ certificate_authority_client_subject_alternative_names: | `certificate_authority_intermediate_ca_organization_name` | Organization name of the intermediate certificate authority owner. | `""` | | `certificate_authority_intermediate_ca_organizational_unit_name` | Organizational unit name of the intermediate certificate authority. | `""` | | `certificate_authority_intermediate_ca_state_or_province_name` | State or province name where the owner of the intermediate certificate authority is located. | `""` | -| `certificate_authority_intermediate_ca_subject_alternative_names` | Subject Alternative Names (SAN) of the intermediate certificate authority. | `[]` | +| `certificate_authority_intermediate_ca_subject_alternative_names` | Subject Alternative Names (SAN) of the intermediate certificate authority. Each entry must be prefixed by its type, for example `DNS:example.local` or `IP:10.11.12.13`. | `[]` | | `certificate_authority_intermediate_ca_not_after` | Time in the future from now when the TLS certificate should expire | `+1825d` | | `certificate_authority_intermediate_ca_not_before` | Time in the past from now when the TLS certificate should be valid. | `+0s` | | `certificate_authority_intermediate_ca_tls_key_content` | Content of a custom used intermediate certificate authority. Will only be imported, when `certificate_authority_intermediate_ca_create: false`. | `""` | @@ -75,7 +76,7 @@ certificate_authority_client_subject_alternative_names: | `certificate_authority_client_organization_name` | Organization name of the client certificate owner. | `""` | | `certificate_authority_client_organizational_unit_name` | Common Name (CN) of the client certificate. | `""` | | `certificate_authority_client_state_or_province_name` | State or province name where the owner of the client certificate is located. | `""` | -| `certificate_authority_client_subject_alternative_names` | Subject Alternative Names (SAN) of the client certificate. | `[]` | +| `certificate_authority_client_subject_alternative_names` | Subject Alternative Names (SAN) of the client certificate. Each entry must be prefixed by its type, for example `DNS:example.local` or `IP:10.11.12.13`. | `[]` | | `certificate_authority_client_not_after` | Time in the future from now when the TLS certificate should expire | `+397d` | | `certificate_authority_client_not_before` | Time in the past from now when the TLS certificate should be valid. | `+0s` | | `certificate_authority_client_tls_key_passphrase` | Passphrase for the private key of the generated or imported client certificate. | `""` | diff --git a/defaults/main.yaml b/defaults/main.yaml index 2afccd7..40146a1 100644 --- a/defaults/main.yaml +++ b/defaults/main.yaml @@ -15,7 +15,7 @@ certificate_authority_root_ca_import: true ## @param certificate_authority_root_ca_organization_name Organization name of the root certificate authority owner. ## @param certificate_authority_root_ca_organizational_unit_name Organizational unit name of the root certificate authority. ## @param certificate_authority_root_ca_state_or_province_name State or province name where the owner of the root certificate authority is located. -## @param certificate_authority_root_ca_subject_alternative_names Subject Alternative Names (SAN) of the root certificate authority. +## @param certificate_authority_root_ca_subject_alternative_names Subject Alternative Names (SAN) of the root certificate authority. Each entry must be prefixed by its type, for example `DNS:example.local` or `IP:10.11.12.13`. ## @param certificate_authority_root_ca_not_after Time in the future from now when the TLS certificate should expire ## @param certificate_authority_root_ca_not_before Time in the past from now when the TLS certificate should be valid. certificate_authority_root_ca_path: "/etc/ansible-playbook/pki/ca" @@ -53,7 +53,7 @@ certificate_authority_intermediate_ca_create: true ## @param certificate_authority_intermediate_ca_organization_name Organization name of the intermediate certificate authority owner. ## @param certificate_authority_intermediate_ca_organizational_unit_name Organizational unit name of the intermediate certificate authority. ## @param certificate_authority_intermediate_ca_state_or_province_name State or province name where the owner of the intermediate certificate authority is located. -## @param certificate_authority_intermediate_ca_subject_alternative_names Subject Alternative Names (SAN) of the intermediate certificate authority. +## @param certificate_authority_intermediate_ca_subject_alternative_names Subject Alternative Names (SAN) of the intermediate certificate authority. Each entry must be prefixed by its type, for example `DNS:example.local` or `IP:10.11.12.13`. ## @param certificate_authority_intermediate_ca_not_after Time in the future from now when the TLS certificate should expire ## @param certificate_authority_intermediate_ca_not_before Time in the past from now when the TLS certificate should be valid. certificate_authority_intermediate_ca_path: "/etc/ansible-playbook/pki/intermediate" @@ -91,7 +91,7 @@ certificate_authority_client_create: true ## @param certificate_authority_client_organization_name Organization name of the client certificate owner. ## @param certificate_authority_client_organizational_unit_name Common Name (CN) of the client certificate. ## @param certificate_authority_client_state_or_province_name State or province name where the owner of the client certificate is located. -## @param certificate_authority_client_subject_alternative_names Subject Alternative Names (SAN) of the client certificate. +## @param certificate_authority_client_subject_alternative_names Subject Alternative Names (SAN) of the client certificate. Each entry must be prefixed by its type, for example `DNS:example.local` or `IP:10.11.12.13`. ## @param certificate_authority_client_not_after Time in the future from now when the TLS certificate should expire ## @param certificate_authority_client_not_before Time in the past from now when the TLS certificate should be valid. certificate_authority_client_path: "/etc/ansible-playbook/pki/client" diff --git a/meta/argument_specs.yaml b/meta/argument_specs.yaml index 5cbd3b1..c5451bd 100644 --- a/meta/argument_specs.yaml +++ b/meta/argument_specs.yaml @@ -49,7 +49,7 @@ argument_specs: type: str default: "" certificate_authority_root_ca_subject_alternative_names: - description: "Subject Alternative Names (SAN) of the root certificate authority." + description: "Subject Alternative Names (SAN) of the root certificate authority. Each entry must be prefixed by its type, for example DNS:example.local or IP:10.11.12.13." type: list elements: str default: [] @@ -121,7 +121,7 @@ argument_specs: type: str default: "" certificate_authority_intermediate_ca_subject_alternative_names: - description: "Subject Alternative Names (SAN) of the intermediate certificate authority." + description: "Subject Alternative Names (SAN) of the intermediate certificate authority. Each entry must be prefixed by its type, for example DNS:example.local or IP:10.11.12.13." type: list elements: str default: [] @@ -193,7 +193,7 @@ argument_specs: type: str default: "" certificate_authority_client_subject_alternative_names: - description: "Subject Alternative Names (SAN) of the client certificate." + description: "Subject Alternative Names (SAN) of the client certificate. Each entry must be prefixed by its type, for example DNS:example.local or IP:10.11.12.13." type: list elements: str default: [] diff --git a/tasks/client_certificate_protected.yaml b/tasks/client_certificate_protected.yaml index b6e7423..73d8676 100644 --- a/tasks/client_certificate_protected.yaml +++ b/tasks/client_certificate_protected.yaml @@ -7,7 +7,7 @@ passphrase: "{{ certificate_authority_client_tls_key_passphrase }}" cipher: auto -- name: Create a certificate signing request (CSR) for client certificate without subject alternative names (SANs) +- name: Create a certificate signing request (CSR) for client certificate community.crypto.openssl_csr: common_name: "{{ certificate_authority_client_common_name }}" countryName: "{{ certificate_authority_client_country_name }}" @@ -21,28 +21,7 @@ privatekey_passphrase: "{{ certificate_authority_client_tls_key_passphrase }}" privatekey_path: "{{ certificate_authority_client_path }}/privkey.pem" state_or_province_name: "{{ certificate_authority_client_state_or_province_name }}" - when: | - certificate_authority_client_subject_alternative_names is not defined or - (certificate_authority_client_subject_alternative_names is defined and - certificate_authority_client_subject_alternative_names | length <= 0) - -- name: Create a certificate signing request (CSR) for client certificate with subject alternative names (SANs) - community.crypto.openssl_csr: - common_name: "{{ certificate_authority_client_common_name }}" - countryName: "{{ certificate_authority_client_country_name }}" - email_address: "{{ certificate_authority_client_email_address }}" - extendedKeyUsage: - - clientAuth - - serverAuth - organization_name: "{{ certificate_authority_client_organization_name }}" - organizational_unit_name: "{{ certificate_authority_client_organizational_unit_name }}" - path: "{{ certificate_authority_client_path }}/cert-req.pem" - privatekey_path: "{{ certificate_authority_client_path }}/privkey.pem" - privatekey_passphrase: "{{ certificate_authority_client_tls_key_passphrase }}" - state_or_province_name: "{{ certificate_authority_client_state_or_province_name }}" - subject_alt_name: "{{ certificate_authority_client_subject_alternative_names | map('regex_replace', '^', 'DNS:') | list | join(',') | quote }}" - when: certificate_authority_client_subject_alternative_names is defined and - certificate_authority_client_subject_alternative_names | length > 0 + subject_alt_name: "{{ certificate_authority_client_subject_alternative_names if certificate_authority_client_subject_alternative_names | length > 0 else omit }}" - name: Create signed client certificate - unprotected intermediate Certificate Authority (CA) community.crypto.x509_certificate: diff --git a/tasks/client_certificate_unprotected.yaml b/tasks/client_certificate_unprotected.yaml index 4fbaa65..1094bc4 100644 --- a/tasks/client_certificate_unprotected.yaml +++ b/tasks/client_certificate_unprotected.yaml @@ -5,7 +5,7 @@ path: "{{ certificate_authority_client_path }}/privkey.pem" type: "{{ certificate_authority_client_tls_key_type }}" -- name: Create a certificate signing request (CSR) for client certificate without subject alternative names (SANs) +- name: Create a certificate signing request (CSR) for client certificate community.crypto.openssl_csr: common_name: "{{ certificate_authority_client_common_name }}" countryName: "{{ certificate_authority_client_country_name }}" @@ -18,27 +18,7 @@ path: "{{ certificate_authority_client_path }}/cert-req.pem" privatekey_path: "{{ certificate_authority_client_path }}/privkey.pem" state_or_province_name: "{{ certificate_authority_client_state_or_province_name }}" - when: | - certificate_authority_client_subject_alternative_names is not defined or - (certificate_authority_client_subject_alternative_names is defined and - certificate_authority_client_subject_alternative_names | length <= 0) - -- name: Create a certificate signing request (CSR) for client certificate with subject alternative names (SANs) - community.crypto.openssl_csr: - common_name: "{{ certificate_authority_client_common_name }}" - countryName: "{{ certificate_authority_client_country_name }}" - email_address: "{{ certificate_authority_client_email_address }}" - extendedKeyUsage: - - clientAuth - - serverAuth - organization_name: "{{ certificate_authority_client_organization_name }}" - organizational_unit_name: "{{ certificate_authority_client_organizational_unit_name }}" - path: "{{ certificate_authority_client_path }}/cert-req.pem" - privatekey_path: "{{ certificate_authority_client_path }}/privkey.pem" - state_or_province_name: "{{ certificate_authority_client_state_or_province_name }}" - subject_alt_name: "{{ certificate_authority_client_subject_alternative_names | map('regex_replace', '^', 'DNS:') | list | join(',') | quote }}" - when: certificate_authority_client_subject_alternative_names is defined and - certificate_authority_client_subject_alternative_names | length > 0 + subject_alt_name: "{{ certificate_authority_client_subject_alternative_names if certificate_authority_client_subject_alternative_names | length > 0 else omit }}" - name: Create signed client certificate - unprotected intermediate Certificate Authority (CA) community.crypto.x509_certificate: diff --git a/tasks/intermediate_certificate_authority_protected.yaml b/tasks/intermediate_certificate_authority_protected.yaml index 45fbec7..8ca2bfa 100644 --- a/tasks/intermediate_certificate_authority_protected.yaml +++ b/tasks/intermediate_certificate_authority_protected.yaml @@ -20,6 +20,7 @@ privatekey_passphrase: "{{ certificate_authority_intermediate_ca_tls_key_passphrase }}" privatekey_path: "{{ certificate_authority_intermediate_ca_path }}/privkey.pem" state_or_province_name: "{{ certificate_authority_intermediate_ca_state_or_province_name }}" + subject_alt_name: "{{ certificate_authority_intermediate_ca_subject_alternative_names if certificate_authority_intermediate_ca_subject_alternative_names | length > 0 else omit }}" use_common_name_for_san: false - name: Create signed client certificate - unprotected root Certificate Authority (CA) diff --git a/tasks/intermediate_certificate_authority_unprotected.yaml b/tasks/intermediate_certificate_authority_unprotected.yaml index fbc80ed..f919219 100644 --- a/tasks/intermediate_certificate_authority_unprotected.yaml +++ b/tasks/intermediate_certificate_authority_unprotected.yaml @@ -17,6 +17,7 @@ path: "{{ certificate_authority_intermediate_ca_path }}/cert-req.pem" privatekey_path: "{{ certificate_authority_intermediate_ca_path }}/privkey.pem" state_or_province_name: "{{ certificate_authority_intermediate_ca_state_or_province_name }}" + subject_alt_name: "{{ certificate_authority_intermediate_ca_subject_alternative_names if certificate_authority_intermediate_ca_subject_alternative_names | length > 0 else omit }}" use_common_name_for_san: false - name: Create signed client certificate - unprotected root Certificate Authority (CA) diff --git a/tasks/root_certificate_authority_protected.yaml b/tasks/root_certificate_authority_protected.yaml index 1f29c2d..ee73456 100644 --- a/tasks/root_certificate_authority_protected.yaml +++ b/tasks/root_certificate_authority_protected.yaml @@ -20,6 +20,7 @@ privatekey_passphrase: "{{ certificate_authority_root_ca_tls_key_passphrase }}" privatekey_path: "{{ certificate_authority_root_ca_path }}/privkey.pem" state_or_province_name: "{{ certificate_authority_root_ca_state_or_province_name }}" + subject_alt_name: "{{ certificate_authority_root_ca_subject_alternative_names if certificate_authority_root_ca_subject_alternative_names | length > 0 else omit }}" use_common_name_for_san: false - name: Create self-signed certificate for root CA diff --git a/tasks/root_certificate_authority_unprotected.yaml b/tasks/root_certificate_authority_unprotected.yaml index ebcec28..108c5a4 100644 --- a/tasks/root_certificate_authority_unprotected.yaml +++ b/tasks/root_certificate_authority_unprotected.yaml @@ -17,6 +17,7 @@ path: "{{ certificate_authority_root_ca_path }}/cert-req.pem" privatekey_path: "{{ certificate_authority_root_ca_path }}/privkey.pem" state_or_province_name: "{{ certificate_authority_root_ca_state_or_province_name }}" + subject_alt_name: "{{ certificate_authority_root_ca_subject_alternative_names if certificate_authority_root_ca_subject_alternative_names | length > 0 else omit }}" use_common_name_for_san: false - name: Create self-signed certificate for root CA