Skip to content

Support bringing your own CA key/certificate - #105

Open
Saeid-Abadi wants to merge 5 commits into
mainfrom
feature/bring-your-own-ca
Open

Saeid-Abadi wants to merge 5 commits into
mainfrom
feature/bring-your-own-ca

Conversation

@Saeid-Abadi

Copy link
Copy Markdown
Contributor

Add ca_own_ca, ca_own_ca_key and ca_own_ca_cert so operators can install an existing CA key/certificate instead of having the role generate a new one. CA key/cert generation and the role's own CA renewal logic are skipped when ca_own_ca is enabled.

Add ca_own_ca, ca_own_ca_key and ca_own_ca_cert so operators can
install an existing CA key/certificate instead of having the role
generate a new one. CA key/cert generation and the role's own CA
renewal logic are skipped when ca_own_ca is enabled.
@Saeid-Abadi
Saeid-Abadi force-pushed the feature/bring-your-own-ca branch from 1ea83c3 to 11f8029 Compare July 17, 2026 11:25
Saeid-Abadi and others added 4 commits July 22, 2026 15:24
The "Fetch CA certificate" task ran for every host and fetched ca.crt
to the same flat destination on the controller. With hosts running in
parallel this races on the local file and intermittently fails with
"checksum mismatch". The CA certificate is identical for all hosts, so
fetch it once with run_once.

@afeefghannam89 afeefghannam89 left a comment

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.

One more thought: there's no molecule scenario with ca_own_ca: true yet, Maybe you want so configure one to catch possible problems while providing own ca .

Side note: moving to a new CA later should work. x509_certificate signs a host cert again by itself when the CA changed. So put in the new key and cert, run the role again, and every host gets a new cert. The keys stay the same. Might be good to say that in the README.

Comment thread tasks/ca.yml
that:
- ca_own_ca_key is defined and ca_own_ca_key | length > 0
- ca_own_ca_cert is defined and ca_own_ca_cert | length > 0
fail_msg: "ca_own_ca is enabled but ca_own_ca_key/ca_own_ca_cert are not set"

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.

Might be worth checking the files a bit more here. Right now only the paths are checked, so if key and cert don't match or the cert isn't a CA you only find out later when the first host cert gets signed. Comparing the public_key from openssl_privatekey_info and x509_certificate_info, plus a look for CA:TRUE in basic_constraints, would catch that right after the copy.

Comment thread tasks/ca.yml
dest: "{{ ca_ca_dir }}/ca.key"
owner: root
group: root
mode: 0600

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.

nit: when someone switches an existing setup over, this overwrites the generated CA key without a copy. backup: true would keep one around, up to you.

Suggested change
mode: 0600
mode: 0600
backup: true

Comment thread tasks/ca.yml
dest: "{{ ca_ca_dir }}/ca.crt"
owner: root
group: root
mode: 0644

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.

Same idea here if you add it above.

Suggested change
mode: 0644
mode: 0644
backup: true

Comment thread README.md
* `ca_email`: E-Mail address for certificates (default: `root@{{ ansible_fqdn }}`)
* `ca_altname_1`: First alt name (default: `{{ ansible_fqdn }}`)
* `ca_ca_keylength`: CA keylength (default: `2048`)
* `ca_own_ca`: Use an existing/external CA instead of letting the role generate one. When enabled, `ca_own_ca_key` and `ca_own_ca_cert` are copied to `ca_ca_dir` as `ca.key`/`ca.crt` instead of running the CA key/certificate generation. `ca_ca_password` must still match the passphrase of the provided key, since it's used to sign leaf certificates. The role's own CA renewal (`ca_renew`) does not apply to a provided CA; renewing it is up to the operator. (default: `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.

I you like you can check the passphrase directly after copying the private key, something linke this:
openssl_privatekey_info with passphrase: "{{ ca_ca_password }}" returns can_parse_key: false for both an unencrypted key and a wrong passphrase, so one assert on that would catch it early than getting this error while creating the client certificate

Comment thread README.md
* `ca_ca_keylength`: CA keylength (default: `2048`)
* `ca_own_ca`: Use an existing/external CA instead of letting the role generate one. When enabled, `ca_own_ca_key` and `ca_own_ca_cert` are copied to `ca_ca_dir` as `ca.key`/`ca.crt` instead of running the CA key/certificate generation. `ca_ca_password` must still match the passphrase of the provided key, since it's used to sign leaf certificates. The role's own CA renewal (`ca_renew`) does not apply to a provided CA; renewing it is up to the operator. (default: `false`)
* `ca_own_ca_key`: Path on the Ansible control host to the CA private key to install when `ca_own_ca` is `true`. Consider encrypting it with `ansible-vault`. (no default)
* `ca_own_ca_cert`: Path on the Ansible control host to the CA certificate to install when `ca_own_ca` is `true`. (no default)

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.

Maybe a short note on intermediates? Putting the intermediate first and the root after it in this file works for signing (issuer is the intermediate then) and the clients get the whole chain in ca.crt. With just the intermediate in ca.crt, openssl verify fails with unable to get issuer certificate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants