Skip to main content

Secrets Management DevOps Guide

Secrets Management DevOps Guide (Docker, Kubernetes, GitHub)

This guide explains how to run and consume the CRUDService secrets engine across Docker and Kubernetes, and how to automate secret reads/rotations from CI (GitHub Actions). It is derived from the production code paths and supported providers.

What this implements

  • Providers: yaml (dev-only), openbao+kv2, hashicorp+kv2 via hvac
  • Canonical URIs: <provider>[+engine]://<mount>/<path>#<fragment>?<params>
  • Endpoints: CRUD, rotate, versions/metadata, bulk, copy/move, SSE events
  • PEP: VaultService enforces PDP decisions, audience/scope, sender-binding, anti-replay

Key implementation references:

  • Rotation endpoint and PDP/scope enforcement
    • CRUDService/src/api/secrets_routes.py → POST /api/secrets/rotate
  • Rotation controller (KVv2): write new version + best-effort old/new lookup + Kafka audit
    • CRUDService/src/services/rotation_controller.py
  • Provider strategies: OpenBao/HashiCorp KVv2 via hvac
    • CRUDService/src/vault_strategies/openbao_vault_strategy.py
  • Canonical URI parser and tenant mount guard
    • CRUDService/src/secret_uri.py
  • System mapping to APIs (internal service-to-service use)
    • ServiceConfigs/CRUDService/config/systems/secrets.yaml

1) Configuration model

Global env (PEP, policy, audits)

  • TENANT_ID (default: dev)
  • TENANT_ALLOWED_MOUNTS (CSV, e.g. secret,kv) – tenant mount guard
  • SECRETS_AUDIENCE (default: crud.secrets) – expected audience when aud present
  • SECRETS_API_REQUIRE_AUTH (default: false) – require session/bearer on /api/secrets/*
  • SECRETS_ENFORCE_SCOPES (default: false) – enforce per-endpoint scopes
  • TENANT_SALT or SECRET_TENANT_SALT – HMAC salt to compute non‑leaky resource_ref in audits
  • ENVIRONMENT – non‑dev disables YAML writes/deletes

Optional fallback toggles (not recommended for prod):

  • VAULT_ENABLE_FALLBACK, VAULT_FALLBACK_ORDER – provider fallback when a secret/path is missing

Provider configuration (Vault-compatible)

For openbao or hashicorp (KVv2):

  • Required keys (env): OPENBAO_URL/HASHICORP_URL (or VAULT_URL), and either OPENBAO_TOKEN/HASHICORP_TOKEN (or VAULT_TOKEN) or AppRole with OPENBAO_ROLE_ID + OPENBAO_SECRET_ID (analogous for HashiCorp)
  • Optional: VAULT_TIMEOUT, VAULT_VERIFY_SSL (true/false), VAULT_POOL_SIZE, VAULT_TOKEN_RENEWAL_THRESHOLD, VAULT_MAX_CONCURRENT_OPERATIONS

Dev YAML provider:

  • YAML_VAULT_PATH – path to dev secrets YAML (e.g., /app/config/data/dev_secrets.yaml)

2) Docker Compose

Dev (YAML provider)

services:
crudservice:
image: your-registry/crudservice:latest
environment:
ENVIRONMENT: dev
YAML_VAULT_PATH: /app/config/data/dev_secrets.yaml
TENANT_ID: dev
TENANT_ALLOWED_MOUNTS: secret
SECRETS_API_REQUIRE_AUTH: "false"
SECRETS_ENFORCE_SCOPES: "false"
# Optional auditing salt
# TENANT_SALT: "..."
volumes:
- ../ServiceConfigs/CRUDService/config/data/dev_secrets.yaml:/app/config/data/dev_secrets.yaml:ro
ports:
- "9100:9100"

Populate YAML quickly from local .env files using the bootstrap script (dev only):

python scripts/yaml_dev_bootstrap.py --manifest scripts/bootstrap_manifest.yaml --out ..\ServiceConfigs\CRUDService\config\data\dev_secrets.yaml --mount secret --clear

Prod (OpenBao KVv2)

services:
crudservice:
image: your-registry/crudservice:latest
environment:
ENVIRONMENT: prod
TENANT_ID: acme
TENANT_ALLOWED_MOUNTS: secret
SECRETS_API_REQUIRE_AUTH: "true"
SECRETS_ENFORCE_SCOPES: "true"
SECRETS_AUDIENCE: crud.secrets
TENANT_SALT: ${TENANT_SALT}
# Vault-compatible provider (OpenBao shown)
OPENBAO_URL: https://openbao.acme.local
OPENBAO_TOKEN: ${OPENBAO_TOKEN} # or use AppRole below
# OPENBAO_ROLE_ID: ${OPENBAO_ROLE_ID}
# OPENBAO_SECRET_ID: ${OPENBAO_SECRET_ID}
VAULT_TIMEOUT: 30
VAULT_VERIFY_SSL: "true"
VAULT_POOL_SIZE: 10
VAULT_TOKEN_RENEWAL_THRESHOLD: 600
VAULT_MAX_CONCURRENT_OPERATIONS: 50
ports:
- "9100:9100"

Notes:

  • The service reads secrets provider credentials from environment variables (no _FILE convention). Inject them through your secret management for Compose deployments.
  • For HashiCorp Vault, replace OPENBAO_* with HASHICORP_* (same keys) or use VAULT_* fallbacks where applicable.

3) Kubernetes

Use a Secret for tokens/AppRole, and a ConfigMap (or inline env) for non‑sensitive settings.

apiVersion: v1
kind: Secret
metadata:
name: crud-secrets-provider
type: Opaque
stringData:
OPENBAO_TOKEN: "${OPENBAO_TOKEN}"
# Or AppRole:
# OPENBAO_ROLE_ID: "..."
# OPENBAO_SECRET_ID: "..."
---
apiVersion: v1
kind: ConfigMap
metadata:
name: crud-config
data:
ENVIRONMENT: "prod"
TENANT_ID: "acme"
TENANT_ALLOWED_MOUNTS: "secret"
SECRETS_API_REQUIRE_AUTH: "true"
SECRETS_ENFORCE_SCOPES: "true"
SECRETS_AUDIENCE: "crud.secrets"
VAULT_VERIFY_SSL: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: crudservice
spec:
replicas: 2
selector:
matchLabels:
app: crudservice
template:
metadata:
labels:
app: crudservice
spec:
containers:
- name: crudservice
image: your-registry/crudservice:latest
ports:
- containerPort: 9100
env:
- name: ENVIRONMENT
valueFrom: { configMapKeyRef: { name: crud-config, key: ENVIRONMENT } }
- name: TENANT_ID
valueFrom: { configMapKeyRef: { name: crud-config, key: TENANT_ID } }
- name: TENANT_ALLOWED_MOUNTS
valueFrom: { configMapKeyRef: { name: crud-config, key: TENANT_ALLOWED_MOUNTS } }
- name: SECRETS_API_REQUIRE_AUTH
valueFrom: { configMapKeyRef: { name: crud-config, key: SECRETS_API_REQUIRE_AUTH } }
- name: SECRETS_ENFORCE_SCOPES
valueFrom: { configMapKeyRef: { name: crud-config, key: SECRETS_ENFORCE_SCOPES } }
- name: SECRETS_AUDIENCE
valueFrom: { configMapKeyRef: { name: crud-config, key: SECRETS_AUDIENCE } }
- name: OPENBAO_URL
value: "https://openbao.acme.local"
- name: OPENBAO_TOKEN
valueFrom: { secretKeyRef: { name: crud-secrets-provider, key: OPENBAO_TOKEN } }
# Or AppRole:
# - name: OPENBAO_ROLE_ID
# valueFrom: { secretKeyRef: { name: crud-secrets-provider, key: OPENBAO_ROLE_ID } }
# - name: OPENBAO_SECRET_ID
# valueFrom: { secretKeyRef: { name: crud-secrets-provider, key: OPENBAO_SECRET_ID } }
- name: VAULT_TIMEOUT
value: "30"
- name: VAULT_VERIFY_SSL
valueFrom: { configMapKeyRef: { name: crud-config, key: VAULT_VERIFY_SSL } }
- name: VAULT_POOL_SIZE
value: "10"
- name: VAULT_TOKEN_RENEWAL_THRESHOLD
value: "600"
- name: VAULT_MAX_CONCURRENT_OPERATIONS
value: "50"

Dev on Kubernetes with YAML provider (not recommended beyond local clusters): mount a ConfigMap with dev_secrets.yaml and set YAML_VAULT_PATH accordingly.

4) CI automation (GitHub Actions)

The API supports reads and rotations with PDP/scope enforcement. When SECRETS_API_REQUIRE_AUTH=true and SECRETS_ENFORCE_SCOPES=true, the access token must carry:

  • Audience: crud.secrets (or your SECRETS_AUDIENCE)
  • Scopes: e.g., secrets.read, secrets.rotate, etc.

Example: rotate a KVv2 secret fragment via GitHub Actions.

name: Rotate secret
on:
workflow_dispatch:
jobs:
rotate:
runs-on: ubuntu-latest
steps:
- name: Call CRUDService rotate (OpenBao KVv2)
env:
API_BASE: ${{ secrets.CRUD_BASE_URL }} # e.g., https://crud.yourdomain
TOKEN: ${{ secrets.CRUD_BEARER_TOKEN }} # must include audience/scope
run: |
uri="openbao+kv2://secret/app/api#token"
body=$(jq -nc --arg u "$uri" --argjson v '{"token":"new-value"}' '{uri:$u, value:$v}')
curl -sS -X POST "$API_BASE/api/secrets/rotate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data "$body"

Read a pinned version:

      - name: Read versioned value
env:
API_BASE: ${{ secrets.CRUD_BASE_URL }}
TOKEN: ${{ secrets.CRUD_BEARER_TOKEN }}
run: |
uri="openbao+kv2://secret/app/api#token&version=12"
curl -sS "$API_BASE/api/secrets/value?uri=$uri" -H "Authorization: Bearer $TOKEN"

Bulk operations (set/delete/undelete/destroy/rotate) are available via POST /api/secrets/bulk.

5) Supported operations and examples

All operations use Canonical URIs. For KVv2, fragments represent individual keys within the secret object; without a fragment, payloads must be JSON objects.

  • Read (provider-backed):
curl "$CRUD/api/secrets/value?uri=openbao+kv2://secret/app/api#token" -H "Authorization: Bearer $TOKEN"
  • Rotate (YAML dev behaves like update):
curl -X POST "$CRUD/api/secrets/rotate" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"uri": "openbao+kv2://secret/app/api#token",
"value": {"token":"s3cr3t-new"}
}'
  • Versions metadata:
curl "$CRUD/api/secrets/versions?uri=openbao+kv2://secret/app/api" -H "Authorization: Bearer $TOKEN"
  • Undelete versions:
curl -X POST "$CRUD/api/secrets/undelete" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"uri": "openbao+kv2://secret/app/api",
"versions": [3,4]
}'
  • Destroy versions:
curl -X POST "$CRUD/api/secrets/destroy-versions" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"uri": "openbao+kv2://secret/app/api",
"versions": [1,2]
}'

6) Security guidance

  • Enable SECRETS_API_REQUIRE_AUTH and SECRETS_ENFORCE_SCOPES in production
  • Prefer AppRole (role_id/secret_id) over static tokens for Vault
  • Keep VAULT_VERIFY_SSL=true; only disable for controlled dev clusters
  • Restrict TENANT_ALLOWED_MOUNTS to the precise set used per tenant
  • Set TENANT_SALT to enable non‑leaky audit references in Kafka and SSE
  • YAML provider is dev‑only; it is automatically blocked for writes/deletes when ENVIRONMENT is not dev/test

7) Troubleshooting

  • 403 on rotate/read: missing scope or PDP denial; ensure token scopes and PDP rules allow the operation
  • 400 UNSUPPORTED_ENGINE: canonical URI engine not supported (KVv2 required for Vault providers)
  • 400 TENANT_MOUNT_MISMATCH: mount not in TENANT_ALLOWED_MOUNTS
  • 404 on fragment: path exists but key is absent – check fragment vs object payload
  • Token renewal failures: check provider token/AppRole and network access; hvac renewal is automatic before reads/writes
  • Version errors on pinned reads: deleted/destroyed versions raise explicit errors; omit version to read latest

8) Internal service mapping

Internal components can call the secrets API using the system mapping at ServiceConfigs/CRUDService/config/systems/secrets.yaml (inject INTERNAL_EXECUTION_TOKEN). This mapping exposes value, create_or_update, delete, destroy, rotate, bulk, copy, move, and metadata operations that forward to the HTTP endpoints.