shared

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: AGPL-3.0 Imports: 14 Imported by: 0

Documentation

Index

Constants

View Source
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

View Source
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).

View Source
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.

View Source
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).

View Source
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.

View Source
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.

View Source
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.

View Source
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

func BuildUpstreamName(dbbatVersion, username, clientAppName string, maxLen int) string

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 IsDDLQuery

func IsDDLQuery(sql string) bool

IsDDLQuery checks if a query is a DDL operation.

func IsPasswordChangeQuery

func IsPasswordChangeQuery(sql string) bool

IsPasswordChangeQuery checks if a query attempts to modify user/role passwords.

func IsWriteQuery

func IsWriteQuery(sql string) bool

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

func ValidateMySQLQuery(sql string, grant *store.Grant) error

ValidateMySQLQuery runs shared validation plus MySQL-specific blocked patterns.

func ValidateOracleQuery

func ValidateOracleQuery(sql string, grant *store.Grant) error

ValidateOracleQuery runs shared validation plus Oracle-specific blocked patterns.

func ValidateQuery

func ValidateQuery(sql string, grant *store.Grant) error

ValidateQuery checks SQL against grant controls. Used by both PG and Oracle proxies.

Types

type CountingConn added in v0.10.0

type CountingConn struct {
	net.Conn
	// contains filtered or unexported fields
}

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).

func (*CountingConn) Read added in v0.10.0

func (c *CountingConn) Read(p []byte) (int, error)

Read implements net.Conn. Successful byte counts are added to the read counter even when the call returns an error (n > 0 with err is a valid outcome on a closing conn — those bytes did cross the wire).

func (*CountingConn) Write added in v0.10.0

func (c *CountingConn) Write(p []byte) (int, error)

Write implements net.Conn with the same byte-counting semantics as Read.

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

func (g *LimitGuard) Watch(ctx context.Context, interval time.Duration, onViolation func(error))

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.

Jump to

Keyboard shortcuts

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