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

policy

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: 13 Imported by: 0

Documentation

Overview

Package policy compiles Headmesh tenancy state into a Headscale (v0.29+) policy document.

The compiler is a pure function: the same Snapshot and instant always produce byte-identical output. Tenant isolation is enforced twice — once by construction here, and once by Verify, an independent checker that re-derives every cross-organisation edge from the rendered document and rejects anything not justified by an active grant or a platform rule. Nothing is sent to a cell unless Verify passes.

Index

Constants

View Source
const CompilerVersion = "1"

CompilerVersion is embedded in every rendered document so a cell can report which compiler produced its active policy.

View Source
const (

	// QuarantineTag is applied to nodes that registered outside Headmesh in an enforced cell. No rule ever
	// references it, so quarantined nodes can reach nothing and nothing can reach them.
	QuarantineTag = "tag:hm-quarantine"
)

Tag prefixes. Every Headmesh-managed tag starts with "tag:hm-", which lets Verify recognise (and refuse) any foreign principal in a compiled document.

Variables

View Source
var ErrInvalid = errors.New("invalid policy input")

ErrInvalid wraps every validation failure.

View Source
var ErrViolation = errors.New("policy violation")

ErrViolation wraps every isolation or well-formedness violation Verify finds.

Functions

func DeviceTag

func DeviceTag(device string) string

DeviceTag is unique to one device.

func DeviceTags

func DeviceTags(d Device) []string

DeviceTags returns the full tag set Headmesh assigns to a tagged device, sorted. Quarantined devices get only QuarantineTag; removed devices get none.

func OrgTag

func OrgTag(org string) string

OrgTag is carried by every tagged device of an org.

func Principals

func Principals(d Device) []string

Principals returns the policy principals a live device is known by. Inactive devices have none.

func RoleTag

func RoleTag(org, role string) string

RoleTag is carried by every tagged device of org with role.

func ValidateRole

func ValidateRole(r string) error

ValidateRole reports whether r is a valid role name (it becomes part of a Headscale tag).

func Verify

func Verify(s Snapshot, at time.Time, doc Document) error

Verify checks a policy document against the snapshot it should have been compiled from:

  • only Headmesh principals of known orgs and devices appear (no "*", autogroups, groups, hosts or IPs);
  • every referenced tag is declared in tagOwners and nothing else is;
  • every cross-org edge is justified, port by port, by a grant active at instant at or by a platform rule;
  • "*" ports never cross orgs, SSH rules never cross orgs, and tests only name known principals.

Verify re-derives justification from the snapshot rather than from the compiler's intermediate state, so it catches compiler bugs as well as hand-edited documents.

Types

type Compiled

type Compiled struct {
	HuJSON   []byte
	SHA256   string
	Document Document
	Stats    Stats
}

Compiled is a rendered policy ready to send to a cell.

func Compile

func Compile(s Snapshot, at time.Time, opt Options) (*Compiled, error)

Compile renders the policy for snapshot s as it should be enforced at instant at. It validates s, builds the document, then runs Verify on its own output before returning, so a compiler bug can never produce a policy that crosses tenants.

type Device

type Device struct {
	Short string
	Org   string
	Roles []string
	User  string // non-empty for user-owned nodes in adopted cells
	State DeviceState
	// Registered is true once the device has a Headscale node. Only registered devices resolve to addresses, and
	// Headscale rejects policy tests whose principals resolve to none, so canaries consider only these.
	Registered bool
}

Device is one node. Tagged devices carry org, role and device tags; user-owned devices (adopted cells only) are addressed through their Headscale user.

type DeviceState

type DeviceState int

DeviceState mirrors the lifecycle of a device record.

const (
	DeviceActive DeviceState = iota + 1
	DeviceQuarantined
	DeviceRemoved
)

type Document

type Document struct {
	TagOwners map[string][]string `json:"tagOwners"`
	Grants    []GrantRule         `json:"grants"`
	SSH       []SSHRule           `json:"ssh,omitempty"`
	Tests     []PolicyTest        `json:"tests,omitempty"`
}

Document is the subset of the Headscale v0.29 policy format Headmesh emits. Every tag a live device carries is declared in tagOwners even when no rule references it. Legacy "acls" are never emitted: "grants" is always present (an empty list denies everything; omitting both would allow everything).

func ParseDocument

func ParseDocument(b []byte) (Document, error)

ParseDocument reads a HuJSON policy (comments and trailing commas allowed) back into a Document. Unknown sections — including legacy "acls" — are rejected, as is a missing or null "grants" list, because Headscale treats a policy without grants or acls as allow-all.

func (Document) Allows

func (doc Document) Allows(src, dst Device, proto string, port int) bool

Allows reports whether doc lets src open a connection to dst on proto/port. It implements exactly the subset of Tailscale grant semantics Headmesh emits — tag and user principals with explicit or "*" ports — and is used by the access-explain API and by property tests that check compiled policies against the model.

func (Document) AllowsPrincipals

func (doc Document) AllowsPrincipals(sp, dp []string, proto string, port int) bool

AllowsPrincipals reports whether a node known by principals sp may reach a node known by principals dp on proto/port. Cell agents use it to check local reachability invariants without a device model.

type Grant

type Grant struct {
	ID        string
	Grantor   string
	Grantee   string
	Src       Selector // within Grantee
	Dst       string   // device Short within Grantor
	Ports     []Port
	StartsAt  time.Time
	ExpiresAt time.Time
	Revoked   bool
}

Grant is time-limited cross-org access: devices of Grantee (Src) may reach exactly one device of Grantor (Dst) on Ports. ExpiresAt is mandatory.

func (Grant) ActiveAt

func (g Grant) ActiveAt(t time.Time) bool

ActiveAt reports whether the grant is in force at t.

type GrantRule

type GrantRule struct {
	Src []string `json:"src"`
	Dst []string `json:"dst"`
	IP  []string `json:"ip"`
}

GrantRule is one entry of the "grants" list.

type Options

type Options struct {
	// TagOwners is the owner list emitted for every Headmesh tag. Headmesh assigns tags through the Headscale API,
	// so no user needs to own them; the default (empty) means "only the API may assign".
	TagOwners []string
	// Canaries is the number of other orgs each org's deny test samples. Zero means the default (5); negative
	// disables policy tests entirely.
	Canaries int
	// CanaryPort is the port the canary tests probe. Defaults to tcp 22.
	CanaryPort int
}

Options tune compilation. The zero value is valid and production-safe.

type Org

type Org struct {
	Short    string   // 10-char base32 id used in tags; immutable
	Template Template // intra-org baseline
	Parent   string   // Short of the platform org that owns this child org, if any
	Platform bool     // true when this org may own child orgs and define PlatformRules
	SSH      bool     // emit an intra-org Tailscale SSH rule for non-root users
	// Users are Headscale users whose personal (untagged) nodes belong to this org. Only used for cells that
	// were adopted with existing user-owned nodes; Headmesh-enrolled nodes are always tagged.
	Users []string
}

Org is one isolated tenant network inside a cell.

type PlatformRule

type PlatformRule struct {
	ID       string
	Platform string
	Src, Dst PlatformSelector
	Ports    []Port
}

PlatformRule is standing access between a platform and its child orgs (for example gateway nodes → every GPU host on tcp:22, or every GPU host → the platform's SPIRE server). Exactly one side is ScopeChildren, so child orgs can never reach each other through a platform rule.

type PlatformSelector

type PlatformSelector struct {
	Scope Scope
	Role  string // empty means every device in scope
}

PlatformSelector selects devices of a platform org or of all its child orgs, optionally by role.

type PolicyTest

type PolicyTest struct {
	Src    string   `json:"src"`
	Accept []string `json:"accept,omitempty"`
	Deny   []string `json:"deny,omitempty"`
}

PolicyTest is a Headscale policy test; Headscale rejects the whole policy if any test fails.

type Port

type Port string

Port is one entry of a grant's "ip" list, for example "tcp:22", "udp:53", "tcp:8000-8100" or "*" (all protocols and ports; intra-org only).

const PortAny Port = "*"

PortAny means every protocol and port. It is only accepted for intra-org rules.

func ParsePort

func ParsePort(s string) (Port, error)

ParsePort validates and normalises a port spec.

type Revision

type Revision struct {
	EffectiveAt time.Time
	Compiled    *Compiled
}

Revision is a compiled policy and the instant it takes effect.

func Timeline

func Timeline(s Snapshot, now time.Time, horizon time.Duration, maxRevisions int, opt Options) ([]Revision, error)

Timeline compiles the policy in force at now plus one revision at every scheduled boundary (a rule or grant starting or expiring) within horizon, keeping at most max revisions. Cell agents apply future revisions on schedule, so scheduled expiries are enforced even while the control server is unreachable. Consecutive revisions with identical output are collapsed.

type Rule

type Rule struct {
	ID        string
	Org       string
	Src, Dst  Selector
	Ports     []Port
	StartsAt  time.Time
	ExpiresAt time.Time
}

Rule is intra-org access. A zero StartsAt/ExpiresAt means unbounded on that side.

func (Rule) ActiveAt

func (r Rule) ActiveAt(t time.Time) bool

ActiveAt reports whether the rule is in force at t.

type SSHRule

type SSHRule struct {
	Action string   `json:"action"`
	Src    []string `json:"src"`
	Dst    []string `json:"dst"`
	Users  []string `json:"users"`
}

SSHRule is one entry of the "ssh" list.

type Scope

type Scope int

Scope says whether a platform selector refers to the platform's own devices or to its child orgs' devices.

const (
	ScopePlatform Scope = iota + 1
	ScopeChildren
)

type Selector

type Selector struct {
	Kind   SelectorKind
	Role   string // for SelectRole
	Device string // device Short, for SelectDevice
}

Selector refers to devices within a single org.

func All

func All() Selector

All selects every device of the org.

func DeviceSel

func DeviceSel(short string) Selector

DeviceSel selects exactly one device.

func Role

func Role(r string) Selector

Role selects devices carrying role r.

func Users

func Users() Selector

Users selects the org's user-owned nodes.

type SelectorKind

type SelectorKind int

SelectorKind picks which devices of an org a rule side refers to.

const (
	SelectAll SelectorKind = iota + 1
	SelectRole
	SelectDevice
	// SelectUsers selects only the org's user-owned (personal) nodes. Adopted cells use it for rules such as
	// "the operator's own devices may reach everything".
	SelectUsers
)

type Snapshot

type Snapshot struct {
	Cell          string
	Orgs          []Org
	Devices       []Device
	Rules         []Rule
	Grants        []Grant
	PlatformRules []PlatformRule
}

Snapshot is a consistent view of one cell's tenancy state.

func (Snapshot) Validate

func (s Snapshot) Validate() error

Validate checks referential integrity and naming rules. Compile calls it; callers may use it to reject writes early.

type Stats

type Stats struct {
	Orgs, Devices, Rules, Grants, PlatformRules, Tags, Tests int
}

Stats summarises a compiled policy for logs and metrics.

type Template

type Template string

Template seeds an organisation's intra-org access before any explicit rules.

const (
	// TemplateTeam lets every device in the org reach every other device in the org on any port.
	TemplateTeam Template = "team"
	// TemplateLocked allows nothing until rules are added. Used for platform child orgs.
	TemplateLocked Template = "locked"
)

Jump to

Keyboard shortcuts

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