controlplane

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package controlplane is the authenticated API surface over the agent's resource store: the one boundary the CLI, the web dashboard, and remote peers read and act through. This file is the authentication and authorization model; server.go serves the read/watch API over it.

The model is scope-based and identity-first: every request resolves to a Principal carrying a Scope, and a handler requires a minimum scope. Local, single-operator use authenticates with a bearer token; cross-instance delegated authority (public-key, attenuable capability tokens) is a later layer that plugs in behind the same Authenticator boundary, so the write and remote paths inherit authentication that already exists.

Package controlplane is the read model over the resource store: one generic way to list, describe, and watch any registered kind. The command-line reader, a future remote API, and a dashboard all read through this package instead of each re-deriving per-kind queries, so there is a single read path to keep correct. A kind becomes listable by supplying a Descriptor (its display columns); nothing here is hard-coded to a specific kind, so adding a kind never edits this package.

Index

Constants

View Source
const (
	// AuditStream is the spine stream every control-plane access decision is recorded
	// on, separate from the resource stream the watch tails.
	AuditStream = "controlplane.audit"
	// EvAccess is the event type of one access decision.
	EvAccess = "controlplane.access"
	// EvApproval is the event type of one signed-approval decision for a dangerous
	// verb: who authorized (the contributing key ids), the bound action and target, and
	// whether it was granted. It is the non-repudiable "who approved what" record,
	// distinct from the access decision EvAccess.
	EvApproval = "controlplane.approval"
)

Audit constants name the immutable record of who accessed the control plane. Every authenticated request records its decision on AuditStream as an EvAccess event, so the access history is a replayable fold over the spine, not a best-effort log.

View Source
const IdentityVaultRef = "controlplane/instance-identity"

IdentityVaultRef is the vault reference the instance identity seed is sealed under.

View Source
const ProofHeader = "Flynn-Proof"

ProofHeader is the request header carrying the signed possession proof.

Variables

View Source
var ErrUnauthenticated = errors.New("controlplane: unauthenticated")

ErrUnauthenticated means no valid credential was presented.

Functions

func Attenuate

func Attenuate(tok string, holder *Identity, nextAudience ed25519.PublicKey, scope Scope, actions []string, notAfter time.Time) (string, error)

Attenuate appends a narrowing block to a token. The holder must be the token's current leaf audience (it signs with the key that block named), and the new block restricts the scope and names an explicit action subset and the next audience. Attenuation can only shrink authority; Verify rejects any block that tries to widen it. The result is a longer chain delegating a strictly smaller authority to nextAudience.

func ExternalName

func ExternalName(pub ed25519.PublicKey, base, purpose string, c Constraints) (string, error)

ExternalName projects a public key into a name valid under c, beginning with the human-readable base and ending in a suffix derived from the key. The suffix is a hash of the key under this resource purpose, encoded into c.Charset, so two different identities almost never share a name and the same identity always derives the same one. It is the external-namespace companion to PrincipalID, which renders the same key as the internal id. base is the literal prefix that appears in the name (for example "flynn-agent"); purpose names the resource role and feeds only the hash (for example "fly-app"), so one identity derives distinct names for distinct roles. It returns an error when base or the constraints cannot yield a valid name (for example a base too long for c.MaxLen).

func Issue

func Issue(issuer *Identity, audience ed25519.PublicKey, scope Scope, grant capability.Grant, notAfter time.Time) (string, error)

Issue mints a root capability token: the issuer signs scope and grant to an audience public key (the holder authorized to exercise or further attenuate it), valid until notAfter. The token verifies offline against the issuer's public key.

func Name

func Name() func(resource.Resource) string

Name projects the resource's logical name. It is the column nearly every kind lists first.

func ParsePrincipalID

func ParsePrincipalID(id string) (ed25519.PublicKey, error)

ParsePrincipalID decodes a principal id minted by PrincipalID back into its Ed25519 public key. It is the inverse of PrincipalID, used to recover the leaf key a verified token was issued to so a possession proof can be checked against it. A malformed id or a key of the wrong length fails closed.

func PrincipalID

func PrincipalID(pub ed25519.PublicKey) string

PrincipalID renders an Ed25519 public key as a stable, self-certifying principal id.

func SignProof

func SignProof(holder *Identity, method, path, nonce string, now time.Time) (string, error)

SignProof builds the signed possession proof a holder presents alongside its token. The holder signs that it possesses the leaf key (identified by holder.ID()) and binds the proof to this exact request (method, path) at this instant, with a single-use nonce. The returned value is placed in the ProofHeader. The nonce must be unique per request (ids.Token in production); reusing one is refused by the server as a replay.

func SpecField

func SpecField(path ...string) func(resource.Resource) string

SpecField projects a string value from the resource Spec JSON at path, or the empty string when the path is absent. Scalars are stringified, so a Descriptor can surface a typed field without this package importing the domain that defines it.

func StatusField

func StatusField(path ...string) func(resource.Resource) string

StatusField projects a string value from the resource Status JSON at path, the observed-state counterpart of SpecField.

Types

type ActionFunc

type ActionFunc func(ctx context.Context, p Principal, res resource.Resource) (any, error)

ActionFunc executes an action verb against a resolved resource, once the gate has admitted it. It runs with the effective (intersected) capability grant already bound to ctx, so any further dispatch it performs is checked against the same authority. Its result is JSON-encoded into the response; an error is mapped to an HTTP status.

type ActionSpec

type ActionSpec struct {
	// Action is the dispatch action name the gate checks against the effective grant.
	// It is what authority the verb requires, named so the same grant vocabulary that
	// governs local dispatch governs remote action.
	Action string
	// MinScope is the coarse access level the verb requires (operator or admin). A
	// token below it is refused before the grant gate is even consulted.
	MinScope Scope
	// Dangerous marks a verb that the signed-approval gate must also clear (remote
	// dispatch, credential release, destructive/irreversible). It is a declarative
	// property so a new dangerous verb cannot forget the gate: a Dangerous verb fails
	// closed if no approval verifier is configured, and requires a fresh, bound,
	// single-use signature before it runs, regardless of how broad the caller's scope
	// or grant is.
	Dangerous bool
	// Quorum is how many distinct authorized signatures a dangerous verb requires
	// (M-of-N). Zero is treated as one for a Dangerous verb; it is ignored for a
	// non-dangerous verb.
	Quorum int
	// Run performs the verb after admission.
	Run ActionFunc
}

ActionSpec declares an action subresource: a kube-style verb on a resource kind, distinct from a read. The control-plane owns the gate; the kill-switch and lifecycle layers own the actual verbs and register them with WithAction.

type Authenticator

type Authenticator interface {
	Authenticate(r *http.Request) (Principal, error)
}

Authenticator resolves a request to a Principal, or an error if it cannot be authenticated. Implementations must be safe for concurrent use.

type Authority

type Authority struct {
	Subject string
	Scope   Scope
	Grant   capability.Grant
}

Authority is the effective permission a verified token confers: the leaf subject it was delegated to, the scope, and the capability grant (the intersection of every block in the chain). It is the value a request gate checks an action against.

func Verify

func Verify(tok string, issuer ed25519.PublicKey, now time.Time) (Authority, error)

Verify checks a token against the trusted issuer key as of now and returns the effective authority it confers: the leaf subject, the minimum scope across the chain, and the intersection of every block's grant. It fails closed on any broken signature, any block that tries to widen authority (a higher scope or an action the parent did not hold), an expired block, or a malformed token, so a token can never confer more than the issuer delegated.

type Change

type Change struct {
	Kind     ChangeKind
	Resource resource.Resource
}

Change is one resource transition reported by a Watcher.

type ChangeKind

type ChangeKind string

ChangeKind labels how a resource changed between two polls.

const (
	// Added is a resource that appeared since the last poll.
	Added ChangeKind = "Added"
	// Modified is a resource whose stored version advanced since the last poll.
	Modified ChangeKind = "Modified"
	// Deleted is a resource that was live at the last poll and is now gone.
	Deleted ChangeKind = "Deleted"
)

type Column

type Column struct {
	Header  string
	Project func(resource.Resource) string
}

Column is one display column of a kind's table: a header and a pure projection from a resource to its cell text. A projection must be deterministic (no clock, no randomness) so a List renders identically on every machine and under replay.

type Constraints

type Constraints struct {
	Charset    string
	Separator  string
	MinLen     int
	MaxLen     int
	LeadLetter bool
}

Constraints describes a provider namespace's rules so one derivation serves every provider: the rules are data, not a per-provider code path. The derived suffix uses only `Charset`; the whole name is at least `MinLen` and at most `MaxLen`; when `LeadLetter` is set the name begins with an ASCII letter (the safest-everywhere lead, valid even where a leading digit is not). `Separator` is the literal joiner between the human-readable base and the derived suffix; it is legal in a name but never appears in the suffix, so a derived name can never carry a leading, trailing, or doubled separator.

func DNSName

func DNSName(maxLen int) Constraints

DNSName returns the constraints for an RFC 1123 DNS label capped at maxLen: lowercase alphanumerics joined by single hyphens, beginning with a letter. This one shape covers the common providers, whose names are all DNS labels: a Fly app (it becomes `<app>.fly.dev`), a DNS record, and a DNS-addressable object-store bucket. A provider's own length limit is the only thing that varies, so it is the single parameter; a caller that needs a different floor sets `MinLen` on the returned value.

func (Constraints) Validate

func (c Constraints) Validate(name string) error

Validate reports whether name satisfies the constraints: every character is in the charset or is a separator, the length is within the window, the name begins with a letter when required, and it neither begins nor ends with a separator (the rule every DNS-label provider enforces). A caller's override is checked through this before it is used.

type DelegationAuthenticator

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

DelegationAuthenticator resolves a presented capability token to a Principal, implementing the Authenticator boundary the server already gates every request through. It is the bridge from the cryptographic delegation layer to the request model: a bearer token is verified offline against the trusted issuer key, and the authority it proves (subject, scope, and the monotonically attenuated Grant) becomes the Principal the action gate later checks. Because Verify fails closed on any forged, widened, expired, or malformed chain, an unverifiable token is simply unauthenticated; nothing here can mint authority the token did not already carry.

func NewDelegationAuthenticator

func NewDelegationAuthenticator(issuer ed25519.PublicKey, clk clock.Clock) *DelegationAuthenticator

NewDelegationAuthenticator builds an authenticator that accepts tokens issued (and transitively delegated) under issuer, reading the current time from clk so expiry is checked against the same clock the rest of the system uses (System in production, Manual in tests). A nil clock falls back to the system clock; an issuer of the wrong size makes every token fail to verify, which is the fail-closed outcome.

func (*DelegationAuthenticator) Authenticate

func (a *DelegationAuthenticator) Authenticate(r *http.Request) (Principal, error)

Authenticate verifies the request's bearer token and resolves it to a Principal carrying the verified Grant. A missing or unverifiable token is ErrUnauthenticated, never a partial or escalated authority.

type DenyAll

type DenyAll struct{}

DenyAll is an authenticator that refuses every request. It is the fail-closed default: a server constructed without an explicit authenticator gets this, so a forgotten or misconfigured auth setup locks the door rather than serving openly. An unauthenticated API surface is therefore not representable: there is no "no auth" mode, only "deny everything" or a real authenticator.

func (DenyAll) Authenticate

func (DenyAll) Authenticate(*http.Request) (Principal, error)

Authenticate always refuses.

type Descriptor

type Descriptor struct {
	Kind    string
	Columns []Column
}

Descriptor declares how a kind is presented in the read model: its resource kind name and the columns a list shows. Supplying a Descriptor is the only step needed to make a kind appear in list/describe and, later, the remote API and dashboard, because every reader projects through it.

type Detail

type Detail struct {
	Resource resource.Resource
	Columns  []string
	Row      Row
	Events   []spine.Event
}

Detail is a single resource fully described: the resource itself, its one-row projection through the descriptor (with the column headers), and its recent change history from the event log.

func Describe

func Describe(ctx context.Context, store resource.Store, log spine.Log, d Descriptor, id string, tail int) (Detail, error)

Describe returns the resource addressed by id together with its recent change history. History is the resource's own mutations on the store's stream (when it was created, updated, or deleted), decoded and filtered to this resource, in order and capped to the last tail (tail <= 0 returns all). It reads through the same store and event log the rest of the system writes, so a describe and a replay of the same resource agree. A nil log yields an empty history.

type FieldDelta

type FieldDelta struct {
	Field string
	A     string
	B     string
}

FieldDelta is one field whose value differs between two resources, addressed by its dotted path under spec or status (for example "spec.model", "status.phase").

func Diff

func Diff(a, b resource.Resource) []FieldDelta

Diff compares two resources field by field over their Spec and Status, returning the fields that differ in a stable, path-sorted order. A field present on only one side compares against the empty string on the other. It walks the stored JSON rather than a typed struct, so it is kind-agnostic: flynn diff and any future face (the remote API, a dashboard) share this one comparison for every kind, the same way they share List and Describe. Envelope metadata (ids, versions, timestamps) is excluded; the diff is of declared and observed content, not bookkeeping.

type Identity

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

Identity is an instance's self-issued Ed25519 keypair: its verifiable identity on the fleet and the key it signs delegations with. It extends the bookkeeping OriginInstanceID into something another instance can cryptographically check, with no central authority.

func GenerateIdentity

func GenerateIdentity() (*Identity, error)

GenerateIdentity creates a fresh instance identity. Persisting the key across restarts (sealed in the vault) is a later hardening step; a generated-in-memory identity is enough to issue and verify within a process and across a fleet that exchanges public keys.

func IdentityFromSeed

func IdentityFromSeed(seed []byte) (*Identity, error)

IdentityFromSeed reconstructs an identity from a 32-byte Ed25519 seed, the inverse of Seed. A seed of the wrong length is rejected rather than silently truncated, so a corrupted or foreign sealed value fails closed instead of yielding a usable-but-wrong identity.

func LoadOrCreateIdentity

func LoadOrCreateIdentity(ctx context.Context, v SeedVault, ref string) (*Identity, error)

LoadOrCreateIdentity returns the instance's stable identity: it reads the sealed seed from the vault and reconstructs the keypair, or, on first run (no sealed seed yet), generates a fresh identity and seals its seed before returning it. Either way the returned identity is the same one a later restart will load, so the instance's public key and principal id survive restarts rather than churning per process.

It fails closed on a present-but-corrupt seed (a wrong length or undecodable value): rather than silently minting a new identity and orphaning every token issued to the old key, it surfaces the error so the operator notices a tampered or truncated vault.

func (*Identity) ExternalName

func (i *Identity) ExternalName(base, purpose string, c Constraints) (string, error)

ExternalName projects this identity into a name valid under c. It is the external-name companion to ID, mirroring how ExternalName(pub, ...) companions PrincipalID(pub): the identity owns both renderings of itself. See the package-level ExternalName for the meaning of base and purpose.

func (*Identity) ID

func (i *Identity) ID() string

ID returns the identity's stable principal id, derived from its public key so it is self-certifying: the id and the verifying key are one and the same.

func (*Identity) Public

func (i *Identity) Public() ed25519.PublicKey

Public returns the identity's public key, the value other instances verify its delegations against.

func (*Identity) Seed

func (i *Identity) Seed() []byte

Seed returns the identity's 32-byte Ed25519 seed, the single secret the rest of the keypair derives from. It is the value LoadOrCreateIdentity seals in the vault. Holding it is equivalent to holding the private key, so callers must treat it as a secret and never log or persist it unsealed.

type NameSource

type NameSource string

NameSource records how ResolveName produced a name, so the choice is observable: an explicit name, a stable name derived from the instance identity, or an ephemeral name derived from a throwaway identity because none was in scope (the name will not be stable across runs).

const (
	// NameOverride means the caller supplied the name verbatim.
	NameOverride NameSource = "override"
	// NameIdentity means the name was derived from the instance's stable identity.
	NameIdentity NameSource = "identity"
	// NameEphemeral means no identity was in scope, so the name was derived from a
	// throwaway identity and will differ on the next run.
	NameEphemeral NameSource = "ephemeral"
)

type Option

type Option func(*Server)

Option configures a Server.

func WithAction

func WithAction(verb string, spec ActionSpec) Option

WithAction registers an action verb (a kube-style subresource) and its gate. The verb is served at POST /v1/{kind}/{name}/<verb>, refuses a token below spec.MinScope, and admits only if the caller's grant intersected with the instance's local grant permits spec.Action. Verbs with an empty Action or a nil Run are ignored, so a half-declared action cannot open an ungated route.

func WithAdmitter

func WithAdmitter(a dispatch.Admitter) Option

WithAdmitter overrides the capability waist the action gate admits a verb through. The default is capability.Admitter, which reads the effective grant from the request context; this hook exists for tests.

func WithApprovals

func WithApprovals(v *approval.Verifier, host string) Option

WithApprovals wires the verifier that checks the signed approvals a dangerous verb requires, and the host id an approval must be bound to (it should match the verifier's own host). Without it, a verb marked Dangerous fails closed: it cannot run, because the second factor cannot be verified. A non-dangerous verb is unaffected.

func WithLocalGrant

func WithLocalGrant(g capability.Grant) Option

WithLocalGrant sets this instance's own action authority: the ceiling every remote caller's grant is intersected against, so no presented token can act beyond what the instance itself admits locally. The default is AllowAll (locally unconstrained).

func WithLogger

func WithLogger(l observe.Logger) Option

WithLogger sets the logger used for audit and errors (default: a discard logger).

func WithWatchPoll

func WithWatchPoll(d time.Duration) Option

WithWatchPoll overrides the watch poll interval. A non-positive value is ignored.

type PollWatcher

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

PollWatcher detects changes by comparing successive list snapshots of a kind. It holds the last-seen sync version per resource id; each Poll returns the transitions since the previous Poll and advances the baseline. A production caller drives Poll on a ticker; tests drive it directly, so change detection is deterministic and free of any clock. The first Poll reports every current resource as Added.

func NewPollWatcher

func NewPollWatcher(store resource.Store, kind string, sel resource.Selector) *PollWatcher

NewPollWatcher returns a watcher over the live resources of kind matching sel.

func (*PollWatcher) Poll

func (w *PollWatcher) Poll(ctx context.Context) ([]Change, error)

Poll lists the kind and returns the changes since the previous Poll: Added for ids not seen before, Modified for ids whose SyncVersion advanced, and Deleted for ids that were present before and are absent now. Changes are ordered by resource name then id, so the output is deterministic.

type PossessionAuthenticator

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

PossessionAuthenticator wraps an inner Authenticator and additionally requires the caller to prove possession of the leaf key the resolved principal is bound to. The inner authenticator establishes authority (the verified, attenuated Grant); this layer establishes that the presenter actually holds the key, not just a copy of the token.

A request is admitted only if the inner authenticator resolves a principal AND a valid possession proof binds to that principal: either a signed ProofHeader, or, when enabled, an mTLS client certificate carrying the leaf key. Any failure collapses to ErrUnauthenticated.

func RequirePossession

func RequirePossession(inner Authenticator, clk clock.Clock, opts ...PossessionOption) *PossessionAuthenticator

RequirePossession wraps inner so that, beyond resolving a principal, every request must prove possession of the principal's leaf key. clk supplies the time proofs are checked against (System in production, Manual in tests); a nil clock falls back to the system clock.

func (*PossessionAuthenticator) Authenticate

func (p *PossessionAuthenticator) Authenticate(r *http.Request) (Principal, error)

Authenticate resolves the request through the inner authenticator, then requires a valid possession proof for the resolved principal. A missing or invalid proof, or a proof that does not bind to the resolved principal, is ErrUnauthenticated, so a stolen token alone never authenticates.

type PossessionOption

type PossessionOption func(*PossessionAuthenticator)

PossessionOption configures a PossessionAuthenticator.

func WithProofSkew

func WithProofSkew(d time.Duration) PossessionOption

WithProofSkew overrides the accepted timestamp skew for signed proofs. A non-positive value is ignored, keeping the safe default.

func WithTLSPossession

func WithTLSPossession() PossessionOption

WithTLSPossession enables mTLS channel binding as an accepted possession proof: a request over a TLS connection whose verified client certificate carries the leaf public key is admitted without a signed header. Off by default, since it requires the server to be configured to request and verify client certificates.

type Principal

type Principal struct {
	ID    string
	Scope Scope
	Grant capability.Grant
}

Principal is the authenticated identity behind a request, with the authority it carries. It is recorded with every action so the audit trail attributes who did what.

Authority is two-axis. Scope is the coarse access level (read/operator/admin) a handler requires. Grant is the fine-grained action authority the principal holds: a local operator token carries capability.AllowAll (scope is its only limit), while a delegated principal carries the verified, monotonically attenuated Grant its capability token proved. The action gate at the dispatch waist intersects this Grant with the target's own local grant, so a narrowed remote grant can never act beyond what the target would do locally. The zero Grant denies every action; an empty principal (an unauthenticated attempt) therefore carries no authority by construction.

type ResolvedName

type ResolvedName struct {
	Value  string
	Source NameSource
}

ResolvedName is a name together with how it was chosen.

func ResolveName

func ResolveName(id *Identity, base, purpose, override string, c Constraints) (ResolvedName, error)

ResolveName is the single port every call site names an external resource through, so the override-then-identity-then-fallback policy is defined once rather than re-implemented per provider. An explicit override always wins (validated against c so a bad name fails fast rather than at the provider). Otherwise the name is derived from id. When id is nil no identity is in scope, so a throwaway identity is minted and used, and the result is marked NameEphemeral so the caller can see the name will not be stable. See ExternalName for base and purpose.

type Row

type Row struct {
	ID    string
	Name  string
	Cells []string
}

Row is one resource projected to its cells, carrying the resource id and name so a renderer can address or link it independently of the displayed columns.

type Scope

type Scope int

Scope is the access level behind a request. Scopes are ordered: Admin contains Operator contains Read. A handler states the minimum it requires.

const (
	// ScopeNone is no access; the zero value, so an unset Principal can do nothing.
	ScopeNone Scope = iota
	// ScopeRead permits get, list, and watch.
	ScopeRead
	// ScopeOperator adds lifecycle and dispatch actions (pause, resume, halt, run).
	ScopeOperator
	// ScopeAdmin adds credentials, configuration, and upgrade.
	ScopeAdmin
)

func (Scope) Allows

func (s Scope) Allows(required Scope) bool

Allows reports whether this scope satisfies the required minimum. Because scopes are ordered, a higher scope satisfies a lower requirement.

func (Scope) String

func (s Scope) String() string

String renders the scope for logs and audit.

type SeedVault

type SeedVault interface {
	Lookup(ctx context.Context, ref string) (secret.Text, error)
	Set(ctx context.Context, ref string, value secret.Text) error
}

SeedVault is the slice of a credential vault this package needs to persist an identity: read a sealed value, write one. It is exactly satisfied by *vault.Store, so the production wiring passes the real vault while a test passes an in-memory stub, and controlplane never has to import the vault backend.

type Server

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

Server is the read/watch control-plane API over a resource store. It serves get/list/watch for any registered kind, gated by the Authenticator, plus action subresources (operator/admin verbs) behind the same auth boundary and a second, finer gate: the caller's verified grant intersected with this instance's own local grant, so a narrowed remote grant can never act beyond what the target would do locally.

func NewServer

func NewServer(store resource.Store, log spine.Log, auth Authenticator, opts ...Option) *Server

NewServer builds the read/watch API over store, tailing log (the store's resource stream) for watch, authenticated by auth. A nil auth fails closed: the server denies every request rather than serving openly, so an unauthenticated API cannot be created by omission.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the HTTP handler for the API. The watch route is registered before the by-name route so the literal "watch" segment wins over the {name} wildcard.

type Table

type Table struct {
	Columns []string
	Rows    []Row
}

Table is a rendered, kind-agnostic result: the column headers and one row per resource, in a stable order. A renderer (text, JSON, HTML) formats it; the read model itself never formats.

func List

func List(ctx context.Context, store resource.Store, d Descriptor, sel resource.Selector) (Table, error)

List returns every live resource of the descriptor's kind whose labels satisfy sel (a nil selector matches all), each projected through the descriptor's columns. Results follow the store's order (scope then name), so the table is deterministic.

type TokenAuthenticator

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

TokenAuthenticator authenticates a bearer token against a fixed table. It compares in constant time and scans every entry regardless of an early match, so neither a token's value nor which tokens exist leaks through timing.

func GeneratedOperator

func GeneratedOperator(id string, scope Scope, mint func() (string, error)) (*TokenAuthenticator, string, error)

GeneratedOperator mints a fresh, cryptographically-random bearer token and returns an authenticator that accepts exactly that token as a single operator-scoped principal, along with the token so the caller can present it to the operator once. It is the auth-on-by-default path: bringing up the API with no operator-supplied credential yields a secured-by-default endpoint with zero configuration, so there is never a reason to fall back to an open one. The provided mint is the entropy source (ids.Token in production); a test can inject a deterministic one.

func NewTokenAuthenticator

func NewTokenAuthenticator(tokens map[string]Principal) *TokenAuthenticator

NewTokenAuthenticator builds an authenticator over a token -> Principal table.

func (*TokenAuthenticator) Authenticate

func (a *TokenAuthenticator) Authenticate(r *http.Request) (Principal, error)

Authenticate resolves the request's bearer token to its Principal.

type Watcher

type Watcher interface {
	Poll(ctx context.Context) ([]Change, error)
}

Watcher reports resource changes for a kind. It is the interface a remote API serves over a streaming transport; locally it is driven by polling. Defining it alongside List and Describe keeps list, get, and watch one read model, so adding live streaming later is a new transport over this interface, not a new query path bolted on.

Jump to

Keyboard shortcuts

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