Skip to main content

Supported Databases

DBBat ships with five 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):5433DBB_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.
Microsoft SQL ServerTDS (hand-rolled):1434DBB_LISTEN_MSSQLSQL authentication only (LOGIN7), TLS terminated at the proxy. SQLBatch and RPC are both enforced, not just logged. MARS and integrated/Entra ID auth are refused.

Any of these five 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 five 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, TDS SQLBatch/RPC) in the same queries table.
  • One capture format records session traffic for any engine as standard pcapng — see the capture-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​

Verified end-to-end (authenticate, query, and observability capture) against Oracle 23ai:

ClientStatus
go-oraWorks — SQL, rows and bind values end-to-end
Python oracledb (thin)Works — verifier 18453
SQLcl 26.1+ (ojdbc)Works — classic O5LOGON, verifier 18453
sqlplus (OCI instant client)Works — via the wide (4-byte) TTC encoding, no DISABLE_OOB needed
ojdbc11 / DBeaverConnects, SQL logged; result-row capture partial

Row capture is best-effort: the TTC binary layout varies across client versions, so some clients or query shapes capture partial rows. SQL text extraction is reliable across all of them.

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 accepted with zlib (capped at 64 KiB inflated before authentication); snappy/zstd are declined.
  • 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).

Microsoft SQL Server​

Implemented as a hand-rolled TDS proxy in internal/proxy/mssql. See the full SQL Server notes for protocol-level details.

  • Auth termination: SQL authentication only. The username and password come out of LOGIN7 and go through the same Argon2id path as the other proxies; a password prefixed with dbb_ is verified as an API key. The LOGIN7 Database field names the DBBat entry, not a database on the target. Refusals are returned as a proper TDS ERROR + DONE stream, so drivers surface the reason instead of a dropped socket.
  • TLS: TDS encapsulates its TLS handshake inside TDS packets, so the proxy terminates it at the listener. DBB_MSSQL_TLS_CERT_FILE / _KEY_FILE provide the cert; if empty, a self-signed cert is generated at startup. DBB_MSSQL_TLS_DISABLE=true answers ENCRYPT_NOT_SUP and keeps the listener plaintext.
  • Query interception: whole logical messages are classified before anything is forwarded. SQLBatch (text decoded from UCS-2LE) and RPC are both enforced, not merely logged — otherwise wrapping a write in sp_executesql would bypass read_only. Enforcement runs on the statement template; parameter values are captured for the query row.
  • block_copy maps onto bulk copy: BULK INSERT, INSERT BULK and OPENROWSET(BULK …), plus the BulkLoadBCP message that streams the rows.
  • Approval holds work here like everywhere else, and a client Attention cancels a parked statement.

Deliberately unsupported​

FeatureWhy
MARS (MultipleActiveResultSets=True)Refused in PRELOGIN — it multiplexes independent request streams over one connection, which the interception pipeline is not built for.
Integrated authentication (NTLM / Kerberos / SSPI)Refused with a message telling the user to connect with a SQL login.
Azure AD / Entra ID federated authDeclined in PRELOGIN and refused again if the LOGIN7 FEATUREEXT block still carries a FEDAUTH request. DBBat can neither mint nor validate a federated token.
Changing a password through the proxyNot supported.
TDS below 7.4 (pre SQL Server 2012)The token forms DBBat emits are TDS 7.2+ wide forms; the floor is enforced at login.

Stored-procedure bodies are not visible to DBBat — it sees EXEC dbo.Something, not what it does — so such a call fails closed under a restrictive 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, MongoDB, and SQL Server.

  • 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 proxy5433 (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)
SQL Server proxy1434 (SQL Server itself usually binds 1433)
REST API + web UI4200

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

5433 is also the conventional port for pgbouncer. On a host that already runs pgbouncer, set DBB_LISTEN_PG to a free port (e.g. :5434) to avoid a collision.

1434 looks like a collision with SQL Server and is not: the SQL Server Browser service that owns 1434 is UDP-only, so 1434/tcp is free even on a host already running SQL Server.