Documentation
¶
Overview ¶
Package controller is the only authority that changes what the gateway enforces: it owns the database and the audit log of a deployment, compiles policy generations and publishes them, runs Confirmed Policy Activation and Temporary Policy Overrides with their deadlines and startup recovery, and publishes trust keys, blocked accounts and blocked addresses.
Every change goes through one of its exported operations, which serialise under one lock, record a durable transition and audit it; publishing a generation, committing a transition and settling a deadline are unexported. Publish, which installs a whole policy without activation, exists for tests and the fault-injection build's test route only. The controller never serves the data plane and never reads the clock except through compile's trusted time. The gateway wiring and the Admin API call it.
Index ¶
- Constants
- func CandidateGeneration(p policy.Policy, parent int64, overrides []store.Override) (*compile.Generation, error)
- type Change
- type Config
- type Controller
- func (c *Controller) Activate(ctx context.Context, t store.TransitionRecord, candidate int64, ...) (Outcome, int)
- func (c *Controller) Addresses() *store.BlockedAddresses
- func (c *Controller) Admission() string
- func (c *Controller) Audit() *audit.AuditLog
- func (c *Controller) BeginStop()
- func (c *Controller) Block(ctx context.Context, t store.TransitionRecord, reason string) (json.RawMessage, int)
- func (c *Controller) BlockAddress(ctx context.Context, t store.TransitionRecord, entry store.BlockedAddress) (json.RawMessage, int)
- func (c *Controller) Blocked() *store.BlockedAccounts
- func (c *Controller) Capture(ctx context.Context) (*compile.Generation, store.Authority, error)
- func (c *Controller) ChangePassword(ctx context.Context, admin store.Administrator, passwordHash string) (Outcome, int)
- func (c *Controller) CreateOverride(ctx context.Context, t store.TransitionRecord, request OverrideRequest) (Outcome, int)
- func (c *Controller) Current() *compile.Generation
- func (c *Controller) Enrol(ctx context.Context, e store.Enrolment, passwordHash string) (Outcome, int)
- func (c *Controller) EstablishBase(ctx context.Context, kind, actor string, candidate int64, checksum string) (Outcome, int)
- func (c *Controller) Expired(g *compile.Generation)
- func (c *Controller) Gauges() (revision int64, overrides, blocked int)
- func (c *Controller) Observations() *monitor.Observations
- func (c *Controller) Open() error
- func (c *Controller) Publish(ctx context.Context, actor string, change Change) (Outcome, int)
- func (c *Controller) Reads() store.Reads
- func (c *Controller) ReloadKeys(ctx context.Context, t store.TransitionRecord) (json.RawMessage, int)
- func (c *Controller) Resolve(ctx context.Context, t store.TransitionRecord, provisional int64, ...) (Outcome, int)
- func (c *Controller) RevokeOverride(ctx context.Context, t store.TransitionRecord, id int64) (Outcome, int)
- func (c *Controller) Sessions() *session.Table
- func (c *Controller) Settings() Config
- func (c *Controller) StageCandidate(ctx context.Context, t store.TransitionRecord, document []byte) (Outcome, int)
- func (c *Controller) Start(admin http.Handler) error
- func (c *Controller) State() string
- func (c *Controller) Stop() error
- func (c *Controller) StopWithin(ctx context.Context) error
- func (c *Controller) Unblock(ctx context.Context, t store.TransitionRecord) (json.RawMessage, int)
- func (c *Controller) UnblockAddress(ctx context.Context, t store.TransitionRecord, entry store.BlockedAddress) (json.RawMessage, int)
- func (c *Controller) ValidateOverride(ctx context.Context, actor string, expected int64, request OverrideRequest) (Outcome, int)
- func (c *Controller) Verifier() *pipeline.Verifier
- func (c *Controller) WithdrawCandidate(ctx context.Context, t store.TransitionRecord, id int64) (Outcome, int)
- type Outcome
- type OverrideRequest
Constants ¶
const ( DefaultAuditWait = 2 * time.Second MinAuditBytes = 4 << 10 MaxAuditBytes = 1 << 30 MaxAuditFiles = 1000 DefaultIdle = 15 * time.Minute DefaultLifetime = 8 * time.Hour MinSession = time.Second MaxSession = 24 * time.Hour )
Defaults and bounds of the controller's settings, which the configuration file check applies too.
const ShutdownWait = 5 * time.Second
ShutdownWait bounds how long Stop waits for admin requests in progress; the native gateway waits that long for the requests of both its listeners together.
Variables ¶
This section is empty.
Functions ¶
func CandidateGeneration ¶
func CandidateGeneration(p policy.Policy, parent int64, overrides []store.Override) (*compile.Generation, error)
effectiveGeneration compiles the active base with the active overrides into one generation. It expires at the earliest of the provisional deadline (if any) and the overrides' expiry times, so a generation never serves past the first of them. Its identity names the base revision and every override. CandidateGeneration compiles p, stored with parent revision parent, with overrides applied, as an activation would publish it; it publishes nothing.
Types ¶
type Change ¶
type Change struct {
ID string `json:"transition_id"`
Expected int64 `json:"expected_revision"`
Policy json.RawMessage `json:"policy"`
}
Change is one whole-policy publication, used by the test publication hook. The transition ID makes retries safe: a repeated ID returns the recorded outcome instead of applying again.
type Config ¶
type Config struct {
Database string
AuditLog string
AdminListen string
AdminIdleTimeout time.Duration
AdminSessionLifetime time.Duration
AuditTimeout time.Duration
AuditMaxBytes int64
AuditMaxFiles int
JWT pipeline.TrustConfig
// ReloadTrust reads the trust settings again from the startup
// configuration, never from HTTP; nil refuses every key reload.
ReloadTrust func() (pipeline.TrustConfig, error)
}
Config is the controller's configuration. Zero limits take their defaults.
type Controller ¶
type Controller struct {
// contains filtered or unexported fields
}
Controller is the only component that changes the effective policy. It owns the database, compiles generations and publishes them by atomic pointer swap. Request handlers only read the current pointer.
Prepare creates it, Open opens its deployment, Start serves its Admin API and Stop releases everything it owns. The application that calls them owns its lifecycle.
func Prepare ¶
func Prepare(cfg Config, logger *slog.Logger) (*Controller, error)
Prepare checks cfg and builds the controller, opening nothing; its errors are about the settings only.
func (*Controller) Activate ¶
func (c *Controller) Activate(ctx context.Context, t store.TransitionRecord, candidate int64, checksum string, seconds int64) (Outcome, int)
Activate makes a validated candidate the provisional active base. The candidate must have been prepared against the current confirmed revision, which becomes its rollback target, and its checksum must match the one the administrator confirmed. It is decoded and compiled again in full before anything is committed. Its deadline is persisted with the activation, so a restart can neither extend nor forget it, and it is never extended.
func (*Controller) Addresses ¶
func (c *Controller) Addresses() *store.BlockedAddresses
Addresses returns the published blocked address list.
func (*Controller) Admission ¶
func (c *Controller) Admission() string
Admission reports whether protected admission can run at this moment: "ready" when a published generation is unexpired and the audit log accepts acknowledgements, otherwise why it cannot (the stop, the deployment mode, "unavailable", "generation_expired" or "audit_failed"). A ready gateway may still refuse every request its policy denies, and the next request may still fail; this is an observation, not a promise.
func (*Controller) Audit ¶
func (c *Controller) Audit() *audit.AuditLog
Audit returns the audit log the controller opened; it stays open until the stop.
func (*Controller) BeginStop ¶
func (c *Controller) BeginStop()
BeginStop ends protected admission and every change of authority: the published generation is withdrawn, so a request that has not captured one yet is refused; mutations and the deadline timer change nothing any more; Start is refused. A request that captured a generation earlier runs to its end with it. Stop begins with it; the native gateway calls it before it stops its data plane.
func (*Controller) Block ¶
func (c *Controller) Block(ctx context.Context, t store.TransitionRecord, reason string) (json.RawMessage, int)
Block adds t's subject to the blocked accounts, or keeps an entry with the same reason, and records t's receipt. Each method here sets t's kind itself, so a caller cannot run one change under another's name.
func (*Controller) BlockAddress ¶
func (c *Controller) BlockAddress(ctx context.Context, t store.TransitionRecord, entry store.BlockedAddress) (json.RawMessage, int)
BlockAddress adds entry's range to the blocked address list.
func (*Controller) Blocked ¶
func (c *Controller) Blocked() *store.BlockedAccounts
Blocked returns the published blocked accounts.
func (*Controller) Capture ¶
func (c *Controller) Capture(ctx context.Context) (*compile.Generation, store.Authority, error)
Capture reads the generation in force and the stored authority together, under the lock, without settling a deadline or publishing anything. The generation is nil when none is in force or it expired; the authority is then not read.
func (*Controller) ChangePassword ¶
func (c *Controller) ChangePassword(ctx context.Context, admin store.Administrator, passwordHash string) (Outcome, int)
ChangePassword replaces an administrator's password, provided the credential is still the one the caller verified, and ends every session of that administrator.
func (*Controller) CreateOverride ¶
func (c *Controller) CreateOverride(ctx context.Context, t store.TransitionRecord, request OverrideRequest) (Outcome, int)
CreateOverride creates and publishes a temporary override of the confirmed base. Nothing is committed unless every check passes and the complete effective policy with the override compiles.
func (*Controller) Current ¶
func (c *Controller) Current() *compile.Generation
Current returns the published generation, or nil when nothing may be served.
func (*Controller) Enrol ¶
func (c *Controller) Enrol(ctx context.Context, e store.Enrolment, passwordHash string) (Outcome, int)
Enrol consumes an enrolment and stores the administrator's new credential. The password hash is computed by the caller, outside the controller lock. Every existing session of the administrator ends.
func (*Controller) EstablishBase ¶
func (c *Controller) EstablishBase(ctx context.Context, kind, actor string, candidate int64, checksum string) (Outcome, int)
EstablishBase confirms a validated deny-all candidate as the first confirmed base of an installation (genesis) or as the base that ends a declared recovery. Each has one server-chosen identity, so a retried or duplicated request can never create a second base.
func (*Controller) Expired ¶
func (c *Controller) Expired(g *compile.Generation)
Expired is called by the request path when the generation it captured has expired. It starts at most one check at a time, so a late or missing timer cannot keep the gateway refusing every request.
func (*Controller) Gauges ¶
func (c *Controller) Gauges() (revision int64, overrides, blocked int)
Gauges reads the published policy revision, active temporary rules and blocked accounts for the metrics listener.
func (*Controller) Observations ¶
func (c *Controller) Observations() *monitor.Observations
Observations returns the in-memory observations of requests and events.
func (*Controller) Open ¶
func (c *Controller) Open() error
Open takes the deployment lock, opens the database and the audit log and publishes the effective policy; it opens no listener, Start does. Its errors are store errors (store.ErrHeld when another process holds the deployment); on one, nothing it opened is left open. A database without usable authority is not an error: the controller serves no protected request, and its health and audit log say why.
func (*Controller) Publish ¶
Publish applies a complete policy as the new confirmed base. Production binaries expose no route to it: whole-policy activation belongs to the confirmed-activation lifecycle, and only the test publication hook calls it. It cannot create a provisional base.
func (*Controller) Reads ¶
func (c *Controller) Reads() store.Reads
Reads returns the store's read methods; nil while no database is open.
func (*Controller) ReloadKeys ¶
func (c *Controller) ReloadKeys(ctx context.Context, t store.TransitionRecord) (json.RawMessage, int)
ReloadKeys reads the verification keys again and publishes them when the issuer and audience are unchanged.
func (*Controller) Resolve ¶
func (c *Controller) Resolve(ctx context.Context, t store.TransitionRecord, provisional int64, disposition string) (Outcome, int)
Resolve confirms or rolls back the current provisional base on an administrator's explicit request. The request names the provisional revision it means and the confirmed revision it was prepared against; a request for any other state is refused. At or after the deadline neither is possible: the request is refused and the automatic rollback runs instead, however late its timer is.
func (*Controller) RevokeOverride ¶
func (c *Controller) RevokeOverride(ctx context.Context, t store.TransitionRecord, id int64) (Outcome, int)
RevokeOverride ends an active override on an administrator's request. It is allowed while a provisional base is pending. An override that has already ended, including one whose expiry time has passed, keeps its recorded disposition.
func (*Controller) Sessions ¶
func (c *Controller) Sessions() *session.Table
Sessions returns the administrator sessions, which the controller revokes when an administrator's credentials change.
func (*Controller) Settings ¶
func (c *Controller) Settings() Config
Settings returns the configuration with its defaults applied.
func (*Controller) StageCandidate ¶
func (c *Controller) StageCandidate(ctx context.Context, t store.TransitionRecord, document []byte) (Outcome, int)
StageCandidate validates a complete policy with the canonical compiler and stores it as an immutable candidate. Nothing is published: the effective policy and its generation are unchanged.
func (*Controller) Start ¶
func (c *Controller) Start(admin http.Handler) error
Start serves admin, the Admin API, on its loopback listener. Protected admission does not wait for it: it follows the generation published since Open. Start succeeds at most once, and never once Stop has begun.
func (*Controller) State ¶
func (c *Controller) State() string
State returns the deployment mode, or the reason no authority is served.
func (*Controller) Stop ¶
func (c *Controller) Stop() error
Stop begins the stop (see BeginStop), shuts the Admin API down, waiting at most ShutdownWait for admin requests in progress, then closes the audit log, waiting at most auditDrainWait for the events queued until then to be written, and the database. It returns nil only when every step completed. It also releases a controller that was never started. Stop may be called any number of times, also concurrently: the controller stops once, and every call returns that stop's result.
func (*Controller) StopWithin ¶
func (c *Controller) StopWithin(ctx context.Context) error
StopWithin is Stop with ctx bounding the wait for admin requests in progress; the native gateway shares that bound with its data plane.
func (*Controller) Unblock ¶
func (c *Controller) Unblock(ctx context.Context, t store.TransitionRecord) (json.RawMessage, int)
Unblock removes t's subject from the blocked accounts.
func (*Controller) UnblockAddress ¶
func (c *Controller) UnblockAddress(ctx context.Context, t store.TransitionRecord, entry store.BlockedAddress) (json.RawMessage, int)
UnblockAddress removes entry's range from the blocked address list.
func (*Controller) ValidateOverride ¶
func (c *Controller) ValidateOverride(ctx context.Context, actor string, expected int64, request OverrideRequest) (Outcome, int)
ValidateOverride answers whether a proposed override would be created now, without creating it. It runs every check creation runs, including compiling the effective policy, and records the answer in the audit log only.
func (*Controller) Verifier ¶
func (c *Controller) Verifier() *pipeline.Verifier
Verifier returns the published token verifier.
func (*Controller) WithdrawCandidate ¶
func (c *Controller) WithdrawCandidate(ctx context.Context, t store.TransitionRecord, id int64) (Outcome, int)
WithdrawCandidate marks a validated candidate withdrawn.
type Outcome ¶
type Outcome struct {
Transition string `json:"transition_id"`
Outcome string `json:"outcome"`
Reason string `json:"reason,omitempty"`
Field string `json:"field,omitempty"`
Revision int64 `json:"revision,omitempty"`
Generation string `json:"generation,omitempty"`
Candidate int64 `json:"candidate,omitempty"`
Provisional int64 `json:"provisional,omitempty"`
RollbackTarget int64 `json:"rollback_target,omitempty"`
Deadline time.Time `json:"deadline,omitzero"`
Disposition string `json:"disposition,omitempty"`
// For a temporary override: its identifier, the control and target it
// governs and its expiry time. Override also names the active override a
// refused candidate conflicts with.
Override int64 `json:"override,omitempty"`
Target string `json:"target,omitempty"`
Expires time.Time `json:"expires_at,omitzero"`
}
Outcome is the controller receipt for a mutation. For a provisional base, Revision is the active base after the transition, Provisional the revision the lifecycle concerns and RollbackTarget the confirmed revision it returns to.
type OverrideRequest ¶
type OverrideRequest struct {
Type string `json:"type"`
Control string `json:"control"`
Operation string `json:"operation"`
Role *string `json:"role"`
Count *int64 `json:"count"`
ExpiresInSeconds int64 `json:"expires_in_seconds"`
}
OverrideRequest is an administrator's proposed override. Role and Count are pointers so that a member the control does not take is refused rather than ignored.