June 13, 2026
Every LLM provider call through Tamga touches at least three secrets: the upstream provider API key (OpenAI, Anthropic, etc.), the admin key for Tamga's own API, and the database credentials for the audit store. In a production deployment with SSO, you add JWT signing keys and Clerk/OAuth client secrets.
Tamga v0.1.0 read all secrets from environment variables (`TAMGA_ADMIN_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `DATABASE_URL`, etc.). This is fine for:
It fails for:
Tamga v0.1.1 introduces a `SecretsProvider` interface:
type SecretsProvider interface {
Resolve(ctx context.Context, key string) (string, error)
HealthCheck(ctx context.Context) error
Enabled() bool
Close() error
}
This is deliberately minimal. Providers implement it; callers depend only on the interface. The factory function (`NewFromConfig`) decides which provider to build based on configuration.
**EnvProvider** — the existing behavior, formalized. Reads from `os.Getenv`. Always available, always the fallback.
**VaultProvider** — HashiCorp Vault KV v2. Authenticates via token (env var, token file for Kubernetes service accounts, or AppRole). Reads from `secret/tamga/<key>`. All HTTP calls have 5-second timeouts. Supports TLS/mTLS for Vault connections.
**FallbackProvider** — chains primary (Vault) and secondary (Env). If Vault is unreachable, automatically falls back to env vars. When Vault recovers, automatically resumes using it. Exposes `Degraded()` and `LastError()` for observability.
Secrets are read on every request (admin key validation, upstream API key selection). Calling Vault on every request would add 5-50ms of latency.
The `CachedProvider` wraps any provider with an in-memory TTL cache (default 300 seconds). Cache hits return in nanoseconds. `Invalidate(key)` and `InvalidateAll()` allow immediate rotation.
A critical design choice: if Vault is unreachable at startup, Tamga logs a WARN and continues with env var fallback. The proxy does not refuse to start. This avoids a hard dependency on Vault availability for the hot path.
The implementation is open source at `proxy/internal/secrets/`. The 689-line design document at `docs/SECRETS_MANAGEMENT_DESIGN.md` covers the full architecture.