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
- Variables
- func ApplyMigrations(ctx context.Context, tx *sql.Tx, from, to int) error
- func BlockRange(text string) (netip.Prefix, bool)
- func CheckDatabaseSchema(db *sql.DB, version int) error
- func Connect(path, journal string) (*sql.DB, error)
- func CreatePrivate(path string) error
- func DatabaseURI(path string) string
- func DeleteBlockedAddress(ctx context.Context, tx *sql.Tx, cidr string, now time.Time) error
- func Digest(kind, target string, expected sql.NullInt64, payload any) string
- func DocumentChecksum(document []byte) string
- func IsAdminName(name string) bool
- func LockDeployment(database string) (*os.File, error)
- func RegularPath(path string) error
- func SaveBlockedAddress(ctx context.Context, tx *sql.Tx, entry BlockedAddress, now time.Time) error
- func ValidBlockReason(reason string) bool
- func Verify(db *sql.DB) error
- type ActivationRecord
- type Administrator
- type Authority
- type BlockedAccount
- type BlockedAccounts
- type BlockedAddress
- type BlockedAddresses
- type CandidateRecord
- type DB
- func (s *DB) Activation(ctx context.Context, column string, id int64) (ActivationRecord, bool, error)
- func (s *DB) Activations(ctx context.Context, after int64, limit int) ([]ActivationRecord, error)
- func (s *DB) Administrator(ctx context.Context, name string) (Administrator, bool, error)
- func (s *DB) Administrators(ctx context.Context) ([]adminEntry, error)
- func (s *DB) BlockedAccounts(ctx context.Context) (BlockedAccounts, error)
- func (s *DB) BlockedAddresses(ctx context.Context) (BlockedAddresses, error)
- func (s *DB) Candidate(ctx context.Context, id int64) (CandidateRecord, bool, error)
- func (s *DB) Candidates(ctx context.Context, after int64, limit int) ([]CandidateRecord, error)
- func (s *DB) Close() error
- func (s *DB) CommitCandidate(ctx context.Context, t TransitionRecord, c CandidateRecord, now time.Time) (int64, error)
- func (s *DB) CommitControl(ctx context.Context, t TransitionRecord, body json.RawMessage, ...) error
- func (s *DB) CommitEnrolment(ctx context.Context, t TransitionRecord, e Enrolment, passwordHash string, ...) error
- func (s *DB) CommitOverride(ctx context.Context, t TransitionRecord, o Override, now time.Time) error
- func (s *DB) CommitOverrideEnd(ctx context.Context, t TransitionRecord, id int64, status string, ...) error
- func (s *DB) CommitPassword(ctx context.Context, t TransitionRecord, name string, credential int64, ...) error
- func (s *DB) CommitResolution(ctx context.Context, t TransitionRecord, a Authority, disposition string, ...) error
- func (s *DB) CommitRevision(ctx context.Context, t TransitionRecord, change RevisionChange, now time.Time) (int64, error)
- func (s *DB) CommitWithdrawal(ctx context.Context, t TransitionRecord, id int64, now time.Time) error
- func (s *DB) ControlResult(ctx context.Context, id string) (json.RawMessage, error)
- func (s *DB) DeclareRecovery(ctx context.Context, t TransitionRecord, episode string, now time.Time) error
- func (s *DB) Enrolment(ctx context.Context, secretHash string) (Enrolment, bool, error)
- func (s *DB) History(ctx context.Context, filter HistoryFilter, after int64, limit int) ([]HistoryEntry, error)
- func (s *DB) HistoryEntry(ctx context.Context, id string) (HistoryEntry, bool, error)
- func (s *DB) IssueEnrolment(ctx context.Context, t TransitionRecord, e Enrolment, now time.Time) error
- func (s *DB) Load(ctx context.Context) (Authority, error)
- func (s *DB) MarkAudited(ctx context.Context, id string, now time.Time) error
- func (s *DB) MarkPublished(ctx context.Context, id string, how string, now time.Time) error
- func (s *DB) Migrate(ctx context.Context, from int, t TransitionRecord, now time.Time) error
- func (s *DB) NextOverrideID(ctx context.Context) (int64, error)
- func (s *DB) Override(ctx context.Context, id int64) (Override, bool, error)
- func (s *DB) OverrideOf(ctx context.Context, transition string) (Override, bool, error)
- func (s *DB) Overrides(ctx context.Context, clause string, args ...any) ([]Override, error)
- func (s *DB) Ping() error
- func (s *DB) Policy(ctx context.Context, id int64) (policy.Policy, error)
- func (s *DB) RaiseHighWater(ctx context.Context, now time.Time) error
- func (s *DB) RecordRejection(ctx context.Context, t TransitionRecord, now time.Time) error
- func (s *DB) Revision(ctx context.Context, id int64) (RevisionInfo, bool, error)
- func (s *DB) Revisions(ctx context.Context, after int64, limit int) ([]RevisionInfo, error)
- func (s *DB) Snapshot(path string) error
- func (s *DB) Transition(ctx context.Context, id string) (TransitionRecord, bool, error)
- func (s *DB) Unaudited(ctx context.Context) ([]string, error)
- func (s *DB) Unpublished(ctx context.Context) ([]string, error)
- type Enrolment
- type HistoryEntry
- type HistoryFilter
- type Override
- type Permission
- type Reads
- type RevisionChange
- type RevisionInfo
- type TransitionRecord
Constants ¶
const ( MinAddressExpiry = time.Minute MaxAddressExpiry = 30 * 24 * time.Hour )
Bounds of an entry's optional expiry, from the time it is written.
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.
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.
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.
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.
const ActivationColumns = `` /* 161-byte string literal not displayed */
const MaxBlockedAddresses = 1024
MaxBlockedAddresses bounds the list, which is checked on every request.
Variables ¶
var DatabaseVersion = len(migrations)
DatabaseVersion is the schema version this build reads and writes.
var ErrHeld = errors.New("held by another gateway process")
ErrHeld marks a database that another gateway process holds.
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.
ErrUnavailable marks state that must never be served: missing, empty, corrupt or unrecognised authority.
Functions ¶
func ApplyMigrations ¶
ApplyMigrations applies the migrations after schema version from up to version to, and records the new version.
func BlockRange ¶
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 ¶
CheckDatabaseSchema compares the database schema with the one the migrations up to version create.
func Connect ¶
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 ¶
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 ¶
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 ¶
DeleteBlockedAddress removes one entry and expired ones in tx.
func Digest ¶
Digest identifies a mutation request by its kind, target, required revision and canonical payload; a resent request must match it.
func DocumentChecksum ¶
func IsAdminName ¶
Administrator names are short lowercase identifiers, safe to log.
func LockDeployment ¶
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 ¶
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 ¶
SaveBlockedAddress writes one entry and removes expired ones in tx.
func ValidBlockReason ¶
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.
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 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.
type BlockedAddresses ¶
type BlockedAddresses map[string]BlockedAddress
BlockedAddresses is immutable after publication, keyed by range.
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 ¶
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 ¶
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 ¶
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 (*DB) Administrator ¶
func (*DB) Administrators ¶
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) Candidates ¶
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 (*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) 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 (*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) MarkAudited ¶
MarkAudited records that the transition's outcome event was durably written.
func (*DB) MarkPublished ¶
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 ¶
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 ¶
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) OverrideOf ¶
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) RecordRejection ¶
func (*DB) Snapshot ¶
Snapshot writes a consistent copy of the database to path, which must not exist yet.
func (*DB) Transition ¶
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 ¶
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 ¶
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.