From e5ce60dce2952914d2cc98ad6ad2679845876764 Mon Sep 17 00:00:00 2001 From: Shimon Shtein Date: Tue, 14 Apr 2026 11:35:21 +0300 Subject: [PATCH 1/6] Parameterize the expiry date --- src/roles/certificates/defaults/main.yml | 4 ++++ src/roles/certificates/tasks/ca.yml | 2 +- src/roles/certificates/tasks/issue.yml | 4 ++-- src/vars/default_certificates.yml | 3 +++ src/vars/installer_certificates.yml | 3 +++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index de5b54c0e..344d9122b 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -7,3 +7,7 @@ certificates_ca_directory_requests: "{{ certificates_ca_directory }}/requests" certificates_cnames: [] certificates_algorithm_type: RSA certificates_algorithm_size: 4096 +# Validity periods in days (used as +Nd for community.crypto not_after). +certificates_ca_validity_days: 7300 +certificates_server_validity_days: 7300 +certificates_client_validity_days: 7300 diff --git a/src/roles/certificates/tasks/ca.yml b/src/roles/certificates/tasks/ca.yml index cc7011f63..605f0661c 100644 --- a/src/roles/certificates/tasks/ca.yml +++ b/src/roles/certificates/tasks/ca.yml @@ -66,4 +66,4 @@ privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key" privatekey_passphrase: "{{ certificates_ca_password }}" provider: selfsigned - selfsigned_not_after: "+7300d" + selfsigned_not_after: "+{{ certificates_ca_validity_days }}d" diff --git a/src/roles/certificates/tasks/issue.yml b/src/roles/certificates/tasks/issue.yml index 7eed99050..9191410c4 100644 --- a/src/roles/certificates/tasks/issue.yml +++ b/src/roles/certificates/tasks/issue.yml @@ -28,7 +28,7 @@ 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" + ownca_not_after: "+{{ certificates_server_validity_days }}d" - name: 'Create client private key' community.crypto.openssl_privatekey: @@ -58,4 +58,4 @@ 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" + ownca_not_after: "+{{ certificates_client_validity_days }}d" diff --git a/src/vars/default_certificates.yml b/src/vars/default_certificates.yml index 09f47c5c9..8a20d9bd5 100644 --- a/src/vars/default_certificates.yml +++ b/src/vars/default_certificates.yml @@ -1,4 +1,7 @@ --- +certificates_ca_validity_days: 7300 +certificates_server_validity_days: 7300 +certificates_client_validity_days: 7300 certificates_ca_directory: /root/certificates ca_key_password: "{{ certificates_ca_directory }}/private/ca.pwd" ca_certificate: "{{ certificates_ca_directory }}/certs/ca.crt" diff --git a/src/vars/installer_certificates.yml b/src/vars/installer_certificates.yml index c6ab83af3..1fee61c43 100644 --- a/src/vars/installer_certificates.yml +++ b/src/vars/installer_certificates.yml @@ -1,4 +1,7 @@ --- +certificates_ca_validity_days: 7300 +certificates_server_validity_days: 7300 +certificates_client_validity_days: 7300 ca_key_password: "/root/ssl-build/katello-default-ca.pwd" ca_certificate: "/root/ssl-build/katello-default-ca.crt" ca_key: "/root/ssl-build/katello-default-ca.key" From 57408c7fe1df01d9ed9bb28664e31d747452476c Mon Sep 17 00:00:00 2001 From: Shimon Shtein Date: Sun, 19 Apr 2026 18:01:41 +0300 Subject: [PATCH 2/6] Expose certificate lifetime to the user --- .../_certificate_validity/metadata.obsah.yaml | 11 +++++++++++ src/playbooks/deploy/metadata.obsah.yaml | 1 + 2 files changed, 12 insertions(+) create mode 100644 src/playbooks/_certificate_validity/metadata.obsah.yaml diff --git a/src/playbooks/_certificate_validity/metadata.obsah.yaml b/src/playbooks/_certificate_validity/metadata.obsah.yaml new file mode 100644 index 000000000..a62d7fbcc --- /dev/null +++ b/src/playbooks/_certificate_validity/metadata.obsah.yaml @@ -0,0 +1,11 @@ +--- +variables: + certificates_ca_validity_days: + help: Lifetime of the generated CA certificate, in days. + parameter: --cert-ca-validity-days + certificates_server_validity_days: + help: Lifetime of the generated TLS server certificate, in days. + parameter: --cert-server-validity-days + certificates_client_validity_days: + help: Lifetime of the generated TLS client certificate, in days. + parameter: --cert-client-validity-days diff --git a/src/playbooks/deploy/metadata.obsah.yaml b/src/playbooks/deploy/metadata.obsah.yaml index 0c2b4d499..5de33f81f 100644 --- a/src/playbooks/deploy/metadata.obsah.yaml +++ b/src/playbooks/deploy/metadata.obsah.yaml @@ -32,6 +32,7 @@ variables: include: - _certificate_source + - _certificate_validity - _database_mode - _database_connection - _tuning From 06396c86fc47868627191a1cbe6baff8143f7e3c Mon Sep 17 00:00:00 2001 From: Shimon Shtein Date: Tue, 21 Apr 2026 13:58:59 +0300 Subject: [PATCH 3/6] Merge client and server cert validity --- src/playbooks/_certificate_validity/metadata.obsah.yaml | 9 +++------ src/roles/certificates/defaults/main.yml | 3 +-- src/roles/certificates/tasks/issue.yml | 4 ++-- src/vars/default_certificates.yml | 3 +-- src/vars/installer_certificates.yml | 3 +-- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/playbooks/_certificate_validity/metadata.obsah.yaml b/src/playbooks/_certificate_validity/metadata.obsah.yaml index a62d7fbcc..4ee24328a 100644 --- a/src/playbooks/_certificate_validity/metadata.obsah.yaml +++ b/src/playbooks/_certificate_validity/metadata.obsah.yaml @@ -3,9 +3,6 @@ variables: certificates_ca_validity_days: help: Lifetime of the generated CA certificate, in days. parameter: --cert-ca-validity-days - certificates_server_validity_days: - help: Lifetime of the generated TLS server certificate, in days. - parameter: --cert-server-validity-days - certificates_client_validity_days: - help: Lifetime of the generated TLS client certificate, in days. - parameter: --cert-client-validity-days + certificates_validity_days: + help: Lifetime of the generated server and client certificates, in days. + parameter: --cert-validity-days diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index 344d9122b..c92e3b8b2 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -9,5 +9,4 @@ certificates_algorithm_type: RSA certificates_algorithm_size: 4096 # Validity periods in days (used as +Nd for community.crypto not_after). certificates_ca_validity_days: 7300 -certificates_server_validity_days: 7300 -certificates_client_validity_days: 7300 +certificates_validity_days: 7300 diff --git a/src/roles/certificates/tasks/issue.yml b/src/roles/certificates/tasks/issue.yml index 9191410c4..32fc01e3b 100644 --- a/src/roles/certificates/tasks/issue.yml +++ b/src/roles/certificates/tasks/issue.yml @@ -28,7 +28,7 @@ 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: "+{{ certificates_server_validity_days }}d" + ownca_not_after: "+{{ certificates_validity_days }}d" - name: 'Create client private key' community.crypto.openssl_privatekey: @@ -58,4 +58,4 @@ 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: "+{{ certificates_client_validity_days }}d" + ownca_not_after: "+{{ certificates_validity_days }}d" diff --git a/src/vars/default_certificates.yml b/src/vars/default_certificates.yml index 8a20d9bd5..822b6bcee 100644 --- a/src/vars/default_certificates.yml +++ b/src/vars/default_certificates.yml @@ -1,7 +1,6 @@ --- certificates_ca_validity_days: 7300 -certificates_server_validity_days: 7300 -certificates_client_validity_days: 7300 +certificates_validity_days: 7300 certificates_ca_directory: /root/certificates ca_key_password: "{{ certificates_ca_directory }}/private/ca.pwd" ca_certificate: "{{ certificates_ca_directory }}/certs/ca.crt" diff --git a/src/vars/installer_certificates.yml b/src/vars/installer_certificates.yml index 1fee61c43..2506d5d93 100644 --- a/src/vars/installer_certificates.yml +++ b/src/vars/installer_certificates.yml @@ -1,7 +1,6 @@ --- certificates_ca_validity_days: 7300 -certificates_server_validity_days: 7300 -certificates_client_validity_days: 7300 +certificates_validity_days: 7300 ca_key_password: "/root/ssl-build/katello-default-ca.pwd" ca_certificate: "/root/ssl-build/katello-default-ca.crt" ca_key: "/root/ssl-build/katello-default-ca.key" From ab2ec7118c46d7556b3222e701cc365bf1ad2a7b Mon Sep 17 00:00:00 2001 From: Shimon Shtein Date: Tue, 21 Apr 2026 14:09:57 +0300 Subject: [PATCH 4/6] Add renew flag --- src/playbooks/_certificate_validity/metadata.obsah.yaml | 9 +++++++-- src/roles/certificates/defaults/main.yml | 1 + src/roles/certificates/tasks/issue.yml | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/playbooks/_certificate_validity/metadata.obsah.yaml b/src/playbooks/_certificate_validity/metadata.obsah.yaml index 4ee24328a..4adeb663c 100644 --- a/src/playbooks/_certificate_validity/metadata.obsah.yaml +++ b/src/playbooks/_certificate_validity/metadata.obsah.yaml @@ -2,7 +2,12 @@ variables: certificates_ca_validity_days: help: Lifetime of the generated CA certificate, in days. - parameter: --cert-ca-validity-days + parameter: --certificate-ca-validity-days certificates_validity_days: help: Lifetime of the generated server and client certificates, in days. - parameter: --cert-validity-days + parameter: --certificate-validity-days + certificates_renew: + help: Regenrate server and client certificates previously generated by foremanctl. Does not regenerate the CA. + parameter: --certificate-renew + action: store_true + persist: false diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index c92e3b8b2..921648885 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -10,3 +10,4 @@ certificates_algorithm_size: 4096 # Validity periods in days (used as +Nd for community.crypto not_after). certificates_ca_validity_days: 7300 certificates_validity_days: 7300 +certificates_renew: false diff --git a/src/roles/certificates/tasks/issue.yml b/src/roles/certificates/tasks/issue.yml index 32fc01e3b..cc57f1b8b 100644 --- a/src/roles/certificates/tasks/issue.yml +++ b/src/roles/certificates/tasks/issue.yml @@ -29,6 +29,7 @@ ownca_privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key" ownca_privatekey_passphrase: "{{ certificates_ca_password }}" ownca_not_after: "+{{ certificates_validity_days }}d" + force: "{{ certificates_renew | bool }}" - name: 'Create client private key' community.crypto.openssl_privatekey: @@ -59,3 +60,4 @@ ownca_privatekey_path: "{{ certificates_ca_directory_keys }}/ca.key" ownca_privatekey_passphrase: "{{ certificates_ca_password }}" ownca_not_after: "+{{ certificates_validity_days }}d" + force: "{{ certificates_renew | bool }}" From 5e15694ee70fae78d5335f010c0533e388907946 Mon Sep 17 00:00:00 2001 From: Shimon Shtein Date: Thu, 23 Apr 2026 15:50:24 +0300 Subject: [PATCH 5/6] Remove redundant defaults --- src/vars/default_certificates.yml | 2 -- src/vars/installer_certificates.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/vars/default_certificates.yml b/src/vars/default_certificates.yml index 822b6bcee..09f47c5c9 100644 --- a/src/vars/default_certificates.yml +++ b/src/vars/default_certificates.yml @@ -1,6 +1,4 @@ --- -certificates_ca_validity_days: 7300 -certificates_validity_days: 7300 certificates_ca_directory: /root/certificates ca_key_password: "{{ certificates_ca_directory }}/private/ca.pwd" ca_certificate: "{{ certificates_ca_directory }}/certs/ca.crt" diff --git a/src/vars/installer_certificates.yml b/src/vars/installer_certificates.yml index 2506d5d93..c6ab83af3 100644 --- a/src/vars/installer_certificates.yml +++ b/src/vars/installer_certificates.yml @@ -1,6 +1,4 @@ --- -certificates_ca_validity_days: 7300 -certificates_validity_days: 7300 ca_key_password: "/root/ssl-build/katello-default-ca.pwd" ca_certificate: "/root/ssl-build/katello-default-ca.crt" ca_key: "/root/ssl-build/katello-default-ca.key" From 3484cc64f0472481e65f6c9d42cee42f83321531 Mon Sep 17 00:00:00 2001 From: Shimon Shtein Date: Thu, 23 Apr 2026 19:23:52 +0300 Subject: [PATCH 6/6] Improve documentation --- docs/user/certificates.md | 64 +++++++++++++++--------- src/roles/certificates/defaults/main.yml | 1 - 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/docs/user/certificates.md b/docs/user/certificates.md index 93cbcdb06..ae5fb161a 100644 --- a/docs/user/certificates.md +++ b/docs/user/certificates.md @@ -71,10 +71,31 @@ To remove all CNAMEs from an existing certificate, use `--reset-certificate-cnam 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. +### Validity period + +When certificates are generated by foremanctl, you can control lifetimes in days: + +| CLI flag | Ansible variable | What it controls | +| -------- | ---------------- | ---------------- | +| `--certificate-ca-validity-days` | `certificates_ca_validity_days` | Lifetime of the CA certificate (`ca.crt`) | +| `--certificate-validity-days` | `certificates_validity_days` | Lifetime of server and client certificates issued for each hostname | + +Defaults: both are 7300 days (about 20 years). + +### Renewing certificates + +To re-issue server and client certificates that foremanctl generated earlier, while keeping the same CA (`ca.crt` and CA key), use: + +```bash +foremanctl deploy --certificate-renew +``` + +The `--certificate-renew` flag is **not persisted** in foremanctl’s answers file (one-shot). + ### Current Limitations - Cannot provide custom certificate files during deployment -- Fixed 20-year certificate validity period +- Uses the same lifetime for both client and server certificates - Limited certificate customization options ## Internal Design @@ -90,26 +111,23 @@ src/roles/certificates/ ├── tasks/ │ ├── main.yml # Entry point - orchestrates CA and certificate generation │ ├── ca.yml # CA certificate generation -│ └── issue.yml # Host certificate issuance -├── defaults/main.yml # Default configuration variables -└── templates/ - ├── openssl.cnf.j2 # OpenSSL configuration template - └── serial.j2 # Serial number template +│ └── issue.yml # Host certificate issuance (server + client per hostname) +└── defaults/main.yml # Default configuration variables (validity, algorithm, paths) ``` #### Certificate Generation Workflow 1. **CA Generation** (when `certificates_ca: true`): - - Install OpenSSL and create directory structure - - Generate 4096-bit RSA private key - - Create self-signed CA certificate (CN: "Foreman Self-signed CA", 20-year validity) + - Install dependencies (`python3-cryptography`) and create directory layout under `certificates_ca_directory` + - Generate RSA private key (size from `certificates_algorithm_size`, default 4096) + - Build CA CSR and self-signed CA certificate (`CN: Foreman Self-signed CA`), with not-after from `certificates_ca_validity_days` 2. **Host Certificate Issuance** (for each hostname in `certificates_hostnames`): - - Generate 4096-bit RSA private key - - Create certificate signing request (CSR) with Subject Alternative Names - - Include primary hostname and any additional CNAMEs from `certificate_cname` - - Sign certificate with CA (includes serverAuth/clientAuth extensions) - - Generate both server and client certificates per hostname + - Generate server key and CSR with SANs (hostname plus `certificates_cnames`) + - Sign server certificate with the CA (`ownca_not_after` from `certificates_validity_days`; `force` when `certificates_renew`) + - Generate client key and CSR, sign client certificate the same way + +Generation uses **`community.crypto`** (keys, CSRs, X.509) and **`python3-cryptography`**. #### Variable System @@ -149,10 +167,12 @@ The `certificate_checks` role uses `foreman-certificate-check` binary to validat ### Technical Specifications **Certificate Properties:** -- Key Size: 4096-bit RSA -- Hash Algorithm: SHA256 -- Validity Period: 7300 days (20 years) -- Extensions: serverAuth, clientAuth, nsSGC, msSGC +- Key algorithm / size: RSA 4096 by default (`certificates_algorithm_type`, `certificates_algorithm_size`) +- Hash Algorithm: SHA256 (via `community.crypto` defaults) +- Validity: + - CA: `certificates_ca_validity_days` (default 7300 days) + - Server and client certificates: `certificates_validity_days` (default 7300 days) +- Extensions: serverAuth / clientAuth as appropriate for server and client certificates **Directory Structure:** ``` @@ -162,8 +182,6 @@ The `certificate_checks` role uses `foreman-certificate-check` binary to validat └── requests/ # Certificate signing requests ``` -**OpenSSL Configuration:** -- Custom configuration template supports SAN extensions -- Multiple DNS entries supported: `subjectAltName = DNS:{{ certificates_hostname }}{% for cname in certificate_cname %},DNS:{{ cname }}{% endfor %}` -- Uses OpenSSL's `req` and `ca` commands for generation and signing -- CNAMEs configured via `certificate_cname` variable (list of additional DNS names) +**SANs and CNAMEs:** +- Server SANs are built in Ansible (`openssl_csr`) from the hostname and optional `certificates_cnames` (from `--certificate-cname`). +- Client CSR uses a SAN entry for the hostname being issued. diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index 921648885..37d6a2ec8 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -7,7 +7,6 @@ certificates_ca_directory_requests: "{{ certificates_ca_directory }}/requests" certificates_cnames: [] certificates_algorithm_type: RSA certificates_algorithm_size: 4096 -# Validity periods in days (used as +Nd for community.crypto not_after). certificates_ca_validity_days: 7300 certificates_validity_days: 7300 certificates_renew: false