HashiCorp Vault
The HashiCorp Vault adapter delivers certificate chains and matching private keys into a Vault KV version 2 secrets engine. Your applications, Vault Agent templates or sidecars read the certificate from Vault instead of from a local file. The Mesh Node writes each certificate as an immutable generation, moves a check-and-set (CAS) protected pointer to it, optionally runs a pinned reload executable, and commits only when consumers serve the exact new leaf certificate. Any failure moves the pointer back.
This adapter writes certificates that Sectigo Edge issued into Vault. It does not use Vault's PKI secrets engine as an issuing CA.
Security model
- The console stores only the
local/adapter reference, profile, target node and revision. The Vault address, token and private keys never leave your environment. - The Vault token is read from a private local file on every request — never from node JSON, an argument, an environment variable or the console.
- Connections require HTTPS with certificate verification (TLS 1.2 or later). An optional CA file pins Vault's issuer; an optional client certificate supports Vault TLS authentication. Proxies and redirects are refused.
- Generations are written with
cas=0, so an existing slot is never overwritten; the pointer is updated with CAS against its current version. - Mesh Node startup never contacts Vault. If Vault is unreachable, only this integration becomes Degraded; everything else on the node keeps working, and the node retries on every poll until it recovers.
- Routine reconciliation checks Vault version metadata and does not read private keys.
Vault data layout
All paths are relative to the KV mount and base path you configure.
| Path | Contents |
|---|---|
<base>/generations/slot-NNNNNN | One immutable generation: protocol (sectigo-edge.vault-kv-generation.v1), slot, certificate_pem (leaf then ordered chain), private_key_pem, certificate_sha256, created_at |
<base>/pointer | The active selection: protocol (sectigo-edge.vault-kv-pointer.v1), generation, active, active_sha256, optional standby/standby_sha256 and previous/previous_sha256, and updated_at |
Consumers should read <base>/pointer, then the generation named by active, and check that its certificate_sha256 equals active_sha256. Reading both in one Vault Agent template, or re-reading on a short interval, picks up promotions automatically.
Prepare Vault
Enable (or reuse) a KV v2 mount and create a narrowly scoped policy for the node — create/update/read on the base path's data and read on its metadata:
path "secret/data/sectigo-edge/prod-service/*" {
capabilities = ["create", "update", "read"]
}
path "secret/metadata/sectigo-edge/prod-service/*" {
capabilities = ["read"]
}
vault secrets enable -path=secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/
vault policy write sectigo-edge-prod sectigo-edge-prod.hcl
Success! Uploaded policy: sectigo-edge-prod
Skip vault secrets enable if the mount exists. Grant consumers read access to <base>/pointer and <base>/generations/* only — never write.
Prepare the node host
Create a token bound to that policy (periodic, or renewed by your normal token process) and store only the token (optionally followed by one newline) in a dedicated file.
- Linux
- Windows
vault token create -policy=sectigo-edge-prod -period=24h -field=token \
| sudo tee /etc/sectigo-edge/vault.token >/dev/null
sudo chown edgepki:edgepki /etc/sectigo-edge/vault.token
sudo chmod 0600 /etc/sectigo-edge/vault.token
sudo install -d -o edgepki -g edgepki -m 0700 /var/lib/sectigo-edge/vault
The node rejects token and bootstrap-key files readable by group or others. The installed systemd unit only allows writes to /var/lib/edgepki; add a drop-in (sudo systemctl edit edgepki-node) with ReadWritePaths=/var/lib/sectigo-edge/vault.
vault token create -policy=sectigo-edge-prod -period=24h -field=token | Out-File -Encoding ascii -NoNewline C:\ProgramData\Sectigo\Edge\vault.token
icacls C:\ProgramData\Sectigo\Edge\vault.token /inheritance:r /grant:r 'SYSTEM:R' 'BUILTIN\Administrators:F'
Restrict the token and bootstrap-key files to the node's service identity with an explicit ACL (SYSTEM for the default LocalSystem service, or your gMSA).
Also place the currently used certificate chain (leaf first) and its key in bootstrap files; they become the first generation the first time the console assigns desired state.
Node configuration
{
"hashicorp_vault_enabled": true,
"hashicorp_vault_adapter_reference": "local/vault-production",
"hashicorp_vault_profile_id": "prod-service",
"hashicorp_vault_address": "https://vault.example.internal:8200",
"hashicorp_vault_namespace": "",
"hashicorp_vault_token_file": "/etc/sectigo-edge/vault.token",
"hashicorp_vault_ca_certificate_file": "/etc/sectigo-edge/vault-ca.pem",
"hashicorp_vault_kv_mount": "secret",
"hashicorp_vault_kv_base_path": "sectigo-edge/prod-service",
"hashicorp_vault_managed_directory": "/var/lib/sectigo-edge/vault",
"hashicorp_vault_bootstrap_certificate_file": "/etc/sectigo-edge/bootstrap/vault-chain.pem",
"hashicorp_vault_bootstrap_private_key_file": "/etc/sectigo-edge/bootstrap/vault-key.pem",
"hashicorp_vault_request_timeout_seconds": 10,
"hashicorp_vault_rollback_window_seconds": 3600,
"hashicorp_vault_reload_binary_path": "/opt/example/bin/reload-tls",
"hashicorp_vault_reload_binary_sha256": "<64 lowercase hex characters>",
"hashicorp_vault_reload_arguments": ["--certificate", "{certificate}"],
"hashicorp_vault_reload_timeout_seconds": 15
}
| Key | Notes |
|---|---|
hashicorp_vault_address | Exact HTTPS origin, no path, query, fragment or credentials |
hashicorp_vault_namespace | Vault Enterprise namespace; leave empty otherwise |
hashicorp_vault_kv_mount | A single path segment |
hashicorp_vault_kv_base_path | Relative, no empty, . or .. segments, no leading/trailing /, at most 512 characters |
hashicorp_vault_client_certificate_file / _private_key_file | Optional, set together, for Vault TLS auth |
hashicorp_vault_request_timeout_seconds | 2–60 |
hashicorp_vault_rollback_window_seconds | 60–86400 |
hashicorp_vault_reload_* | Optional. When a binary path is set: SHA-256 pin required, up to 32 fixed arguments, 2–60 s timeout. Pin, arguments and timeout are rejected without a binary path. The only substitution is {certificate}. |
Use the reload executable to signal consumers that do not poll Vault (for example an application endpoint or a Vault Agent reload). It runs without a shell after the pointer moves and before health checks, and its digest is re-checked before every run.
Configure in the console
- Open ConsoleIntegrations and select Configure on HashiCorp Vault KV v2.
- Select Enable this integration and the exact enrolled node.
- Enter the profile (for example
prod-service) and the adapter reference (for examplelocal/vault-production) exactly as inconfig.json. - Select Save desired state. On this first assignment the node contacts Vault, writes the bootstrap generation and reports Healthy.
Policy requirements
- Allow
hashicorp_vaultin allowed deployment modes only on profiles permitted to activate this adapter. The Sectigo platform policy must also allow it (platform policy version 3 and later). - Use dual-slot rotation: renewal lead time, overlap, health grace,
manualorautomatic_after_healthpromotion, and a rollback window. - A reference, profile, node or revision mismatch denies the transaction even after issuance. Disabling or unassigning sends an authority-withdrawal tombstone.
How rotation and rollback work
- The node durably stores the signed intent, CSR, generated key, SANs, policy digest, adapter reference and revision.
- The chain and key are written as the next generation with
cas=0. After a crash an existing generation is accepted only if its content digest matches what was intended. - A write-ahead record is committed locally, the pointer is moved with CAS and the optional reload executable runs.
- The node retries the exact HTTPS health URL with backoff until the live leaf matches, or until the rollback window ends — giving polling consumers time to pick up the new pointer.
- Success commits the generation and keeps the previous slot for the rollback window; any failure restores the previous pointer.
- After a crash, reconciliation finishes or reverts the recorded transition: an unconfirmed promotion is reverted, an interrupted rollback is completed.
Rotation requests use POST /v1/managed/rotate with "integrationKind": "hashicorp_vault" (see NGINX → How rotation works); manual promotion uses Verify & promote in ConsoleRotation.
Verify
vault kv get -mount=secret sectigo-edge/prod-service/pointer
======= Secret Path =======
secret/data/sectigo-edge/prod-service/pointer
======= Data =======
Key Value
--- -----
active slot-000004
active_sha256 Hk1yQ8vT2nW5zR0cJ7mB4xE9pL3sA6dF1gU8iO2kN5c
generation 4
previous slot-000003
previous_sha256 b5Zq0kVf3sR8yW1nT6uE2hJ9cL4xA7mD0pG5iK3oB1Q
protocol sectigo-edge.vault-kv-pointer.v1
updated_at 2026-10-01T15:20:11Z
Example output (metadata section omitted). Then confirm the card is Healthy and that sectigo-edge status shows "kind": "hashicorp_vault" with "health": "healthy". Before production, qualify against a real Vault server with your consumer, reload executable and health URL: promotion, rollback, failed health, and a node restart during promotion.
Troubleshooting
| Code | Meaning | What to do |
|---|---|---|
hashicorp_vault_not_configured | The node has no Vault settings | Add the keys and restart |
adapter_reference_invalid / profile_not_mapped | Console and node disagree | Make them identical |
vault_awaiting_desired_state | Configured locally but not yet assigned by the console | Enable and assign the integration |
vault_unavailable | Vault unreachable, TLS failure or token rejected | Check network, CA file, token validity and policy; the node retries automatically |
vault_bootstrap_failed | The first generation could not be written | Check token permissions on the base path and the bootstrap pair |
vault_state_invalid | A generation was modified outside the adapter (digest or version mismatch) or local state is unreadable | Repair the cause; the node never adopts content it did not write |
vault_pointer_invalid | The pointer was modified outside the adapter | Restore it; check that consumers have no write access |
vault_recovery_pending | An interrupted transition is unresolved | Fix connectivity or permissions so recovery can complete |
hashicorp_vault_integration_unavailable / hashicorp_vault_integration_binding_mismatch | Rotation refused | Fix desired state, then retry |
