Documentation
¶
Index ¶
- Constants
- Variables
- func BuildUpstreamName(dbbatVersion, username, clientAppName string, maxLen int) string
- func DialUpstream(ctx context.Context, resolver ServerResolver, encryptionKey []byte, ...) (net.Conn, error)
- func IsDDLQuery(sql string) bool
- func IsPasswordChangeQuery(sql string) bool
- func IsWriteQuery(sql string) bool
- func ValidateMongoCommand(cmd, dbName string, body bson.Raw, db *store.Server, grant *store.Grant) error
- func ValidateMySQLQuery(sql string, grant *store.Grant) error
- func ValidateOracleQuery(sql string, grant *store.Grant) error
- func ValidateQuery(sql string, grant *store.Grant) error
- type CountingConn
- type Dialer
- type LimitGuard
- type ServerResolver
Constants ¶
const DefaultLimitPollInterval = 250 * time.Millisecond
DefaultLimitPollInterval is how often the watchdog re-evaluates limits when no explicit interval is given. Small enough to cut a runaway stream promptly, large enough that the poll cost (two atomic loads + a time compare) is negligible.
Variables ¶
var ( // ErrByteQuotaExceeded indicates the grant's max_bytes_transferred quota // was crossed while data was flowing. ErrByteQuotaExceeded = errors.New("bandwidth quota exceeded for this grant") // ErrGrantExpired indicates the grant's expiry time passed while the // session was still open. ErrGrantExpired = errors.New("grant expired") // ErrGrantRevoked indicates the grant backing the session was revoked // (by an admin, via the API) while the connection was still live. ErrGrantRevoked = errors.New("grant revoked") )
Limit-enforcement errors shared across proxy implementations. They are surfaced both at command boundaries (a new query rejected because the grant is exhausted/expired) and mid-stream (a running query aborted the moment a limit is crossed).
var ( ErrReadOnlyViolation = errors.New("write operations not permitted with read-only access") ErrDDLBlocked = errors.New("DDL operations not permitted: your access grant blocks schema modifications") ErrPasswordChangeBlocked = errors.New("password modification is not allowed through the proxy") ErrOraclePatternBlocked = errors.New("blocked: this Oracle operation is not permitted through the proxy") ErrMySQLPatternBlocked = errors.New("blocked: this MySQL operation is not permitted through the proxy") )
Validation errors shared across proxy implementations.
var ( ErrMongoReadOnly = errors.New("dbbat: grant is read-only") ErrMongoDDLBlocked = errors.New("dbbat: grant blocks DDL operations") ErrMongoCommandBlocked = errors.New("dbbat: command not permitted through dbbat") ErrMongoUnknownCommand = errors.New("dbbat: command not on the proxy allowlist") ErrMongoDatabaseBlocked = errors.New("dbbat: access to this database is not permitted") )
Mongo-specific validation errors (contract §7 surfaces these as the errmsg of an Unauthorized (13) reply).
var ErrBastionNotSSH = errors.New("ssh: via_uid does not reference an ssh server")
ErrBastionNotSSH is returned when a via_uid resolves to a non-ssh row.
var ErrNoSSHAuthMethod = errors.New("ssh: bastion has no usable auth method (private key or password)")
ErrNoSSHAuthMethod is returned when a bastion row has neither a private key nor a password to authenticate with.
var ErrSSHHostKeyMismatch = errors.New("ssh: host key mismatch with pinned known_host_key")
ErrSSHHostKeyMismatch is returned when a bastion presents a host key that differs from the TOFU-pinned one recorded on first connect.
var ErrServerViaCycleDial = errors.New("ssh: via_uid chain forms a cycle")
ErrServerViaCycleDial mirrors store.ErrServerViaCycle for the dial path.
Functions ¶
func BuildUpstreamName ¶ added in v0.16.0
BuildUpstreamName composes the canonical dbbat-branded application/program name sent to upstream databases, so a DBA looking at the target's session views (pg_stat_activity.application_name, V$SESSION.PROGRAM, MySQL's process list) can attribute a session to the dbbat user who initiated it.
Format:
dbbat/$version @$username
and, when the client declared an application/program name dbbat was able to intercept:
dbbat/$version @$username for $appName
The result is truncated to fit maxLen, preferring to truncate $appName first so the "dbbat/$version @$username" prefix survives intact. If even the bare prefix exceeds maxLen, the prefix itself is truncated as a last resort. maxLen <= 0 is treated as "no room at all" and returns "".
func DialUpstream ¶ added in v0.17.0
func DialUpstream(ctx context.Context, resolver ServerResolver, encryptionKey []byte, srv *store.Server) (net.Conn, error)
DialUpstream dials srv's host:port using the process-wide pooled dialer. resolver loads the via chain and persists TOFU host keys; encryptionKey decrypts bastion SSH secrets.
func IsPasswordChangeQuery ¶
IsPasswordChangeQuery checks if a query attempts to modify user/role passwords.
func IsWriteQuery ¶
IsWriteQuery checks if a query is a write operation.
func ValidateMongoCommand ¶ added in v0.16.0
func ValidateMongoCommand(cmd, dbName string, body bson.Raw, db *store.Server, grant *store.Grant) error
ValidateMongoCommand enforces grant controls and the $db policy on a MongoDB command (contract §2). It operates on the command name and the kind-0 body. db is the session's resolved target database; grant carries the controls.
func ValidateMySQLQuery ¶ added in v0.7.0
ValidateMySQLQuery runs shared validation plus MySQL-specific blocked patterns.
func ValidateOracleQuery ¶
ValidateOracleQuery runs shared validation plus Oracle-specific blocked patterns.
Types ¶
type CountingConn ¶ added in v0.10.0
CountingConn wraps a net.Conn and atomically tracks the number of bytes read from and written to it. The two counters live outside the wrapper so a session can share them across multiple wrapped conns (e.g. client and upstream): writes to one direction on one wrapper match reads from the same direction on the other.
Total() is safe to call concurrently with Read/Write — useful for taking per-query snapshots while the proxy is mid-stream.
func NewCountingConn ¶ added in v0.10.0
func NewCountingConn(conn net.Conn, bytesRead, bytesWritten *atomic.Int64) *CountingConn
NewCountingConn wraps conn so Read accumulates into bytesRead and Write accumulates into bytesWritten. Either counter may be nil to disable that direction (rare; the typical caller passes both).
type Dialer ¶ added in v0.17.0
type Dialer struct {
// contains filtered or unexported fields
}
Dialer opens upstream connections, tunneling through SSH bastions when a server row's ViaUID is set. It pools one *ssh.Client per bastion (keyed by server UID) so that N concurrent proxy sessions multiplex over a single SSH connection; a dead client is transparently reconnected.
func NewDialer ¶ added in v0.17.0
func NewDialer() *Dialer
NewDialer builds an empty Dialer with its own bastion pool.
func (*Dialer) Close ¶ added in v0.18.0
func (d *Dialer) Close()
Close tears down every pooled bastion client. Used by short-lived dialers (connectivity checks) so a probe does not leak an SSH connection.
func (*Dialer) ConnectBastion ¶ added in v0.18.0
func (d *Dialer) ConnectBastion( ctx context.Context, resolver ServerResolver, encryptionKey []byte, uid uuid.UUID, ) (*ssh.Client, error)
ConnectBastion dials (or reuses a pooled connection to) the SSH bastion row identified by uid, completing the handshake and — on first connect — pinning the presented host key via resolver.SetKnownHostKey.
It exists so a connectivity check can validate a `protocol: ssh` row on its own, with no database target behind it. Callers that want to force a real dial (rather than reuse a pooled client) must use a fresh Dialer.
func (*Dialer) DialUpstream ¶ added in v0.17.0
func (d *Dialer) DialUpstream(ctx context.Context, resolver ServerResolver, encryptionKey []byte, srv *store.Server) (net.Conn, error)
DialUpstream dials srv's host:port directly, or through srv.ViaUID's SSH bastion chain when set (recursing for multi-hop jump hosts).
type LimitGuard ¶ added in v0.16.0
type LimitGuard struct {
// contains filtered or unexported fields
}
LimitGuard evaluates a grant's time-window and bandwidth limits against the live wire-byte counters. It is designed to be called on the data path: Check() performs at most two atomic loads and a wall-clock comparison, with no allocation and no locking.
A guard built from a nil grant (or a grant with no limits) never trips, so callers can construct one unconditionally.
func NewLimitGuard ¶ added in v0.16.0
func NewLimitGuard(grant *store.Grant, from, to *atomic.Int64) *LimitGuard
NewLimitGuard builds a guard for grant, reading live traffic from the two atomic counters (either may be nil). grant may be nil — the resulting guard enforces nothing.
func (*LimitGuard) Check ¶ added in v0.16.0
func (g *LimitGuard) Check() error
Check reports the first limit that has been crossed, or nil if the grant is still within bounds. Bandwidth is checked before expiry so the "gigabytes in seconds" case is attributed to the byte quota, but either is a valid abort reason.
func (*LimitGuard) Watch ¶ added in v0.16.0
Watch polls Check on a ticker until a limit is crossed or ctx is canceled. On the first violation it invokes onViolation with the offending error and returns; onViolation is never called more than once. interval <= 0 falls back to DefaultLimitPollInterval.
Watch is the guaranteed, protocol-agnostic enforcement path: it fires even when a query is blocked producing no traffic (idle expiry) and even for protocols whose client library owns the wire (MySQL). onViolation typically force-closes the client and upstream conns to tear the session down.
func (*LimitGuard) WithRevocation ¶ added in v0.16.0
func (g *LimitGuard) WithRevocation(revoked *atomic.Bool) *LimitGuard
WithRevocation attaches the session's shared revocation flag to the guard so Check/Watch also trip when the grant is revoked mid-session. Returns the guard for fluent construction. A nil flag is a no-op (nothing to watch), keeping the plain NewLimitGuard signature stable for callers/tests that don't track revocation.
type ServerResolver ¶ added in v0.17.0
type ServerResolver interface {
GetServerByUID(ctx context.Context, uid uuid.UUID) (*store.Server, error)
SetKnownHostKey(ctx context.Context, uid uuid.UUID, hostKey string) error
}
ServerResolver resolves server rows and persists TOFU host keys. Satisfied by *store.Store; an interface so the dialer can be unit-tested with a fake.