The proxy implements pass-through authentication, where client credentials are validated against the real PostgreSQL backend. This maintains PostgreSQL's native security model while adding query analysis capabilities.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Client (psql) │
│ user: alice, pass: **** │
└─────────────────────────────────┬───────────────────────────────────────────┘
│
│ 1. Connect with credentials
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PROXY (:5432) │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Authentication Handler │ │
│ │ │ │
│ │ 1. Receive username/password from client │ │
│ │ 2. Attempt connection to real PostgreSQL with those credentials │ │
│ │ 3. If connection succeeds → auth passes, store connection │ │
│ │ 4. If connection fails → reject client with auth error │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 2. Validate credentials │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Connection Pool (per user) │ │
│ │ │ │
│ │ alice@db1 ──────► [conn1, conn2, conn3] │ │
│ │ bob@db1 ──────► [conn1, conn2] │ │
│ │ alice@db2 ──────► [conn1] │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 3. Use pooled connection │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Query Handler │ │
│ │ │ │
│ │ 1. Check approval cache (keyed by query fingerprint) │ │
│ │ 2. If not cached → send to LLM for analysis │ │
│ │ 3. Execute approved queries using user's pooled connection │ │
│ │ 4. Return results to client │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────┬───────────────────────────────────────────┘
│
│ 4. Query execution
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Real PostgreSQL (:5433) │
│ │
│ Users: alice, bob, carol │
│ Databases: db1, db2 │
│ Permissions: Standard PostgreSQL GRANT/REVOKE │
└─────────────────────────────────────────────────────────────────────────────┘
Client Proxy PostgreSQL
│ │ │
│──── StartupMessage ──────────►│ │
│ (user, database) │ │
│ │ │
│◄─── AuthenticationMD5 ────────│ │
│ (request password) │ │
│ │ │
│──── PasswordMessage ─────────►│ │
│ (hashed password) │ │
│ │──── Connect ─────────────────►│
│ │ (user, pass, database) │
│ │ │
│ │◄─── AuthenticationOk ─────────│
│ │ (credentials valid) │
│ │ │
│◄─── AuthenticationOk ─────────│ │
│ (proxy accepts client) │ │
│ │ │
│◄─── ReadyForQuery ────────────│ │
│ │ │
Client Proxy PostgreSQL
│ │ │
│──── StartupMessage ──────────►│ │
│──── PasswordMessage ─────────►│ │
│ │──── Connect ─────────────────►│
│ │ │
│ │◄─── ErrorResponse ────────────│
│ │ (invalid credentials) │
│ │ │
│◄─── ErrorResponse ────────────│ │
│ "authentication failed" │ │
│ │ │
| Property | Description |
|---|---|
| User isolation | Each client authenticates as themselves; queries run with their permissions |
| Permission model | PostgreSQL's GRANT/REVOKE system remains authoritative |
| Password security | Proxy never stores passwords; only validates them once |
| Database access | Users can only access databases they have permission for |
| Audit trail | PostgreSQL logs show the actual user executing queries |
| Property | Description |
|---|---|
| Query analysis | All queries pass through LLM before execution |
| Query caching | Approved/rejected decisions cached to reduce LLM calls |
| DDL monitoring | Schema changes trigger metadata cache refresh |
Each authenticated user gets their own connection pool:
type ConnectionPool struct {
mu sync.RWMutex
pools map[string]*UserPool // key: "user@database"
}
type UserPool struct {
conns chan *sql.Conn
maxConns int
connStr string // Built from user's credentials
}{username}@{database}
Examples:
alice@productionbob@analyticsreadonly@production
- First query: Create pool, establish connection
- Subsequent queries: Reuse pooled connection
- Idle timeout: Close connections after 10 minutes of inactivity
- Max connections: Limit per user (default: 5)
# Backend PostgreSQL (only host:port, no credentials)
PG_HOST=localhost
PG_PORT=5433
# Proxy listen address
PROXY_LISTEN=:5432
# Connection pool settings
POOL_MAX_CONNS_PER_USER=5
POOL_IDLE_TIMEOUT=10m
# SSL/TLS (optional)
PROXY_TLS_CERT=/path/to/cert.pem
PROXY_TLS_KEY=/path/to/key.pemThe proxy does NOT have a PG_BACKEND_URL with embedded credentials. Instead:
// OLD (insecure)
connStr = "postgres://admin:secret@localhost:5433/mydb"
// NEW (pass-through)
func buildConnString(user, pass, database string) string {
return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable",
url.QueryEscape(user),
url.QueryEscape(pass),
cfg.PGHost,
cfg.PGPort,
url.QueryEscape(database),
)
}server, err := wire.NewServer(
wire.Port(5432),
wire.Auth(wire.CleartextPassword(validateCredentials)),
)
func validateCredentials(ctx context.Context, username, password string) (bool, error) {
// Get database from startup parameters
database := wire.DatabaseFromContext(ctx)
// Try to connect to real PostgreSQL
connStr := buildConnString(username, password, database)
conn, err := sql.Open("postgres", connStr)
if err != nil {
return false, nil // Auth failed
}
if err := conn.Ping(); err != nil {
conn.Close()
return false, nil // Auth failed
}
// Store connection for this session
pool.Add(username, database, conn)
return true, nil // Auth succeeded
}Each client session carries authentication context:
type SessionContext struct {
Username string
Database string
PoolKey string
ConnectedAt time.Time
}| Method | Support | Notes |
|---|---|---|
trust |
Yes | No password required |
password |
Yes | Cleartext (not recommended) |
md5 |
Yes | Hashed password |
scram-sha-256 |
Planned | PostgreSQL 10+ default |
cert |
Planned | Client certificate |
gss |
No | Kerberos not supported |
ldap |
No | Use PostgreSQL's native LDAP |
- Single backend: All users connect to the same PostgreSQL instance
- No connection multiplexing: Each user gets dedicated connections
- Session state: SET commands affect only that user's connections
- Prepared statements: Cached per-connection, not globally
// main.go
realDB, err = sql.Open("postgres", os.Getenv("PG_BACKEND_URL"))
wire.ListenAndServe(":5432", handler)// main.go
server, err := wire.NewServer(
wire.Port(5432),
wire.Auth(wire.CleartextPassword(validateCredentials)),
wire.SimpleQuery(handler),
)
server.ListenAndServe()# Should succeed (valid user)
psql -h localhost -p 5432 -U alice -d mydb
Password: ****
mydb=>
# Should fail (wrong password)
psql -h localhost -p 5432 -U alice -d mydb
Password: wrong
psql: error: connection refused: authentication failed
# Should fail (unknown user)
psql -h localhost -p 5432 -U nobody -d mydb
psql: error: connection refused: authentication failed
# Should fail (no access to database)
psql -h localhost -p 5432 -U alice -d restricted_db
psql: error: connection refused: permission denied for database