Vault Paths

Vault KV-v2 path convention — per-tenant tree, per-cluster Kubernetes auth mounts, ESO role bindings.

Vault OSS does not provide Enterprise namespaces, so the lab uses strict path prefixes. The convention is one mount (KV-v2 at secret/), three top-level subtrees (secret/ocp/, secret/platform/, secret/apps/), and per-cluster Kubernetes auth mounts (auth/kubernetes-<cluster>). Every real application gets its own policy, role, namespace-scoped SecretStore, and ExternalSecret. No ClusterSecretStore is used for tenant traffic.

Path convention

Path patternOwnerEngineMounted inBound roleReference
secret/ocp/platform/*platform-adminKV-v2shared across clustersocp-<cluster>-eso (per cluster)plans/disconnected-rebuild/environments/dc-lab/vault-oss-vm-plan.md
secret/ocp/hub-dc-v6/*platform-adminKV-v2hub clusterocp-hub-dc-v6-esoconnection-details/openshift-hub-dc-v6.md
secret/ocp/spoke-dc-v6/*platform-adminKV-v2spoke clusterocp-spoke-dc-v6-esoconnection-details/openshift-spoke-dc-v6.md
secret/ocp/<cluster>/registries/*platform-adminKV-v2per-clusterplatform ESOconnection-details/app-registry-pullsecret.md
secret/ocp/<cluster>/quay/config-bundleplatform-adminKV-v2spoke (Quay namespace)platform ESOproject_obc_to_operand_secret_bridge.md
secret/ocp/platform/kafka/rke2-clientplatform-adminKV-v2sharedplatform ESORUNBOOK.md
secret/platform/gitlab/admin-patplatform-adminKV-v2local automation(no ESO; shell scripts)plans/disconnected-rebuild/gitlab-bootstrap-design.md
secret/platform/gitlab/runners/<class>platform-adminKV-v2local automation(no ESO; bootstrap scripts)plans/gitlab-runner-classes-and-tags.md
secret/platform/gitlab-runner/<class>/registration-tokenplatform-adminKV-v2local automationrunner bootstrap scriptplans/gitlab-runner-access-model.md
secret/platform/gitlab-runner/<class>/minio-cache-credentialsplatform-adminKV-v2local automationrunner cacheplans/gitlab-runner-classes-and-tags.md
secret/platform/minio/terraform-hmacplatform-adminKV-v2local automationterraformplans/disconnected-rebuild/gitlab-bootstrap-design.md
secret/apps/<division>/<app>/<env>/*division teamKV-v2tenant namespaceapps-<cluster>-<division>connection-details/vault-app-secrets.md
secret/apps/<division>/<app>/ci/quay-robotdivision team (CI)KV-v2openshift-pipelines (Tekton)apps-<cluster>-<division>reference_quay_robot_token_convention.md

Path naming rules

  • All-lowercase, hyphen-separated. No underscores in <division> or <app>.
  • <division> is part of the namespace prefix (apps-<division>-*), the Vault role (apps-<cluster>-<division>), the policy (apps-<division>-read), and the path subtree (secret/apps/<division>/...).
  • <env> is one of dev, stage, prod.

Vault auth mounts (per cluster)

Mount pathUsed byTrust anchor
auth/kubernetes-hub-dc-v6hub-dc-v6 ESO + tenant SAshub-dc-v6 API CA + TokenReview reviewer SA
auth/kubernetes-spoke-dc-v6spoke-dc-v6 ESO + tenant SAsspoke-dc-v6 API CA + TokenReview reviewer SA

Policies

Policy nameCapabilities
ocp-<cluster>-esoread on secret/data/ocp/platform/* + secret/data/ocp/<cluster>/*; list on the corresponding metadata paths
apps-<division>-readread on secret/data/apps/<division>/*; list on secret/metadata/apps/<division>/*

Role binding example (per-division, per-cluster)

{
  "bound_service_account_names":      ["app-eso"],
  "bound_service_account_namespaces": ["apps-<division>-*"],
  "token_policies":                   ["apps-<division>-read"],
  "token_ttl":                        "1h",
  "token_max_ttl":                    "4h",
  "audience":                         "vault"
}

ESO SecretStore (per-tenant)

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: vault-apps
  namespace: apps-<DIVISION>-<APP>
spec:
  provider:
    vault:
      server: https://vault.sub.comptech-lab.com:8200
      path: secret
      version: v2
      caBundle: <base64 vault CA>
      auth:
        kubernetes:
          mountPath: kubernetes-<CLUSTER>
          role: apps-<CLUSTER>-<DIVISION>
          serviceAccountRef:
            name: app-eso
            audiences:
              - vault

kind: SecretStore (namespace-scoped) — not ClusterSecretStore. The namespace glob refuses to issue a token outside apps-<division>-*.

Onboarding flow (new division)

  1. Pick a lowercase, hyphen-only division name (e.g. payments).
  2. Run scripts/vault-apps-onboard.sh <division> <cluster> server-side to create/refresh the policy and role.
  3. Create the tenant namespace via GitOps using the tenants/_template/secretstore-vault-apps.yaml shape.
  4. Seed the secret: vault kv put secret/apps/<division>/<app>/<env>/<key> <field>=<value>.
  5. Reference it from the app’s ExternalSecret (kind SecretStore named vault-apps).

Failure modes

SymptomRoot causeFix
ESO operand SecretSyncedError, “permission denied”Tenant SA not in the role’s bound_service_account_namespaces globRe-run the onboarding script with the correct division; confirm namespace prefix.
Vault login hangs / times out from external-secrets nsDefault-deny NetworkPolicy in external-secrets blocks egress to Vault VMApply allow-egress NetworkPolicy targeting Vault (see project_eso_egress_to_vault.md).
apps-<division>-read policy missingVault was re-initialized without re-running onboardingRe-run vault-apps-onboard.sh per division.
QuayRegistry stallssecret/ocp/spoke-dc-v6/quay/config-bundle emptyPopulate the Vault path; restart Quay operator.

Internal only

Vault server URL, root token custody, unseal key custody, Raft node IPs, snapshot bucket credentials, and the Kubernetes auth reviewer SA token for each cluster are kept in opp-full-plat/plans/disconnected-rebuild/environments/dc-lab/vault-oss-vm-plan.md and opp-full-plat/secrets/. None of those values are republished.

Last regenerated from connection-details/vault-app-secrets.md, plans/disconnected-rebuild/environments/dc-lab/vault-oss-vm-plan.md, plans/gitlab-runner-access-model.md, plans/gitlab-runner-classes-and-tags.md, reference_quay_robot_token_convention.md, project_eso_egress_to_vault.md.

Last reviewed: 2026-05-11