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 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

  1. Resolve the vault password from the configured vault_password_file; if none is configured, prompt for it 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 -c local playbooks/certificate-authority-generate.yml --tags generate_ca

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:

  1. 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's vault_password_file or, if none is configured, the one you were prompted for.
  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.