Skip to main content

HashiCorp Vault module

Writes certificate + key + chain into a Vault KV secret path (KV v1 or v2, selected per target). Rollback writes a previous retained certificate version back as a new KV version. Supports Token and AppRole authentication.

Overview

  • Transport: Vault HTTP API.
  • Auth: Token (paste a Vault token) or AppRole (role_id + secret_id).
  • Target: KV secret engine — KV v1 or KV v2, selected per target via the engine_type field (kv1 | kv2, default kv2). The secret path is a literal string (no templating) — leave it empty for a per-certificate auto path, or override it per distribution.
  • Payload: {"certificate": ..., "private_key": ..., "chain": ...}. The private key is always written; scope which secrets the token may read via the Vault policy. Field names customisable.
  • Rollback: supported — a rollback writes the previous retained certificate version as a new KV version (Rollback). Works on KV v1 and KV v2 alike.
  • Namespace: Enterprise Vault namespace is supported via the X-Vault-Namespace header.

Prerequisites

  • Vault cluster reachable from the backend on its API port (typically 8200 HTTPS).
  • A KV secret engine mounted (e.g. at secret/) — KV v1 or KV v2.
  • A policy granting create, update, and read on the target path pattern. Auto-generated paths live under the certautopilot/ directory, so a policy covering secret/data/certautopilot/* and secret/metadata/certautopilot/* works out of the box:
path "secret/data/certautopilot/*" {
capabilities = ["create", "update", "read"]
}
path "secret/metadata/certautopilot/*" {
capabilities = ["read", "list", "delete"]
}
# Live pickers only (see below): the mount list and the ROOT listing of the
# mount — the first thing the Secret path picker asks for.
path "sys/mounts" {
capabilities = ["read"]
}
path "secret/metadata" {
capabilities = ["list"]
}

Create the module credential

  1. Settings → Distribution → CredentialsNewHashiCorp Vault.
  2. Pick the auth method:
    • Token: paste a Vault token.
    • AppRole: paste role_id + secret_id.
  3. Save.

Create a Vault target

  1. Settings → Distribution → TargetsNew. Module: HashiCorp Vault.
  2. Fields:
    • Addresshttps://vault.example.com:8200.
    • Namespace — optional; required for Vault Enterprise namespaces.
    • KV mount pathsecret (default).
    • Engine typekv2 (default) or kv1.
    • Secret path — optional. Empty = auto-generated per certificate as certautopilot/cap-<token>-<domain> (token: 8-char hash derived from the certificate, stable across renewals), so one target can serve many certificates. Can also be overridden per distribution from the certificate's Distributions → Overrides drawer.
    • Field names — customise which JSON keys hold the leaf certificate, private key and chain (defaults certificate, private_key, chain). There is no fullchain key — cert_key holds the leaf only. Four reserved _certautopilot_* metadata keys are managed automatically and cannot be renamed.
    • TLS CA cert — PEM for a private-CA-signed Vault endpoint.
    • TLS skip verify — for bring-up only.
    • HTTP timeout — default 30 s.
    • Credential.
  3. Save → health check authenticates with the target's credential and then probes sys/mounts/<mount>/tune; a 403 there is tolerated and reported as a warning.

Choosing from Vault (live pickers)

The target form and the per-certificate override drawer can read Vault and offer what is there instead of asking you to type it:

  • KV mount path (target form only) — the KV secrets engines from sys/mounts of the target's namespace (mounts of child namespaces are not listed — set the child namespace on the target), each with its KV version. Picking a mount sets the Engine type to match; a mount whose version differs from the current engine type is flagged in the list, and a secret-path listing against a mismatched or missing mount says so instead of looking empty.
  • Secret path (target form and per-target override rows) — the entries under the typed path's folder in the target's mount (LIST only — no secret is ever read): folders (pick one to list what is inside; .. (up) goes back) and existing secrets (the deploy overwrites the certificate keys of the one you pick). A folder is not a secret path — the form refuses to save one; pick or type a name inside it. The Default (all targets) row of the override drawer stays manual-entry.

The credential needs read on sys/mounts for the mount picker and list on <mount>/metadata plus <mount>/metadata/* (KV v2) or <mount> plus <mount>/* (KV v1) for the path picker — the root listing is the first thing it asks for; without them the picker reports the permission problem and you type the value instead. Listings are capped at 500 entries and nothing is read until you open a list. Listing through a saved target is a project operator action; browsing from an unsaved form, or after changing the address, namespace, TLS settings, mount or engine type on an existing target, needs project admin (or save first).

Execution flow

  1. Auth with the configured method; acquire a short-lived Vault token.
  2. Resolve the secret path (explicit target path, per-distribution override, or the per-certificate auto path certautopilot/cap-<token>-<domain>).
  3. POST the JSON payload to the secret path. The /data/ segment is a KV v2 convention: KV v2 writes go to /v1/<mount>/data/<path> (Vault bumps the version automatically), while KV v1 writes go to /v1/<mount>/<path> with no /data/ segment and no versioning. The module picks the shape from the target's engine_type field.
Changing the secret path cleans up the old location

After the new path is written, the module soft-deletes the certificate at the previous path — but only on KV v2, and only when that path still holds exactly the fingerprint CertAutoPilot last wrote there. On KV v1 (where deletion is irreversible) or when the origin cannot be confirmed, it logs a warning and leaves the secret alone.

Vault cannot tell you who reads a path, so anything still pointed at the old location breaks at that moment. Recover with vault kv undelete. The cleanup needs update on <mount>/delete/<path> in the policy; without it the delete simply fails as a warning.

Rollback

Supported via previous-version re-deploy: a rollback writes a previous retained certificate version's content to the secret path as a new KV version — the module's normal deploy path, fed older material. It does not rewind Vault's version counter (no version-restore), and it works on KV v1 and KV v2 alike — there is no KV v2 requirement anymore. Eligibility, the version picker, and auto-rollback: Rollback.

Security notes

  • The private key is always written to Vault as part of the payload — if that matters to your threat model, scope the backend's Vault policy tightly (and audit reads) so only the intended consumers can retrieve the secret.
  • Vault's audit log captures every write; pair it with CertAutoPilot's audit trail for a full chain of custody.
  • The backend's Vault token is scoped to the policy above; it cannot read secrets outside that path pattern.

Troubleshooting

"permission denied"

Policy is missing one of create/update/read on the target path, or the path pattern doesn't match. Test with vault token capabilities <token> secret/data/certautopilot/cap-1a2b3c4d-example-com.

"namespace not found"

Either typo, or you hit a non-Enterprise Vault — namespaces are an Enterprise-only feature.

"kv v1 write format doesn't match"

The mount points at a KV v1 engine but the target's Engine type is set to kv2 (or vice versa). Set engine_type to match the mount — both KV v1 and KV v2 are supported (including for rollback). No migration is needed.

See also

Behaviours worth knowing

  • The write is a merge, not a replace. Unrelated keys already at the secret path are preserved, and chain is left untouched when the certificate has no chain.
  • No check-and-set. The module does not send options.cas, so a concurrent writer to the same path is silently overwritten.
  • Renaming a key name leaves the old key behind. Change cert_key and the previous key keeps holding the old certificate forever — remove it yourself.
  • KV v1 has no versions. Rollback overwrites the secret in place; only KV v2 creates a new version.
  • Cleanup needs update, not delete. The soft-delete call is a POST to <mount>/delete/<path>, which Vault authorizes under the update capability.