Secrets Management
Secrets Management
Section titled “Secrets Management”How TapPass manages API keys, encryption keys, and credentials — and how to integrate with your enterprise secret manager.
Overview: Two Types of Secrets
Section titled “Overview: Two Types of Secrets”TapPass handles two categories of secrets with different trust models:
| Secret | Who uses it | Where it lives | Who manages it |
|---|---|---|---|
TapPass API keys (tp_, tp_dev_) | Developers → TapPass | Issued by TapPass, stored by developer | CISO / Admin |
| LLM provider keys (OpenAI, Anthropic, etc.) | TapPass → LLM provider | Server-side environment variable | Platform team |
| Vault encryption key | TapPass internal | Env var or KMS | Platform team |
The key design principle: developers never see the real LLM API key. They authenticate to TapPass with a tp_ key. TapPass authenticates to the LLM provider with the real key. The developer’s key is revocable, auditable, and scoped. The LLM key is centrally managed.
Developer's machine TapPass (your infra) LLM Provider┌──────────────┐ ┌──────────────────┐ ┌──────────┐│ Claude Code │── tp_dev_xxx ────────▶│ Authenticate │ │ ││ Cursor │ (TapPass key) │ 49-step pipeline │── sk-xxx ──▶│ OpenAI ││ CrewAI │ │ Audit trail │ (real key) │ Anthropic│└──────────────┘ └──────────────────┘ └──────────┘ Developer never LLM key lives here Developer sees sk-xxx (env var or KMS) never seesPart 1: TapPass API Keys (Developer → TapPass)
Section titled “Part 1: TapPass API Keys (Developer → TapPass)”Per-developer keys
Section titled “Per-developer keys”Each developer gets their own key, mapped to their email in the audit trail:
# Admin creates a key for Alice (expires in 90 days)curl -X POST https://tappass.company.com/agents/research-bot/developer-keys \ -H "Authorization: Bearer $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{"developer_email": "alice@company.com", "expires_days": 90}'
# Response (shown ONCE — store securely):# {# "api_key": "tp_dev_aBcDeFgHiJkLmNoPqRsTuVwXyZ...",# "key_id": "dk_abc123",# "expires_at": "2026-06-14T10:00:00Z"# }# Alice uses it (two env vars, no code changes):export OPENAI_BASE_URL=https://tappass.company.com/v1export OPENAI_API_KEY=tp_dev_aBcDeFgHiJkLmNoPqRsTuVwXyZ...Key lifecycle
Section titled “Key lifecycle”| Operation | How | Who |
|---|---|---|
| Create | POST /agents/{id}/developer-keys | Admin, developer |
| List | GET /agents/{id}/developer-keys | Auditor+ |
| Revoke | DELETE /agents/{id}/developer-keys/{key_id} | Admin |
| Expiry | Automatic (1–365 days, configurable) | System |
| Rotation | Create new key → distribute → revoke old key | Admin |
Agent-level authentication (zero-trust)
Section titled “Agent-level authentication (zero-trust)”For production agents (not humans in IDEs), TapPass supports SPIFFE/SPIRE:
- No API keys at all — certificate-based mutual TLS
- SPIRE issues short-lived X.509 certs (1h TTL, auto-rotated)
- Identity:
spiffe://company.com/agent/research-bot - See Identity docs
Part 2: LLM Provider Keys (TapPass → OpenAI/Anthropic)
Section titled “Part 2: LLM Provider Keys (TapPass → OpenAI/Anthropic)”The real LLM API keys live server-side only. Five ways to get them there, from simple to enterprise:
Option A: Environment variable (dev/PoC)
Section titled “Option A: Environment variable (dev/PoC)”# .env file or Docker envOPENAI_API_KEY=sk-proj-xxxANTHROPIC_API_KEY=sk-ant-xxxSimple but the key is in plaintext on disk. Fine for development, not for production.
Option B: Kubernetes Secret (basic production)
Section titled “Option B: Kubernetes Secret (basic production)”kubectl create secret generic tappass-llm-secrets -n tappass \ --from-literal=OPENAI_API_KEY=sk-proj-xxx \ --from-literal=ANTHROPIC_API_KEY=sk-ant-xxxThen reference in the Helm values:
secrets: llmSecret: "tappass-llm-secrets"The secret is mounted as env vars in the TapPass pod. Better than plaintext .env, but the key is stored in etcd (encrypted at rest if you configured it).
Option C: External Secrets Operator (recommended)
Section titled “Option C: External Secrets Operator (recommended)”ESO syncs secrets from your vault into Kubernetes Secrets automatically. Ready-to-use templates in deploy/examples/secrets/:
| Secret Manager | Template |
|---|---|
| HashiCorp Vault | hashicorp-vault.yaml |
| Azure Key Vault | azure-keyvault.yaml |
| AWS Secrets Manager | aws-secrets-manager.yaml |
| CyberArk Conjur | cyberark-conjur.yaml |
Example for HashiCorp Vault:
apiVersion: external-secrets.io/v1beta1kind: ExternalSecretmetadata: name: tappass-llm-keysspec: refreshInterval: 1h # ← auto-syncs rotated keys from Vault secretStoreRef: name: hashicorp-vault kind: ClusterSecretStore target: name: tappass-llm-secrets data: - secretKey: OPENAI_API_KEY remoteRef: key: secret/data/tappass/llm property: openai-api-keyKey rotation flow: rotate the key in your vault → ESO syncs within refreshInterval → new K8s Secret created → TapPass pod picks up new env var on next restart (or use a reloader like Stakater Reloader for zero-downtime rotation).
Option D: HashiCorp Vault Agent Injector (Vault-native orgs)
Section titled “Option D: HashiCorp Vault Agent Injector (Vault-native orgs)”If you run Vault Agent as a sidecar:
# Pod annotations for Vault Agent Injectorspec: template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "tappass" vault.hashicorp.com/agent-inject-secret-llm: "secret/data/tappass/llm" vault.hashicorp.com/agent-inject-template-llm: | {{- with secret "secret/data/tappass/llm" -}} export OPENAI_API_KEY="{{ .Data.data.openai_api_key }}" export ANTHROPIC_API_KEY="{{ .Data.data.anthropic_api_key }}" {{- end -}}Option E: GitLab CI/CD → Kubernetes
Section titled “Option E: GitLab CI/CD → Kubernetes”deploy: script: - kubectl create secret generic tappass-llm-secrets -n tappass \ --from-literal=OPENAI_API_KEY=$OPENAI_API_KEY \ --from-literal=ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ --dry-run=client -o yaml | kubectl apply -f -Where $OPENAI_API_KEY is a GitLab CI/CD masked variable. Or better: GitLab → HashiCorp Vault → ESO (see Option C).
Part 3: Vault Encryption Key (TapPass Internal)
Section titled “Part 3: Vault Encryption Key (TapPass Internal)”TapPass encrypts credentials at rest (OAuth tokens, provider secrets) using AES-256-GCM. The encryption key can come from multiple sources.
Option A: Raw key (env var)
Section titled “Option A: Raw key (env var)”# Generatepython -c "import secrets,base64; print(base64.b64encode(secrets.token_bytes(32)).decode())"
# SetTAPPASS_VAULT_KEY="your-base64-key-here"Option B: Envelope encryption via KMS
Section titled “Option B: Envelope encryption via KMS”The raw encryption key never appears in env vars. Instead, TapPass fetches or unwraps it from your KMS at startup.
# AWS KMS — envelope encryption (DEK wrapped by KMS)TAPPASS_VAULT_KEY_KMS="aws_kms://arn:aws:kms:eu-west-1:123456789:key/key-id"
# GCP Cloud KMS — envelope encryptionTAPPASS_VAULT_KEY_KMS="gcp_kms://projects/my-project/locations/europe-west1/keyRings/tappass/cryptoKeys/vault-key"
# Azure Key Vault — key stored as secretTAPPASS_VAULT_KEY_KMS="azure_kv://tappass-kv/vault-encryption-key"
# HashiCorp Vault KV v2 — key stored in VaultTAPPASS_VAULT_KEY_KMS="vault://secret/data/tappass/vault-key"
# HashiCorp Vault Transit — envelope encryption via Transit engineTAPPASS_VAULT_KEY_KMS="vault_transit://tappass-encryption-key"KMS backend details
Section titled “KMS backend details”AWS KMS
Section titled “AWS KMS”Uses envelope encryption: KMS wraps/unwraps a Data Encryption Key (DEK). The DEK is stored in TAPPASS_VAULT_DEK (auto-generated on first run).
TAPPASS_VAULT_KEY_KMS="aws_kms://key-id-or-arn"TAPPASS_VAULT_DEK="" # auto-generated, store after first runAWS_REGION="eu-west-1" # or set via IAM role on EKSRequires: pip install tappass[kms] (installs boto3)
GCP Cloud KMS
Section titled “GCP Cloud KMS”Same envelope encryption pattern as AWS KMS.
TAPPASS_VAULT_KEY_KMS="gcp_kms://projects/my-project/locations/europe-west1/keyRings/tappass-kr/cryptoKeys/vault-key"TAPPASS_VAULT_DEK="" # auto-generated, store after first runGOOGLE_APPLICATION_CREDENTIALS="/path/to/sa-key.json" # or workload identity on GKERequires: pip install tappass[kms] (installs google-cloud-kms)
Azure Key Vault
Section titled “Azure Key Vault”Stores the encryption key as a secret in Azure Key Vault. Uses DefaultAzureCredential (managed identity on AKS, or service principal).
TAPPASS_VAULT_KEY_KMS="azure_kv://tappass-kv/vault-encryption-key"AZURE_TENANT_ID="your-tenant-id" # not needed with managed identityAZURE_CLIENT_ID="your-client-id" # not needed with managed identityAZURE_CLIENT_SECRET="your-secret" # not needed with managed identityStore the key in Azure Key Vault:
KEY=$(python -c "import secrets,base64; print(base64.b64encode(secrets.token_bytes(32)).decode())")az keyvault secret set --vault-name tappass-kv --name vault-encryption-key --value "$KEY"Requires: pip install tappass[kms] (installs azure-keyvault-secrets, azure-identity)
HashiCorp Vault KV v2
Section titled “HashiCorp Vault KV v2”Reads the encryption key from Vault’s KV v2 secrets engine.
TAPPASS_VAULT_KEY_KMS="vault://secret/data/tappass/vault-key"VAULT_ADDR="https://vault.internal:8200"Store the key in Vault:
KEY=$(python -c "import secrets,base64; print(base64.b64encode(secrets.token_bytes(32)).decode())")vault kv put secret/tappass/vault-key key="$KEY"Authentication (checked in order):
| Method | Env vars | Recommended for |
|---|---|---|
| Token | VAULT_TOKEN | Dev, CI/CD |
| AppRole | VAULT_ROLE_ID + VAULT_SECRET_ID | Production |
| Kubernetes | VAULT_K8S_ROLE | Kubernetes workloads |
Requires: pip install tappass[kms] (installs hvac)
HashiCorp Vault Transit
Section titled “HashiCorp Vault Transit”Uses Vault’s Transit engine for envelope encryption — same concept as AWS KMS, but with Vault. The Transit engine performs crypto operations without exposing the master key.
TAPPASS_VAULT_KEY_KMS="vault_transit://tappass-encryption-key"TAPPASS_VAULT_DEK="" # auto-generated, store after first runVAULT_ADDR="https://vault.internal:8200"VAULT_ROLE_ID="role-id"VAULT_SECRET_ID="secret-id"Setup:
vault secrets enable transitvault write transit/keys/tappass-encryption-key type=aes256-gcm96Requires: pip install tappass[kms] (installs hvac)
Part 4: Key Rotation
Section titled “Part 4: Key Rotation”Rotating developer keys (tp_dev_)
Section titled “Rotating developer keys (tp_dev_)”# 1. Create new key for AlicePOST /agents/research-bot/developer-keys{"developer_email": "alice@company.com", "expires_days": 90}
# 2. Alice updates her env var
# 3. Revoke old keyDELETE /agents/research-bot/developer-keys/dk_old_key_id
# Audit trail shows both keys and the rotation eventRotating LLM provider keys
Section titled “Rotating LLM provider keys”| Method | Rotation flow | Downtime |
|---|---|---|
| Env var | Update .env → restart TapPass | Brief restart |
| K8s Secret | Update secret → rolling restart | Zero (with rollout) |
| ESO + Vault | Rotate in vault → ESO syncs → Reloader triggers rollout | Zero |
| Vault Agent | Rotate in vault → Agent re-renders → App reloads | Zero |
Rotating the vault encryption key
Section titled “Rotating the vault encryption key”TapPass uses versioned ciphertext (v1: prefix) to support key rotation:
- Update the key in your KMS / env var
- Call
POST /admin/vault/rotate(invalidates the in-memory key cache) - New data is encrypted with the new key
- Old data remains readable (TapPass tries current key first, falls back to previous)
Part 5: Architecture Decision — Why a Reverse Proxy?
Section titled “Part 5: Architecture Decision — Why a Reverse Proxy?”The most common question: “Why not just let each developer use their own OpenAI key?”
| Each dev has their own key | Centralized via TapPass | |
|---|---|---|
| Key sprawl | 50 developers × 3 providers = 150 keys | 3 keys total |
| Revocation | Must revoke 150 keys on breach | Revoke 3 keys (or revoke tp_dev_ keys individually) |
| Audit | No centralized audit trail | Every call logged with developer identity |
| Governance | No PII detection, no injection blocking | 49-step pipeline on every call |
| Cost control | No per-developer budget limits | Per-agent, per-developer cost tracking |
| Compliance | Cannot prove to auditors what happened | Tamper-evident audit trail with hash chain |
| Rotation | Must coordinate with 50 developers | Rotate once, server-side |
The reverse proxy pattern is the same architecture used by API gateways (Kong, Apigee) — but specialized for AI governance.
Quick Reference
Section titled “Quick Reference”Environment variables
Section titled “Environment variables”| Variable | Required | Description |
|---|---|---|
TAPPASS_VAULT_KEY | Yes* | Raw AES-256 encryption key |
TAPPASS_VAULT_KEY_KMS | Yes* | KMS URI (alternative to VAULT_KEY) |
TAPPASS_VAULT_DEK | If KMS | Wrapped DEK (auto-generated on first run) |
VAULT_ADDR | If HashiCorp | Vault server URL |
VAULT_TOKEN | If HC token auth | Vault token |
VAULT_ROLE_ID | If HC AppRole | AppRole role ID |
VAULT_SECRET_ID | If HC AppRole | AppRole secret ID |
VAULT_K8S_ROLE | If HC K8s auth | Kubernetes auth role name |
VAULT_KV_MOUNT | Optional | Vault KV mount point (default: secret) |
VAULT_TRANSIT_MOUNT | Optional | Vault Transit mount point (default: transit) |
* One of TAPPASS_VAULT_KEY or TAPPASS_VAULT_KEY_KMS is required when a database is configured.
Install KMS dependencies
Section titled “Install KMS dependencies”pip install tappass[kms]# Installs: hvac, boto3, google-cloud-kms, azure-keyvault-secrets, azure-identityOr install only what you need:
pip install hvac # HashiCorp Vault onlypip install boto3 # AWS KMS onlypip install google-cloud-kms # GCP Cloud KMS onlypip install azure-keyvault-secrets azure-identity # Azure Key Vault only