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/playbooks/_certificate_validity/metadata.obsah.yaml b/src/playbooks/_certificate_validity/metadata.obsah.yaml new file mode 100644 index 000000000..4adeb663c --- /dev/null +++ b/src/playbooks/_certificate_validity/metadata.obsah.yaml @@ -0,0 +1,13 @@ +--- +variables: + certificates_ca_validity_days: + help: Lifetime of the generated CA certificate, in days. + parameter: --certificate-ca-validity-days + certificates_validity_days: + help: Lifetime of the generated server and client certificates, in 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/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 diff --git a/src/roles/certificates/defaults/main.yml b/src/roles/certificates/defaults/main.yml index de5b54c0e..37d6a2ec8 100644 --- a/src/roles/certificates/defaults/main.yml +++ b/src/roles/certificates/defaults/main.yml @@ -7,3 +7,6 @@ certificates_ca_directory_requests: "{{ certificates_ca_directory }}/requests" certificates_cnames: [] certificates_algorithm_type: RSA certificates_algorithm_size: 4096 +certificates_ca_validity_days: 7300 +certificates_validity_days: 7300 +certificates_renew: false 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..cc57f1b8b 100644 --- a/src/roles/certificates/tasks/issue.yml +++ b/src/roles/certificates/tasks/issue.yml @@ -28,7 +28,8 @@ 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_validity_days }}d" + force: "{{ certificates_renew | bool }}" - name: 'Create client private key' community.crypto.openssl_privatekey: @@ -58,4 +59,5 @@ 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_validity_days }}d" + force: "{{ certificates_renew | bool }}"