QA Use Cases — Vault
Tool page: vault.md · PKI: vault-pki.md ·
Credential locations: Service Directory.
Vault is the lab’s single source of truth for credentials: one KV-v2
mount (secret), standalone on the mini, deliberately not
Argo-managed (source: vault.md Part A). Each story’s Assert line
is the code-facing contract a suite implements verbatim.
Admin stories
VLT-A1 — Seal state is known and correct [config/API]
- Persona: Platform admin. Goal: prove Vault’s seal state is a known, intentional state — never a surprise discovered by a consumer.
- Preconditions: Vault pod Running on the mini.
- Steps: query seal status; if the mini rebooted since the last run, confirm the sealed interval was reported and the owner unseal (runbook 11) was the path back. Agents never unseal.
- Expected: unsealed in steady state; sealed only between a tooling-host boot and the owner’s unseal — that interval is design, not outage (source: vault.md, “Seal model”; standing owner rule).
- Assert:
GET /v1/sys/seal-status→200,sealed == false(steady state); run-over-run, anysealed == truesample must be followed by an owner-unseal event, never by an agent action.
VLT-A2 — KV integrity spot-check [config/API]
- Persona: Platform admin. Goal: the paths every other tool’s stories depend on exist.
- Steps: read metadata (never values into logs) for the spot set: Harbor admin, Grafana admin, ACS Central admin, directory admin, ARC GitHub App paths.
- Expected: every spot path exists with a current version; a missing path is drift — the Service Directory promises it (source: service-directory.md; vault.md KV layout).
- Assert: for each path
pin the spot set:GET /v1/secret/metadata/lab|platform/…p→200,data.current_version >= 1. The spot set lives in the check’s data manifest, not in this prose.
VLT-A3 — Internal CA (PKI) health [config/API]
- Persona: Platform admin. Goal: the lab’s internal CA still stands behind the service-health issuer.
- Steps: read the
pki/mount and root CA; confirm rolelab-health-issuer; confirm ClusterIssuervault-labReady. - Expected: mount enabled, root valid, role allows
health.lab, issuer Ready (source: vault-pki.md — status BUILT 2026-10-25). - Assert:
GET /v1/sys/mounts/pki→type == "pki";GET /v1/pki/roles/lab-health-issuer→allowed_domainscontainshealth.lab;kubectl get clusterissuer vault-lab→READY == True.
VLT-A4 — Backup freshness [CLI]
- Persona: Platform admin. Goal: the one irreplaceable store has a current, drill-proven backup.
- Steps: list dated archives on the tooling host and the off-box copy on the agent VM; compare checksums; date the newest archive against the last migration-scale change and its drill record.
- Expected: newest archive post-dates the last migration-scale change, has a same-day restore drill on record, checksums match (source: vault.md — “a backup counts only with a same-day drill”).
- Assert:
sha256(archive@mini) == sha256(archive@vm);archive_date >= last_migration_change_date; drill record exists for that archive (the drill-record lookup is the manual residue — see Gaps).
VLT-A5 — Policy and role audit [config/API]
- Persona: Platform admin. Goal: every Kubernetes-auth role is scoped to exactly one KV path, per the tenant pattern.
- Steps: list policies and k8s-auth roles; read each tenant role’s bound policy.
- Expected: no policy grants broader access than its one tenant path; the live tenant list matches the documented tenant table (source: vault.md, “Delivery into clusters”).
- Assert: for each tenant role
r:policy(r)parses to exactly onepath "secret/data/<tenant>"stanza (plus its metadata twin); tenant set == documented set.
User stories
VLT-U1 — Token login reaches the UI [browser]
- Persona: Platform admin (human). Goal: the human path into Vault works through the front door.
- Steps: open
vault.rhels.com, sign in with a token. - Expected: UI loads over the Let’s Encrypt chain (public roots,
no internal-CA pin); the
secretmount is browsable (source: vault.md, LE cutover 2026-10-25). - Assert: page title contains
Vault; post-login URL matches/ui/vault/secrets; TLS chain verifies against system roots.
VLT-U2 — A tenant reads its own secret [config/API]
- Persona: A VSO tenant workload (a production tenant, e.g. the
CRC
agentopstenant). Goal: delivery works end to end for exactly the tenant’s path. - Steps: read the tenant’s
VaultStaticSecretstatus; compare the delivered Kubernetes Secret’s content hash (never its value). - Expected: SYNCED and the delivered Secret present (source: vault.md, tenant table + Part B).
- Assert:
kubectl get vaultstaticsecret <t>→.status.conditions[Ready] == True; delivered Secret exists;sha256(secret.data) == recorded delivery hash(hash only).
VLT-U3 — A tenant is denied outside its policy (negative) [config/API]
- Persona: The same tenant, reaching beyond its grant.
- Steps: with the tenant’s role token, attempt to read another tenant’s KV path.
- Expected: denied, no data returned (source: vault.md — “a tenant can read its own secret and nothing else”).
- Assert:
GET /v1/secret/data/lab/harbor/adminwith tenant token →403.
VLT-U4 — Sealed Vault refuses reads (negative) [config/API]
- Persona: Any consumer. Preconditions: a sealed window (post-reboot, pre-unseal — never induced by the suite).
- Steps: attempt a KV read with a valid token while sealed.
- Expected: the read fails as sealed; consumers pause rather than falling back to stale or plaintext copies (source: vault.md, “Seal model”).
- Assert:
GET /v1/secret/data/<any>whileseal-status.sealed == true→503withsealederror; the suite records this story as not-run outside a natural sealed window (it never seals Vault to test it).
Gaps
- Known-failure: the two proof tenants’
VaultStaticSecrets reportSYNCED=Falsewhile otherwise healthy — undiagnosed (vault.md). VLT-U2 names a production tenant; the proof tenants stay an explicit known-failure until diagnosed. - TBD: no documented cadence for a PKI issuance round-trip (mount health is proven indirectly by the service-health cert stage when CRC runs).
- Manual-only: VLT-A4’s drill-record half — “a same-day restore drill is on record” is a human attestation in the docs/issue record; the archive and checksum halves are coded. Everything else in this set is code-expressible.