Skip to content

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

  1. Prompt for the vault password (twice, second value must match).
  2. Fail if extra_vars/certificate_authority.yml (or whatever path certificate_authority_vault_file points to) already exists.
  3. Generate an RSA private key in memory.
  4. Build a CA-flagged CSR (basic_constraints: CA:TRUE critical, key_usage: keyCertSign critical) in memory.
  5. Self-sign the certificate with the validity from certificate_authority_ca_validity_days (default 10,950 days ≈ 30 years).
  6. Encrypt both PEM blobs into a single Ansible Vault file (ca_private_key: and ca_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

ansible-playbook --limit=localhost playbooks/certificate-authority-generate.yml --tags generate_ca

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:

  1. In the source environment, run ansible-playbook --limit=localhost playbooks/certificate-authority-generate.yml --tags generate_ca. Note the vault password you chose.
  2. Copy the resulting extra_vars/certificate_authority.yml into the destination repository at the same relative path (or wherever you set certificate_authority_vault_file).
  3. Deliver the vault password to the destination operators out-of-band (password manager, secret store, sealed envelope).
  4. 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.