dbaccess

package
v0.2.0-beta.19 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package dbaccess manages database users, temporary credentials and network scope. Statements use quoted identifiers and a pre-hashed password verifier, so no plaintext secret reaches SQL text, container logs or an audit row.

Index

Constants

View Source
const (
	ActionUserCreate  = "database.user.create"
	ActionUserRotate  = "database.user.rotate"
	ActionUserDisable = "database.user.disable"
	ActionUserEnable  = "database.user.enable"
	ActionUserDelete  = "database.user.delete"

	ActionTempIssue  = "database.temp.issue"
	ActionTempRevoke = "database.temp.revoke"
	ActionTempExpire = "database.temp.expire"

	ActionGrantApply  = "database.access.grant"
	ActionGrantRemove = "database.access.ungrant"

	ActionRulesApply  = "database.network.rules.apply"
	ActionRulesRemove = "database.network.rules.remove"
	ActionMakePrivate = "database.network.make_private"
	ActionScopeSet    = "database.network.scope.set"
	ActionTLSSet      = "database.network.tls.set"
)

Audit action names written to the audit log's method column. They never carry a credential, only the database and the subject role or principal.

View Source
const (
	TLSRequired = "required"
	TLSOptional = "optional"
)

TLS enforcement states as read from the server.

View Source
const (
	EnvTTLMinMinutes     = "APP_DB_TEMP_TTL_MIN_MINUTES"
	EnvTTLMaxMinutes     = "APP_DB_TEMP_TTL_MAX_MINUTES"
	EnvTTLDefaultMinutes = "APP_DB_TEMP_TTL_DEFAULT_MINUTES"
	EnvSweepSeconds      = "APP_DB_TEMP_SWEEP_SECONDS"

	// TempConnLimit caps concurrent sessions for a temporary role.
	TempConnLimit = 5
)

Environment variables that tune temporary credential lifetimes, in minutes.

View Source
const ListRolesSQL = `` /* 335-byte string literal not displayed */

ListRolesSQL selects every non-system role with its attributes and live connection count, as CSV.

View Source
const SystemActor = "database-access-sweeper"

SystemActor names the sweeper in audit rows.

View Source
const (

	// TempRolePrefix marks roles minted by the temporary access path so a
	// sweeper pass and a human can tell them apart at a glance.
	TempRolePrefix = "tmp_"
)

Variables

View Source
var (
	ErrRoleExists   = errors.New("role already exists")
	ErrRoleNotFound = errors.New("role not found")
	ErrProtected    = errors.New("role is protected")
	ErrTTL          = errors.New("invalid ttl")
)

Sentinel errors the API maps to status codes.

View Source
var ErrPreset = errors.New("unknown preset")

ErrPreset is returned for an unknown preset.

View Source
var (

	// ErrRoleName is wrapped by every role-name validation failure.
	ErrRoleName = errors.New("invalid role name")
)
View Source
var ErrScopeUnplaced = errors.New("database has nothing to scope to")

ErrScopeUnplaced is returned when the database has no project or environment to scope to.

Functions

func Allows

func Allows(scope Scope, db, app Placement) (bool, string)

Allows reports whether app may reach a database placed at db under scope.

func CreateRoleSQL

func CreateRoleSQL(p CreateParams) (string, error)

CreateRoleSQL builds the transaction that creates the role and applies its preset. Every identifier is validated and quoted; the password is a verifier.

func DropRoleSQL

func DropRoleSQL(role, adminRole string) (string, error)

DropRoleSQL ends the role's sessions, hands anything it owns to the admin role (never dropping data), strips its privileges and drops it. Safe to run again against a role that is already gone.

func GeneratePassword

func GeneratePassword() (string, error)

GeneratePassword returns a random URL-safe password.

func NewTempRoleName

func NewTempRoleName() (string, error)

NewTempRoleName returns a fresh tmp_ role name.

func Protected

func Protected(r Role, adminRole string) bool

Protected reports whether an existing role must never be altered or dropped through this package: the platform admin, any superuser, and system roles.

func QuoteIdent

func QuoteIdent(id string) string

QuoteIdent double-quotes a Postgres identifier.

func QuoteLiteral

func QuoteLiteral(s string) string

QuoteLiteral single-quotes a Postgres string literal.

func RotateSQL

func RotateSQL(role, verifier string) (string, error)

RotateSQL replaces role's password with verifier and ends its sessions.

func ScramVerifier

func ScramVerifier(password string) (string, error)

ScramVerifier hashes password into a Postgres SCRAM-SHA-256 verifier, so the SQL that sets it never carries the plaintext (a failed statement is logged verbatim by the server).

func SetLoginSQL

func SetLoginSQL(role string, login bool) (string, error)

SetLoginSQL enables or disables login. Disabling also ends open sessions.

func SweepIntervalFromEnv

func SweepIntervalFromEnv(getenv func(string) string) time.Duration

SweepIntervalFromEnv returns how often the sweeper runs.

func TempPresetAllowed

func TempPresetAllowed(p Preset) bool

TempPresetAllowed reports whether a temporary credential may use p. Owner is excluded: short-lived access should never be able to alter schema.

func ValidPreset

func ValidPreset(p Preset) bool

ValidPreset reports whether p is a known preset.

func ValidateRoleName

func ValidateRoleName(name, adminRole string) error

ValidateRoleName checks name against the identifier rules and the names the platform and Postgres reserve. adminRole is the platform's own admin role for this database, which can never be recreated or shadowed.

Types

type CreateParams

type CreateParams struct {
	Role       string
	Database   string
	AdminRole  string
	Preset     Preset
	Verifier   string
	ConnLimit  int
	ValidUntil time.Time
}

CreateParams describes a role to create. Verifier is a SCRAM verifier from ScramVerifier, never a plaintext password.

type CreateParamsIn

type CreateParamsIn struct {
	Role       string
	Preset     Preset
	ConnLimit  int
	ValidUntil time.Time
	// Temporary skips the user-name reservation so a generated tmp_ name is accepted.
	Temporary bool
}

CreateParamsIn is what a caller chooses; the rest is derived.

type Credential

type Credential struct {
	Role       string
	Password   string
	ValidUntil time.Time
}

Credential is a freshly minted login. Password is shown once by the caller and never stored.

type Exec

type Exec interface {
	ExecWithInput(ctx context.Context, containerID string, cmd []string, stdin io.Reader) (io.ReadCloser, error)
}

Exec is the slice of the container runtime this package needs.

type Placement

type Placement struct {
	Name          string `json:"name"`
	ProjectID     string `json:"project_id,omitempty"`
	EnvironmentID string `json:"environment_id,omitempty"`
	// ProjectName and EnvironmentName are only used to word reasons.
	ProjectName     string `json:"project_name,omitempty"`
	EnvironmentName string `json:"environment_name,omitempty"`
}

Placement is where a database or an app sits in the platform's grouping.

type Postgres

type Postgres struct {
	Exec        Exec
	ContainerID string
	Admin       string
	Database    string
}

Postgres manages roles in one managed Postgres container. The platform's admin role and the database share a name, which is what Admin holds.

func (Postgres) Create

func (p Postgres) Create(ctx context.Context, in CreateParamsIn) (Credential, error)

Create makes a login role with the preset's privileges.

func (Postgres) Drop

func (p Postgres) Drop(ctx context.Context, name string) error

Drop removes a role. A role that is already gone is not an error.

func (Postgres) DropTemp

func (p Postgres) DropTemp(ctx context.Context, name string) error

DropTemp removes a temporary role without the existence listing, so the sweeper stays idempotent and cheap. Only tmp_ names are accepted.

func (Postgres) ListRoles

func (p Postgres) ListRoles(ctx context.Context) ([]Role, error)

ListRoles returns every non-system role with its attributes.

func (Postgres) Rotate

func (p Postgres) Rotate(ctx context.Context, name string) (Credential, error)

Rotate sets a new random password and ends the role's sessions.

func (Postgres) SetLogin

func (p Postgres) SetLogin(ctx context.Context, name string, login bool) error

SetLogin enables or disables a role's ability to log in.

func (Postgres) SetRequireTLS

func (p Postgres) SetRequireTLS(ctx context.Context, require bool) error

SetRequireTLS rewrites the catch-all network rules to hostssl (or back to host) and reloads the server. Local socket access, which the platform's own exec path uses, is untouched, so this cannot lock the control plane out.

func (Postgres) TLSState

func (p Postgres) TLSState(ctx context.Context) (string, error)

TLSState reads whether the server refuses non-TLS TCP connections.

type Preset

type Preset string

Preset is a named privilege bundle on the database's public schema.

const (
	PresetReadOnly  Preset = "read_only"
	PresetReadWrite Preset = "read_write"
	PresetOwner     Preset = "owner"
)

Presets.

type Revoker

type Revoker interface {
	RevokeTemp(ctx context.Context, database, role string) error
}

Revoker drops one temporary role. It must be idempotent.

type Role

type Role struct {
	Name        string `json:"name"`
	CanLogin    bool   `json:"can_login"`
	Superuser   bool   `json:"superuser"`
	CreateDB    bool   `json:"create_db"`
	CreateRole  bool   `json:"create_role"`
	Replication bool   `json:"replication"`
	ConnLimit   int    `json:"connection_limit"`
	ValidUntil  string `json:"valid_until,omitempty"`
	Connections int    `json:"connections"`
}

Role is one database role as the engine reports it.

func ParseRoles

func ParseRoles(rows [][]string) ([]Role, error)

ParseRoles turns ListRolesSQL's CSV rows into roles.

type Scope

type Scope string

Scope is how far a database's network reach extends.

const (
	ScopePlatform    Scope = "platform"
	ScopeProject     Scope = "project"
	ScopeEnvironment Scope = "environment"
)

Scopes. Platform is the historical behaviour: any app that references the database is attached to it.

func (Scope) Valid

func (s Scope) Valid() bool

Valid reports whether s is a known scope.

type StatementError

type StatementError struct{ Message string }

StatementError is a database-side failure with only the engine's own ERROR lines kept, so no statement text is echoed back.

func (*StatementError) Error

func (e *StatementError) Error() string

type StoreAdapter

type StoreAdapter struct{ DB *store.DB }

StoreAdapter exposes the store's access-user table as a SweepStore.

func (StoreAdapter) ClaimTempCredential

func (a StoreAdapter) ClaimTempCredential(ctx context.Context, id string, now time.Time, lease time.Duration) (bool, error)

ClaimTempCredential implements SweepStore.

func (StoreAdapter) CompleteTempCredential

func (a StoreAdapter) CompleteTempCredential(ctx context.Context, id string, now time.Time) error

CompleteTempCredential implements SweepStore.

func (StoreAdapter) DueTempCredentials

func (a StoreAdapter) DueTempCredentials(ctx context.Context, now time.Time) ([]TempCredential, error)

DueTempCredentials implements SweepStore.

func (StoreAdapter) ReleaseTempCredential

func (a StoreAdapter) ReleaseTempCredential(ctx context.Context, id string) error

ReleaseTempCredential implements SweepStore.

type SweepAuditor

type SweepAuditor interface {
	RecordRevoked(ctx context.Context, c TempCredential)
}

SweepAuditor records that the system revoked a credential.

type SweepStore

type SweepStore interface {
	// DueTempCredentials lists credentials past ExpiresAt that are not yet revoked.
	DueTempCredentials(ctx context.Context, now time.Time) ([]TempCredential, error)
	// ClaimTempCredential moves one to revoking. A stale claim from a
	// crashed pass is reclaimable after lease. False means someone else has it.
	ClaimTempCredential(ctx context.Context, id string, now time.Time, lease time.Duration) (bool, error)
	CompleteTempCredential(ctx context.Context, id string, now time.Time) error
	// ReleaseTempCredential returns a claimed credential for the next pass.
	ReleaseTempCredential(ctx context.Context, id string) error
}

SweepStore is the persistence the sweeper needs. Claim must be an atomic compare-and-set so overlapping passes revoke each credential once.

type Sweeper

type Sweeper struct {
	Store   SweepStore
	Revoker Revoker
	Auditor SweepAuditor
	Logger  *slog.Logger
	Now     func() time.Time
	// Lease is how long a revoking claim is honoured before another pass retakes it.
	Lease time.Duration
}

Sweeper revokes temporary credentials at expiry, on a timer and once at boot, so a control plane restart never extends a credential's life.

func (*Sweeper) Run

func (s *Sweeper) Run(ctx context.Context, interval time.Duration) error

Run sweeps immediately, then every interval until ctx ends.

func (*Sweeper) Sweep

func (s *Sweeper) Sweep(ctx context.Context) int

Sweep runs one pass and returns how many credentials it revoked.

type TTLLimits

type TTLLimits struct {
	Min, Max, Default time.Duration
}

TTLLimits bound how long a temporary credential may live.

func DefaultTTLLimits

func DefaultTTLLimits() TTLLimits

DefaultTTLLimits returns the built-in 15 minute to 24 hour window.

func TTLLimitsFromEnv

func TTLLimitsFromEnv(getenv func(string) string) TTLLimits

TTLLimitsFromEnv reads overrides through getenv, ignoring unparseable or inconsistent values.

func (TTLLimits) Clamp

func (l TTLLimits) Clamp(requested time.Duration) (time.Duration, bool)

Clamp returns requested limited to the window, and whether it had to be changed. A zero request means the default.

type TempCredential

type TempCredential struct {
	ID        string
	Database  string
	Role      string
	Preset    Preset
	CreatedBy string
	CreatedAt time.Time
	ExpiresAt time.Time
}

TempCredential is one tracked short-lived login. Its secret is never held.

func FromStore

FromStore converts a store record.

type Verdict

type Verdict struct {
	App     Placement `json:"app"`
	Allowed bool      `json:"allowed"`
	Reason  string    `json:"reason,omitempty"`
}

Verdict is one referencing app's fate under a scope.

func DryRun

func DryRun(scope Scope, db Placement, apps []Placement) ([]Verdict, error)

DryRun evaluates every referencing app under scope without changing anything. It errors when the database has no placement to scope to.

func Lost

func Lost(vs []Verdict) []Verdict

Lost returns the verdicts that would lose reachability.

Jump to

Keyboard shortcuts

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