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

cellagent

package
v0.1.0-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: Apache-2.0, AGPL-3.0 Imports: 22 Imported by: 0

Documentation

Overview

Package cellagent is the logic of headmesh-cellagent, the process that runs beside each Headscale.

The agent applies desired state from the Headmesh server to its local Headscale, but it never trusts that state blindly. Every change passes local guardrails that protect a production tailnet even from a buggy or compromised control server:

  • operations must carry a valid Ed25519 signature from a pinned server key, must not be expired and are executed at most once;
  • observe mode and an operator "freeze" refuse every change;
  • protected nodes can never be deleted, retagged, renamed, expired or given routes;
  • deletes and retags are rate limited; exit-node routes are never approved;
  • a policy is applied only if it parses, keeps every local reachability invariant, and passes Headscale's own CheckPolicy (which also runs the policy's isolation tests);
  • the last policy that applied cleanly is kept on disk for one-command rollback.

Future policy revisions (scheduled grant expiries) are applied on time by Tick even while the server is unreachable.

Index

Constants

This section is empty.

Variables

View Source
var ErrRefused = errors.New("refused by cell guardrail")

ErrRefused marks a change a guardrail rejected. Results carry refused_by_guardrail so the server can alert.

Functions

func SignOp

func SignOp(key ed25519.PrivateKey, op *cellv1.Op) error

SignOp signs op for the server side; the agent verifies with the matching public key.

func ToNode

func ToNode(n *headscalev1.Node) *cellv1.Node

ToNode converts a Headscale node to the cell protocol's node.

Types

type Agent

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

Agent applies desired state to one Headscale under local guardrails. It is safe for concurrent use.

func New

func New(cfg Config, hs headscale.API) (*Agent, error)

New returns an Agent. It restores the record of executed operations from StateDir so a restart cannot replay an operation.

func (*Agent) AcceptServerKey

func (a *Agent) AcceptServerKey(key []byte) error

AcceptServerKey records the op-signing key the server announced, refusing keys that are not pinned.

func (*Agent) Freeze

func (a *Agent) Freeze() error

Freeze stops all changes until Unfreeze. It is a file in StateDir so it survives restarts and can be set by an operator with `headmesh-cellagent freeze` even while the agent runs.

func (*Agent) Frozen

func (a *Agent) Frozen() bool

Frozen reports whether the freeze flag is set.

func (*Agent) HandleOp

func (a *Agent) HandleOp(ctx context.Context, op *cellv1.Op) *cellv1.OpResult

HandleOp verifies and executes one operation and returns its result. It never panics on bad input.

func (*Agent) Observe

func (a *Agent) Observe(ctx context.Context) *cellv1.Observed

Observe returns a full snapshot of Headscale's nodes and the active policy digest.

func (*Agent) Rollback

func (a *Agent) Rollback(ctx context.Context) error

Rollback re-applies the last policy that applied cleanly. It is the operator's undo button and works without the server.

func (*Agent) Run

Run keeps a Sync stream to the server open, reconnecting with jittered exponential backoff, and applies the policy timeline on every tick whether or not the server is reachable. It returns when ctx is cancelled.

func (*Agent) SetTimeline

func (a *Agent) SetTimeline(tl []*cellv1.PolicyRevision) error

SetTimeline replaces the desired policy timeline. Revisions must be strictly increasing in both revision number and effective time.

func (*Agent) Status

func (a *Agent) Status() Status

Status reports the agent's state.

func (*Agent) Tick

func (a *Agent) Tick(ctx context.Context) *cellv1.PolicyApplied

Tick applies the newest revision that is due and newer than the active one. Revisions that became due and were superseded while the agent was busy or offline are skipped: only the latest due state matters.

func (*Agent) Unfreeze

func (a *Agent) Unfreeze() error

Unfreeze lifts a freeze.

type Config

type Config struct {
	CellName string
	Mode     cellv1.Mode
	// StateDir holds last-known-good policy, the freeze flag and the record of executed operations.
	StateDir string
	// ServerKeys are the Ed25519 public keys allowed to sign operations. The key announced in Welcome must be
	// one of them, so a compromised transport cannot substitute its own.
	ServerKeys     []ed25519.PublicKey
	Invariants     []Invariant
	ProtectedNodes []uint64
	// MaxDeletesPerMinute and MaxRetagsPerHour bound blast radius. Zero means the defaults (5 and 50).
	MaxDeletesPerMinute int
	MaxRetagsPerHour    int
	// MaxPreAuthKeyTTL bounds pre-auth key lifetimes. Zero means 24h.
	MaxPreAuthKeyTTL time.Duration
	Now              func() time.Time
}

Config controls an Agent. Cell-local safety settings live here, on the cell host, not on the server.

type HelloInfo

type HelloInfo struct {
	AgentVersion         string
	HeadscaleVersion     string
	NoisePublicKeySHA256 string
}

HelloInfo identifies this agent to the server.

type Invariant

type Invariant struct {
	Name  string   `json:"name"`
	Src   []string `json:"src"`
	Dst   []string `json:"dst"`
	Proto string   `json:"proto"`
	Port  int      `json:"port"`
}

Invariant is reachability that must survive every policy change on this cell, for example "control nodes reach GPU hosts on tcp:22". Principals use Headscale policy syntax (tags or user@).

type RunOptions

type RunOptions struct {
	TickInterval    time.Duration // how often scheduled revisions are checked (default 1s)
	ObserveInterval time.Duration // how often a full node snapshot is sent (default 30s)
	MinBackoff      time.Duration // reconnect backoff floor (default 1s)
	MaxBackoff      time.Duration // reconnect backoff ceiling (default 30s)
	Logger          *slog.Logger
}

RunOptions tune the run loop. Zero values use production defaults.

type Status

type Status struct {
	Cell           string    `json:"cell"`
	Mode           string    `json:"mode"`
	Frozen         bool      `json:"frozen"`
	ActiveRevision uint64    `json:"active_revision"`
	ActiveSHA256   string    `json:"active_sha256"`
	PendingCount   int       `json:"pending_revisions"`
	NextChangeAt   time.Time `json:"next_change_at,omitzero"`
}

Status is the local, operator-facing summary printed by `headmesh-cellagent status`.

Jump to

Keyboard shortcuts

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