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
64 changes: 41 additions & 23 deletions docs/user/certificates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
ehelms marked this conversation as resolved.

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
Comment thread
evgeni marked this conversation as resolved.

## Internal Design
Expand All @@ -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

Expand Down Expand Up @@ -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:**
```
Expand All @@ -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.
13 changes: 13 additions & 0 deletions src/playbooks/_certificate_validity/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +2 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If I set the expiration period for both the client and server certificates to 5 days each, my question is whether the customer will receive a notification before the certificates expire, as I do not see that happening here.

My second point is that --certificate-renew does not renew the CA certificate. If I set the CA certificate validity to 5 days, how will the CA certificate be renewed?

Also, you mentioned that the certificate is considered to have a lifetime. What exactly does that mean if I explicitly set its validity period to 5 days?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

whether the customer will receive a notification before the certificates expire, as I do not see that happening here.

Is it something that we previously set up?

My second point is that --certificate-renew does not renew the CA certificate. If I set the CA certificate validity to 5 days, how will the CA certificate be renewed?

We have discussed it previously: CA renewal is a process and I want to treat it in a separate story.

Also, you mentioned that the certificate is considered to have a lifetime. What exactly does that mean if I explicitly set its validity period to 5 days?

I may be missing something, but if you set validity period to 5 days, it means that the lifetime of the certificate would be 5 days from the run of the module.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

whether the customer will receive a notification before the certificates expire, as I do not see that happening here.

Is it something that we previously set up?

I guess no, i do not see in the old installer.

Copy link
Copy Markdown

@amolpati30 amolpati30 Apr 27, 2026

Choose a reason for hiding this comment

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

If the server and client certificates have expired and I want to renew them, will this work using this option?
foremanctl deploy --certificate-validity-days 365 --certificate-renew or
foremanctl deploy --certificate-renew

I tried renewing the expired server and client certificates, but it does not seem to be supported. Is it required to have a valid CA certificate first before renewing the server and client certificates?

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.

foremanctl deploy --certificate-renew should be sufficient, in my understanding

was the CA expired too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You can do both. In the shorter case, I guess you will be getting the default validity (7300 days). Unless @evgeni says that we preserve the validity days parameter by default.

Copy link
Copy Markdown
Member Author

@ShimShtein ShimShtein Apr 27, 2026

Choose a reason for hiding this comment

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

Is it required to have a valid CA certificate first before renewing the server and client certificates?

Yes, the CA has to be valid. We are signing the server and client certs with the CA.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

foremanctl deploy --certificate-renew should be sufficient, in my understanding

was the CA expired too?

Yes, the CA certificate has also expired.

Copy link
Copy Markdown
Member

@evgeni evgeni Apr 27, 2026

Choose a reason for hiding this comment

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

You can do both. In the shorter case, I guess you will be getting the default validity (7300 days). Unless @evgeni says that we preserve the validity days parameter by default.

If you haven't marked it as persist: false, it will of course be preserved (which I think is the correct way to do it)

action: store_true
persist: false
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.

Flashbacks to theforeman/foreman-installer#764 and this nice feeling that today it's just a persist: false.

1 change: 1 addition & 0 deletions src/playbooks/deploy/metadata.obsah.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ variables:

include:
- _certificate_source
- _certificate_validity
- _database_mode
- _database_connection
- _tuning
Expand Down
3 changes: 3 additions & 0 deletions src/roles/certificates/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/roles/certificates/tasks/ca.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
6 changes: 4 additions & 2 deletions src/roles/certificates/tasks/issue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}"
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.

IMHO here we see the real value of using community.crypto. With our hand crafted openssl statements this would have been a pain.


- name: 'Create client private key'
community.crypto.openssl_privatekey:
Expand Down Expand Up @@ -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 }}"