Help us shape the future of Go! Take the 2026 Go Developer Survey

api

package
v0.5.1 Latest Latest
Warning

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

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

Documentation

Overview

Package api serves the operator surface: the public REST/JSON API consumed by the console and by automation, on a listener of its own (ADR-0021). Its handlers are hand-shaped over the shared domain layer (ADR-0007 as amended): they carry the semantics a browser-facing surface needs and hold no domain logic, calling the same domain functions the command line calls. Design rules for estate scale: aggregation is server-side, unbounded reads paginate by cursor, a fan-out mutation returns a recorded intent and never blocks on convergence, and bulk operations exist only where the console's screens demand them.

The read endpoints (a grouped flow rollup, a page of one workload's windows, the fleet and one workload in one shape, a workload's rendered policy) are thin handlers over internal/readmodel: each reads its parameters, calls one read model function, and encodes the result.

The write endpoints (rulesets, rules, services, address groups, a workload's labels, bulk mode changes, the selector preview, the dry-run render, and token management) are thin handlers over internal/policy, internal/fleet, internal/enroll, and internal/operator: each decodes its body, reads the version a conditional write names, calls one domain function, and encodes the result. Admission runs in the domain and its findings are returned as they are; a write without If-Match is refused as precondition required and one naming a stale version as precondition failed with the current one. The contract is api/openapi.yaml.

The package owns the listener's transport rules: TLS only; a middleware that reads a session cookie or a bearer operator token and resolves it, through the injected authn.Authenticator, to the one operator principal; an origin guard on every request that is not a read; a fixed-window throttle on the login endpoint; problem documents for every error. Handlers see a principal and nothing about how it was authenticated.

Index

Constants

View Source
const (
	ProblemUnauthenticated      = problemTypeBase + "unauthenticated"
	ProblemInvalidCredentials   = problemTypeBase + "invalid-credentials"
	ProblemNoPassword           = problemTypeBase + "no-password"
	ProblemCrossOrigin          = problemTypeBase + "cross-origin"
	ProblemTooManyAttempts      = problemTypeBase + "too-many-attempts"
	ProblemInvalidRequest       = problemTypeBase + "invalid-request"
	ProblemInvalidParameter     = problemTypeBase + "invalid-parameter"
	ProblemValidation           = problemTypeBase + "validation"
	ProblemPreconditionRequired = problemTypeBase + "precondition-required"
	ProblemPreconditionFailed   = problemTypeBase + "precondition-failed"
	ProblemMatchCountMismatch   = problemTypeBase + "match-count-mismatch"
	ProblemDuplicateName        = problemTypeBase + "duplicate-name"
	ProblemInUse                = problemTypeBase + "in-use"
	ProblemAlreadyRevoked       = problemTypeBase + "already-revoked"
	ProblemAgentOffline         = problemTypeBase + "agent-offline"
	ProblemWorkloadRetired      = problemTypeBase + "workload-retired"
	ProblemNotFound             = problemTypeBase + "not-found"
	ProblemMethodNotAllowed     = problemTypeBase + "method-not-allowed"
	ProblemInternal             = problemTypeBase + "internal"
)

The problem types the surface emits. A console branches on ProblemNoPassword to show its fresh-install state, and on ProblemUnauthenticated to show the login form. ProblemInvalidRequest is a body that cannot be read; ProblemInvalidParameter is a query or path parameter that cannot be, and its detail names the parameter. ProblemValidation is a body that was read and refused by admission, with its findings. ProblemPreconditionRequired is a write without a version; ProblemPreconditionFailed is a write naming a version the resource no longer holds. ProblemMatchCountMismatch is a mode change whose selection resolved to a different number of workloads than the operator expected. ProblemDuplicateName, ProblemInUse, and ProblemAlreadyRevoked are the conflicts an authored or token write can meet. ProblemAgentOffline is a directed reconnect refused because the workload's agent is offline as last recorded. ProblemWorkloadRetired is a write naming a workload the operator retired, whose attributes are frozen, or a second retirement of one (ADR-0023).

View Source
const (
	DefaultLoginAttempts = 10
	DefaultLoginWindow   = time.Minute
)

Login throttling defaults: attempts per source address per fixed window.

View Source
const (
	OperatorCertFile = "operator.crt"
	OperatorKeyFile  = "operator.key"
)

The files a generated operator certificate is persisted as, beside the authority directory's other operator-provisioned configuration.

View Source
const APIPrefix = "/api/v1"

APIPrefix is where the surface is mounted. Every other path belongs to the console.

View Source
const ContentTypeProblem = "application/problem+json"

ContentTypeProblem is the media type of every error the surface returns.

View Source
const SessionCookie = "innerwall_session"

SessionCookie is the name of the cookie that carries a session identifier.

Variables

This section is empty.

Functions

func ConsoleHandler

func ConsoleHandler(tree fs.FS) http.Handler

ConsoleHandler serves the console tree at the mount point. A nil tree, or one without an entry point, is the mount point answering not found. Otherwise a file at the request path is served as itself; content-addressed files under the asset directory are marked immutable, everything else must be revalidated; and any other path is the entry point, so a deep link into the console loads the console. Only reads are served: the surface's writes are all under the API prefix.

func CrossOriginGuard

func CrossOriginGuard(next http.Handler) http.Handler

CrossOriginGuard refuses every request that is not a read and that a browser marks as coming from another origin. The console is served from the same origin as the façade and the session cookie does not travel on cross-site navigations, so this check is the whole cross-site request forgery defense and no token scheme is needed (ADR-0021).

Two signals are consulted. A browser that sends Sec-Fetch-Site says outright whether the request is same-origin; anything else is refused. A browser that sends only Origin is held to the origin this listener is serving: the request's own host, over TLS. A client that sends neither header is not a browser and is not subject to forgery through ambient credentials, so it passes.

func Fingerprint

func Fingerprint(der []byte) string

Fingerprint is the SHA-256 digest of a certificate's DER encoding, in the colon-separated form a browser's certificate viewer shows.

func ListenerCertificate

func ListenerCertificate(certFile, keyFile, dir string, hosts []string, now time.Time, log *slog.Logger) (tls.Certificate, error)

ListenerCertificate returns the operator listener's certificate. When certFile and keyFile are both set they are loaded and must be valid; when both are empty a self-signed certificate persisted in dir is used, generated on first start or when the persisted one has expired, and its SHA-256 fingerprint is logged either way so the first connection can be verified out of band (ADR-0021). Setting one of the two paths without the other is a configuration error.

func NewHTTPServer

func NewHTTPServer(tlsCfg *tls.Config, handler http.Handler) *http.Server

NewHTTPServer wraps the handler in a server with the listener's TLS configuration and header timeouts set.

func ServerTLSConfig

func ServerTLSConfig(cert tls.Certificate) *tls.Config

ServerTLSConfig is the operator listener's TLS configuration: the certificate above, current TLS only, and both HTTP versions offered.

Types

type Deps

type Deps struct {
	// Authenticator resolves the credentials the surface is presented,
	// behind the login endpoint and the authentication middleware.
	Authenticator authn.Authenticator
	// Operators is the operator domain, behind operator token management.
	Operators *operator.Service
	// Reads is the read model the read endpoints call.
	Reads *readmodel.Reader
	// Authoring is the authored model's write domain, behind the
	// ruleset, rule, service, and address group endpoints.
	Authoring *policy.Authoring
	// Fleet is the fleet write domain, behind label edits, mode
	// changes, the selector preview, the dry run, and the directed
	// reconnect.
	Fleet *fleet.Service
	// Enroll is the enrollment domain, behind provisioning token
	// management.
	Enroll *enroll.Service
	// Site is the label the console header shows; empty when none is
	// configured.
	Site string
	// GatewayAddress is the host:port agents reach the agent gateway at,
	// as the operator configured it for the console's enroll command;
	// empty when none is configured. It is never derived from the bind
	// address or the host's interfaces.
	GatewayAddress string
	// Console is the built console tree served at every path outside the
	// API prefix; nil when this build carries none (ADR-0008).
	Console fs.FS
	// LoginAttempts and LoginWindow configure the login throttle; the
	// defaults apply when zero.
	LoginAttempts int
	LoginWindow   time.Duration
	Log           *slog.Logger
	// Now is the clock; time.Now if nil.
	Now func() time.Time
}

Deps is what the operator surface is built from.

type Finding

type Finding struct {
	Path    string `json:"path"`
	Rule    string `json:"rule"`
	Message string `json:"message"`
}

Finding is one admission failure: where it is, which rule it breaks, and what to tell the operator.

type Me

type Me struct {
	DisplayName    *string `json:"display_name"`
	Site           string  `json:"site"`
	GatewayAddress *string `json:"gateway_address"`
	Version        string  `json:"version"`
}

Me is the operator as the console sees it: the display name set with the password (null until one is set, and the console renders a generic fallback), the site label configured on the control plane (empty when none is), the advertised agent gateway address (null when none is configured, and the console keeps its placeholder), and the version of the control plane answering, as internal/buildinfo names it. Only an authenticated request reaches it, so the version is never disclosed before login.

type Middleware added in v0.5.0

type Middleware struct {
	Name string
	Wrap func(http.Handler) http.Handler
}

Middleware is one named layer in front of the API routes.

type Problem

type Problem struct {
	Type   string `json:"type"`
	Title  string `json:"title"`
	Status int    `json:"status"`
	Detail string `json:"detail,omitempty"`
	// Errors are the admission findings of a validation problem, one per
	// field the caller must fix.
	Errors []Finding `json:"errors,omitempty"`
	// CurrentVersion is the version the resource holds now, on a
	// precondition-failed problem.
	CurrentVersion string `json:"current_version,omitempty"`
	// Expected and Matched are the counts of a match-count problem.
	Expected *int `json:"expected,omitempty"`
	Matched  *int `json:"matched,omitempty"`
	// LastSeenAt is when the agent was last heard from, on an
	// agent-offline problem, where it is always present and null when the
	// agent never was.
	LastSeenAt *nullableInstant `json:"last_seen_at,omitempty"`
	// RetiredAt is when the workload was retired, on a workload-retired
	// problem.
	RetiredAt string `json:"retired_at,omitempty"`
}

Problem is an error document. Type is a stable identifier a client branches on; the other fields are for people, except the extensions, which carry what a client needs to act: the findings of a refused write, the current version of a resource a stale write named, the two counts of a refused mode change, the last-seen instant of an agent a directed reconnect found offline, and the instant a workload a write named was retired.

type Route added in v0.5.0

type Route struct {
	Method  string
	Pattern string
	Handler http.HandlerFunc
}

Route is one endpoint: a method and a path pattern whose segments are literals or one-segment parameters written {name}.

type Server

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

Server holds the surface's handlers and the listener's middleware. The handlers are hand-shaped over the domain layer (ADR-0007 as amended): each carries the transport semantics of its endpoint and calls the same domain functions the command line calls, holding no logic of its own.

func New

func New(d Deps) *Server

New constructs the surface.

func (*Server) Handler

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

Handler builds the listener's handler from the server's composition.

func (*Server) Routes

func (s *Server) Routes() []string

Routes lists every method and path pattern the surface serves, in the form "METHOD /api/v1/path/{param}", so the hand-authored contract can be checked against what is actually mounted.

func (*Server) Surface added in v0.5.0

func (s *Server) Surface() *Surface

Surface composes the operator surface from the server's handlers: the origin guard, the login throttle, and the authentication middleware, in that order, in front of every route, and the console at every other path.

type Surface added in v0.5.0

type Surface struct {
	// Middleware wraps the API routes, outermost first.
	Middleware []Middleware
	// Routes are the endpoints under APIPrefix.
	Routes []Route
	// Console serves every path outside APIPrefix.
	Console http.Handler
}

Surface is the composition of the operator listener's handler: the middleware in front of the API routes, the routes under APIPrefix, and the console at every other path. The listener's handler is built from it and from nothing else, so what the surface serves, and in what order its middleware runs, is read off this structure.

func (*Surface) Handle added in v0.5.0

func (sf *Surface) Handle(method, pattern string, h http.HandlerFunc)

Handle registers an endpoint.

func (*Surface) Handler added in v0.5.0

func (sf *Surface) Handler() http.Handler

Handler builds the listener's handler: the routes behind the middleware, outermost first, under APIPrefix, and the console at every other path.

func (*Surface) RouteNames added in v0.5.0

func (sf *Surface) RouteNames() []string

RouteNames lists every method and path pattern registered, in the form "METHOD /api/v1/path/{param}", sorted.

func (*Surface) Use added in v0.5.0

func (sf *Surface) Use(m Middleware)

Use appends a middleware inside those already added.

type Throttle

type Throttle struct {
	// Limit is the number of attempts allowed per window;
	// DefaultLoginAttempts if zero.
	Limit int
	// Window is the fixed window length; DefaultLoginWindow if zero.
	Window time.Duration
	// Now is the clock; time.Now if nil.
	Now func() time.Time
	// contains filtered or unexported fields
}

Throttle is a fixed-window attempt limiter keyed by source address. It lives in one process and protects that process: a brake on online password guessing, not a distributed rate limiter (ADR-0021). The key is the connection's own address, never a forwarded header, so a proxy in front collapses every client into one source and the brake tightens accordingly. The table is bounded (throttleMaxSources), so the brake is bounded too: a guesser that rotates through more source addresses than the bound within one window pushes its own oldest windows out and resets their counts. Against that, the throttle costs the process bounded memory and constant work per attempt, and nothing more is claimed; protection from a distributed guesser sits in front of the control plane (ADR-0021).

func (*Throttle) Allow

func (t *Throttle) Allow(key string) (ok bool, retryAfter time.Duration)

Allow records one attempt from key and reports whether it is within the window's budget; when it is not, retryAfter is the time until the window resets.

func (*Throttle) Middleware

func (t *Throttle) Middleware(next http.Handler) http.Handler

Middleware applies the throttle to the login route only.

func (*Throttle) Sources

func (t *Throttle) Sources() int

Sources returns the number of sources the table holds.

Jump to

Keyboard shortcuts

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