Secretless Proxy
O Secretless Proxy é um sidecar Go que intercepta a conexão da aplicação ao banco/cache, pede um lease dinâmico no CV, conecta no upstream com a credencial efêmera, e faz bridge byte-puro bidirecional.
A app nunca toca a credencial — conecta em localhost:<porta> com
user/pass arbitrários, e o proxy injeta a cred real transparentemente.
A v1.9 introduziu o proxy com Postgres apenas. A v3.0 expandiu para MySQL, Redis e MongoDB, adicionou TLS upstream e lease pool.
Por que
| Modelo padrão (lease via SDK) | Secretless Proxy |
|---|---|
| App importa SDK CV | App não muda — driver Postgres normal |
| App pede lease, recebe user/pass, configura connection | App conecta localhost:5432 user=foo pass=bar |
| Lease vive no env/memória da app | Lease vive só no proxy |
| Logs da app podem vazar credencial | App nunca vê credencial real |
| Reconectar = pedir novo lease | Proxy pede lease e gerencia transparentemente |
Trade-off: uma rede hop a mais + complexidade de deploy do sidecar.
Setup
1. Pré-requisitos no CipherVault
- Backend Postgres + Role criadas em Dynamic Secrets
- AppConnection com permissão
dynamic:request_leaseno role
2. Subir proxy
secretless-proxy \
--listen :5432 \
--upstream db-billing.internal.acme.com.br:5432 \
--protocol postgres \
--upstream-tls \
--upstream-ca-file /etc/ssl/cv-internal-ca.pem \
--lease-pool-size 5 \
--cv-url https://cv.acme.com.br \
--conn-id app_01HXY... \
--role-id 42 \
--cert ./client.crt \
--key ./client.key \
--sig-key ./sig.key
Protocolos suportados (v3.0+)
| Protocolo | --protocol | Auth no upstream |
|---|---|---|
| Postgres | postgres | StartupMessage intercept (CleartextPassword, MD5, SCRAM-SHA-256) |
| MySQL | mysql | Handshake v10, nativePasswordAuth (SHA1-XOR), HandshakeResponse41 rewrite |
| Redis | redis | AUTH no upstream com cred do lease, intercepta AUTH do client (responde +OK fake) |
| MongoDB | mongo | SCRAM-SHA-256 client (PBKDF2 + HMAC RFC 7677), wire framing OP_MSG/OP_QUERY |
Exemplo Kubernetes (MySQL)
apiVersion: apps/v1
kind: Deployment
metadata:
name: billing-api
spec:
template:
spec:
containers:
- name: api
image: acme/billing-api:2.0.0
env:
- name: DATABASE_URL
# App conecta no proxy local
value: mysql://dummy:dummy@localhost:3306/billing
- name: secretless-proxy
image: ciphervault/secretless-proxy:3.0
args:
- --listen=:3306
- --upstream=mysql-prd.internal.acme.com.br:3306
- --protocol=mysql
- --upstream-tls
- --lease-pool-size=5
- --cv-url=https://cv.acme.com.br
- --conn-id=app_01HXY...
- --role-id=42
ports:
- { name: metrics, containerPort: 9090 }
volumeMounts:
- name: cv-creds
mountPath: /etc/cv
readOnly: true
volumes:
- name: cv-creds
secret:
secretName: cv-app-credentials
TLS upstream
| Flag | Descrição |
|---|---|
--upstream-tls | Liga TLS na conexão proxy → upstream |
--upstream-ca-file <path> | CA bundle para validar cert do upstream |
--upstream-tls-skip-verify | ⚠️ Apenas dev — bypass de validação |
Lease pool (v3.0+)
Pré-cria leases idle para reduzir RTT do primeiro request:
| Flag / config | Onde | Descrição |
|---|---|---|
--lease-pool-size N | proxy | Pool client-side de N leases pré-criados; drena no shutdown |
role.lease_reuse_max_uses | role no CV | Mesma (role, app_connection) reaproveita lease server-side via SKIP LOCKED |
dynamic_leases.use_count | tabela | Contador para cap de reuso |
Resultado: primeiro request cai de ~100ms (lease cold) para ~5ms (lease warm).
3. App conecta normalmente
import psycopg2
conn = psycopg2.connect(
"postgres://anything:anything@localhost:5432/billing"
)
# user/pass na URL são ignorados — proxy os substitui
Como funciona
┌─────┐ Postgres wire ┌───────────────┐ Postgres wire ┌──────────┐
│ App │ ───────────────▶ │ secretless- │ ────────────────▶ │ Postgres │
│ │ user=foo │ proxy │ user=cv_lease.. │ │
│ │ pass=bar │ (sidecar) │ pass=<gerada> │ │
└─────┘ └───────┬───────┘ └──────────┘
│ HTTP+mTLS
▼
┌───────────────────┐
│ CipherVault │
│ POST /lease │
│ role-id=42 │
└───────────────────┘
- App abre TCP em
localhost:5432 - Proxy intercepta
Postgres StartupMessage - Proxy chama
POST /dynamic-secrets/leasesno CV via mTLS+DPoP, recebe{username, password, expires_at} - Proxy abre conexão upstream com
username/passwordreais - Daí em diante: byte-puro bidirecional (sem parsing extra)
- Em
Close, proxy chamaPOST /dynamic-secrets/leases/:id/release
Limitações atuais
- 1:1 com upstream — um proxy fala com um único
host:port. Para múltiplos DBs, múltiplos proxies (cada um com seu listen port). - Auth no upstream limitada por protocolo — Postgres suporta CleartextPassword/MD5/SCRAM; MySQL apenas
mysql_native_password; Redis apenasAUTH user pass; Mongo apenasSCRAM-SHA-256.
Observabilidade
Métricas Prometheus expostas em :9090/metrics (gated por env
CV_METRICS_ENABLED=true):
cv_proxy_connections_active{protocol}— gaugecv_proxy_lease_requests_total{result}— contadorcv_proxy_lease_pool_size{state}— gauge (warm/cold)cv_proxy_bytes_total{direction,protocol}— contadorcv_proxy_errors_total{type}— contador
Health endpoint: GET /health (HTTP 200 quando proxy está pronto e
lease pool aquecido).
Boas práticas
-
Use Secretless Proxy quando:
- App é legada e não pode importar SDK
- Você quer garantir que credencial nunca aparece em log/env
- Compliance exige "app não conhece credencial real"
-
Use SDK + Dynamic Secrets quando:
- App pode importar SDK e gerenciar lease
- Você precisa de pool de connections sofisticado
- Você precisa de TLS end-to-end
-
NetworkPolicy obrigatório — restrinja egress do proxy apenas pra
cv.acme.com.br+ DB upstream -
Health check — incluir TCP probe em
localhost:5432no liveness/readiness do pod (proxy escuta antes de pedir lease, então proxy down = liveness fail) -
Logs do proxy vão para stdout — ingerir no SIEM como qualquer outro container