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 role encrypts with the password behind the configured vault_password_file (from ansible.cfg), so the CA file is decryptable by the rest of the repo without any prompt. Only when no vault_password_file is configured does it prompt for the password twice and fail if the two values differ. A subsequent run is rejected if the vault file already exists, so the CA cannot be silently overwritten. |
The role reuses the same vault password the rest of the repo already uses (resolved from vault_password_file — a plain file or an executable password client such as scripts/aws-vault-pass.sh), which is what keeps the generated file decryptable by every other playbook. It never persists the password itself. When no vault password file is configured, the password is prompted for, held in memory for the run only, and its later persistence 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
- Resolve the vault password from the configured
vault_password_file; if none is configured, prompt for it 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
With a vault_password_file configured (the default in this repo), the CA is encrypted with that password and no prompt appears. If no vault password file is configured, you will be prompted twice for the vault password. Either way, 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 -c local playbooks/certificate-authority-generate.yml --tags generate_ca. Note the vault password used — either the one behind that environment'svault_password_fileor, if none is configured, the one you were prompted for. - 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.