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
- Variables
- func DeviceTag(device string) string
- func DeviceTags(d Device) []string
- func OrgTag(org string) string
- func Principals(d Device) []string
- func RoleTag(org, role string) string
- func ValidateRole(r string) error
- func Verify(s Snapshot, at time.Time, doc Document) error
- type Compiled
- type Device
- type DeviceState
- type Document
- type Grant
- type GrantRule
- type Options
- type Org
- type PlatformRule
- type PlatformSelector
- type PolicyTest
- type Port
- type Revision
- type Rule
- type SSHRule
- type Scope
- type Selector
- type SelectorKind
- type Snapshot
- type Stats
- type Template
Constants ¶
const CompilerVersion = "1"
CompilerVersion is embedded in every rendered document so a cell can report which compiler produced its active policy.
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 ¶
var ErrInvalid = errors.New("invalid policy input")
ErrInvalid wraps every validation failure.
var ErrViolation = errors.New("policy violation")
ErrViolation wraps every isolation or well-formedness violation Verify finds.
Functions ¶
func DeviceTags ¶
DeviceTags returns the full tag set Headmesh assigns to a tagged device, sorted. Quarantined devices get only QuarantineTag; removed devices get none.
func Principals ¶
Principals returns the policy principals a live device is known by. Inactive devices have none.
func ValidateRole ¶
ValidateRole reports whether r is a valid role name (it becomes part of a Headscale tag).
func Verify ¶
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 ¶
Compiled is a rendered policy ready to send to a cell.
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 ¶
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 ¶
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 ¶
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.
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 ¶
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.
type Revision ¶
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.
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.
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.
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.
type Stats ¶
type Stats struct {
Orgs, Devices, Rules, Grants, PlatformRules, Tags, Tests int
}
Stats summarises a compiled policy for logs and metrics.