Secrets Architecture
TapPass Secrets & Identity Architecture
Section titled “TapPass Secrets & Identity Architecture”┌─────────────────────────────────────────────────────────────────────────────┐│ SPIRE (Identity Layer) ││ ││ SPIRE Server ──issues──► SPIRE Agent ──attests──► spiffe-helper ││ trust domain: Workload API writes to disk: ││ tappass.internal socket ││ /run/spire/certs/ ││ svid.pem (1h TTL) ││ svid_key.pem ││ svid_bundle.pem ││ jwt_svid.token (5m TTL)││ │ ││ auto-rotates ││ hourly │└───────────────────────────────────────────────────────────┼─────────────────┘ │ read-only mount┌───────────────────────────────────────────────────────────┼─────────────────┐│ CyberArk Conjur (Secrets Vault) │ ││ │ ││ ┌─────────────────────┐ ┌────────────────────────▼───────────┐ ││ │ Conjur Server │◄───────►│ TapPass ConjurBackend │ ││ │ │ │ │ ││ │ Stores: │ auth: │ 1. Read jwt_svid.token from disk │ ││ │ TAPPASS_ADMIN_ │ JWT │ 2. POST /authn-jwt → Conjur token │ ││ │ API_KEY │ or │ 3. GET /secrets → secret value │ ││ │ TAPPASS_JWT_SECRET │ API │ 4. Cache in memory (5min TTL) │ ││ │ TAPPASS_VAULT_KEY │ key │ │ ││ │ ANTHROPIC_API_KEY │ │ On error: │ ││ │ OPENAI_API_KEY │ │ → Return stale cached value │ ││ │ SSO_CLIENT_SECRET │ │ → Log warning │ ││ │ POSTGRES_PASSWORD │ │ → Never crash │ ││ │ WEBHOOK_SECRETS │ │ │ ││ │ ... │ │ Performance: │ ││ │ │ │ → Cache hit: 2.4μs │ ││ │ Audit log: │ │ → Conjur fetch: ~30ms │ ││ │ who read what when │ │ → 10k reads/sec sustained │ ││ └─────────────────────┘ └────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐│ ││ TapPass secrets.get("name") ││ ││ ┌────────────────────┐ ┌──────────────────────────────┐ ││ │ Is it a secret? │─── yes ───────►│ Vault Backend │ ││ │ │ │ (Conjur / future: Vault, │ ││ │ _API_KEY suffix? │ │ AWS SM, Azure KV, ...) │ ││ │ _SECRET suffix? │ │ │ ││ │ _PASSWORD suffix? │ │ Cache → fetch → stale → │ ││ │ _TOKEN suffix? │ │ fallback │ ││ │ Known secret name? │ └──────────────────────────────┘ ││ │ │ ││ │ │─── no ────────► os.environ.get(name) ││ │ │ (instant, no HTTP, no vault) ││ └────────────────────┘ ││ ││ Only secrets hit the vault. Config never does. ││ 28 secret names → vault. 39 config names → env. Zero wasted calls. ││ │└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐│ ││ Consumers — what reads secrets and what reads config ││ ││ SECRETS (via vault): CONFIG (via os.environ): ││ ││ ┌─────────────────────┐ ┌──────────────────────┐ ││ │ LLM Gateway │ │ Server config │ ││ │ ANTHROPIC_API_KEY │ │ TAPPASS_URL │ ││ │ OPENAI_API_KEY │ │ TAPPASS_PORT │ ││ │ MISTRAL_API_KEY │ │ TAPPASS_HOST │ ││ │ DEEPSEEK_API_KEY │ │ TAPPASS_PRODUCTION │ ││ │ GROQ_API_KEY │ └──────────────────────┘ ││ └─────────────────────┘ ││ ┌─────────────────────┐ ┌──────────────────────┐ ││ │ Vault (encryption) │ │ Database / Redis │ ││ │ TAPPASS_VAULT_KEY │ │ DATABASE_URL │ ││ │ TAPPASS_JWT_SECRET │ │ TAPPASS_KV_URL │ ││ │ TAPPASS_VAULT_DEK │ │ SUPABASE_URL │ ││ └─────────────────────┘ └──────────────────────┘ ││ ┌─────────────────────┐ ┌──────────────────────┐ ││ │ Auth / Identity │ │ Policy / OPA │ ││ │ TAPPASS_ADMIN_ │ │ TAPPASS_OPA_URL │ ││ │ API_KEY │ │ TAPPASS_OPA_MODE │ ││ │ SSO_CLIENT_SECRET │ │ TAPPASS_OPA_CACHE │ ││ │ TAPPASS_ED25519_KEY│ └──────────────────────┘ ││ └─────────────────────┘ ││ ┌─────────────────────┐ ┌──────────────────────┐ ││ │ Webhooks / Email │ │ SPIRE / Identity │ ││ │ STRIPE_WEBHOOK_ │ │ SPIFFE_CERT_DIR │ ││ │ SECRET │ │ SPIFFE_ENDPOINT_ │ ││ │ REVOLUT_WEBHOOK_ │ │ SOCKET │ ││ │ SECRET │ │ TAPPASS_SPIFFE_ │ ││ │ RESEND_API_KEY │ │ TRUST_DOMAINS │ ││ └─────────────────────┘ └──────────────────────┘ ││ ┌─────────────────────┐ ┌──────────────────────┐ ││ │ Pipeline / PII │ │ Observability │ ││ │ PII_ENCRYPTION_KEY │ │ OTEL_EXPORTER_URL │ ││ │ AZURE_CS_KEY │ │ TAPPASS_ALERT_URL │ ││ └─────────────────────┘ └──────────────────────┘ ││ ││ Total: ~28 secrets via vault Total: ~39 config via env ││ │└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐│ ││ Deployment Modes ││ ││ install.sh → Step 7: "Secrets Management" ││ ││ ┌────────────────────────────┐ ┌────────────────────────────────────┐ ││ │ Mode A: Env vars │ │ Mode B: CyberArk Conjur │ ││ │ (default) │ │ (enterprise) │ ││ │ │ │ │ ││ │ .env.prod: │ │ .env.prod: │ ││ │ all secrets + config │ │ config only │ ││ │ chmod 600 │ │ + CONJUR_APPLIANCE_URL │ ││ │ │ │ + CONJUR_ACCOUNT │ ││ │ Containers: │ │ NO secrets in file │ ││ │ tappass, opa, postgres, │ │ │ ││ │ redis, spire, tunnel │ │ Containers: │ ││ │ │ │ all above + conjur, │ ││ │ deploy.sh uses: │ │ conjur-postgres │ ││ │ docker-compose.prod.yml │ │ │ ││ │ │ │ deploy.sh auto-detects: │ ││ │ │ │ + docker-compose.conjur.yml │ ││ └────────────────────────────┘ └────────────────────────────────────┘ ││ ││ Same code. Same binary. Backend auto-detected from CONJUR_APPLIANCE_URL. ││ │└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐│ Security Properties ││ ││ Process: UID 999 (tappass), not root ││ Capabilities: All inheritable dropped ││ Core dumps: Disabled (RLIMIT_CORE=0) ││ Cache: Bounded (500), jittered TTL, LRU eviction ││ Errors: Stale fallback (no outage on Conjur downtime) ││ Auth: SPIFFE JWT → API key fallback → fail ││ Rotation: Auto within 5min TTL, or immediate via /admin/invalidate ││ Config/Secret: Separated — config never hits vault, secrets never in env ││ Audit: Every Conjur fetch logged (who, what, when, result) ││ │└─────────────────────────────────────────────────────────────────────────────┘