dbbat

command module
v0.25.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: AGPL-3.0 Imports: 31 Imported by: 0

README

DBBat - Database Observability Proxy

Give your devs — and your AI agents — access to prod.

A transparent database proxy that gives developers and AI agents controlled access to production databases. Speaks PostgreSQL, Oracle, MySQL/MariaDB, MongoDB, and Microsoft SQL Server wire protocols, so unmodified clients, drivers, and agent tools connect as usual. Every query logged. Every connection tracked.

Documentation

Full documentation is available at dbbat.com:

Deploying with Kubernetes: the Helm chart, and how the public demo is deployed and upgraded.

Why DBBat?

The Problem:

  • Production databases should not be directly accessible to developers for security and compliance reasons
  • Developers often need access to production data to diagnose issues, debug problems, and understand user behavior
  • AI agents increasingly need the same access — to investigate incidents, answer data questions, run migrations — with even less inherent trust than a human operator
  • Traditional solutions are binary: either full access (risky) or no access (blocks troubleshooting)

The Solution:

DBBat acts as a monitoring proxy that allows controlled access to production databases — for humans and AI agents alike — with:

  • Complete monitoring: Every query and result is logged with full traceability
  • Strict limitations: Time-windowed access, fine-grained controls, query quotas, and data transfer limits
  • Full audit trail: Track who accessed what, when, and what data they retrieved
  • Encrypted credentials: Database passwords never exposed to users
  • Granular access control: Grant temporary access to specific databases with precise permissions

Supported Databases

Engine Protocol Default proxy port Notes
PostgreSQL PostgreSQL wire (pgx/v5) :5433 (DBB_LISTEN_PG) First-class support; auth terminated at proxy
Oracle TNS / TTC :1522 (DBB_LISTEN_ORA) O5LOGON proxy auth; go-ora end-to-end (other clients reach AUTH only — see docs/oracle.md)
MySQL MySQL wire (go-mysql-org/go-mysql) :3307 (DBB_LISTEN_MYSQL) caching_sha2_password (default), mysql_clear_password; TLS terminated at proxy
MariaDB MySQL wire (same listener) :3307 (DBB_LISTEN_MYSQL) Same as MySQL — mysql_native_password not supported, STMT_BULK_EXECUTE refused
MongoDB MongoDB wire (OP_MSG, hand-rolled) :27018 (DBB_LISTEN_MONGO) SCRAM-SHA-256 or PLAIN-over-TLS at the proxy; upstream via SCRAM-SHA-256 — see docs/mongodb.md
SQL Server TDS (hand-rolled) :1434 (DBB_LISTEN_MSSQL) PRELOGIN + encapsulated TLS + LOGIN7, SQL auth only (no NTLM/Kerberos/Entra) — see docs/mssql.md. 1434/tcp is free: the SQL Server Browser that owns 1434 is UDP-only

Each engine has its own listener; enable only the ones you need by setting the matching DBB_LISTEN_* environment variable. PostgreSQL is enabled by default; Oracle/MySQL/MongoDB/SQL Server listen on their default ports unless explicitly disabled in config.

Any of these upstreams can be reached directly or through an SSH bastion — see SSH tunnels.

Features

  • Multi-engine proxy: PostgreSQL, Oracle, MySQL/MariaDB, MongoDB, SQL Server on independent listeners
  • User Management: Local user database with username/password (Argon2id) and admin/viewer/connector roles
  • API Keys: Long-lived bearer tokens (dbb_…) for programmatic access; cannot create or revoke other keys (security restriction)
  • Slack OAuth (optional): Sign-in via Slack workspace, optional auto-provisioning
  • Slack Grant Approvals (optional): Grant requests notify a Slack channel with Approve/Deny buttons (inbound endpoint or Socket Mode); auto-approved requests notify without buttons
  • Server Configuration: Store target server connections with AES-256-GCM encrypted credentials; protocol field per server (postgresql, oracle, mysql, mariadb, mongodb, mssql, ssh)
  • SSH Tunnels: Route upstream connections for any protocol through an SSH bastion (via_uid), with host-key TOFU pinning and a shared pooled dialer
  • Connection & Query Tracking: Logs every connection, every query (SQL text, parameters, duration, rows affected, errors), and optionally captures result rows (query_rows table) up to configurable size limits
  • Access Control: Time-windowed grants (starts_at / expires_at), independent controls (read_only, block_copy, block_ddl), and optional quotas (max_query_counts, max_bytes_transferred)
  • Grant Requests & Auto-Approval: Users request access against grant definitions; definitions flagged auto_approve skip admin review and materialize the grant instantly, with a required justification and a dedicated audit trail
  • Live Enforcement: Limits are enforced mid-stream (not just between commands), and revoking a grant blocks further queries and disconnects sessions already in flight
  • Upstream Identity: The dbbat username is encoded into the upstream connection metadata (application_name / program_name / AUTH_PROGRAM_NM), so target-side monitoring attributes queries to the real person
  • Read-only enforcement: Defense in depth — SQL inspection, PostgreSQL default_transaction_read_only, MySQL/MariaDB blocks for LOAD DATA/SELECT … INTO OUTFILE/etc., and proxy-side opt-out from LOCAL INFILE
  • Audit Trail: Every user, grant and database change is recorded in audit_log with its actor; dbbat only ever inserts, and the REST API exposes reads only (GET /api/v1/audit)
  • Tamper-evident: Every audit entry and every logged query carries an HMAC over its content plus the previous record's MAC, keyed by a subkey derived from DBB_KEY that is never stored in the database — so modifying, deleting or reordering a record is detectable even by someone with write access to dbbat's own PostgreSQL store. dbbat audit verify walks the chain and names the first break (see docs/audit-chain.md)
  • Rate Limiting: Per-user request limits and exponential backoff on failed login
  • Authentication Cache: Optional in-memory cache (TTL + max size) shared across REST and proxy auth paths
  • Session Packet Captures: Optional capture of post-auth session traffic as tcpdump-compatible .pcapng files, so Wireshark dissects them out of the box; same format across all protocols (see docs/dump-format.md) with dbbat dump anonymise for sharing
  • MCP for AI agents: Claude Code, Claude Desktop or any MCP client can query databases through the same pipeline — an agent's statement is executed by dialing dbbat's own proxy listener as the API key's owner, so grants, quotas, logging and mid-flight approval holds all apply with no second execution path (see docs/mcp.md)
  • REST API: OpenAPI 3.0 documented (/api/docs), versioned under /api/v1/
  • Web UI: Embedded React frontend served at /app — servers (/servers, listing SSH bastions alongside database servers), grants, connections and queries all have detail pages
  • Demo / Test modes: Self-provisioning sample data for safe trials and E2E testing

Quick Start

Running with Docker
docker run -d \
  -e DBB_DSN="postgres://user:pass@host:5432/dbbat?sslmode=require" \
  -p 5433:5433 \
  -p 1522:1522 \
  -p 3307:3307 \
  -p 27018:27018 \
  -p 1434:1434 \
  -p 4200:4200 \
  ghcr.io/fclairamb/dbbat

Ports: 5433 PostgreSQL proxy, 1522 Oracle proxy, 3307 MySQL/MariaDB proxy, 27018 MongoDB proxy, 1434 SQL Server proxy, 4200 REST API + web UI.

Running with Docker Compose

See docker-compose installation for a complete example.

Usage Example

All API endpoints are under /api/v1/. See the API Reference for complete documentation. Scripts and CI jobs should standardize on DBBAT_API_KEY, DBBAT_USER, and DBBAT_URL — see Environment Variables for Clients.

Breaking change in v0.17.0: /api/v1/databases was renamed to /api/v1/servers, and /api/v1/ssh-servers was added for bastion management. No /databases alias is kept — update any scripts that used the old path.

1. Login and get a token
TOKEN=$(curl -s -X POST http://localhost:4200/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "admin"}' | jq -r '.token')
2. Create a User
curl -X POST http://localhost:4200/api/v1/users \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "developer",
    "password": "temppass123",
    "roles": ["connector"]
  }'
3. Configure a Target Server
curl -X POST http://localhost:4200/api/v1/servers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production",
    "description": "Production database",
    "protocol": "postgresql",
    "host": "db.example.com",
    "port": 5432,
    "database_name": "myapp",
    "username": "readonly_user",
    "password": "dbpass",
    "ssl_mode": "require"
  }'

For Oracle, set "protocol": "oracle" and add "oracle_service_name": "ORCL". For MySQL/MariaDB, set "protocol": "mysql" (or "mariadb") and use port 3306. For MongoDB, set "protocol": "mongodb", use port 27017, and optionally add "mongo_auth_source": "admin". For SQL Server, set "protocol": "mssql" and use port 1433.

Creating a server whose name is already taken returns 409 DUPLICATE_NAME (same for grant definitions and users).

3b. Reach a Server Through an SSH Bastion (optional)

Servers that aren't directly reachable can be dialled through an SSH tunnel. Register the bastion via /api/v1/ssh-servers, then point the database server at it with via_uid:

BASTION=$(curl -s -X POST http://localhost:4200/api/v1/ssh-servers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "prod-bastion",
    "host": "bastion.example.com",
    "port": 22,
    "username": "ec2-user",
    "ssh_private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n..."
  }' | jq -r '.uid')

curl -X POST http://localhost:4200/api/v1/servers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"prod-behind-bastion\", \"protocol\": \"postgresql\", \"host\": \"10.0.1.20\", \"port\": 5432, \"database_name\": \"myapp\", \"username\": \"readonly_user\", \"password\": \"dbpass\", \"via_uid\": \"$BASTION\"}"

Tunnelling works for all five protocols. The bastion host key is pinned on first use (TOFU) and connections are pooled and shared across sessions. ssh_private_key and ssh_passphrase are write-only and never returned. Set "clear_via_uid": true on update to go back to a direct dial.

4. Grant Access
curl -X POST http://localhost:4200/api/v1/grants \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "<user-uid>",
    "database_id": "<database-uid>",
    "controls": ["read_only"],
    "starts_at": "2024-01-01T00:00:00Z",
    "expires_at": "2024-12-31T23:59:59Z",
    "max_query_counts": 1000,
    "max_bytes_transferred": 10485760
  }'

controls accepts any combination of read_only, block_copy, block_ddl. An empty array means full write access.

5. Connect via Proxy
# PostgreSQL
psql -h localhost -p 5433 -U developer -d production

# Oracle (go-ora-style easy connect)
# Use the database name (or its oracle_service_name) as SERVICE_NAME
# Example with sqlplus: developer/temppass123@//localhost:1522/production

# MySQL / MariaDB
mysql -h 127.0.0.1 -P 3307 -u developer -p production

# MongoDB (authSource carries the DBBat database name)
mongosh "mongodb://developer:temppass123@localhost:27018/?authSource=production&authMechanism=SCRAM-SHA-256"

# SQL Server (the -d database names the DBBat entry; -C trusts the proxy's self-signed cert)
sqlcmd -S localhost,1434 -U developer -P temppass123 -d production -C

Configuration

Variable Description Default
DBB_DSN PostgreSQL DSN for DBBat storage Required
DBB_LISTEN_PG PostgreSQL proxy listen address :5433
DBB_LISTEN_ORA Oracle proxy listen address (empty disables) :1522
DBB_LISTEN_MYSQL MySQL/MariaDB proxy listen address (empty disables) :3307
DBB_LISTEN_MONGO MongoDB proxy listen address (empty disables) :27018
DBB_LISTEN_MSSQL SQL Server (TDS) proxy listen address (empty disables) :1434
DBB_LISTEN_API REST API listen address :4200
DBB_KEY Base64-encoded AES-256 encryption key Auto-generated at ~/.dbbat/key
DBB_KEYFILE Path to file containing encryption key -
DBB_RUN_MODE Run mode: empty (production), test, or demo -
DBB_LOG_LEVEL debug, info, warn, error info
DBB_INSTANCE_ID Identifies this process among replicas sharing the store, so crash-orphaned connections can be reclaimed Hostname
DBB_DUMP_DIR Directory for session packet dumps (empty disables) -
DBB_DUMP_MAX_SIZE Max dump file size per session, in bytes 10485760 (10 MB)
DBB_DUMP_RETENTION Auto-delete dumps older than this (Go duration) 24h
DBB_QUERY_STORAGE_RETENTION Auto-delete query history and captured result rows older than this (Go duration; 0 keeps them forever) 0 (recommended: 720h)
DBB_MCP_ENABLED Serve the MCP endpoint for AI agents at /api/v1/mcp (API-key authenticated; see docs/mcp.md) true
DBB_MYSQL_TLS_DISABLE Disable MySQL TLS termination at the proxy false
DBB_MYSQL_TLS_CERT_FILE PEM cert for MySQL TLS (auto self-signed if empty) -
DBB_MYSQL_TLS_KEY_FILE PEM RSA key for MySQL TLS (auto-generated if empty) -
DBB_OIDC_ISSUER OIDC issuer URL for single sign-on; setting it enables the provider -
DBB_OIDC_CLIENT_ID OIDC client ID (required once the issuer is set) -
DBB_OIDC_CLIENT_SECRET OIDC client secret (required once the issuer is set) -
DBB_OIDC_SCOPES Scopes requested from the issuer (openid always added) openid email profile
DBB_OIDC_DISPLAY_NAME Login-button label SSO
DBB_OIDC_EMAIL_DOMAINS Comma-separated allowlist checked against the verified email claim - (any domain)
DBB_OIDC_GROUPS_CLAIM ID-token claim carrying directory group membership groups
DBB_OIDC_ROLE_MAPPING Binds roles to directory groups (admin=db-admins,viewer=analysts); applied on every login - (roles stay manual)
DBB_AUTH_AUTO_CREATE_USERS Let a verified SSO identity with no local account provision one on first login. Applies to every login provider true
DBB_AUTH_DEFAULT_ROLE Role such an account starts with, and the floor a role mapping never digs below. Must be spelled exactly like a real role (admin, viewer, connector) or startup fails connector
DBB_AUTH_AUTO_CREATE_USERS_<PROVIDER> Same, for one provider only (..._SLACK, ..._OIDC) — for a deployment that trusts its corporate issuer to mint accounts but not its Slack workspace - (the instance-wide value)
DBB_AUTH_DEFAULT_ROLE_<PROVIDER> Same, for one provider only. Validated exactly like the instance-wide role; an unknown provider name is a startup failure - (the instance-wide value)

Each of the two settings resolves per-provider, then instance-wide, then the default. The overrides can equally be written as auth.providers.<name>.* in a config file.

The two instance-wide DBB_AUTH_* settings were previously named DBB_SLACK_AUTH_AUTO_CREATE_USERS / DBB_SLACK_AUTH_DEFAULT_ROLE; those names still work as aliases — instance-wide, not Slack-specific — and the DBB_AUTH_* setting wins whenever both are present, whichever source each came from (environment or config file).

See Configuration for the full set, including rate limiting, query storage, hash presets, auth cache, Slack OAuth, demo target, and dev redirects.

Single sign-on

Any OpenID Connect provider works — Google Workspace, Okta, Microsoft Entra, Keycloak, Authentik. Point DBB_OIDC_ISSUER at your issuer, register https://<your-host>/api/v1/auth/oidc/callback as the redirect URI, and DBBat adds a button to the login page. Every sign-in is carried by an ID token DBBat verifies against the issuer's JWKS, and the code flow uses PKCE (S256).

DBB_OIDC_ROLE_MAPPING="admin=db-admins,viewer=analysts" then lets the directory own role assignment: the mapping is re-applied on every login, so leaving the group demotes at the next sign-in without anyone remembering to click. It is authoritative only for the roles it names, never empties a user's roles below the default one, and never strips the last admin. See Single sign-on (OIDC) for per-provider snippets and the configuration each IdP needs to emit groups.

Security

  • User passwords are hashed with Argon2id
  • Database credentials are encrypted with AES-256-GCM (AAD-bound to the database UID)
  • API keys (dbb_…) are hashed with Argon2id, exactly like passwords — only the 8-character prefix is stored in clear, for lookup, so a leaked database yields no usable key. They cannot create or revoke other keys
  • Failed logins trigger per-username exponential backoff
  • Default admin user (admin / admin) is created on first startup — change it immediately

Architecture

psql / pg client     ─►  DBBat (auth + grant check + log)  ─┐
sqlplus / go-ora     ─►  DBBat (TNS service-name routing)   ─┤   direct dial   ┌─► PostgreSQL / Oracle
mysql / mariadb cli  ─►  DBBat (caching_sha2_password)      ─┼───────────────► ┤
mongosh / driver     ─►  DBBat (SCRAM-SHA-256 / PLAIN-TLS)  ─┤   or SSH tunnel └─► MySQL / MariaDB /
sqlcmd / TDS driver  ─►  DBBat (PRELOGIN + TLS + LOGIN7)    ─┘    (via_uid)         MongoDB / SQL Server

DBBat is a single Go binary backed by a PostgreSQL store (users, servers, grants, connections, queries, audit, dumps).

Development

make dev          # Start dev environment with hot reload (Air + Vite)
make test         # Run Go tests (uses testcontainers)
make test-e2e     # Run Playwright E2E tests
make build-app    # Build frontend + backend
make lint         # Run golangci-lint

See CLAUDE.md for development documentation.

License

AGPL-3.0

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
api
approval
Package approval holds the process-local registry of parked queries.
Package approval holds the process-local registry of parked queries.
auth/oidc
Package oidc implements a generic, configurable-issuer OpenID Connect login provider — the one that lets an organization sign in to dbbat with Google Workspace, Okta, Microsoft Entra, Keycloak, Authentik or anything else that speaks OIDC discovery.
Package oidc implements a generic, configurable-issuer OpenID Connect login provider — the one that lets an organization sign in to dbbat with Google Workspace, Okta, Microsoft Entra, Keycloak, Authentik or anything else that speaks OIDC discovery.
events
Package events implements the in-process publish/subscribe broker behind dbbat's live event stream.
Package events implements the in-process publish/subscribe broker behind dbbat's live event stream.
mcp
Package mcp exposes dbbat's databases to AI agents over the Model Context Protocol, without adding a second way into a database.
Package mcp exposes dbbat's databases to AI agents over the Model Context Protocol, without adding a second way into a database.
notify
Package notify implements notification channels for grant request lifecycle events.
Package notify implements notification channels for grant request lifecycle events.
proxy/conncheck
Package conncheck validates that a configured server row is actually reachable and usable: that an SSH bastion accepts the stored key, and that a database target can be dialed (and authenticated against) — optionally through that bastion.
Package conncheck validates that a configured server row is actually reachable and usable: that an SSH bastion accepts the stored key, and that a database target can be dialed (and authenticated against) — optionally through that bastion.
proxy/kubernetes
Package kubernetes holds the end-to-end integration suite for the Kubernetes port-forward tunnel: a real k3s cluster, a real PostgreSQL pod, and dbbat's own proxy dialing through `pods/portforward`.
Package kubernetes holds the end-to-end integration suite for the Kubernetes port-forward tunnel: a real k3s cluster, a real PostgreSQL pod, and dbbat's own proxy dialing through `pods/portforward`.
proxy/mongodb
Package mongodb implements a transparent MongoDB wire-protocol proxy for dbbat: it terminates client authentication (SASL PLAIN over TLS or a dbb_ API key), authenticates to the upstream MongoDB with stored credentials (SCRAM-SHA-256), and grant-checks, classifies, logs and quota-enforces every command — the same pipeline as the PostgreSQL/Oracle/MySQL proxies.
Package mongodb implements a transparent MongoDB wire-protocol proxy for dbbat: it terminates client authentication (SASL PLAIN over TLS or a dbb_ API key), authenticates to the upstream MongoDB with stored credentials (SCRAM-SHA-256), and grant-checks, classifies, logs and quota-enforces every command — the same pipeline as the PostgreSQL/Oracle/MySQL proxies.
proxy/testsupport
Package testsupport holds fixtures shared by the protocol proxies' test suites (PostgreSQL, MySQL, MongoDB, Oracle, SQL Server).
Package testsupport holds fixtures shared by the protocol proxies' test suites (PostgreSQL, MySQL, MongoDB, Oracle, SQL Server).
proxy/upstream
Package upstream owns the single path dbbat takes from "a server row" to "an authenticated connection to that server".
Package upstream owns the single path dbbat takes from "a server row" to "an authenticated connection to that server".
safe
Package safe holds the goroutine panic guards every other package wraps its detached goroutines in.
Package safe holds the goroutine panic guards every other package wraps its detached goroutines in.
store
Package store provides database access and persistence for DBBat.
Package store provides database access and persistence for DBBat.
version
Package version contains build version information set via ldflags.
Package version contains build version information set via ldflags.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL