The Vault lab
A throwaway HashiCorp Vault (1.21.4, dev mode, in memory), served over TLS
with a certificate from qfence-orchestrator pki, and a PostgreSQL 17 for
its database secrets engine. Everything in it is written with the real
vault CLI, as an operator would write it. It checks that qfence’s Vault
provider (pkg/secrets) reads what Vault’s own tools write, signs in
the ways ADR 0011 §11 names, and holds, renews and revokes leases (§4).
Never use it for anything real: the root token is qfence-lab-root,
and every value below is public.
deploy/vault-lab/lab.sh up
QFENCE_VAULT_LAB=deploy/vault-lab/.lab go test ./pkg/secrets ./cmd/orchestrator -run RealVault -p 1 -v
deploy/vault-lab/lab.sh down-p 1 runs the two packages one after the other: one test pauses Vault.
lab.sh uses only the docker CLI and copies its files into the containers,
so it runs against a local daemon and against a remote one: CI’s
test_vault job runs it in a docker:dind service. It builds
qfence-orchestrator (Go) for its pki command, and needs GNU tar.
What it contains
| Path | Engine | Written with | Fields |
|---|---|---|---|
secret/qfence/lab-fortigate | KV 2 | vault kv put secret/qfence/lab-fortigate token=... | token |
secret/qfence/lab-bmc | KV 2 | vault kv put -mount=secret qfence/lab-bmc ... | username, password |
secret/qfence/deleted | KV 2 | vault kv put, then vault kv delete | none: the latest version is deleted |
secret/other/not-for-qfence | KV 2 | vault kv put | token, outside qfence’s policy |
kv1/qfence/old | KV 1 | vault kv put kv1/qfence/old token=... | token |
database/creds/qfence | database | vault write database/roles/qfence ... | username, password: a new PostgreSQL role per read, leased for an hour (24 h at most) |
database/creds/qfence-short | database | the same, with seconds | leased for 6 s, 10 s at most: renewals, the max TTL and reading again show within a test |
The policy qfence-read reads and lists under qfence/ in both KV engines,
reads the two database roles’ credentials, and renews and revokes the leases
of those two roles alone (sys/leases/renew/database/creds/qfence/* and
its revoke): qfence never writes a secret.
How qfence signs in:
| Method | Here | Its token |
|---|---|---|
| Certificate (the default) | auth/cert, role qfence: a client certificate from .lab/pki/client with the common name qfence-lab. .lab/pki/untrusted holds one from another CA, which Vault refuses | periodic, an hour at a time |
role qfence-short, the same certificate | 6 s, 10 s at most: renewed, then replaced by signing in again | |
| AppRole | auth/approle, role qfence: .lab/approle/role-id and .lab/approle/secret-id | periodic, an hour at a time |
| Static token (development) | .lab/reader.token, qfence-lab-reader | no end |
A provider for the lab, in an orchestrator’s -secrets-config:
providers:
- id: vault
type: vault
site: dc1
address: https://127.0.0.1:18200
ca_file: deploy/vault-lab/.lab/pki/server/ca.pem
mounts:
- {path: secret}
- {path: kv1, kv_version: 1}
- {path: database, engine: leased}
auth:
role: qfence
cert_file: deploy/vault-lab/.lab/pki/client/qfence-lab.pem
key_file: deploy/vault-lab/.lab/pki/client/qfence-lab-key.pemThen vault:secret/qfence/lab-bmc#password names one field of
secret/qfence/lab-bmc, and vault:database/creds/qfence a PostgreSQL
login of this orchestrator’s own, renewed while Vault answers and revoked
when it stops.
The lab’s state is .lab (or VAULT_LAB_DIR): address (the URL),
postgres.url (the superuser’s, for a test to see a leased role come and
go), container, the certificates and the AppRole files. VAULT_LAB_PORT
(18200) and VAULT_LAB_PG_PORT (15433) move the published ports.