Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/user/certificates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/roles/certificate_checks/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/roles/certificates/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
79 changes: 38 additions & 41 deletions src/roles/certificates/tasks/ca.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
- name: 'Install openssl'
- name: 'Install crypto dependencies'
ansible.builtin.package:
name: openssl
name:
- python3-cryptography
Comment thread
stejskalleos marked this conversation as resolved.
state: present

- name: 'Create certs directory'
Expand All @@ -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 }}"
Expand All @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding this helped, but now I am confused, the old OpenSSL config doesn't add that to the CA cert…

Copy link
Copy Markdown
Contributor Author

@stejskalleos stejskalleos Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the original openssl.cnf, the digitalSignature is mentioned in ssl_server and ssl_client sections:

[ 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"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. But this is the CA.

Copy link
Copy Markdown
Member

@evgeni evgeni Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for comparison, this is what gets generated by the various implementations we have

old openssl code:

        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:TRUE
            Netscape Cert Type: 
                SSL CA
            X509v3 Key Usage: 
                Certificate Sign, CRL Sign
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication

your ansible code:

        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Certificate Sign, CRL Sign
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:TRUE

old katello-certs-tools:

            X509v3 Basic Constraints: 
                CA:TRUE
            X509v3 Key Usage: 
                Digital Signature, Key Encipherment, Certificate Sign, CRL Sign
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            Netscape Cert Type: 
                SSL Server, SSL CA

why is key usage marked critical for your code?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aha, I think that's what fooling us here. we need digital signature, but without the usage being marked as critical, this is actually not enforced by the consumer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yepp, just tried it out, dropping key_usage_critical: true "fixes" it too.

now, I'd argue that setting key_usage_critical: true as you did is more correct here, but not sure about the implications.

@ehelms opinions? should we aim at bug-for-bug compatibility with katello-certs-tools or rather not? (I'd prefer not)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think aiming to be "correct" for the new certificate generation code is the way to go. What I worry about is, what happens to installer-based certificates that are inherited and continue to be managed (see #421). Will this try and trigger a regeneration of the CA?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modules might, yes. We talked about a related issue yesterday that people should not get the CA regenerated when e.g. the expiration date changes (it is currently relative to the date the execution happens) and ended up saying that for CA-changing operations we'd require an explicit flag (something like "allow CA changes") so that the CA won't change once generated unless explicitly requested.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, docs say "not valid after" is "not used to determine whether an existing certificate should be regenerated. ", cool.
(The rest still applies, ofc)

key_usage_critical: true
Comment thread
stejskalleos marked this conversation as resolved.
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"
115 changes: 55 additions & 60 deletions src/roles/certificates/tasks/issue.yml
Original file line number Diff line number Diff line change
@@ -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
Comment thread
stejskalleos marked this conversation as resolved.
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"
71 changes: 0 additions & 71 deletions src/roles/certificates/templates/openssl.cnf.j2

This file was deleted.

1 change: 0 additions & 1 deletion src/roles/certificates/templates/serial.j2

This file was deleted.