diff --git a/docs/user/certificates.md b/docs/user/certificates.md index cbcca750f..93cbcdb06 100644 --- a/docs/user/certificates.md +++ b/docs/user/certificates.md @@ -65,6 +65,12 @@ foremanctl deploy \ When CNAMEs are specified, certificates will include all names in the Subject Alternative Name field, allowing the same certificate to be valid for multiple hostnames. +If certificates already exist from a previous deployment, passing `--certificate-cname` will automatically regenerate the server certificate and CSR to include the new CNAMEs. Existing private keys are reused. + +To remove all CNAMEs from an existing certificate, use `--reset-certificate-cname`. + +On each run the role compares the Subject Alternative Names in the existing server certificate against the desired list (hostname + any `--certificate-cname` values). If they differ, the CSR and certificate are regenerated automatically. This means both adding and removing CNAMEs are handled transparently without manual cleanup. + ### Current Limitations - Cannot provide custom certificate files during deployment diff --git a/src/roles/certificate_checks/tasks/main.yml b/src/roles/certificate_checks/tasks/main.yml index 3e05f6fa5..987ec41d3 100644 --- a/src/roles/certificate_checks/tasks/main.yml +++ b/src/roles/certificate_checks/tasks/main.yml @@ -1,4 +1,10 @@ --- +- name: Install openssl + ansible.builtin.package: + name: + - openssl + state: present + - name: Install foreman-certificate-check ansible.builtin.copy: src: foreman-certificate-check diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index 2c7c67b2d..de5b54c0e 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -5,3 +5,5 @@ certificates_ca_directory_keys: "{{ certificates_ca_directory }}/private" certificates_ca_directory_certs: "{{ certificates_ca_directory }}/certs" certificates_ca_directory_requests: "{{ certificates_ca_directory }}/requests" certificates_cnames: [] +certificates_algorithm_type: RSA +certificates_algorithm_size: 4096 diff --git a/src/roles/certificates/tasks/ca.yml b/src/roles/certificates/tasks/ca.yml index f426dd735..cc7011f63 100644 --- a/src/roles/certificates/tasks/ca.yml +++ b/src/roles/certificates/tasks/ca.yml @@ -1,7 +1,8 @@ --- -- name: 'Install openssl' +- name: 'Install crypto dependencies' ansible.builtin.package: - name: openssl + name: + - python3-cryptography state: present - name: 'Create certs directory' @@ -22,31 +23,6 @@ state: directory mode: '0755' -- name: 'Deploy configuration file' - ansible.builtin.template: - src: openssl.cnf.j2 - dest: "{{ certificates_ca_directory }}/openssl.cnf" - owner: root - group: root - mode: '0644' - -- name: 'Create index file' - ansible.builtin.file: - path: "{{ certificates_ca_directory }}/index.txt" - state: touch - owner: root - group: root - mode: '0644' - -- name: 'Ensure serial starting number' - ansible.builtin.template: - src: serial.j2 - dest: "{{ certificates_ca_directory }}/serial" - force: false - owner: root - group: root - mode: '0644' - - name: 'Create CA key password file' ansible.builtin.copy: content: "{{ certificates_ca_password }}" @@ -56,17 +32,38 @@ mode: '0600' no_log: true -- name: 'Creating CA certificate and key' - ansible.builtin.command: > - openssl req -new - -x509 - -nodes - -extensions v3_ca - -days 7300 - -config "{{ certificates_ca_directory }}/openssl.cnf" - -subj "/CN=Foreman Self-signed CA" - -keyout "{{ certificates_ca_directory_keys }}/ca.key" - -out "{{ certificates_ca_directory_certs }}/ca.crt" - -passout "file:{{ certificates_ca_directory_keys }}/ca.pwd" - args: - creates: "{{ certificates_ca_directory_certs }}/ca.crt" +- name: 'Create CA private key' + community.crypto.openssl_privatekey: + path: "{{ certificates_ca_directory_keys }}/ca.key" + type: "{{ certificates_algorithm_type }}" + size: "{{ certificates_algorithm_size }}" + passphrase: "{{ certificates_ca_password }}" + owner: root + group: root + mode: '0600' + +- name: 'Create CA certificate signing request' + community.crypto.openssl_csr: + path: "{{ certificates_ca_directory_requests }}/ca.csr" + privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key" + privatekey_passphrase: "{{ certificates_ca_password }}" + common_name: "Foreman Self-signed CA" + use_common_name_for_san: false + basic_constraints: + - 'CA:TRUE' + basic_constraints_critical: true + key_usage: + - keyCertSign + - cRLSign + - digitalSignature + key_usage_critical: true + create_subject_key_identifier: true + +- name: 'Create self-signed CA certificate' + community.crypto.x509_certificate: + path: "{{ certificates_ca_directory_certs }}/ca.crt" + csr_path: "{{ certificates_ca_directory_requests }}/ca.csr" + privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key" + privatekey_passphrase: "{{ certificates_ca_password }}" + provider: selfsigned + selfsigned_not_after: "+7300d" diff --git a/src/roles/certificates/tasks/issue.yml b/src/roles/certificates/tasks/issue.yml index 2d4796a74..7eed99050 100644 --- a/src/roles/certificates/tasks/issue.yml +++ b/src/roles/certificates/tasks/issue.yml @@ -1,66 +1,61 @@ --- -- name: 'Create server key' - ansible.builtin.command: > - openssl genrsa - -out "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}.key" - 4096 - args: - creates: "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}.key" +- name: 'Create server private key' + community.crypto.openssl_privatekey: + path: "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}.key" + type: "{{ certificates_algorithm_type }}" + size: "{{ certificates_algorithm_size }}" + mode: '0600' -- name: 'Creating server signing request' - ansible.builtin.command: > - openssl req - -new - -config "{{ certificates_ca_directory }}/openssl.cnf" - -key "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}.key" - -subj "/CN={{ certificates_hostname }}" - -addext "subjectAltName = DNS:{{ certificates_hostname }}{% for cname in certificates_cnames %},DNS:{{ cname }}{% endfor %}" - -out "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}.csr" - args: - creates: "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}.csr" +- name: 'Create server CSR' + community.crypto.openssl_csr: + path: "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}.csr" + privatekey_path: "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}.key" + common_name: "{{ certificates_hostname }}" + subject_alt_name: "{{ _certificates_desired_server_sans }}" + key_usage: + - digitalSignature + - keyEncipherment + extended_key_usage: + - serverAuth + vars: + _certificates_desired_server_sans: "{{ (([certificates_hostname] + certificates_cnames) | map('regex_replace', '^', 'DNS:') | list) }}" -- name: 'Sign server signing request' - ansible.builtin.command: > - openssl ca - -create_serial - -batch - -extensions ssl_server - -config "{{ certificates_ca_directory }}/openssl.cnf" - -passin "file:{{ certificates_ca_directory_keys }}/ca.pwd" - -in "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}.csr" - -out "{{ certificates_ca_directory_certs }}/{{ certificates_hostname }}.crt" - args: - creates: "{{ certificates_ca_directory_certs }}/{{ certificates_hostname }}.crt" +- name: 'Sign server certificate' + community.crypto.x509_certificate: + path: "{{ certificates_ca_directory_certs }}/{{ certificates_hostname }}.crt" + csr_path: "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}.csr" + provider: ownca + ownca_path: "{{ certificates_ca_directory_certs }}/ca.crt" + ownca_privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key" + ownca_privatekey_passphrase: "{{ certificates_ca_password }}" + ownca_not_after: "+7300d" -- name: 'Create client key' - ansible.builtin.command: > - openssl genrsa - -out "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}-client.key" - 4096 - args: - creates: "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}-client.key" +- name: 'Create client private key' + community.crypto.openssl_privatekey: + path: "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}-client.key" + type: "{{ certificates_algorithm_type }}" + size: "{{ certificates_algorithm_size }}" + mode: '0600' -- name: 'Creating client signing request' - ansible.builtin.command: > - openssl req - -new - -config "{{ certificates_ca_directory }}/openssl.cnf" - -key "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}-client.key" - -addext "subjectAltName = DNS:{{ certificates_hostname }}" - -subj "/CN={{ certificates_hostname }}" - -out "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}-client.csr" - args: - creates: "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}-client.csr" +- name: 'Create client CSR' + community.crypto.openssl_csr: + path: "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}-client.csr" + privatekey_path: "{{ certificates_ca_directory_keys }}/{{ certificates_hostname }}-client.key" + common_name: "{{ certificates_hostname }}" + subject_alt_name: + - "DNS:{{ certificates_hostname }}" + key_usage: + - digitalSignature + - keyEncipherment + extended_key_usage: + - clientAuth -- name: 'Sign client signing request' - ansible.builtin.command: > - openssl ca - -create_serial - -batch - -extensions ssl_client - -config "{{ certificates_ca_directory }}/openssl.cnf" - -passin "file:{{ certificates_ca_directory_keys }}/ca.pwd" - -in "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}-client.csr" - -out "{{ certificates_ca_directory_certs }}/{{ certificates_hostname }}-client.crt" - args: - creates: "{{ certificates_ca_directory_certs }}/{{ certificates_hostname }}-client.crt" +- name: 'Sign client certificate' + community.crypto.x509_certificate: + path: "{{ certificates_ca_directory_certs }}/{{ certificates_hostname }}-client.crt" + csr_path: "{{ certificates_ca_directory_requests }}/{{ certificates_hostname }}-client.csr" + provider: ownca + ownca_path: "{{ certificates_ca_directory_certs }}/ca.crt" + ownca_privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key" + ownca_privatekey_passphrase: "{{ certificates_ca_password }}" + ownca_not_after: "+7300d" diff --git a/src/roles/certificates/templates/openssl.cnf.j2 b/src/roles/certificates/templates/openssl.cnf.j2 deleted file mode 100644 index e1bb7326c..000000000 --- a/src/roles/certificates/templates/openssl.cnf.j2 +++ /dev/null @@ -1,71 +0,0 @@ -# {{ ansible_managed }} -# -#--------------------------------------------------------------------------- -# OpenSSL configuration file. - -dir = {{ certificates_ca_directory }} - -[ ca ] -default_ca = CA_default -x509_extensions = v3_ca - -[ CA_default ] -serial = $dir/serial -database = $dir/index.txt -new_certs_dir = $dir/certs -private_key = $dir/private/ca.key -certificate = $dir/certs/ca.crt -default_md = sha256 -default_days = 7300 - -preserve = no -email_in_dn = no -policy = policy_match -nameopt = default_ca -certopt = default_ca - -unique_subject = no -copy_extensions = copy - -[ policy_match ] -countryName = optional -stateOrProvinceName = optional -localityName = optional -organizationName = optional -organizationalUnitName = optional -commonName = supplied -emailAddress = optional - -[ req ] -default_bits = 4096 -default_keyfile = privkey.pem -prompt = no -basicConstraints = CA:FALSE -subjectKeyIdentifier = hash -extendedKeyUsage = serverAuth, clientAuth - -[ v3_ca ] -basicConstraints = CA:TRUE -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always,issuer:always -nsCertType = sslCA -keyUsage = cRLSign, keyCertSign -extendedKeyUsage = serverAuth, clientAuth - -[ ssl_server ] -basicConstraints = CA:FALSE -nsCertType = server -keyUsage = digitalSignature, keyEncipherment -extendedKeyUsage = serverAuth, nsSGC, msSGC -nsComment = "OpenSSL Certificate for SSL Web Server" - -[ ssl_client ] -basicConstraints = CA:FALSE -nsCertType = client -keyUsage = digitalSignature, keyEncipherment -extendedKeyUsage = clientAuth -nsComment = "OpenSSL Certificate for SSL Client" - -[ signing_req ] -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer diff --git a/src/roles/certificates/templates/serial.j2 b/src/roles/certificates/templates/serial.j2 deleted file mode 100644 index 83b33d238..000000000 --- a/src/roles/certificates/templates/serial.j2 +++ /dev/null @@ -1 +0,0 @@ -1000