Documentation
¶
Index ¶
- Constants
- Variables
- type AssignGrantRequest
- type ChangePasswordRequest
- type ConnectionInfo
- type ConnectionTestResponse
- type ConnectionTestWarning
- type CreateAPIKeyRequest
- type CreateAPIKeyResponse
- type CreateDatabaseRequest
- type CreateGrantDefinitionRequest
- type CreateGrantRequestRequest
- type CreateServerGroupRequest
- type CreateUserGroupRequest
- type CreateUserRequest
- type DatabaseLimitedResponse
- type DatabaseResponse
- type DenyGrantRequestRequest
- type DeviceAuthorizationRequest
- type DeviceAuthorizationResponse
- type DeviceConsentInfo
- type DeviceConsentRequest
- type DeviceTokenRequest
- type DumpMetadata
- type ErrorBody
- type ErrorCode
- type GrantSummary
- type LoginExchangeRequest
- type LoginExchangeResponse
- type LoginRequest
- type LoginResponse
- type MeResponse
- type OracleServiceNameConflictResponse
- type OracleServiceNameConflictServerResponse
- type PatternValidationResult
- type PreLoginPasswordChangeRequest
- type QueryValidationResult
- type RateLimiter
- type ResetPasswordRequest
- type Server
- func (s *Server) Notifier() *notify.SlackNotifier
- func (s *Server) ResolveQueryApprovalAs(ctx context.Context, user *store.User, queryUID uuid.UUID, ...) error
- func (s *Server) SetDumpStorage(uploader *dump.Uploader)
- func (s *Server) SetEventPlumbing(broker *events.Broker, registry *approval.Registry, notifier approvalEscalator)
- func (s *Server) Shutdown(ctx context.Context) error
- func (s *Server) Start(addr string) error
- func (s *Server) StartEventListener(ctx context.Context)
- type SessionResponse
- type UpdateDatabaseRequest
- type UpdateGrantDefinitionRequest
- type UpdateServerGroupRequest
- type UpdateUserGroupRequest
- type UpdateUserRequest
- type UserResponse
- type ValidatePatternsRequest
Constants ¶
const ( // ApproverHatNone means the viewer may see the item but not resolve it. ApproverHatNone = "" // ApproverHatAdmin — the admin role, which decides everything. ApproverHatAdmin = "admin" // ApproverHatDefinition — a member of the grant definition's // approver_user_group_uids. Query holds only; it wins over the server chain. ApproverHatDefinition = "definition_approver" // ApproverHatServer — a member of a group resolved off the target server or // its server groups (query approvers for a hold, access approvers for a // grant request). ApproverHatServer = "server_approver" )
Approver hats — *why* the current user may resolve something. Returned alongside each pending item so the UI can say which authority the viewer is acting under instead of just enabling a button, which is the difference between a delegated approver trusting the screen and guessing.
const ( // Credential failures FailureReasonInvalidUsername = "invalid_username" // Username not found FailureReasonInvalidPassword = "invalid_password" // Wrong password FailureReasonPasswordChangeReq = "password_change_required" // Initial password not changed // Token failures FailureReasonTokenInvalid = "token_invalid" // Malformed or unknown token FailureReasonTokenExpired = "token_expired" // Token past expiration FailureReasonTokenRevoked = "token_revoked" // Token was revoked // Account status FailureReasonUserDisabled = "user_disabled" // Account disabled by admin FailureReasonUserDeleted = "user_deleted" // Account was deleted )
REST API failure reasons
const AuditEventOAuthRolesSynced = "user.roles_synced"
AuditEventOAuthRolesSynced is written whenever a login changes a user's roles because their directory membership changed. It is the trail an auditor follows to answer "who made this person an admin, and when did they stop being one" once the answer is "the identity provider did".
const AuditEventUserCreated = "user.created"
AuditEventUserCreated is written when an account comes into existence, whether an admin created it through the users API or a verified OAuth identity auto-provisioned it. Deliberately the same event type for both: "where did this account come from" is one question, and it should not need two queries to answer. The details say which path it took.
Variables ¶
var ( // ErrSelfApproval is returned when the requester tries to resolve their // own held statement. Four-eyes means four eyes, admin or not. ErrSelfApproval = errors.New("you cannot resolve your own query") // ErrNotAnApprover is returned when the user is neither an admin nor a // member of the grant's approver groups. ErrNotAnApprover = errors.New("you are not an approver for this query") )
Approval authorization errors surfaced outside the HTTP handlers.
var (
ErrInvalidUID = errors.New("invalid UID")
)
API errors.
var ErrRequestOutOfScope = errors.New("grant request is out of the definition's scope")
ErrRequestOutOfScope is returned when a pending request no longer matches its definition's scope — typically because an admin tightened the scope after the request was filed. It hard-blocks the approval rather than silently granting access the current policy no longer allows; an admin who still wants to grant it can always create a direct grant.
Functions ¶
This section is empty.
Types ¶
type AssignGrantRequest ¶ added in v0.23.0
type AssignGrantRequest struct {
// GrantDefinitionID identifies the definition to instantiate — either its
// uid or its slug, resolved the same way every other definition reference
// is. A slug always resolves to the live version.
GrantDefinitionID string `json:"grant_definition_id" binding:"required"`
UserID uuid.UUID `json:"user_id" binding:"required"`
DatabaseID uuid.UUID `json:"database_id" binding:"required"`
// StartsAt defaults to now. The window's *length* is the definition's
// duration_seconds and is not negotiable here — that is part of the shape.
StartsAt *time.Time `json:"starts_at"`
}
AssignGrantRequest is the body for POST /grants: an admin issuing a grant directly, without the user having to file a request.
There is deliberately no way to describe the grant's *shape* here. A grant is an instance of a grant definition and nothing else; ad-hoc grants — an admin typing controls and quotas into a form — are what made definitions untrustworthy as the policy source of truth, so the shape comes from the definition or the grant does not exist.
type ChangePasswordRequest ¶
type ChangePasswordRequest struct {
Username string `json:"username"`
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
}
ChangePasswordRequest represents the request body for authenticated password change Requires re-authentication via username/password (not Bearer token) Username is optional when changing your own password (inferred from :uid param)
type ConnectionInfo ¶ added in v0.11.0
type ConnectionInfo struct {
DatabaseUID uuid.UUID `json:"database_uid"`
DatabaseName string `json:"database_name"`
Protocol string `json:"protocol"`
Format string `json:"format"` // "uri" or "ez-connect"
URL string `json:"url"`
}
ConnectionInfo describes a ready-to-paste connection URL for a single database.
func BuildConnectionURL ¶ added in v0.11.0
func BuildConnectionURL( db *store.Server, user *store.User, endpoints store.ResolvedEndpoints, apiKey string, ) (ConnectionInfo, bool)
BuildConnectionURL builds a connection URL for the given database, user, and key. When apiKey is "", the placeholder "{DBBAT_KEY}" is substituted in the password slot. Returns (ConnectionInfo{}, false) when the protocol's resolved port is 0.
type ConnectionTestResponse ¶ added in v0.18.0
type ConnectionTestResponse struct {
OK bool `json:"ok"`
Stage string `json:"stage"`
Code string `json:"code"`
Message string `json:"message"`
HostKeyPinned bool `json:"host_key_pinned,omitempty"`
KnownHostKey string `json:"ssh_known_host_key,omitempty"`
// CAPinned / LearnedCACert are the Kubernetes counterparts: whether this
// check performed the TOFU pin, and the bundle in force afterwards. Public
// challenge material, exactly like the host key.
CAPinned bool `json:"k8s_ca_pinned,omitempty"`
K8sLearnedCACert string `json:"k8s_learned_ca_cert,omitempty"`
// Warnings are problems that did not fail the check but that the admin needs
// to see: today, an Oracle row whose upstream service name is also claimed
// by rows spelling their host differently. A check can be green and still
// carry one — the row works, connecting by shared service name does not.
Warnings []ConnectionTestWarning `json:"warnings,omitempty"`
DurationMs int64 `json:"duration_ms"`
}
ConnectionTestResponse is the API shape of a connectivity check. It mirrors conncheck.Result and carries no secret material — only the stage reached, a machine-readable code, a human-readable message, and the bastion's public host key.
type ConnectionTestWarning ¶ added in v0.25.0
ConnectionTestWarning is one advisory finding of a check. The code is the stable part the UI keys off; the message is what it shows.
type CreateAPIKeyRequest ¶
type CreateAPIKeyRequest struct {
Name string `json:"name" binding:"required"`
ExpiresAt *time.Time `json:"expires_at"`
}
CreateAPIKeyRequest represents the request to create an API key
type CreateAPIKeyResponse ¶
type CreateAPIKeyResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Key string `json:"key"` // Only returned once!
KeyPrefix string `json:"key_prefix"`
ExpiresAt *time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
Connections []ConnectionInfo `json:"connections"`
ConnectionsTruncated bool `json:"connections_truncated"`
}
CreateAPIKeyResponse represents the response when creating an API key
type CreateDatabaseRequest ¶
type CreateDatabaseRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Host string `json:"host" binding:"required"`
Port int `json:"port"`
DatabaseName string `json:"database_name"`
Username string `json:"username" binding:"required"`
Password string `json:"password"`
SSLMode string `json:"ssl_mode"`
Protocol string `json:"protocol"`
OracleServiceName string `json:"oracle_service_name"`
MongoAuthSource string `json:"mongo_auth_source"`
Listable *bool `json:"listable"`
ViaUID *uuid.UUID `json:"via_uid"`
// SSH bastion secrets (write-only, never returned).
SSHPrivateKey string `json:"ssh_private_key"`
SSHPassphrase string `json:"ssh_passphrase"`
// Kubernetes cluster material. The ServiceAccount bearer token is sent as
// Password — it is the row's secret, encrypted exactly like a database
// password — while the CA bundle and the namespace are public and stored
// in clear. There is deliberately no kubeconfig field: EKS/GKE kubeconfigs
// authenticate through exec credential plugins, which a server daemon
// cannot run.
//
// K8sCACert is optional: a row that supplies none gets a trust-on-first-use
// pin instead, learned on the first connect and stored separately. Pasting
// the bundle remains the stronger setup — see docs/kubernetes.md.
K8sCACert string `json:"k8s_ca_cert"`
K8sNamespace string `json:"k8s_namespace"`
K8sInsecureSkipTLSVerify bool `json:"k8s_insecure_skip_tls_verify"`
// AccessApproverUserGroupUIDs / QueryApproverUserGroupUIDs name the user
// groups allowed to decide grant *requests* for this server and to release
// approval *holds* on statements against it. Omitted or empty falls back to
// the server groups this server belongs to, and then to admins.
AccessApproverUserGroupUIDs []uuid.UUID `json:"access_approver_user_group_uids"`
QueryApproverUserGroupUIDs []uuid.UUID `json:"query_approver_user_group_uids"`
// TestConnection asks the API to validate the row by actually dialing it
// once created. Opt-in, and never fatal: the outcome comes back as a
// connection_test object alongside the created server.
TestConnection bool `json:"test_connection"`
}
CreateDatabaseRequest represents the request to create a database (or, when protocol is "ssh", an SSH bastion). Password is optional for SSH rows that authenticate with a private key.
type CreateGrantDefinitionRequest ¶ added in v0.10.0
type CreateGrantDefinitionRequest struct {
Name string `json:"name" binding:"required"`
// Slug is a stable, human-typeable, machine-friendly identifier for this
// definition — mandatory at the API level. The server never generates
// one; that's the frontend's job (derive-from-name until the operator
// edits it manually), which keeps the API contract explicit for CLI and
// agent callers.
Slug string `json:"slug" binding:"required"`
Description string `json:"description"`
DurationSeconds int64 `json:"duration_seconds" binding:"required"`
Controls []string `json:"controls"`
MaxQueryCounts *int64 `json:"max_query_counts"`
MaxBytesTransferred *int64 `json:"max_bytes_transferred"`
// Priority, when supplied, is stamped verbatim on every grant
// materialized from this definition instead of the tier its controls
// would earn. null/omitted — the normal case — leaves it auto.
Priority *int16 `json:"priority"`
// AutoApprove, when true, makes grant requests against this definition
// skip the pending/admin-approval step and materialize the grant
// instantly.
AutoApprove bool `json:"auto_approve"`
// UserGroupUIDs restricts the definition to members of these user groups.
// Empty/omitted = every user, which is how every pre-scoping definition
// keeps behaving.
UserGroupUIDs []uuid.UUID `json:"user_group_uids"`
// ServerGroupUIDs restricts the definition to the databases currently
// belonging to these server groups. Empty/omitted = every database.
ServerGroupUIDs []uuid.UUID `json:"server_group_uids"`
// ApprovalPatterns are RE2 patterns that suspend a matching statement
// until a second human approves it. Empty/omitted = no approval gating.
// Compiled here so a bad pattern is a 400 rather than a runtime surprise
// on the proxy hot path.
ApprovalPatterns []string `json:"approval_patterns"`
// SampleQueries are representative SQL statements saved alongside the
// patterns to validate them against — a test bench for pattern
// authoring. See POST /grant-definitions/validate-patterns.
SampleQueries []string `json:"sample_queries"`
// ApproverUserGroupUIDs lists the user groups whose members may resolve
// those holds, in addition to admins. Empty/omitted = admins only.
ApproverUserGroupUIDs []uuid.UUID `json:"approver_user_group_uids"`
// RetiredDatabaseUIDs catches the removed per-database scope. It is not a
// compatibility shim: server groups are a different entity, so there is
// nothing to fold it onto, and silently dropping a scope restriction on
// the floor would fail *open*. Its only job is to make the request a 400
// that names the replacement — see validateDefinitionRequest.
RetiredDatabaseUIDs []uuid.UUID `json:"database_uids"`
// RetiredGroupUIDs / RetiredApproverGroupUIDs catch the pre-rename
// spellings of UserGroupUIDs / ApproverUserGroupUIDs. Server groups made a
// bare "group" ambiguous, so both fields were renamed; the old spellings
// are refused rather than folded onto their replacements — silently
// ignoring a scope restriction fails *open*. See errRetiredGroupUIDs /
// errRetiredApproverGroupUIDs.
RetiredGroupUIDs []uuid.UUID `json:"group_uids"`
RetiredApproverGroupUIDs []uuid.UUID `json:"approver_group_uids"`
}
CreateGrantDefinitionRequest is the JSON body for POST /grant-definitions.
type CreateGrantRequestRequest ¶ added in v0.10.0
type CreateGrantRequestRequest struct {
// GrantDefinitionID identifies the definition being requested — either
// its uid or its slug. Widened from uid-only to accept a slug too,
// rather than adding a sibling field, since this is the one place the
// API takes a bare definition reference as a request-body value (every
// other reference is a path param, resolved the same uid-or-slug way).
GrantDefinitionID string `json:"grant_definition_id" binding:"required"`
DatabaseID uuid.UUID `json:"database_id" binding:"required"`
Justification string `json:"justification"`
}
CreateGrantRequestRequest is the body for POST /grant-requests.
type CreateServerGroupRequest ¶ added in v0.24.0
type CreateServerGroupRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
// MemberUIDs, when non-nil, replaces the group's membership. On create it
// seeds it; on update a nil value leaves membership untouched.
MemberUIDs []uuid.UUID `json:"member_uids"`
// AccessApproverUserGroupUIDs / QueryApproverUserGroupUIDs are the
// group-level fallback for the two approver kinds: they cover every member
// server that names no approvers of its own. Several groups holding the same
// server union. Empty means the group names nobody, which leaves the
// decision to admins.
//
// Unlike member_uids these are written on every update, empty included —
// clearing a list is a real policy change and has to be expressible.
AccessApproverUserGroupUIDs []uuid.UUID `json:"access_approver_user_group_uids"`
QueryApproverUserGroupUIDs []uuid.UUID `json:"query_approver_user_group_uids"`
}
CreateServerGroupRequest is the body for POST /server-groups.
type CreateUserGroupRequest ¶ added in v0.18.0
type CreateUserGroupRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
// MemberUIDs, when non-nil, replaces the group's membership. On create
// it seeds it; on update a nil value leaves membership untouched.
MemberUIDs []uuid.UUID `json:"member_uids"`
}
CreateUserGroupRequest is the body for POST /user-groups.
type CreateUserRequest ¶
type CreateUserRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Roles []string `json:"roles"`
}
CreateUserRequest represents the request to create a user
type DatabaseLimitedResponse ¶
type DatabaseLimitedResponse struct {
UID uuid.UUID `json:"uid"`
Name string `json:"name"`
Description string `json:"description"`
}
DatabaseLimitedResponse represents a database with limited info (non-admin)
type DatabaseResponse ¶
type DatabaseResponse struct {
UID uuid.UUID `json:"uid"`
Name string `json:"name"`
Description string `json:"description"`
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
DatabaseName string `json:"database_name,omitempty"`
Username string `json:"username,omitempty"`
SSLMode string `json:"ssl_mode,omitempty"`
Protocol string `json:"protocol,omitempty"`
OracleServiceName string `json:"oracle_service_name,omitempty"`
MongoAuthSource string `json:"mongo_auth_source,omitempty"`
Listable bool `json:"listable"`
CreatedBy *uuid.UUID `json:"created_by,omitempty"`
ViaUID *uuid.UUID `json:"via_uid,omitempty"`
// SSHKnownHostKey is the TOFU-pinned bastion host key (read-only). Secrets
// (private key, passphrase) are never returned.
SSHKnownHostKey string `json:"ssh_known_host_key,omitempty"`
// Kubernetes cluster material. Public: the CA bundle is challenge material
// and the namespace is scope, so both round-trip. The ServiceAccount token
// never does.
K8sCACert string `json:"k8s_ca_cert,omitempty"`
// K8sLearnedCACert is the bundle dbbat pinned itself on first connect, when
// the row supplied none (read-only). Kept apart from K8sCACert so a client
// can say which of the two is in force: a supplied bundle always wins.
K8sLearnedCACert string `json:"k8s_learned_ca_cert,omitempty"`
K8sNamespace string `json:"k8s_namespace,omitempty"`
K8sInsecureSkipTLSVerify bool `json:"k8s_insecure_skip_tls_verify,omitempty"`
// The two approver lists. Always rendered, empty included: an empty list is
// a meaningful state (fall back to the server groups, then to admins), so
// omitting it would make "cleared" indistinguishable from "unknown".
AccessApproverUserGroupUIDs []uuid.UUID `json:"access_approver_user_group_uids"`
QueryApproverUserGroupUIDs []uuid.UUID `json:"query_approver_user_group_uids"`
// ConnectionTest is present only when the request set test_connection.
ConnectionTest *ConnectionTestResponse `json:"connection_test,omitempty"`
// OracleServiceNameConflict is present only on an Oracle row whose upstream
// service name is also claimed by rows pointing at a different host:port —
// a configuration that makes every connect arriving with the shared service
// name fail ORA-12514 while each row on its own checks out.
OracleServiceNameConflict *OracleServiceNameConflictResponse `json:"oracle_service_name_conflict,omitempty"`
}
DatabaseResponse represents a database with full details (admin only)
type DenyGrantRequestRequest ¶ added in v0.10.0
type DenyGrantRequestRequest struct {
Reason string `json:"reason"`
}
DenyGrantRequestRequest is the body for POST /grant-requests/:uid/deny.
type DeviceAuthorizationRequest ¶ added in v0.19.0
type DeviceAuthorizationRequest struct {
ClientName string `json:"client_name"`
ClientID string `json:"client_id"`
}
DeviceAuthorizationRequest is the request body for POST /auth/device. client_name is a dbbat extension used for the consent-page label; client_id is accepted for OAuth compatibility but ignored (dbbat is not a multi-client authorization server).
type DeviceAuthorizationResponse ¶ added in v0.19.0
type DeviceAuthorizationResponse struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
VerificationURIComplete string `json:"verification_uri_complete"`
ExpiresIn int `json:"expires_in"`
Interval int `json:"interval"`
}
DeviceAuthorizationResponse is the RFC 8628 device authorization response.
type DeviceConsentInfo ¶ added in v0.19.0
type DeviceConsentInfo struct {
ClientName string `json:"client_name"`
UserCode string `json:"user_code"`
Status string `json:"status"`
ExpiresAt time.Time `json:"expires_at"`
}
DeviceConsentInfo is the public detail of a device authorization request, safe to show on the (authenticated) consent page.
type DeviceConsentRequest ¶ added in v0.19.0
type DeviceConsentRequest struct {
UserCode string `json:"user_code" binding:"required"`
Approve bool `json:"approve"`
}
DeviceConsentRequest is the request body for POST /auth/device/consent.
type DeviceTokenRequest ¶ added in v0.19.0
type DeviceTokenRequest struct {
GrantType string `json:"grant_type" binding:"required"`
DeviceCode string `json:"device_code" binding:"required"`
ClientID string `json:"client_id"`
}
DeviceTokenRequest is the request body for POST /auth/device/token.
type DumpMetadata ¶ added in v0.23.0
DumpMetadata reports whether a session capture is available for download and, if so, how large it is — enough for the UI to label the download action and warn before pulling a multi-megabyte file.
type ErrorBody ¶ added in v0.4.0
type ErrorBody struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Detail string `json:"detail,omitempty"`
RetryAfter int `json:"retry_after,omitempty"`
}
ErrorBody is the standard error response structure.
type ErrorCode ¶ added in v0.4.0
type ErrorCode string
ErrorCode is a machine-readable error code returned in API responses.
const ( // ErrCodeInternalError indicates an unexpected server error. ErrCodeInternalError ErrorCode = "INTERNAL_ERROR" // ErrCodeValidationError indicates invalid input. ErrCodeValidationError ErrorCode = "VALIDATION_ERROR" // ErrCodeNotFound indicates the requested resource was not found. ErrCodeNotFound ErrorCode = "NOT_FOUND" ErrCodeUnauthorized ErrorCode = "UNAUTHORIZED" // ErrCodeForbidden indicates insufficient permissions. ErrCodeForbidden ErrorCode = "FORBIDDEN" // ErrCodeInvalidCredentials indicates wrong username or password. ErrCodeInvalidCredentials ErrorCode = "INVALID_CREDENTIALS" // ErrCodePasswordChangeRequired indicates the user must change their password. ErrCodePasswordChangeRequired ErrorCode = "PASSWORD_CHANGE_REQUIRED" // ErrCodeWeakPassword indicates the password does not meet requirements. ErrCodeWeakPassword ErrorCode = "WEAK_PASSWORD" // ErrCodeRateLimited indicates too many requests. ErrCodeRateLimited ErrorCode = "RATE_LIMITED" // ErrCodeConflict indicates a state conflict (e.g. trying to transition // a non-pending grant request, or duplicating a unique resource). ErrCodeConflict ErrorCode = "CONFLICT" // ErrCodeOAuthFailed indicates an OAuth authentication failure. ErrCodeOAuthFailed ErrorCode = "OAUTH_FAILED" // ErrCodeOAuthStateMismatch indicates an invalid or expired OAuth state. ErrCodeOAuthStateMismatch ErrorCode = "OAUTH_STATE_MISMATCH" // ErrCodeOAuthProviderError indicates the OAuth provider returned an error. ErrCodeOAuthProviderError ErrorCode = "OAUTH_PROVIDER_ERROR" // ErrCodeOAuthUserNotLinked indicates no account is linked to the OAuth identity. ErrCodeOAuthUserNotLinked ErrorCode = "OAUTH_USER_NOT_LINKED" // ErrCodeOAuthExchangeInvalid indicates the one-time login exchange code is // unknown, already redeemed or expired. ErrCodeOAuthExchangeInvalid ErrorCode = "OAUTH_EXCHANGE_INVALID" // ErrCodeOAuthWrongWorkspace indicates the wrong OAuth workspace was used. ErrCodeOAuthWrongWorkspace ErrorCode = "OAUTH_WRONG_WORKSPACE" // ErrCodeDuplicateName indicates a resource with that name already exists. ErrCodeDuplicateName ErrorCode = "DUPLICATE_NAME" // ErrCodeTargetMatchesSelf indicates the target matches the storage database. ErrCodeTargetMatchesSelf ErrorCode = "TARGET_MATCHES_SELF" // ErrCodeGrantExpired indicates the access grant has expired. ErrCodeGrantExpired ErrorCode = "GRANT_EXPIRED" // ErrCodeQuotaExceeded indicates a usage quota was exceeded. ErrCodeQuotaExceeded ErrorCode = "QUOTA_EXCEEDED" )
type GrantSummary ¶ added in v0.23.0
type GrantSummary struct {
UID uuid.UUID `json:"uid"`
Controls []string `json:"controls"`
StartsAt time.Time `json:"starts_at"`
ExpiresAt time.Time `json:"expires_at"`
Revoked bool `json:"revoked"`
Priority int16 `json:"priority"`
GrantDefinitionID uuid.UUID `json:"grant_definition_id"`
GrantDefinitionName string `json:"grant_definition_name"`
GrantDefinitionSlug string `json:"grant_definition_slug"`
GrantDefinitionIsActive bool `json:"grant_definition_is_active"`
}
GrantSummary is the slice of an access grant a connection detail page needs to answer "under which grant did this session run?" without a second round trip: controls, validity window, revocation state and priority.
Controls are read from the grant's definition — grants carry no shape of their own — so the summary also names that definition, which is what the page links to for the full policy.
type LoginExchangeRequest ¶ added in v0.23.0
type LoginExchangeRequest struct {
Code string `json:"code" binding:"required"`
}
LoginExchangeRequest is the request body for POST /auth/oauth/exchange.
type LoginExchangeResponse ¶ added in v0.23.0
type LoginExchangeResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
}
LoginExchangeResponse hands the web session token back to the SPA. Shaped like the device-grant token response so both login paths look the same to a client.
type LoginRequest ¶
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
LoginRequest represents the request body for login
type LoginResponse ¶
type LoginResponse struct {
Token string `json:"token"`
ExpiresAt string `json:"expires_at"`
User UserResponse `json:"user"`
}
LoginResponse represents the response for a successful login
type MeResponse ¶
type MeResponse struct {
UID string `json:"uid"`
Username string `json:"username"`
Roles []string `json:"roles"`
PasswordChangeRequired bool `json:"password_change_required"`
Session SessionResponse `json:"session"`
}
MeResponse represents the response for /auth/me
type OracleServiceNameConflictResponse ¶ added in v0.25.0
type OracleServiceNameConflictResponse struct {
// ServiceName is the shared upstream `oracle_service_name`.
ServiceName string `json:"service_name"`
// Upstreams are the distinct `host:port` spellings in play, sorted.
Upstreams []string `json:"upstreams"`
// Servers are the rows claiming the service name, ordered by name.
Servers []OracleServiceNameConflictServerResponse `json:"servers"`
// Message is the ready-made operator-facing sentence, so every surface
// (this API, the connectivity check, the proxy log) says the same thing.
Message string `json:"message"`
}
OracleServiceNameConflictResponse warns that this Oracle row's upstream service name is also claimed by rows spelling their host differently.
It is not a failure of the row: each such row dials and logs in fine on its own. What breaks is a client that connects with the *shared service name* rather than the dbbat server name — the proxy compares candidate upstreams as text, so two spellings of one machine read as two upstreams and the connect is refused ORA-12514. The compare stays textual on purpose, so this warning is how the misconfiguration becomes visible before a user hits it.
type OracleServiceNameConflictServerResponse ¶ added in v0.25.0
type OracleServiceNameConflictServerResponse struct {
UID uuid.UUID `json:"uid"`
Name string `json:"name"`
Host string `json:"host"`
Port int `json:"port"`
}
OracleServiceNameConflictServerResponse identifies one row taking part in a conflict. No credential material: the name and address are what an admin needs to reconcile the spellings.
type PatternValidationResult ¶ added in v0.23.0
type PatternValidationResult struct {
Pattern string `json:"pattern"`
// Error is the compile error message, present only when the pattern
// failed to compile. This is exactly the error NewApprovalGate silently
// swallows (with only a server-side warning log) when building the live
// per-session gate — see approval.go — surfaced here instead of staying
// invisible until a live statement fails to hold.
Error string `json:"error,omitempty"`
}
PatternValidationResult reports whether one pattern (by its position in the request's patterns array) compiled.
type PreLoginPasswordChangeRequest ¶
type PreLoginPasswordChangeRequest struct {
Username string `json:"username" binding:"required"`
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
}
PreLoginPasswordChangeRequest represents the request body for pre-login password change
type QueryValidationResult ¶ added in v0.23.0
type QueryValidationResult struct {
Query string `json:"query"`
Normalized string `json:"normalized"`
Matched bool `json:"matched"`
// MatchedPattern is the source text of the first pattern that matched,
// present only when Matched is true.
MatchedPattern string `json:"matched_pattern,omitempty"`
}
QueryValidationResult reports how one sample query fares against the patterns: the NormalizeSQL form the patterns actually run against, and which pattern matched it first, if any — same semantics as ApprovalGate.Match on the proxy hot path.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter implements a sliding window rate limiter
func NewRateLimiter ¶
func NewRateLimiter(cfg config.RateLimitConfig) *RateLimiter
NewRateLimiter creates a new rate limiter with the given configuration
func (*RateLimiter) Middleware ¶
func (rl *RateLimiter) Middleware() gin.HandlerFunc
Middleware returns a Gin middleware for rate limiting
func (*RateLimiter) PostAuthMiddleware ¶
func (rl *RateLimiter) PostAuthMiddleware() gin.HandlerFunc
PostAuthMiddleware is a rate limiter middleware that runs after authentication It uses the authenticated user ID for rate limiting
func (*RateLimiter) PreAuthMiddleware ¶
func (rl *RateLimiter) PreAuthMiddleware() gin.HandlerFunc
PreAuthMiddleware is a rate limiter middleware that runs before authentication It rate limits by IP for unauthenticated requests
type ResetPasswordRequest ¶ added in v0.3.0
type ResetPasswordRequest struct {
NewPassword string `json:"new_password" binding:"required"`
}
ResetPasswordRequest represents the request body for admin password reset
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server represents the REST API server.
func NewServer ¶
func NewServer(dataStore *store.Store, encryptionKey []byte, logger *slog.Logger, cfg *config.Config) *Server
NewServer creates a new API server.
func (*Server) Notifier ¶ added in v0.20.0
func (s *Server) Notifier() *notify.SlackNotifier
Notifier exposes the server's Slack client so the process wiring can build the approval escalator on top of it instead of opening a second connection. nil when Slack notifications are disabled.
func (*Server) ResolveQueryApprovalAs ¶ added in v0.20.0
func (s *Server) ResolveQueryApprovalAs(ctx context.Context, user *store.User, queryUID uuid.UUID, status, reason string) error
ResolveQueryApprovalAs authorizes and applies a decision for a user that did not come through the HTTP handlers (today: a Slack button click). It repeats every check the REST path makes — pending state, self-approval, approver membership — because "it came from Slack" is not an authorization.
func (*Server) SetDumpStorage ¶ added in v0.23.0
SetDumpStorage installs the blob store holding uploaded session captures, so downloads can fall back to it when the capture is no longer (or never was) in this replica's local spool. Called by the process wiring; nil is fine and means local-only captures.
func (*Server) SetEventPlumbing ¶ added in v0.20.0
func (s *Server) SetEventPlumbing(broker *events.Broker, registry *approval.Registry, notifier approvalEscalator)
SetEventPlumbing installs the shared broker and approval registry. Called by the process wiring so the API and the proxies publish into — and resolve against — the same instances.
func (*Server) StartEventListener ¶ added in v0.20.0
StartEventListener subscribes to the cross-replica notification channels and republishes what arrives into the local broker, so an admin whose WebSocket landed on replica B still sees a query held on replica A.
The payload carries only ids; the row is re-read here. That keeps SQL text out of the database's notification queue and means a late subscriber gets the current state rather than a stale snapshot.
type SessionResponse ¶
type SessionResponse struct {
ExpiresAt string `json:"expires_at"`
CreatedAt string `json:"created_at"`
}
SessionResponse represents session info in me response
type UpdateDatabaseRequest ¶
type UpdateDatabaseRequest struct {
// Name renames the server, subject to the same slug rule creation enforces
// (store.IsValidServerName). It is the one field clients actually type —
// the PostgreSQL/MySQL/MongoDB database name, the Oracle SERVICE_NAME — so
// a rename breaks every saved connection string using the old one, while
// leaving already-authenticated sessions alone. Omitted (null) leaves it
// unchanged; a name already taken, soft-deleted rows included, is a 409.
Name *string `json:"name"`
Description *string `json:"description"`
Host *string `json:"host"`
Port *int `json:"port"`
DatabaseName *string `json:"database_name"`
Username *string `json:"username"`
Password *string `json:"password"`
SSLMode *string `json:"ssl_mode"`
Protocol *string `json:"protocol"`
OracleServiceName *string `json:"oracle_service_name"`
MongoAuthSource *string `json:"mongo_auth_source"`
Listable *bool `json:"listable"`
ViaUID *uuid.UUID `json:"via_uid"`
// ClearViaUID, when true, removes the SSH tunnel (direct dial). Distinct
// from an omitted via_uid, which leaves the tunnel unchanged.
ClearViaUID bool `json:"clear_via_uid"`
// SSH bastion secrets (write-only, never returned).
SSHPrivateKey *string `json:"ssh_private_key"`
SSHPassphrase *string `json:"ssh_passphrase"`
// Kubernetes cluster material; see CreateDatabaseRequest. The bearer token
// is rotated through Password like any other secret.
K8sCACert *string `json:"k8s_ca_cert"`
K8sNamespace *string `json:"k8s_namespace"`
K8sInsecureSkipTLSVerify *bool `json:"k8s_insecure_skip_tls_verify"`
// K8sResetLearnedCACert forgets the TOFU-learned bundle so the next connect
// pins afresh. It is the exit from a stale pin when the cluster's CA
// rotated and you do not have the new bundle to paste; supplying a
// non-empty k8s_ca_cert clears it too, since a supplied bundle supersedes
// anything learned.
K8sResetLearnedCACert bool `json:"k8s_reset_learned_ca_cert"`
// AccessApproverUserGroupUIDs / QueryApproverUserGroupUIDs replace the
// server's approver lists wholesale. Omitted (null) leaves them alone; an
// explicit `[]` clears them, handing the decision back to the server groups
// and then to admins. Effective immediately, for grant requests already
// filed and statements already parked — see docs/approvals.md.
AccessApproverUserGroupUIDs *[]uuid.UUID `json:"access_approver_user_group_uids"`
QueryApproverUserGroupUIDs *[]uuid.UUID `json:"query_approver_user_group_uids"`
// TestConnection asks the API to validate the row by actually dialing it
// once updated. Opt-in, and never fatal.
TestConnection bool `json:"test_connection"`
}
UpdateDatabaseRequest represents the request to update a database
type UpdateGrantDefinitionRequest ¶ added in v0.10.0
type UpdateGrantDefinitionRequest struct {
Name *string `json:"name"`
Slug *string `json:"slug"`
Description *string `json:"description"`
DurationSeconds *int64 `json:"duration_seconds"`
Controls []string `json:"controls"`
MaxQueryCounts *int64 `json:"max_query_counts"`
ClearMaxQueryCounts bool `json:"clear_max_query_counts"`
MaxBytesTransferred *int64 `json:"max_bytes_transferred"`
ClearMaxBytesTransferred bool `json:"clear_max_bytes_transferred"`
Priority *int16 `json:"priority"`
ClearPriority bool `json:"clear_priority"`
AutoApprove *bool `json:"auto_approve"`
UserGroupUIDs []uuid.UUID `json:"user_group_uids"`
ServerGroupUIDs []uuid.UUID `json:"server_group_uids"`
ApprovalPatterns []string `json:"approval_patterns"`
SampleQueries []string `json:"sample_queries"`
ApproverUserGroupUIDs []uuid.UUID `json:"approver_user_group_uids"`
// RetiredDatabaseUIDs is refused rather than folded; see
// CreateGrantDefinitionRequest.RetiredDatabaseUIDs.
RetiredDatabaseUIDs []uuid.UUID `json:"database_uids"`
// RetiredGroupUIDs / RetiredApproverGroupUIDs are refused rather than
// folded; see CreateGrantDefinitionRequest.RetiredGroupUIDs.
RetiredGroupUIDs []uuid.UUID `json:"group_uids"`
RetiredApproverGroupUIDs []uuid.UUID `json:"approver_group_uids"`
}
UpdateGrantDefinitionRequest is the JSON body for PATCH /grant-definitions/:uid. Unlike CreateGrantDefinitionRequest, every field is optional and a field absent from the body is left untouched on the existing definition rather than reset to its zero value. This is what makes a targeted PATCH — like the auto-approve toggle, which only means to flip one field — safe: it used to reuse CreateGrantDefinitionRequest as a full-replace body, so any field the caller didn't round-trip (most notably approval_patterns and approver_user_group_uids) got silently wiped.
Slice fields (controls, user_group_uids, server_group_uids, approval_patterns, approver_user_group_uids) distinguish "absent" from "explicitly cleared": encoding/json leaves a slice field nil when its key is missing (or its value is JSON null), but allocates a non-nil empty slice for a present `[]`. Only a non-nil slice is applied, so an admin can still empty one of these lists via PATCH without sending anything else.
max_query_counts, max_bytes_transferred and priority are themselves nullable in the domain (null means "no limit" / "auto"), so a plain pointer can't tell "absent" from "explicit null" — both decode to a nil pointer. Each gets a companion Clear* flag instead, mirroring UpdateDatabaseRequest.ClearViaUID.
type UpdateServerGroupRequest ¶ added in v0.24.0
type UpdateServerGroupRequest = CreateServerGroupRequest
UpdateServerGroupRequest is the body for PATCH /server-groups/:uid. Same shape as create — this surface is too small to warrant a separate partial type, exactly like the user-group one.
type UpdateUserGroupRequest ¶ added in v0.18.0
type UpdateUserGroupRequest = CreateUserGroupRequest
UpdateUserGroupRequest is the body for PATCH /user-groups/:uid. Same shape as create — this surface is too small to warrant a separate partial type.
type UpdateUserRequest ¶
type UpdateUserRequest struct {
Password *string `json:"password"`
Roles []string `json:"roles"`
// UserGroupUIDs, when non-nil, replaces the user's user-group memberships
// wholesale. Admin-only, like Roles.
UserGroupUIDs []uuid.UUID `json:"user_group_uids"`
// RetiredGroupUIDs catches the pre-rename spelling of UserGroupUIDs. It
// is refused rather than folded onto UserGroupUIDs — silently ignoring a
// scope restriction fails *open*. See errRetiredGroupUIDs.
RetiredGroupUIDs []uuid.UUID `json:"group_uids"`
}
UpdateUserRequest represents the request to update a user
type UserResponse ¶
type UserResponse struct {
UID string `json:"uid"`
Username string `json:"username"`
Roles []string `json:"roles"`
PasswordChangeRequired bool `json:"password_change_required"`
}
UserResponse represents user info in login/me responses
type ValidatePatternsRequest ¶ added in v0.23.0
type ValidatePatternsRequest struct {
// Patterns are the RE2 approval-pattern sources to test — typically a
// grant definition's approval_patterns field, saved or still being
// edited in the dialog.
Patterns []string `json:"patterns"`
// Queries are sample SQL statements to test the patterns against —
// typically a definition's sample_queries field.
Queries []string `json:"queries"`
}
ValidatePatternsRequest is the JSON body for POST /grant-definitions/validate-patterns.
Source Files
¶
- approvals.go
- audit_verify.go
- auth.go
- connection_url.go
- device.go
- errors.go
- failure_reasons.go
- grant_definition_patterns.go
- grant_definitions.go
- grant_requests.go
- grants.go
- keys.go
- mcp.go
- middleware.go
- oauth.go
- oauth_roles.go
- observability.go
- parameters.go
- ratelimit.go
- server.go
- server_groups.go
- servers.go
- servers_connectivity.go
- servers_oracle_conflicts.go
- slack_interactions.go
- slack_socketmode.go
- stream.go
- user_groups.go
- users.go