Supported Databases
DBBat ships with four independent listeners — one per wire protocol family. Enable only the engines you need by setting the matching DBB_LISTEN_* variable; an empty value disables that proxy.
| Engine | Protocol | Default proxy port | Env var | Status |
|---|---|---|---|---|
| PostgreSQL | PostgreSQL wire (pgx/v5) | :5434 | DBB_LISTEN_PG | First-class. Auth terminated at the proxy. MD5 and SCRAM clients work transparently. |
| Oracle | TNS / TTC | :1522 | DBB_LISTEN_ORA | Hand-rolled TTC parser. End-to-end with go-ora; other clients reach AUTH but cannot yet execute queries (see notes below). |
| MySQL | MySQL wire (go-mysql-org/go-mysql) | :3307 | DBB_LISTEN_MYSQL | caching_sha2_password (default), mysql_clear_password. TLS terminated at the proxy. mysql_native_password not supported. |
| MariaDB | MySQL wire (same listener) | :3307 | DBB_LISTEN_MYSQL | Shares the MySQL listener. STMT_BULK_EXECUTE is refused — clients need batch-rewriting disabled. |
| MongoDB | MongoDB wire (OP_MSG, hand-rolled) | :27018 | DBB_LISTEN_MONGO | Clients authenticate with SCRAM-SHA-256 or PLAIN-over-TLS; upstream via SCRAM-SHA-256. Every command is classified, logged, and grant-checked. |
Any of these four can be reached through an SSH tunnel. Point a server's via_uid at an SSH bastion server and its upstream connection is dialled through that bastion — see SSH Tunnelling below.
The same auth + grant + query-logging pipeline runs across all four protocols, so:
- One user store (Argon2id passwords, roles, optional Slack OAuth) authenticates against any engine.
- One server catalogue holds target connections; a
protocolfield marks the engine. - One grant model applies the same controls (
read_only,block_copy,block_ddl) and quotas regardless of upstream engine. - One query log records every statement (
COM_QUERY,COM_STMT_EXECUTE, PostgreSQL Simple/Extended Query, Oracle TTC Execute) in the samequeriestable. - One dump format captures session traffic for any engine — see the dump-format spec.
PostgreSQL
The reference implementation. Both authentication and command-phase traffic are inspected.
- Auth termination: clients authenticate against the DBBat user store; DBBat re-authenticates upstream using the encrypted credentials in the database catalogue.
- Read-only enforcement is layered:
- Regex SQL inspection blocks
INSERT,UPDATE,DELETE,MERGE,CREATE,ALTER,DROP,TRUNCATE,GRANT,REVOKE,COPY FROM,CALL. - The proxy issues
SET SESSION default_transaction_read_only = onat session start. - Attempts to disable read-only (
SET …,RESET,SET ROLE,SET SESSION AUTHORIZATION) are blocked.
- Regex SQL inspection blocks
- Result rows are captured up to
query_storage.max_result_rows/max_result_bytes.
Oracle
Implemented as a hand-rolled TNS/TTC proxy in internal/proxy/oracle. See the full protocol notes for wire-level details.
- Connection routing uses the
SERVICE_NAMEfrom the TNS connect descriptor — match it against either the databasenameor itsoracle_service_name. - Auth: dbbat speaks O5LOGON to the client — any of the user's API keys works as the password (per-user O5LOGON salts; verifiers loaded from the user's API keys). The upstream session is re-authenticated using the database's stored credentials.
- Query extraction: SQL is parsed out of TTC
Execute(function0x03, sub-op0x5e) packets; result rows are decoded for the first response (func=0x10) and continuation packets (func=0x06). - Number/Date decoding: Oracle
NUMBERandDATEformats are decoded for row capture.
Tested clients
| Client | Status |
|---|---|
go-ora | SQL + rows work end-to-end |
Python oracledb (thin) | Authenticates; client rejects captured AUTH OK with DPY-4035 |
ojdbc11 / DBeaver | SQL works, row capture partial |
| SQLcl 23c+ | Reaches AUTH; client rejects captured AUTH OK with ORA-17401 |
sqlplus (OCI) | Not yet supported (NS protocol not implemented; fails with ORA-12630) |
For now, use go-ora (or older thin-driver clients) end-to-end.
MySQL & MariaDB
Implemented in internal/proxy/mysql on top of go-mysql-org/go-mysql. See the full MySQL notes for protocol-level details.
- Auth termination:
caching_sha2_passwordis advertised by default. The fast-auth scramble is intentionally unused — every login takes the full-auth path so the cleartext password can be verified against the Argon2id hash (over TLS or RSA-OAEP). - TLS: terminated at the proxy.
DBB_MYSQL_TLS_CERT_FILE/_KEY_FILEprovide the cert; if empty, a self-signed cert and RSA-2048 keypair are generated at startup. mysql_clear_password: accepted as a fallback for clients that explicitly pin to it.mysql_native_password: not supported (Argon2id is one-way; we cannot derive the SHA1 nested hash).- API key auth: a "password" prefixed with
dbb_is verified as an API key.
Always-blocked operations (regardless of grant)
| Operation | Why |
|---|---|
LOAD DATA INFILE | Reads files from the MySQL server filesystem |
LOAD DATA LOCAL INFILE | Server can request the client to upload arbitrary local files. Refused at SQL level and via CLIENT_LOCAL_FILES capability opt-out on the upstream connection. |
SELECT … INTO OUTFILE / INTO DUMPFILE | Server-side filesystem write |
COM_BINLOG_DUMP / COM_REGISTER_SLAVE | Replication protocol — would let a client tail the binlog |
COM_SHUTDOWN / COM_PROCESS_KILL / COM_DEBUG | Privileged server operations |
STMT_BULK_EXECUTE (MariaDB) | Not supported by go-mysql server side; refused |
Logged commands
| MySQL command | Logged as |
|---|---|
COM_QUERY | SQL text |
COM_STMT_PREPARE | PREPARE: <sql> (logged once at prepare time) |
COM_STMT_EXECUTE | The previously-prepared SQL with parameters in parameters JSONB |
COM_INIT_DB | USE <db> synthetic SQL |
COM_PING, COM_QUIT, COM_STMT_RESET, COM_STMT_CLOSE | Not logged (housekeeping / keepalive noise) |
Tested clients
| Client | Library | Status |
|---|---|---|
| Go | go-sql-driver/mysql | Full coverage in CI |
| MySQL CLI | mysql 8.x | Manual smoke test |
| Python | PyMySQL | Manual smoke test |
| MariaDB CLI | mariadb 10.x | Manual smoke test |
MongoDB
Implemented as a hand-rolled MongoDB wire proxy in internal/proxy/mongodb. See the full MongoDB notes for protocol-level details.
- Wire framing:
OP_MSG(2013) with kind-0 command bodies and kind-1 document sequences, plus the legacyOP_QUERY/OP_REPLYused for the firsthello. BSON encode/decode viago.mongodb.org/mongo-driver/v2/bson.OP_COMPRESSEDis rejected. - Auth termination: two client mechanisms are terminated at the proxy:
SCRAM-SHA-256(driver default) — DBBat runs the server side of SCRAM against a stored verifier inusers.protocol_data.mongodb, so the cleartext password never crosses the wire (TLS optional). Verifiers exist only for passwords set after this shipped.PLAIN-over-TLS (authMechanism=PLAIN) — the driver sends the cleartext password (hence the TLS requirement), verified via the same Argon2id path as the other proxies.dbb_API keys work as the password.
- Upstream: DBBat authenticates to the target with
SCRAM-SHA-256using the decrypted stored credentials. The upstreamauthSourcedefaults toadminand is overridable per server viamongo_auth_source. - Target-database resolution: Mongo has no pre-auth database field, so the SASL
authSourcecarries the DBBat database name (or adbbatuser#databasenameusername, or the user's single active MongoDB grant).
SSH Tunnelling
A target that is not directly reachable from DBBat can be dialled through an SSH bastion. This is transport-level and engine-agnostic: it works identically for PostgreSQL, Oracle, MySQL/MariaDB, and MongoDB.
- Bastion entries live in the same server catalogue, with
protocol: ssh. They describe how to reach the bastion (host, port, SSH user, credentials) and nothing else — an SSH server is never itself a proxied target and can never be granted to a user. Managing them requires theadminrole; they are also exposed under/api/v1/ssh-servers. via_uidon a database server points at such a bastion. When set, DBBat opens the upstream connection through the tunnel instead of dialling the target directly.- Host-key pinning (TOFU): the host key presented on the first successful connection is pinned, and every later connection must match it. The pinned key is readable as
ssh_known_host_keyso it can be checked against the bastion's real fingerprint. - Private keys and passphrases are write-only — settable through the API, never returned by a read.
- Pooled dialer: one SSH transport connection per bastion is shared across many proxied sessions, each session getting its own forwarded channel. The database connections carried inside stay 1:1 with client sessions.
Picking a Port
Default ports are chosen to avoid colliding with a co-located database server:
| Service | Default port |
|---|---|
| PostgreSQL proxy | 5434 (PostgreSQL itself usually binds 5432) |
| Oracle proxy | 1522 (Oracle listener usually binds 1521) |
| MySQL/MariaDB proxy | 3307 (MySQL/MariaDB usually bind 3306) |
| MongoDB proxy | 27018 (MongoDB itself usually binds 27017) |
| REST API + web UI | 4200 |
Override any of them with the matching DBB_LISTEN_* environment variable.