Generating the Ansible Root CA
This page covers generate_ca — the playbook tag that creates a fresh, self-signed root CA for the Ansible-internal CA flow. The CA is what signs every leaf certificate the role issues; the Microsoft AD-integrated flow does not use it.
Security model
The role is built around one rule: the CA's private key is never written to disk in cleartext. The implementation enforces this end to end.
| Stage | Behavior |
|---|---|
| Key generation | community.crypto.openssl_privatekey_pipe produces the RSA key entirely in memory on the control node. The _pipe variant returns the key as a string in module output; nothing is written to a file. |
| CSR + self-signed cert | community.crypto.openssl_csr_pipe and community.crypto.x509_certificate_pipe both run in pipe mode, so the CSR and certificate are also memory-only. |
| Vault encryption | A short inline Python step receives the key and certificate via process environment variables and encrypts them with ansible.parsing.vault.VaultLib before writing to disk. The destination file (extra_vars/certificate_authority.yml by default) is created with os.open(..., O_WRONLY|O_CREAT|O_TRUNC, 0o600), so it lands on disk already encrypted and owner-readable only. |
| Logging | Every task that touches the key has no_log: true. The key never appears in stdout, the Ansible log, the JSON-callback output, or callback plugins. |
| Pre-flight | The playbook prompts for the vault password twice and fails if the two values differ. A subsequent run is rejected if the vault file already exists, so the CA cannot be silently overwritten. |
The vault password is held in memory for the duration of the run and never persisted by the role. Where it is persisted afterward (e.g. an Ansible Vault password file, a secret store) is the operator's choice — see the Ansible-internal CA usage guide for password-handling options used during signing.
What the generate_ca tag does
- Prompt for the vault password (twice, second value must match).
- Fail if
extra_vars/certificate_authority.yml(or whatever pathcertificate_authority_vault_filepoints to) already exists. - Generate an RSA private key in memory.
- Build a CA-flagged CSR (
basic_constraints: CA:TRUEcritical,key_usage: keyCertSigncritical) in memory. - Self-sign the certificate with the validity from
certificate_authority_ca_validity_days(default 10,950 days ≈ 30 years). - Encrypt both PEM blobs into a single Ansible Vault file (
ca_private_key:andca_certificate:keys),chmod 0600.
The whole sequence runs on localhost only — the role hard-codes delegate_to: localhost and run_once: true for this task.
Running it
You will be prompted twice for the vault password. The encrypted file is written to extra_vars/certificate_authority.yml.
One-time, irreversible
Regenerating the CA invalidates every certificate it ever issued and breaks trust for every host that installed the old root. The playbook will refuse to overwrite an existing vault file — delete it explicitly only if you mean it.
Variables
| Variable | Default | Description |
|---|---|---|
certificate_authority_common_name |
Ansible CA |
Common name of the CA certificate |
certificate_authority_key_size |
2048 |
RSA key size for the CA |
certificate_authority_ca_validity_days |
10950 |
CA validity in days (default ~30 years) |
certificate_authority_vault_file |
extra_vars/certificate_authority.yml |
Output path for the encrypted vault file (relative to ansible.cfg) |
Generating the CA in a separate environment
Because the only persistent output is the encrypted vault file, the CA can be generated wherever it is convenient and the file shipped to the environment that will use it. The classic case: generate the CA in your own admin workstation or a security-controlled environment, then commit the encrypted file into the customer's repository.
Workflow:
- In the source environment, run
ansible-playbook --limit=localhost playbooks/certificate-authority-generate.yml --tags generate_ca. Note the vault password you chose. - Copy the resulting
extra_vars/certificate_authority.ymlinto the destination repository at the same relative path (or wherever you setcertificate_authority_vault_file). - Deliver the vault password to the destination operators out-of-band (password manager, secret store, sealed envelope).
- In the destination environment, configure the password (
--ask-vault-pass,--vault-password-file, or an executable password client — see Ansible-internal CA usage) and run the signing/issuing tags as usual.
Because the file is encrypted at rest with AES-256, committing it to a repository is acceptable from a confidentiality standpoint — its security reduces to the strength of the vault password. Use a long, random password. A weak password makes the encrypted file as good as plaintext to anyone with read access to the repo.