devfactory-services-dbgateway
Proxy do wire protocol do PostgreSQL com broker de credencial. Apps em qualquer
Estrutura
README
devfactory-apis-dbgateway
Proxy do wire protocol do PostgreSQL com broker de credencial. Apps em qualquer linguagem usam suas libs/drivers normais (psycopg2, SQLAlchemy, pg, JDBC, Npgsql, GORM, Prisma…) para falar com os bancos da plataforma — sem SDK custom. O usuário conecta com uma credencial da plataforma (project key); a senha real do Aurora/RDS fica escondida no gateway.
psycopg2 / pg / JDBC / Npgsql ──(user=projectId, password=PROJECT KEY)──▶ apis-dbgateway ──(senha REAL)──▶ RDS/Aurora PG
lib padrão, zero código de plataforma 1) valida no apis-aws por projeto: db df_{proj}_{env}
2) provisiona lazy + role app_{proj}_{env}
3) injeta a senha real (senha nunca vai pro cliente)
Self-service end-to-end: o gateway (1) valida a PROJECT KEY no apis-aws (POST /internal/project-keys/validate), (2) provisiona lazy o df_{project}_{env} + role app_{project}_{env} no RDS (como master, na 1ª conexão — igual ao provider relational que cria o db por projeto no 1º uso), com senha da role derivada por HMAC (determinística, sem armazenar por projeto), e (3) conecta como aquela role. Criar projeto → pegar a project key → conectar com qualquer driver: o banco nasce sozinho. Tokens estáticos no config.json servem de fallback/bootstrap.
Connection string que o app usa (igual a qualquer Postgres): postgresql://<projectId>:<PROJECT_KEY>@<dbgateway-host>:5432/<projectId>?sslmode=disable
sslmode=disable: o gateway respondeNao SSLRequest (não termina TLS com o cliente — ver Roadmap);
a ponte cliente↔gateway é interna à VPC e o leg gateway↔RDS é que é TLS.requirefalha aqui.
Odatabasedo handshake é ignorado — o gateway força odf_{projectId}_{env}da project key.
Isolamento (e o limite dele)
- Dado: isolado. O gateway ignora o
databasedo handshake e força odf_{proj}_{env}da - Catálogo: NÃO era isolado. O RDS é compartilhado e
pg_catalog.pg_databaseé world-readable —
project key validada, conectando como o role app_{proj}_{env} (least-privilege, só o próprio db). Não dá para ler dado de outro projeto por este caminho.
com a própria project key (via pgAdmin/psql) qualquer role enumerava os nomes de todos os df_*_{env} (o inventário de projetos vazava, mesmo sem acesso ao dado). Postgres não permite esconder linhas do pg_database sem quebrar ferramentas, então a mitigação é defense-in-depth: REVOKE CONNECT ON DATABASE df_* FROM PUBLIC no provisionamento (ver ensure_provisioned) — o nome ainda pode aparecer no catálogo, mas CONNECT a database de outro projeto é negado. Databases criadas antes deste fix são backfilladas ao reprovisionar (o REVOKE roda mesmo no caminho 42P04 = já existe).
Decisão de arquitetura: gateway próprio (v1)
A plataforma já tem identidade (Entra + RBAC + project-keys) e console self-service. Teleport traria um modelo de identidade/RBAC paralelo (duplicaria o existente) e é pesado; pgcat é mais pooler que broker de identidade. O gateway próprio reusa a identidade da plataforma e mantém o broker simples. Pooling fica para PgBouncer/RDS Proxy atrás do gateway, sem reescrever o broker.
Componentes
gateway.py— o proxy (stdlib Python, sessão 1:1, auth cliente cleartext + backend md5/cleartext/scram).Dockerfile— imagem mínima; config vem do Secretdbgateway-configem/app/config.json.k8s/dev/deployment.yaml— Deployment +apis-dbgateway-svc(ClusterIP) +apis-dbgateway-nlb.github/workflows/deploy.yml— build+push da imagem no ECR (OIDC). Deploy é manual (a role
(NLB interno, ponto de entrada L4 na VPC).
gha-ecr só tem access entry EKS no ns de apps; serviço de plataforma é aplicado à mão).
Backend (dev)
RDS PostgreSQL devfactory-dev-postgres (VPC vpc-0f5b75c7bdfd5a8ed, SG devfactory-dev-pg-sg/5432 da VPC, master em Secrets Manager devfactory/dev/postgres-master, privado). Database por projeto df_{projectId}_{env} + role por projeto (espelha o modelo do serviço relational/SQL Server). Aurora PostgreSQL é drop-in (mesmo wire protocol) — só troca o backend.host.
Deploy (manual)
# config do(s) projeto(s) — backend creds reais, fora do git
cp config.sample.json config.json && $EDITOR config.json
kubectl create secret generic dbgateway-config -n devfactory-v1-dev --from-file=config.json=./config.json
kubectl apply -f k8s/dev/deployment.yaml
# imagem é publicada pelo CI no push em main; rolar para a nova:
kubectl set image deploy/apis-dbgateway apis-dbgateway=<ECR>/devfactory-apis-dbgateway:latest -n devfactory-v1-dev
Roadmap p/ produção
- TLS termination no gateway (hoje responde
Nao SSLRequest). - Pooling: PgBouncer/RDS Proxy atrás (modo sessão p/ não quebrar prepared statements).
- Validação do token via apis-aws
POST /internal/project-keys/validate(em vez de config estática). - Secrets Manager via IRSA para a senha do backend (em vez de Secret k8s).
- Authz por query + auditoria; rate limiting; HA (réplicas + NLB).
- Multi-engine: MySQL/Aurora-MySQL e Mongo (DocumentDB) como proxies por protocolo.
- Exposição pública (se necessária): NLB + PrivateLink (5432 não passa por CloudFront).
PoC original (3 linguagens validadas) em devfactory/samples/db-gateway.