store

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package store is the gateway's one authoritative database: policy versions, draft changes and trials, temporary rules, blocked accounts, administrators and the durable receipt of every change, in SQLite with write-ahead logging.

It is the only code that runs SQL. It holds the database exclusively and locks the deployment, so no second process opens it while it is in use; every change commits with its transition record in one transaction, and state it cannot verify is reported unavailable, never served. It imports only policy and fault. The controller and the local operator commands call it; nothing on the request path does.

Index

Constants

View Source
const (
	MinAddressExpiry = time.Minute
	MaxAddressExpiry = 30 * 24 * time.Hour
)

Bounds of an entry's optional expiry, from the time it is written.

View Source
const (
	OverrideException   = "exception"
	OverrideContainment = "containment"

	ControlPermit    = "permit"
	ControlRate      = "rate"
	ControlOperation = "operation"

	// An override must expire within this many seconds of its creation. The
	// upper bound limits how long a temporary change can stay in force
	// without becoming a confirmed policy change.
	MinOverrideSeconds = 1
	MaxOverrideSeconds = 24 * 60 * 60

	// MaxActiveOverrides bounds the work of compiling an effective policy.
	MaxActiveOverrides = 64
)

A temporary override changes one control of one operation of the active base policy until it expires or is revoked. It never rewrites a stored revision: the effective policy is the active base with every active override applied, compiled into one generation.

Only these changes exist. An exception grants a role an operation it is not permitted, or raises an operation's rate count; a containment removes a role's permit, lowers a rate count, or denies an operation outright. Authentication, the token profile, body and header validation, the audit record, routes and destinations cannot be overridden, so no override can weaken them.

View Source
const (
	OverrideActive      = "active"
	OverrideRevoked     = "revoked"
	OverrideExpired     = "expired"
	OverrideQuarantined = "quarantined"
)

Override states. Only active overrides are applied; the others are final. Quarantined marks an override found active in a restored historical database, which never regains authority.

View Source
const (
	ModeBootstrap = "bootstrap_locked"
	ModeNormal    = "normal"
	ModeRecovery  = "recovery_required"
)

Deployment modes. Only normal may serve traffic; the other two allow only the administrator flows that establish a new confirmed base.

View Source
const (
	DispositionConfirmed   = "confirmed"
	DispositionRolledBack  = "rolled_back"
	DispositionTimedOut    = "timed_out"
	DispositionQuarantined = "quarantined"
)

Final dispositions of a provisional base. Confirmed keeps its policy as the new confirmed base; the others restore the rollback target. Quarantined marks a provisional base found in a restored historical database.

View Source
const ActivationColumns = `` /* 161-byte string literal not displayed */
View Source
const MaxBlockedAddresses = 1024

MaxBlockedAddresses bounds the list, which is checked on every request.

Variables

View Source
var DatabaseVersion = len(migrations)

DatabaseVersion is the schema version this build reads and writes.

View Source
var ErrHeld = errors.New("held by another gateway process")

ErrHeld marks a database that another gateway process holds.

View Source
var ErrNotUpdated = errors.New("no matching row")

ErrNotUpdated reports that a guarded update found no matching row, so the state it depended on had changed.

View Source
var ErrUnavailable = errors.New("authoritative state unavailable")

ErrUnavailable marks state that must never be served: missing, empty, corrupt or unrecognised authority.

Functions

func ApplyMigrations

func ApplyMigrations(ctx context.Context, tx *sql.Tx, from, to int) error

ApplyMigrations applies the migrations after schema version from up to version to, and records the new version.

func BlockRange

func BlockRange(text string) (netip.Prefix, bool)

BlockRange returns the canonical CIDR range of an entry key: host bits zero, IPv4-mapped IPv6 written as IPv4. A range not already in that form is refused, so one range has one stored key.

func CheckDatabaseSchema

func CheckDatabaseSchema(db *sql.DB, version int) error

CheckDatabaseSchema compares the database schema with the one the migrations up to version create.

func Connect

func Connect(path, journal string) (*sql.DB, error)

Connect opens the database with the settings every connection must have and takes the exclusive lock at once, so a second process fails here rather than at its first mutation.

func CreatePrivate

func CreatePrivate(path string) error

CreatePrivate creates an empty database file with mode 0600. Windows uses the parent directory DACL; mode bits do not provide confidentiality there. It fails if the file exists, so nothing is ever replaced. SQLite writes a database or VACUUM INTO output into the empty file it finds.

func DatabaseURI

func DatabaseURI(path string) string

DatabaseURI keeps literal filename characters out of SQLite's URI options and fragment. In particular, a percent escape in a filename is not decoded.

func DeleteBlockedAddress

func DeleteBlockedAddress(ctx context.Context, tx *sql.Tx, cidr string, now time.Time) error

DeleteBlockedAddress removes one entry and expired ones in tx.

func Digest

func Digest(kind, target string, expected sql.NullInt64, payload any) string

Digest identifies a mutation request by its kind, target, required revision and canonical payload; a resent request must match it.

func DocumentChecksum

func DocumentChecksum(document []byte) string

func IsAdminName

func IsAdminName(name string) bool

Administrator names are short lowercase identifiers, safe to log.

func LockDeployment

func LockDeployment(database string) (*os.File, error)

LockDeployment uses a stable companion file: SQLite's own exclusive lock cannot protect a pathname while restore replaces the underlying database. The file is never unlinked, and the OS releases ownership after a crash.

func RegularPath

func RegularPath(path string) error

RegularPath refuses devices, directories and links at a file boundary. Deployment directories must be controlled by the local operator; these checks do not defend against an actor who can replace their ancestors.

func SaveBlockedAddress

func SaveBlockedAddress(ctx context.Context, tx *sql.Tx, entry BlockedAddress, now time.Time) error

SaveBlockedAddress writes one entry and removes expired ones in tx.

func ValidBlockReason

func ValidBlockReason(reason string) bool

SQLite bounds UTF-8 bytes because its TEXT length stops at NUL. Both HTTP input and durable state enforce the contract's Unicode character bound.

func Verify

func Verify(db *sql.DB) error

Verify refuses a database this build must not trust: foreign keys not enforced on the connection, a failed integrity check, another schema version, or a schema that differs from what the migrations create.

Types

type ActivationRecord

type ActivationRecord struct {
	Revision       int64      `json:"revision"`
	Candidate      int64      `json:"candidate"`
	RollbackTarget int64      `json:"rollback_target"`
	Deadline       time.Time  `json:"deadline"`
	ActivatedAt    time.Time  `json:"activated_at"`
	Actor          string     `json:"actor"`
	Disposition    string     `json:"disposition,omitempty"`
	ResolvedAt     *time.Time `json:"resolved_at,omitempty"`
	ResolvedBy     string     `json:"resolved_by,omitempty"`
	Resolution     string     `json:"resolution,omitempty"`
}

ActivationRecord is the durable lifecycle record of one provisional base: the candidate it came from, the confirmed revision it replaced and returns to on rollback, its deadline and its single final disposition.

func ScanActivation

func ScanActivation(rows *sql.Rows) (ActivationRecord, error)

type Administrator

type Administrator struct {
	Name        string
	Permissions Permission
	Password    string // argon2id PHC string
	Status      string
	Credential  int64 // incremented by every credential change
}

type Authority

type Authority struct {
	Mode         string
	Installation string
	Episode      string // recovery episode, when one was declared
	HighWater    time.Time
	Confirmed    int64 // 0 until a base has been established
	Revision     int64 // active base: provisional if present, else confirmed
	Provisional  int64
	Candidate    int64 // the candidate the provisional base was activated from
	Deadline     time.Time
	Document     []byte     // active base document; read only in normal mode
	Overrides    []Override // active overrides in identifier order
}

Authority is the committed state from which a generation is compiled.

type BlockedAccount

type BlockedAccount struct {
	Subject   string    `json:"subject"`
	Reason    string    `json:"reason"`
	BlockedBy string    `json:"blocked_by"`
	BlockedAt time.Time `json:"blocked_at"`
}

type BlockedAccounts

type BlockedAccounts map[string]BlockedAccount

BlockedAccounts is immutable after publication, independently of policy.

type BlockedAddress

type BlockedAddress struct {
	Range     string       `json:"range"`
	Reason    string       `json:"reason"`
	BlockedBy string       `json:"blocked_by"`
	BlockedAt time.Time    `json:"blocked_at"`
	ExpiresAt *time.Time   `json:"expires_at"`
	Prefix    netip.Prefix `json:"-"`
}

BlockedAddress is one blocked range. ExpiresAt nil means until unblocked.

func (BlockedAddress) Active

func (b BlockedAddress) Active(now time.Time) bool

Active reports whether the entry applies at now.

type BlockedAddresses

type BlockedAddresses map[string]BlockedAddress

BlockedAddresses is immutable after publication, keyed by range.

func (BlockedAddresses) Blocks

func (list BlockedAddresses) Blocks(addr netip.Addr, now time.Time) bool

Blocks reports whether an active entry covers the address.

type CandidateRecord

type CandidateRecord struct {
	ID        int64     `json:"id"`
	Parent    int64     `json:"parent,omitempty"`
	Checksum  string    `json:"checksum"`
	DenyAll   bool      `json:"deny_all"`
	Status    string    `json:"status"`
	Actor     string    `json:"actor"`
	CreatedAt time.Time `json:"created_at"`
	Document  []byte    `json:"-"`
}

CandidateRecord is an immutable staged policy and its validation result.

type DB

type DB struct {
	// contains filtered or unexported fields
}

DB is the single writer of control-plane state. The exclusive locking mode keeps a second gateway process, or an operator command, from opening the same database while it is in use.

func Adopt

func Adopt(db *sql.DB) *DB

Adopt makes db, an open connection to a deployment database, a store; the store's close closes it.

func CreateStore

func CreateStore(path, installation string, first Enrolment, install TransitionRecord, now time.Time) (*DB, error)

CreateStore installs a new database at path in bootstrap_locked mode with the first administrator's enrolment and the install transition, all in one transaction. The file must not exist: installation never replaces a database. An interrupted install leaves a file without a schema, which startup and install both refuse.

func OpenForMigration

func OpenForMigration(path string) (*DB, int, error)

OpenForMigration opens a database at an earlier schema version and returns it with that version. A database whose schema is not exactly its version's, or a version 1 database with a provisional base, which has no activation record to migrate, is refused unchanged.

func OpenStore

func OpenStore(path string) (*DB, error)

OpenStore opens an installed database. It never creates one: a missing or empty file means no authority exists.

func (*DB) Activation

func (s *DB) Activation(ctx context.Context, column string, id int64) (ActivationRecord, bool, error)

Activation reads the activation record selected by column, which is revision or candidate.

func (*DB) Activations

func (s *DB) Activations(ctx context.Context, after int64, limit int) ([]ActivationRecord, error)

func (*DB) Administrator

func (s *DB) Administrator(ctx context.Context, name string) (Administrator, bool, error)

func (*DB) Administrators

func (s *DB) Administrators(ctx context.Context) ([]adminEntry, error)

Administrators lists every administrator, ordered by name.

func (*DB) BlockedAccounts

func (s *DB) BlockedAccounts(ctx context.Context) (BlockedAccounts, error)

func (*DB) BlockedAddresses

func (s *DB) BlockedAddresses(ctx context.Context) (BlockedAddresses, error)

BlockedAddresses reads the list, refusing rows the schema cannot express.

func (*DB) Candidate

func (s *DB) Candidate(ctx context.Context, id int64) (CandidateRecord, bool, error)

func (*DB) Candidates

func (s *DB) Candidates(ctx context.Context, after int64, limit int) ([]CandidateRecord, error)

func (*DB) Close

func (s *DB) Close() error

func (*DB) CommitCandidate

func (s *DB) CommitCandidate(ctx context.Context, t TransitionRecord, c CandidateRecord, now time.Time) (int64, error)

CommitCandidate stores a validated candidate with its committed transition.

func (*DB) CommitControl

func (s *DB) CommitControl(ctx context.Context, t TransitionRecord, body json.RawMessage, change func(*sql.Tx) error, now time.Time) error

CommitControl keeps the state change, transition and exact replay response in the same SQLite transaction. The callback changes only controller-owned state.

func (*DB) CommitEnrolment

func (s *DB) CommitEnrolment(ctx context.Context, t TransitionRecord, e Enrolment, passwordHash string, now time.Time) error

CommitEnrolment consumes an enrolment and writes the administrator's new credential in one transaction. Only one request can consume a secret.

func (*DB) CommitOverride

func (s *DB) CommitOverride(ctx context.Context, t TransitionRecord, o Override, now time.Time) error

CommitOverride stores a new active override with its committed transition. The unique index on active targets refuses a second active override of the same target even if a caller skipped the conflict check.

func (*DB) CommitOverrideEnd

func (s *DB) CommitOverrideEnd(ctx context.Context, t TransitionRecord, id int64, status string, now time.Time) error

CommitOverrideEnd records the final status of an active override with its committed transition. The guarded update fails unless the override is still active, so it can end only once.

func (*DB) CommitPassword

func (s *DB) CommitPassword(ctx context.Context, t TransitionRecord, name string, credential int64, passwordHash string, now time.Time) error

CommitPassword replaces an administrator's password if the credential has not changed since it was verified.

func (*DB) CommitResolution

func (s *DB) CommitResolution(ctx context.Context, t TransitionRecord, a Authority, disposition string, now time.Time) error

CommitResolution records the final disposition of the provisional base and the base that follows it, in one transaction: confirmation makes the provisional revision the confirmed base, every other disposition leaves the earlier confirmed base active. Both guarded updates fail unless the provisional base is still the one the caller resolved.

func (*DB) CommitRevision

func (s *DB) CommitRevision(ctx context.Context, t TransitionRecord, change RevisionChange, now time.Time) (int64, error)

CommitRevision atomically stores a new revision, makes it the active base, applies the deployment mode and candidate changes that go with it, and records the committed transition. This is the authoritative commit point; the runtime pointer swap follows it.

func (*DB) CommitWithdrawal

func (s *DB) CommitWithdrawal(ctx context.Context, t TransitionRecord, id int64, now time.Time) error

CommitWithdrawal withdraws a validated candidate with its committed transition.

func (*DB) ControlResult

func (s *DB) ControlResult(ctx context.Context, id string) (json.RawMessage, error)

func (*DB) DeclareRecovery

func (s *DB) DeclareRecovery(ctx context.Context, t TransitionRecord, episode string, now time.Time) error

DeclareRecovery puts a restored database into recovery_required under a new episode and quarantines everything whose authority cannot be trusted after a historical restore: administrators, candidates, pending enrolments, any provisional base, whose activation is closed as quarantined rather than confirmed or rolled back, and every override still active in the backup, which may since have been revoked. The activation and all those overrides name the restore transition as their resolution. History and revisions are kept as evidence.

func (*DB) Enrolment

func (s *DB) Enrolment(ctx context.Context, secretHash string) (Enrolment, bool, error)

func (*DB) History

func (s *DB) History(ctx context.Context, filter HistoryFilter, after int64, limit int) ([]HistoryEntry, error)

History lists up to limit transitions after the cursor in the filter's order. Transitions are never deleted and each new one gets a larger rowid, so a newest-first listing that pages from its first answer never skips or repeats an entry; entries recorded later appear only in a new listing.

func (*DB) HistoryEntry

func (s *DB) HistoryEntry(ctx context.Context, id string) (HistoryEntry, bool, error)

func (*DB) IssueEnrolment

func (s *DB) IssueEnrolment(ctx context.Context, t TransitionRecord, e Enrolment, now time.Time) error

IssueEnrolment replaces any pending enrolment for the administrator and records the operator transition, in one transaction.

func (*DB) Load

func (s *DB) Load(ctx context.Context) (Authority, error)

func (*DB) MarkAudited

func (s *DB) MarkAudited(ctx context.Context, id string, now time.Time) error

MarkAudited records that the transition's outcome event was durably written.

func (*DB) MarkPublished

func (s *DB) MarkPublished(ctx context.Context, id string, how string, now time.Time) error

MarkPublished records that the runtime now serves the transition's result. It is a receipt only; authority was established by the commit.

func (*DB) Migrate

func (s *DB) Migrate(ctx context.Context, from int, t TransitionRecord, now time.Time) error

Migrate applies the migrations from version to the current one in a single transaction with the operator transition, then checks the result. A database whose migrated content fails the integrity or foreign key check is left at its earlier version: the checks run before the commit, so damaged rows are refused rather than carried into the new version.

func (*DB) NextOverrideID

func (s *DB) NextOverrideID(ctx context.Context) (int64, error)

NextOverrideID is the identifier the next override will have. Rows are never deleted and the controller is the only writer, so it is known before the override is committed and can label the generation compiled for it.

func (*DB) Override

func (s *DB) Override(ctx context.Context, id int64) (Override, bool, error)

Override reads one override by its identifier.

func (*DB) OverrideOf

func (s *DB) OverrideOf(ctx context.Context, transition string) (Override, bool, error)

OverrideOf reads the override a temporary-override transition created, revoked or expired; the unique creation column and the override_resolution index allow at most one of each. A restore transition, which can quarantine several overrides, is not one.

func (*DB) Overrides

func (s *DB) Overrides(ctx context.Context, clause string, args ...any) ([]Override, error)

Overrides reads the overrides selected by the WHERE and ORDER clause.

func (*DB) Ping

func (s *DB) Ping() error

Ping reports whether the database connection is still usable.

func (*DB) Policy

func (s *DB) Policy(ctx context.Context, id int64) (policy.Policy, error)

Policy returns stored revision id decoded; a missing revision is an error.

func (*DB) RaiseHighWater

func (s *DB) RaiseHighWater(ctx context.Context, now time.Time) error

func (*DB) RecordRejection

func (s *DB) RecordRejection(ctx context.Context, t TransitionRecord, now time.Time) error

func (*DB) Revision

func (s *DB) Revision(ctx context.Context, id int64) (RevisionInfo, bool, error)

func (*DB) Revisions

func (s *DB) Revisions(ctx context.Context, after int64, limit int) ([]RevisionInfo, error)

func (*DB) Snapshot

func (s *DB) Snapshot(path string) error

Snapshot writes a consistent copy of the database to path, which must not exist yet.

func (*DB) Transition

func (s *DB) Transition(ctx context.Context, id string) (TransitionRecord, bool, error)

func (*DB) Unaudited

func (s *DB) Unaudited(ctx context.Context) ([]string, error)

Unaudited lists committed transitions whose outcome event was never acknowledged by the audit log.

func (*DB) Unpublished

func (s *DB) Unpublished(ctx context.Context) ([]string, error)

Unpublished lists committed transitions that changed the effective policy, by producing a revision or by creating or ending an override, but have no publication receipt.

type Enrolment

type Enrolment struct {
	Name        string
	Secret      string // SHA-256 of the enrolment secret
	Permissions Permission
	ExpiresAt   time.Time
}

type HistoryEntry

type HistoryEntry struct {
	Seq         int64      `json:"seq"`
	ID          string     `json:"transition_id"`
	Kind        string     `json:"kind"`
	Actor       string     `json:"actor"`
	Target      string     `json:"target"`
	Expected    *int64     `json:"expected_revision,omitempty"`
	Outcome     string     `json:"outcome"`
	Reason      string     `json:"reason,omitempty"`
	Revision    int64      `json:"revision,omitempty"`
	Candidate   int64      `json:"candidate,omitempty"`
	CommittedAt time.Time  `json:"recorded_at"`
	PublishedAt *time.Time `json:"published_at,omitempty"`
	PublishedBy string     `json:"published_by,omitempty"`
	AuditedAt   *time.Time `json:"audited_at,omitempty"`
}

HistoryEntry is one transition as administrators see it.

type HistoryFilter

type HistoryFilter struct {
	NewestFirst bool
	Kinds       []string
	Outcome     string
	Actor       string
}

HistoryFilter selects and orders a history listing. Empty members select everything.

type Override

type Override struct {
	ID         int64      `json:"id"`
	Type       string     `json:"type"`
	Control    string     `json:"control"`
	Operation  string     `json:"operation"`
	Role       string     `json:"role,omitempty"`
	Count      int64      `json:"count,omitempty"`
	Target     string     `json:"target"`
	Base       int64      `json:"base_revision"` // active base when it was created
	CreatedAt  time.Time  `json:"created_at"`
	ExpiresAt  time.Time  `json:"expires_at"`
	Actor      string     `json:"actor"`
	Creation   string     `json:"creation"`
	Status     string     `json:"status"`
	ResolvedAt *time.Time `json:"resolved_at,omitempty"`
	ResolvedBy string     `json:"resolved_by,omitempty"`
	Resolution string     `json:"resolution,omitempty"`
}

Override is one stored temporary override and its lifecycle.

func (Override) Dependencies

func (o Override) Dependencies() []string

Dependencies lists the control targets an override relies on: its own, the operation it applies to and, for a permit, the role. A candidate that changes any of them would change or invalidate the override.

type Permission

type Permission int

Permission is a set of administrator rights. Each admin route requires exactly one; holding read never implies the right to change anything.

const (
	PermRead Permission = 1 << iota
	PermReadHistory
	PermManageSubjects
	PermManagePolicy

	PermAll = PermRead | PermReadHistory | PermManageSubjects | PermManagePolicy
)

func ParsePermissions

func ParsePermissions(list string) (Permission, error)

ParsePermissions reads a comma-separated list of permission names.

func (Permission) Names

func (p Permission) Names() []string

type Reads

type Reads interface {
	Load(ctx context.Context) (Authority, error)
	Policy(ctx context.Context, id int64) (policy.Policy, error)
	Revision(ctx context.Context, id int64) (RevisionInfo, bool, error)
	Revisions(ctx context.Context, after int64, limit int) ([]RevisionInfo, error)
	Candidate(ctx context.Context, id int64) (CandidateRecord, bool, error)
	Candidates(ctx context.Context, after int64, limit int) ([]CandidateRecord, error)
	Activation(ctx context.Context, column string, id int64) (ActivationRecord, bool, error)
	Activations(ctx context.Context, after int64, limit int) ([]ActivationRecord, error)
	Override(ctx context.Context, id int64) (Override, bool, error)
	Overrides(ctx context.Context, clause string, args ...any) ([]Override, error)
	History(ctx context.Context, filter HistoryFilter, after int64, limit int) ([]HistoryEntry, error)
	HistoryEntry(ctx context.Context, id string) (HistoryEntry, bool, error)
	Administrator(ctx context.Context, name string) (Administrator, bool, error)
	Administrators(ctx context.Context) ([]adminEntry, error)
	Enrolment(ctx context.Context, secretHash string) (Enrolment, bool, error)
}

Reads is every store read the Admin API makes. It has no write method, so code holding only Reads cannot change stored state; changes go through the controller.

type RevisionChange

type RevisionChange struct {
	Parent    int64 // 0: no parent
	Origin    string
	Document  []byte
	Actor     string
	Deadline  time.Time // zero: the revision becomes the confirmed base; else provisional until then
	Mode      string    // deployment mode after the commit
	Candidate int64     // candidate this revision establishes, if any
}

RevisionChange is a new active base and the state committed with it.

type RevisionInfo

type RevisionInfo struct {
	ID        int64           `json:"id"`
	Parent    int64           `json:"parent,omitempty"`
	Origin    string          `json:"origin"`
	Checksum  string          `json:"checksum"`
	Actor     string          `json:"actor"`
	CreatedAt time.Time       `json:"created_at"`
	Document  json.RawMessage `json:"document,omitempty"`
}

RevisionInfo describes a stored revision; Document is set only when one revision is read.

type TransitionRecord

type TransitionRecord struct {
	ID        string
	Kind      string
	Actor     string
	Target    string
	Expected  sql.NullInt64 // revision the request required, if any
	Digest    string        // canonical request digest
	Outcome   string
	Reason    string
	Revision  int64 // revision the mutation produced, if any
	Candidate int64 // candidate the mutation produced, if any
	Audited   bool  // the outcome event was durably recorded
	// Override is the override the mutation concerns, for its receipt and
	// events. It is not a column: the override row names its transitions.
	Override int64
}

TransitionRecord is the durable history entry and idempotency record of one mutation. Its ID is the mutation identity: a repeated ID returns this record instead of applying the mutation again.

Jump to

Keyboard shortcuts

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