Skip to main content

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.

EngineProtocolDefault proxy portEnv varStatus
PostgreSQLPostgreSQL wire (pgx/v5):5434DBB_LISTEN_PGFirst-class. Auth terminated at the proxy. MD5 and SCRAM clients work transparently.
OracleTNS / TTC:1522DBB_LISTEN_ORAHand-rolled TTC parser. End-to-end with go-ora; other clients reach AUTH but cannot yet execute queries (see notes below).
MySQLMySQL wire (go-mysql-org/go-mysql):3307DBB_LISTEN_MYSQLcaching_sha2_password (default), mysql_clear_password. TLS terminated at the proxy. mysql_native_password not supported.
MariaDBMySQL wire (same listener):3307DBB_LISTEN_MYSQLShares the MySQL listener. STMT_BULK_EXECUTE is refused — clients need batch-rewriting disabled.
MongoDBMongoDB wire (OP_MSG, hand-rolled):27018DBB_LISTEN_MONGOClients 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 protocol field 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 same queries table.
  • 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:
    1. Regex SQL inspection blocks INSERT, UPDATE, DELETE, MERGE, CREATE, ALTER, DROP, TRUNCATE, GRANT, REVOKE, COPY FROM, CALL.
    2. The proxy issues SET SESSION default_transaction_read_only = on at session start.
    3. Attempts to disable read-only (SET …, RESET, SET ROLE, SET SESSION AUTHORIZATION) are blocked.
  • 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_NAME from the TNS connect descriptor — match it against either the database name or its oracle_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 (function 0x03, sub-op 0x5e) packets; result rows are decoded for the first response (func=0x10) and continuation packets (func=0x06).
  • Number/Date decoding: Oracle NUMBER and DATE formats are decoded for row capture.

Tested clients

ClientStatus
go-oraSQL + rows work end-to-end
Python oracledb (thin)Authenticates; client rejects captured AUTH OK with DPY-4035
ojdbc11 / DBeaverSQL 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_password is 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_FILE provide 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)

OperationWhy
LOAD DATA INFILEReads files from the MySQL server filesystem
LOAD DATA LOCAL INFILEServer 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 DUMPFILEServer-side filesystem write
COM_BINLOG_DUMP / COM_REGISTER_SLAVEReplication protocol — would let a client tail the binlog
COM_SHUTDOWN / COM_PROCESS_KILL / COM_DEBUGPrivileged server operations
STMT_BULK_EXECUTE (MariaDB)Not supported by go-mysql server side; refused

Logged commands

MySQL commandLogged as
COM_QUERYSQL text
COM_STMT_PREPAREPREPARE: <sql> (logged once at prepare time)
COM_STMT_EXECUTEThe previously-prepared SQL with parameters in parameters JSONB
COM_INIT_DBUSE <db> synthetic SQL
COM_PING, COM_QUIT, COM_STMT_RESET, COM_STMT_CLOSENot logged (housekeeping / keepalive noise)

Tested clients

ClientLibraryStatus
Gogo-sql-driver/mysqlFull coverage in CI
MySQL CLImysql 8.xManual smoke test
PythonPyMySQLManual smoke test
MariaDB CLImariadb 10.xManual 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 legacy OP_QUERY/OP_REPLY used for the first hello. BSON encode/decode via go.mongodb.org/mongo-driver/v2/bson. OP_COMPRESSED is 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 in users.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-256 using the decrypted stored credentials. The upstream authSource defaults to admin and is overridable per server via mongo_auth_source.
  • Target-database resolution: Mongo has no pre-auth database field, so the SASL authSource carries the DBBat database name (or a dbbatuser#databasename username, 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 the admin role; they are also exposed under /api/v1/ssh-servers.
  • via_uid on 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_key so 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:

ServiceDefault port
PostgreSQL proxy5434 (PostgreSQL itself usually binds 5432)
Oracle proxy1522 (Oracle listener usually binds 1521)
MySQL/MariaDB proxy3307 (MySQL/MariaDB usually bind 3306)
MongoDB proxy27018 (MongoDB itself usually binds 27017)
REST API + web UI4200

Override any of them with the matching DBB_LISTEN_* environment variable.