cockpit

package
v0.182.3 Latest Latest
Warning

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

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

Documentation

Overview

Package cockpit is the loopback daemon's Cockpit surface: the request guard every Cockpit route sits behind, the API mux, and the mounts that attach both to the dashboard listener (spec/features/cockpit).

Index

Constants

View Source
const (
	PagePrefix = web.MountPath
	APIPrefix  = "/api/v1/cockpit/"
)

PagePrefix and APIPrefix are the two subtrees Cockpit owns on the loopback listener (cockpit#req:cockpit-mount).

View Source
const (
	PrincipalAnonymousLocal = "anonymous-local"
	PrincipalOwner          = "owner"
)

The two principals a request resolves to.

View Source
const (
	LoginPath  = PagePrefix + "session/login"
	LogoutPath = PagePrefix + "session/logout"
)

LoginPath and LogoutPath are the two session routes under PagePrefix (cockpit#req:owner-session). The login code travels in LoginPath's "code" query parameter; the session key travels in the login URL's fragment, under LoginKeyFragment, which the browser sends to no server.

View Source
const CheckedAtHeader = "X-Wb-Cockpit-Checked-At"

CheckedAtHeader is the response header in which a metadata route says when the daemon last found what it serves to be current, as an RFC 3339 time. It is not part of the body, so a body that did not change keeps its ETag and is answered 304 while the header moves on: a client reads the freshness of what it holds from it.

View Source
const LoginCodeRPCPath = "/wb.cockpit.v1/login-code"

LoginCodeRPCPath is the owner-channel route that mints a login code. The daemon registers it on the mux its unix socket serves, behind the owner token, and never on the loopback listener. The daemon's file bridge dispatches into that same mux but forwards only the DaemonService procedures it lists, so it refuses this path.

View Source
const LoginKeyFragment = "key"

LoginKeyFragment is the name the session key has in the fragment of the login URL `wb cockpit` prints: `<LoginPath>?code=<code>#key=<key>` (cockpit#req:session-key).

View Source
const SessionKeyHeader = "X-Wb-Cockpit-Session-Key"

SessionKeyHeader is the request header that carries the session key (cockpit#req:session-key). The cookie is sent by the browser to every server on the loopback host, whatever its port; the key is held by the Cockpit page alone, in the storage of its own origin, and sent only by its own requests.

Variables

This section is empty.

Functions

func CanonicalHost

func CanonicalHost(listenAddress string) string

CanonicalHost picks the one loopback name Cockpit's session cookie is scoped to from the daemon's listen address: the address the daemon listens on when that is a loopback IP literal (127.0.0.2, ::1, ...), written in its shortest form, otherwise "127.0.0.1" (a listener named "localhost", or an address that does not parse or is not loopback). A page is therefore served on the address the daemon really has, and every other loopback name redirects to it.

func Guard

func Guard(canonical string, next http.Handler) http.Handler

Guard refuses a request whose Host header does not name a loopback host with status 421, before next runs: that is what stops a page that rebinds DNS to the loopback address. The port is not checked beyond being a number, so an SSH forward to another local port works.

canonical is the loopback name the daemon's listener has (see CanonicalHost). A GET or HEAD for a page on any other loopback name is redirected to the same path on http://<canonical>:<port>, using the port the browser reached; any other method there is refused with 421 so it is never replayed to another origin. An API request on an alias is served.

func Gzip added in v0.175.0

func Gzip(data []byte) []byte

Gzip compresses data at the best level. It is the compressor a Payload is built with in production: the bytes are computed once, when the body is stored, so the level costs nothing per request.

func ServePayload added in v0.175.0

func ServePayload(writer http.ResponseWriter, request *http.Request, payload Payload)

ServePayload answers a GET with payload: gzip when the request accepts it, identity otherwise, each with its own strong ETag, `Vary: Origin, Accept-Encoding` and `Cache-Control: no-cache` (a client may keep the body but revalidates on every use). If-None-Match accepts either ETag form, and `*`, and a match is answered 304 with no body.

func ServePrivatePayload added in v0.175.0

func ServePrivatePayload(writer http.ResponseWriter, request *http.Request, payload Payload)

ServePrivatePayload is ServePayload for a body no client and no intermediary may keep: `Cache-Control: no-store`. The ETags are still sent and still honoured, so a reader that holds the body in memory is answered 304.

Types

type Capability

type Capability string

Capability is one permission a Cockpit route or action requires (cockpit#req:capability-vocabulary).

const (
	CapabilityFleetRead       Capability = "fleet.read"
	CapabilityMachineRead     Capability = "machine.read"
	CapabilityRepoRead        Capability = "repo.read"
	CapabilityWorktreeRead    Capability = "worktree.read"
	CapabilityBranchRead      Capability = "branch.read"
	CapabilityPRRead          Capability = "pr.read"
	CapabilityAgentRead       Capability = "agent.read"
	CapabilityRepoContentRead Capability = "repo.content.read"
)

The capability vocabulary. A Feature that defines an action adds the action's capability.

type KeyDigest

type KeyDigest [sha256.Size]byte

KeyDigest is the SHA-256 digest of a session key: the only form of the key the daemon keeps once it has handed the key to the owner channel.

func DigestKey

func DigestKey(key string) KeyDigest

DigestKey is the digest of key.

type LoginCode

type LoginCode struct {
	Code      string
	Key       string
	ExpiresAt time.Time
}

LoginCode is a freshly minted login code, the session key of the session the code will start, and the moment the code stops being exchangeable. The key is minted with the code because the owner channel is the only way it ever leaves the daemon: `wb cockpit` puts it in the login URL's fragment, which no server is sent (cockpit#req:session-key).

type MachineRoute added in v0.175.0

type MachineRoute struct {
	MachineID string   `json:"machine_id"`
	SSH       SSHRoute `json:"ssh"`
}

MachineRoute is how the owner reaches one machine over SSH: the id of its machine entry in the fleet document and the host, optional login and wb command of the local configuration (session_move.targets.<machine>.ssh). It names hosts and users, which are not metadata (cockpit#req:anonymous-local-reads-metadata-only): it is sent to an owner session and to nobody else.

type MetadataHandler

type MetadataHandler func(http.ResponseWriter, *http.Request, Principal)

MetadataHandler serves one metadata route for a resolved principal.

type Options

type Options struct {
	// CanonicalHost is the loopback name the listener has (see CanonicalHost).
	CanonicalHost string
	// Config is wb.yaml's cockpit: section, after defaults.
	Config wbconfig.CockpitConfig
	// Now is the clock login codes and sessions expire against; nil means
	// time.Now.
	Now func() time.Time
	// Random is the source of login codes and session identifiers; nil means
	// crypto/rand.
	Random io.Reader
	// Sessions holds the owner sessions; nil means a fresh in-memory store.
	Sessions SessionStore
}

Options is what the daemon hands Cockpit at startup.

type OwnerHandler

type OwnerHandler func(http.ResponseWriter, *http.Request)

OwnerHandler serves one owner route for a request with an owner session.

type Payload added in v0.175.0

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

Payload is a JSON body prepared once for every request that will read it: the identity bytes, their gzip encoding and the strong ETags of the two (cockpit-views#req:compressed-responses). Preparing it is the only place the compressor runs, so serving never compresses.

func NewPayload added in v0.175.0

func NewPayload(identity []byte, compress func([]byte) []byte) Payload

NewPayload prepares identity, compressing it once with compress.

func (Payload) Size added in v0.175.0

func (payload Payload) Size() int

Size is the length of the identity body in bytes.

type Principal

type Principal struct {
	Name         string       `json:"principal"`
	Capabilities []Capability `json:"capabilities"`
}

Principal is who a request acts as and what it may do.

func (Principal) Has

func (principal Principal) Has(capability Capability) bool

Has reports whether the principal holds capability.

type SSHRoute added in v0.175.0

type SSHRoute struct {
	Host   string `json:"host"`
	User   string `json:"user,omitempty"`
	WBPath string `json:"wb_path"`
}

SSHRoute is the SSH part of a MachineRoute.

type Server

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

Server is Cockpit's state for one daemon run: its login codes, its owner sessions and its routes.

func New

func New(options Options) *Server

New builds Cockpit over the embedded application.

func (*Server) HandleMetadata

func (server *Server) HandleMetadata(name string, capability Capability, handler MetadataHandler)

HandleMetadata registers the metadata GET route APIPrefix+name, which requires capability. A metadata route is the only kind the hosted origin may read (cockpit#req:cross-origin-allowance), so its handler must return nothing beyond the metadata field set, and registering one with a capability that is not a metadata capability panics. Routes are registered before Mounts is called.

func (*Server) HandleOwner

func (server *Server) HandleOwner(method, path string, capability Capability, handler OwnerHandler)

HandleOwner registers an owner route — one that returns content or changes state — at path, under PagePrefix or APIPrefix, for one method. It requires an owner session holding capability; an empty capability requires the session alone. A route whose method is not GET changes state, and also requires the canonical origin and a JSON content type (cockpit#req:owner-routes). No owner route ever receives the cross-origin allowance. Routes are registered before Mounts is called.

func (*Server) IsOwner added in v0.175.0

func (server *Server) IsOwner(request *http.Request) bool

IsOwner reports whether request acts as the owner principal, for a route the daemon serves on the same listener outside Cockpit's mounts and so outside Guard (cockpit#req:daemon-log-is-owner-only). It applies what the mounts apply to an owner read: the Host header names a loopback host (cockpit#req:host-header-check), the request comes from the Cockpit page or carries no Origin at all, never from the hosted or a foreign origin, and it carries a live owner session (cockpit#req:owner-session): the session cookie and, in SessionKeyHeader, the session key bound to it (cockpit#req:session-key). The cookie alone, which any other server on the loopback host is sent and can replay, is not the owner. Nothing else makes a request the owner's: there is no anonymous fallback here.

func (*Server) LocalReader added in v0.175.0

func (server *Server) LocalReader(request *http.Request) bool

LocalReader reports whether request was made on this machine by the Cockpit page itself or by a client that names no origin: the Host header names a loopback host (cockpit#req:host-header-check) and the Origin is the canonical one or absent, never the hosted origin and never a foreign one. It is the one classification of "a reader on this machine", for a caller that must tell such a reader from the hosted page, which reads the same metadata routes (cockpit-views#req:remote-exporter-transports counts only the former as demand).

func (*Server) LoginCodeHandler

func (server *Server) LoginCodeHandler() http.Handler

LoginCodeHandler serves LoginCodeRPCPath. The caller mounts it behind the owner token; it does no authentication of its own.

func (*Server) MintLoginCode

func (server *Server) MintLoginCode() (LoginCode, error)

MintLoginCode issues a single-use login code. Only the owner channel may reach it.

func (*Server) Mounts

func (server *Server) Mounts() map[string]http.Handler

Mounts returns Cockpit's two subtrees, each behind Guard with the canonical host, in the shape dashboard.Options.Mounts takes. They are mounted whether or not wb.yaml has a hub: section. Taking the mounts freezes the route registries: a registration after it panics.

func (*Server) MountsWith

func (server *Server) MountsWith(others map[string]http.Handler) map[string]http.Handler

MountsWith returns Cockpit's mounts added to others (the hub's, which is nil without a hub: section). The others are copied, never modified.

func (*Server) SetMachineRoutes added in v0.175.0

func (server *Server) SetMachineRoutes(source func() []MachineRoute)

SetMachineRoutes names the source of the owner-only machine_routes of the session response. It is set before Mounts is called.

type SessionStore

type SessionStore interface {
	// Create starts a session at now, bound to the session key whose digest
	// is key, and returns its identifier.
	Create(now time.Time, key KeyDigest) (string, error)
	// Valid reports whether id names a session that has not ended or expired
	// at now and key is the session key it was created with. The key is
	// compared in constant time.
	Valid(id, key string, now time.Time) bool
	// End ends the session id names, if any, and forgets its key.
	End(id string)
}

SessionStore holds owner sessions. The daemon's store is in memory, so every session ends when the daemon restarts; the interface is the seam a test replaces. An identifier and a key are secrets: an implementation never logs either, and never holds the key itself.

Directories

Path Synopsis
Package fleet is Cockpit's fleet read model: one versioned document of this machine's repositories, worktrees, branches, pull requests and agents, and of every other machine's published snapshot, kept current by a background snapshotter and served without a request ever running Git (cockpit#req:fleet-read-model, cockpit#req:no-fleet-scan-on-the-request-path).
Package fleet is Cockpit's fleet read model: one versioned document of this machine's repositories, worktrees, branches, pull requests and agents, and of every other machine's published snapshot, kept current by a background snapshotter and served without a request ever running Git (cockpit#req:fleet-read-model, cockpit#req:no-fleet-scan-on-the-request-path).
Package machinemetrics samples this machine's CPU, load, memory and disk into an in-memory ring buffer for the Cockpit's machine-metrics route (cockpit-views#req:metrics-sampler).
Package machinemetrics samples this machine's CPU, load, memory and disk into an in-memory ring buffer for the Cockpit's machine-metrics route (cockpit-views#req:metrics-sampler).

Jump to

Keyboard shortcuts

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