step-ca Private CA, Initialised Once, Proven By Issuing

Smallstep step-ca and the step CLI from the upstream releases (sha256-verified) as a hardened system service on loopback, initialised once by its own user: root and intermediate keys, ca.json and a JWK provisioner. The live test reads the CA's health, has it issue a certificate, verifies it against the root and sees a wrong password refused. Original role, live-tested on Rocky Linux 10.

ansibleSecrets & KMS

Verification

Live-tested

Really deployed to a container sandbox, proven idempotent (a second run changes nothing), verified against the role’s assertions, then torn down.

Conformance

  • Static validation (yamllint · ansible-lint)

Provenance

  • SHA-256 checksum
  • Signature (pending)

Functional

  • Live-tested - applied, verified, destroyed

Last verified 2026-09-20 · podman 4.9.3 · ansible 2.21.4 · how we verify

Documentation

step-ca

Smallstep's step-ca from the upstream release (sha256-verified) with the step CLI beside it, as a private certificate authority running as a hardened system service on loopback, initialised once by the role. Original role for EL 10, live-tested with podman on Rocky Linux 10.

No package, so the checksum is the whole story. EL 10 carries no step-ca; Smallstep ships a release with a checksum file beside it. The role downloads both and has Ansible's get_url refuse the asset unless its SHA-256 is the one in the vendor's file, then installs the binaries as root's in /usr/local/bin, pinned by step_ca_version.

A service account, a hardened unit, a loopback listener. step is a system user with no shell that owns the data directory and nothing else; the unit runs with NoNewPrivileges, PrivateTmp, ProtectHome and ProtectSystem=strict. The listener is 127.0.0.1:9000 by default, for a proxy that authenticates or a client on the same host; the live test reads the listening sockets and expects loopback only.

Initialised once, by its own user. step ca init writes the root and intermediate keys, both certificates, ca.json and the first (JWK) provisioner under step_ca_data_dir, running as the step user so nothing in the PKI is root's; a second run finds ca.json and leaves the PKI alone. The password that encrypts the keys is yours (step_ca_password) or, left empty, generated on the host once into a 0600 file the CA alone reads.

Proven by issuing. The live test reads the CA's health through the step CLI trusting only the CA's own root, has the admin provisioner issue a certificate for probe.internal, verifies that it chains to the root, reads the subject and issuer back from the certificate, and, as a control, sees a wrong provisioner password refused with "invalid password". The CA serves HTTPS with a certificate of its own for the names in step_ca_dns_names; a client trusts it by --root.

Loopback by default, on purpose. A CA that ACME clients and hosts reach needs a listener on a real address: set step_ca_listen_address before the first run (it is written into ca.json by the init) and add an ACME provisioner with step ca provisioner add. Everything the CA owns lives in one directory, step_ca_data_dir, which is STEPPATH for the service and for the init.

License

Commercial - IaC Bazaar EULA. (c) IaC Bazaar.

Usage code & full reference need an account

The complete copy-paste usage, the full input/output reference, and operational notes are free with an account - shown here and bundled in the download. Sign in and this section fills in.

  • Variables
  • Test

Related modules

Static validatedLive test pending

vault-kv-engine

KV v1 overwrites in place, so a bad write is the end of the previous secret; cas_required defaults to false, so two writers that read the same version both succeed and the second silently replaces the first; and version history is unbounded by default. v2 always, check-and-set on (off by name), versions bounded by count and age, the mount's lease ceilings set rather than inherited.

View module
Static validatedLive test pending

vault-pki-certificate-authority

Issuing from the root puts every leaf one signature from the root's compromise; a PKI role's defaults issue nothing until somebody reaches for allow_any_name, which issues for every hostname; and without AIA and CRL URLs a leaf is valid and unverifiable. A root that signs one intermediate, roles bound to allowed_domains, 30-day leaves under a 90-day ceiling, URLs on both mounts.

View module
Static validatedLive test pending

vault-approle

A secret ID with no TTL and no use limit is a password, and both default to unlimited; a role with no bound CIDRs logs in from anywhere; and a role with no max TTL mints tokens that renew forever. Secret IDs that live an hour and are used once, roles bound to the ranges they run from with unbound accepted by name, and token ceilings set.

View module
Static validatedLive test pending

vault-database-secrets

The credential Vault connects with is still a password somebody knows until Vault rotates it; a role with no max TTL issues credentials that renew forever; and creation statements are the privilege, so a careless one is a superuser factory. Root rotation daily, TTLs per role, statements that grant exactly the PostgreSQL role you name, and the connection verified at apply.

View module
Static validatedLive test pending

vault-oidc-auth

An OIDC role with no bound claims admits every user of the identity provider; a role with no bound audience accepts tokens minted for other services; and the client secret lands in state. Bound claims expected with none accepted by name, an audience required, callbacks listed rather than assumed, token ceilings set, and the write-only secret path named for Terraform 1.11+.

View module
Live-tested

ansible-openbao

OpenBao (the MPL-licensed Vault fork) as a server with raft storage, from the upstream release (sha256-verified), as a hardened system service on loopback; TLS on the listener when you give it a certificate. The role does not initialise it; the live test does, on its throwaway container: init, unseal, enable KV v2, write a secret, read it back. Original role, live-tested on Rocky Linux 10.

View module