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

acl

package
v0.6.0 Latest Latest
Warning

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

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

Documentation

Overview

Package acl is the read side of tailkeeper's policy engine: it parses a tailnet policy file, brings it to a normal form, compares two of them, and places a failure on a line. It is pure. It opens no file, makes no request, and knows nothing of masking or pseudonyms: texts go in as bytes and results come out as values, and the tools that use it (internal/tools) own every string that reaches a caller.

A policy file is HuJSON. The server that stores it reads it with Go's strict encoding/json after the comments and trailing commas are gone: keys of a struct match without regard to case and an unknown field is an error. The server also refuses a text that writes a key twice, at any depth, a top-level pair that differs only in case included (probe A-4, 20 Oct 2026), so such a text can be neither stored nor validated. A generic HuJSON tree keeps both keys, and Go's decoder would keep one: a repeated map entry is replaced whole, a repeated struct field that holds an object merges into the earlier one, and an array or a scalar replaces it. Parse reports every repeated key as a Finding, and everything after Parse reads the text with the decoder's rule, so that two texts can still be compared and a failure placed on a line. That is the reading Diff uses; it is not what the server does with such a text.

Every entry point that takes text runs the depth pre-scan of internal/guard before the first call into the HuJSON library: that library recurses once for each level of nesting and has no bound, and a stack overflow cannot be recovered from.

Index

Constants

View Source
const (
	DefaultPage = 50
	MaxPage     = 200
)

Page sizes: a page holds at most this many changes, and stops once the changes it holds cost pageCost (see DisplayCost); the change that crosses it is the last. A list of what a change gained or lost stops at listCost, and counts the rest. With these a page, in the characters a host receives, stays under what the tool declares: 56,000 and the last change (at most about 80,000, with its scalar strings and the two lists of each of two fields), and the findings of the two sides, which their caller bounds in the same way.

View Source
const (
	// MaxBytes is the largest text Parse reads: guard.MaxPolicyBytes, the one policy cap of
	// the repository. These are tailkeeper's caps; Tailscale publishes none. The callers
	// apply their own, smaller ones to what they accept (1 MiB over MCP).
	MaxBytes = guard.MaxPolicyBytes

	// MaxNodes is the most values (every member name, member value, element and the root
	// count as one) Parse builds. The cap is sized from the allocation measured at it:
	// parsing costs about 420 bytes of allocation a value (about 0.22 GB at the cap), and a
	// comparison holds two trees and their normal forms. Only the total allocation is
	// pinned (by TestTheNodeCapAndItsMeasuredAllocationArePinnedTogether); peak heap is not
	// measured. A policy of 9-value rules can hold about 58,000 of them. The cap is checked
	// on the guard's count before the parser allocates anything.
	MaxNodes = 1 << 19
)
View Source
const (
	TailscaleWrittenAsOf   = "2026-10-17"
	TailscaleWrittenSource = "design.md 12.6: Funnel node attribute, private log streaming accept rule, app connector presets"
)

Tailscale edits the policy file on the owner's behalf for some features, so a comparison of the file in git with the live policy shows them as drift for ever. They are listed here, with where the list comes from and when it was made, and a diff classifies a change to one of them apart from the rest so a drift job can ignore it. The output carries the date, so a reader knows how old the list is.

Source: docs/design.md section 12.6 and Tailscale's documentation of the three features. Review this list when a feature of the admin console starts writing to the policy.

View Source
const MaxAppended = 4096

MaxAppended is the most tests AppendTests adds in one call, of each kind.

View Source
const MaxAtoms = 1 << 18

MaxAtoms is the most atoms one normal form may hold: the access rules expand over their destinations and protocols, and a rule can be built to expand far past its size. Past it Normalise returns too_complex, never a truncated normal form that would read as "no change".

View Source
const MaxFindings = 100

MaxFindings is how many findings Parse keeps; the rest are counted.

View Source
const MaxFormatted = 4 * MaxBytes

MaxFormatted is the most bytes Format will produce. It lays every element on a line of its own, indents it by its depth, pads the names of a block of members to one width and indents each line of a block comment, so the output can be many times the size of the input: a million levels of nesting is refused by the guard, but a thousand levels with ten thousand elements is a few megabytes of tabs. The size is bounded before the library runs, from the tree (see formatBound); a text over it is a too_complex error.

View Source
const MaxLocations = 100

MaxLocations is the most places a location search lists.

View Source
const MaxNames = 1000

MaxNames is the most names a summary lists in each group.

View Source
const MaxPatchOps = 256

MaxPatchOps is the most operations a patch may hold, and the most tests members StripTests removes. Each operation on an array can move every element after it, so the cap bounds the time a patch can spend (the meter charges the rest: see chargePatch).

View Source
const MaxWork = 1 << 26

MaxWork is the work one call may spend, in the engine's own units (a value visited, an atom built, a comparison made). Real policies use a small fraction of it; text built to be slow runs into it and gets a too_complex error. It counts operations, not time, so the same input reads the same on every machine.

Variables

View Source
var (
	ErrInvalid    = &Error{Code: CodeInvalid}
	ErrTooComplex = &Error{Code: CodeTooComplex}
)

ErrInvalid and ErrTooComplex are the targets for errors.Is.

View Source
var NotChecked = []string{"access_change", "app_capabilities", "downgrades", "lockout", "ssh_lockout", "via"}

NotChecked names what a diff does not establish. It is structural: it cannot say whether a change in a group, a host, a tag owner or an alias widens who can reach what, and it does not simulate lockout. Those are the audit catalogue and the downgrade checks of later releases.

Functions

func AppendTests

func AppendTests(ctx context.Context, text []byte, tests, sshTests [][]byte) ([]byte, error)

AppendTests adds tests to the top level of a policy text as new tests and sshTests members at its end, so the result is the original text plus a tail. Each entry is one test object, as text; a list that is empty adds no member. The text must hold neither section already (StripTests first): a second one is a repeated key, which the server refuses and Go's decoder would resolve by keeping the later of the two. The entries are bounded in all, not only each: the policy and the entries together may not pass MaxBytes bytes or MaxNodes values, and each list holds at most MaxAppended.

func ApplyPatch

func ApplyPatch(ctx context.Context, text, patch []byte) ([]byte, error)

ApplyPatch applies an RFC 6902 patch to a policy text and returns the patched text with the comments and layout of what the patch did not touch. The patch may be HuJSON. A patch that fails to apply, or leaves something that is not a policy, is an invalid error and no text. A patch past MaxPatchOps operations, or whose operations would need more work than the budget on this policy, is a too_complex error.

func ChangeNotes

func ChangeNotes() []string

ChangeNotes is every sentence a Change's Note can be: a closed list of tailkeeper's own words. A note qualifies a change and is never built from a policy, so a caller that treats the field as fixed text can hold each result to this list.

func DisplayCost

func DisplayCost(s string) int

DisplayCost is the most characters a string takes in a result once a host has it as JSON text, counted per byte: an angle bracket or an ampersand is written as a six-character escape, a control character is shown as an eight-character escape of its own whose backslash JSON doubles, a quote or a backslash takes two, and a byte of a multi-byte character is counted as two (a character that JSON escapes takes six for its three bytes). It is an upper bound, and the measure by which a page and a list are cut.

func Format

func Format(ctx context.Context, text []byte) ([]byte, error)

Format formats a text the way Tailscale's pusher does before it compares. The output is at most MaxFormatted bytes: a text that would format to more is a too_complex error.

func NormalFormBytes

func NormalFormBytes(ctx context.Context, d *Doc) ([]byte, error)

NormalFormBytes is the canonical bytes of d's normal form, for a keyed digest that says whether two policies mean the same (see NormalForm.WriteTo). What WriteTo writes was paid for, by its bytes, while the normal form was built.

func Standardize

func Standardize(ctx context.Context, text []byte) ([]byte, error)

Standardize replaces the comments and trailing commas of a text with spaces, keeping every line and byte offset, as Tailscale's own pusher does before it validates.

func StripTests

func StripTests(ctx context.Context, text []byte) ([]byte, error)

StripTests removes every tests and sshTests member from the top level of a policy text, whatever the case of its name and however many times it is written (up to MaxPatchOps members). The rest keeps its text, but the HuJSON library also drops the comments attached to a removed member and the whitespace around it. A text with none comes back as it was.

Types

type Change

type Change struct {
	Class   Class
	Op      Op
	Section string // acls, grants, "acls+grants" for access rules that are in both, a section name, or an unknown section's name
	Key     string // the entry's key, or a summary of the atom or assertion
	Old     Text   // the canonical value on the old side (empty when added)
	New     Text   // and on the new side (empty when removed)
	Fields  []FieldDelta
	// OldN and NewN count a repeated entry on each side (tests, SSH rules); 0 when the
	// section has no repeats to count.
	OldN, NewN int
	// Note is a constant sentence that qualifies the change.
	Note string
	// OldPointer and NewPointer are JSON Pointers into each text, and OldLine and NewLine
	// the lines, for what the change names; empty or 0 when there is none.
	OldPointer, NewPointer string
	OldLine, NewLine       int
}

Change is one difference between two policies.

type Class

type Class string

Class is the kind of a change, and the order they are listed in: a reader looks at the first classes first. The first five are changes; the last two are only counted.

const (
	// ClassDefaults: acls, grants or ssh moved between absent, [] and non-empty. An absent
	// acls is allow-all, "acls": [] is deny-all, and removing ssh turns Tailscale SSH off.
	ClassDefaults Class = "defaults"
	// ClassTestsReduced: a test or SSH test assertion was removed or weakened.
	ClassTestsReduced Class = "testsReduced"
	// ClassWidening: a structural change that can only widen access (see widening.go).
	ClassWidening Class = "widening"
	// ClassTailscaleWritten: a change to a construct Tailscale writes itself; expected.
	ClassTailscaleWritten Class = "tailscaleWritten"
	// ClassSemantic: everything else that changes what the server enforces.
	ClassSemantic Class = "semantic"
	// ClassCommentOnly and ClassFormatOnly are counts, never entries.
	ClassCommentOnly Class = "commentOnly"
	ClassFormatOnly  Class = "formatOnly"
)

type Code

type Code string

Code names why the engine refused an input.

const (
	// CodeInvalid is a text the engine cannot read as a policy: bad encoding, bad syntax,
	// nesting past the guard, a top level that is not an object, a bad cursor.
	CodeInvalid Code = "invalid"
	// CodeTooComplex is a text or a comparison past one of the engine's budgets: its size,
	// the number of values, the number of atoms or the work spent on it. It is never a
	// partial answer.
	CodeTooComplex Code = "too_complex"
)

type Counts

type Counts struct {
	Defaults, TestsReduced, Widening, TailscaleWritten, Semantic int
	CommentOnly, FormatOnly                                      int
}

Counts are the number of changes of each class, over every change: the page of a result shows some, and a gate evaluates all.

type DiffOptions

type DiffOptions struct {
	// Section lists only the changes in one section: a known section name in any case, or
	// "unknown" for the sections the table does not know.
	Section string
	// Cursor continues a listing from the NextChange of an earlier result of the same pair.
	Cursor string
	// PageSize is the most changes listed; 0 means DefaultPage.
	PageSize int
	// IgnoreTailscaleWritten leaves the Tailscale-written changes out of the list and out of
	// Equal; they are still counted.
	IgnoreTailscaleWritten bool
	// OldShow and NewShow are how a caller sees the text of the old and of the new policy:
	// whatever a result quotes of a side is cut after its Show has run, and the digest of a
	// cursor is of the normal forms as they show. A nil Show shows the text as it is.
	OldShow, NewShow Show
	// Seal keys the digest that ties a cursor to its comparison: it is given that digest and
	// returns a keyed one. A nil Seal leaves the digest unkeyed.
	Seal func(digest []byte) string
}

DiffOptions narrow what a diff lists. None of them changes what it counts.

type DiffResult

type DiffResult struct {
	// SemanticallyEqual is true only if the two normal forms are equal: no change of the
	// five classes, whatever the options. False can be a false "changed" (see
	// NormalFormLimits); true is exact.
	SemanticallyEqual bool
	// Equal is SemanticallyEqual once the changes IgnoreTailscaleWritten leaves out are left
	// out.
	Equal   bool
	Counts  Counts
	Changes []Change // the page
	// Matched is how many changes the section filter and the options select.
	Matched   int
	Truncated bool
	// NextChange continues the listing. It identifies the comparison it was issued for: the two
	// normal forms as the two sides show them (so it tells nothing about a policy that the result
	// does not), the section filter and the Tailscale-written option, and, with a Seal, a key
	// that only the holder of it can compute. It is not a secret and is not signed: it keeps a
	// listing from continuing on the wrong comparison, and nothing more.
	NextChange string
	// NormalFormLimits, NotChecked and the as-of date qualify the answer.
	NormalFormLimits     []string
	NotChecked           []string
	TailscaleWrittenAsOf string
	// Test counts on each side, in elements.
	TestsBefore, TestsAfter       int
	SSHTestsBefore, SSHTestsAfter int
	// Ops is the work the whole comparison spent, in the units of MaxWork.
	Ops int64
}

DiffResult is the comparison of two policies.

func Diff

func Diff(ctx context.Context, oldDoc, newDoc *Doc, o DiffOptions) (*DiffResult, error)

Diff compares two parsed policies. It normalises both, so a rewrite that means the same (whitespace, comments, key order and case, acls or grants, a rule split or merged) is no change, and everything the normaliser cannot prove equal is. It spends the work budget, honours ctx and returns too_complex rather than an answer that is cut short.

type Doc

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

Doc is a parsed policy: the text, its HuJSON tree with Go's decoder's reading of repeated keys worked out (the server refuses such a text; see the package comment), and what Parse found wrong with the structure. A Doc is built by Parse only and is read-only afterwards.

func Parse

func Parse(ctx context.Context, src []byte) (*Doc, error)

Parse reads a policy text. It refuses, in this order and before any call into the HuJSON library: a byte order mark, UTF-16, a NUL, invalid UTF-8, a text over MaxBytes, nesting over 1,000 levels and more than MaxNodes values. After the parse it refuses a top level that is not an object: a JSON array posted to the validate endpoint is run as tests against the live policy, and would read as a pass for a proposal nobody validated.

func ParseShown

func ParseShown(ctx context.Context, src []byte, show Show) (*Doc, error)

ParseShown is Parse for a policy whose text a caller may see only through show: every piece of the policy that a result quotes (a key, a pointer, a text of a change) is cut after show has made it what the caller sees, so that where it is cut tells nothing about the text show hides. A nil show leaves the text as it is.

func (*Doc) Findings

func (d *Doc) Findings() []Finding

Findings are the repeated keys Parse found, at most MaxFindings, in document order.

func (*Doc) FindingsOmitted

func (d *Doc) FindingsOmitted() int

FindingsOmitted is how many repeated keys Parse found beyond MaxFindings.

func (*Doc) Len

func (d *Doc) Len() int

Len is the size of the text in bytes.

func (*Doc) Lines

func (d *Doc) Lines() int

Lines is the number of lines in the text: one more than its line feeds.

func (*Doc) Nodes

func (d *Doc) Nodes() int

Nodes is the number of values in the tree (names, values, elements and the root).

func (*Doc) Position

func (d *Doc) Position(off int) (line, col int)

Position is the line and column of a byte offset in the text Parse read, counted as the HuJSON parser and the server count: lines end at "\n", columns are bytes from 1, a lone carriage return is whitespace.

func (*Doc) UnknownSections

func (d *Doc) UnknownSections() []string

UnknownSections are the top-level names (each once, as first spelled) that the table of sections does not know. In the live policy that means tailkeeper is stale: Tailscale accepted a section this version does not know. In a proposal it means the server will probably reject it ("json: unknown field"). The wording differs; neither is an error here.

func (*Doc) UnknownSectionsShown

func (d *Doc) UnknownSectionsShown() []string

UnknownSectionsShown are the names UnknownSections lists, in step with it, as the caller may see them: each cut after Show ran on it. The names themselves are for comparing, these for printing.

type Error

type Error struct {
	Code Code
	// Message says what is wrong and, where there is one, the fix. It may quote the input,
	// so a caller treats it as untrusted text.
	Message string
	// Line and Column place the error in the text (from 1, bytes); Line is 0 when the
	// error has no position.
	Line, Column int
}

Error is the engine's typed error. Cancellation of the caller's context is not one: it comes back as the context's own error.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Is

func (e *Error) Is(target error) bool

Is makes errors.Is(err, ErrInvalid) and errors.Is(err, ErrTooComplex) match by code.

type Failure

type Failure struct {
	Src string
	Dst string
	// SSH: the failure is of an entry of sshTests, not tests.
	SSH bool
}

Failure is what the server's message says about a failed test: the source of the test (the legacy field is named user) and, if the message names one, the destination.

type FieldDelta

type FieldDelta struct {
	Field                  string
	Added, Removed         []string
	AddedMore, RemovedMore int
}

FieldDelta is what a changed element gained and lost in one field, so a reader sees the widening entry even when the element is too long to show whole. Each item is cut at 1 KiB, and at most maxDeltaItems of each side are listed (the rest are counted), with the wide selectors first.

type Finding

type Finding struct {
	Code FindingCode
	// Pointer is the JSON Pointer of the object that holds the key, cut at 1 KiB.
	Pointer string
	// Key and FirstKey are the two spellings, the later and the first (each cut at 1 KiB).
	Key, FirstKey string
	// Line and Column place the later key; FirstLine and FirstColumn the first.
	Line, Column           int
	FirstLine, FirstColumn int
	Keeps                  Keeps
}

Finding is one repeated key. The generic HuJSON tree keeps both and Go's decoder keeps one reading, which Keeps names; everything after Parse reads the policy that way. A text with a finding is one the server refuses.

type FindingCode

type FindingCode string

FindingCode names a structural problem Parse found.

const (
	// FindingDuplicateKey is an object that holds the same key twice, at any depth: a
	// group defined twice inside "groups" is one.
	FindingDuplicateKey FindingCode = "duplicate_key"
	// FindingCaseCollision is an object whose field names the server's decoder folds to
	// the same name though they are spelled differently ("tests" and "Tests"). It is
	// reported only where the server decodes into a struct, which is where the normal form
	// folds too: the top level, the elements of the array sections, the fields of
	// autoApprovers and derpMap and the structs inside derpMap (a region and its nodes), and
	// the values of postures, attrConfig and externalTailnets. The keys of a map ("group:a"
	// and "group:A") are distinct. The server refuses a top-level collision (probe A-4,
	// 20 Oct 2026); below the top level the report follows the decoder, which was not probed.
	FindingCaseCollision FindingCode = "case_collision"
)

type Keeps

type Keeps string

Keeps says which value Go's decoder keeps when a key is repeated. That is the reading the normal form and Diff use. It is not the server's: the server refuses a text with a repeated key at any depth, a case collision at the top level included (probe A-4, 20 Oct 2026), and keeps neither value.

const (
	// KeepsLater: the later value replaces the earlier one (an array, a scalar, or a map
	// entry).
	KeepsLater Keeps = "later"
	// KeepsMerged: both are objects and the decoder merges them, the later value winning
	// per key.
	KeepsMerged Keeps = "merged"
)

type Location

type Location struct {
	Match   Match
	Pointer string // JSON Pointer, cut at 1 KiB
	Line    int
	Column  int
}

Location is a place in the text.

func Locate

func Locate(d *Doc, f Failure) (locations []Location, cut int)

Locate finds the tests a failure can be about. It matches the source, written src or user, and, when the failure names a destination, the destination among the accept, allow and deny entries (an SSH test's dst), by their text. A failure that names no destination, or whose destination no test with that source lists, gets every test with that source, labelled as such: the answer is never "cannot tell". Lines refer to d, the caller's own document; a caller that built the text it sent maps them back.

It lists at most MaxLocations, and cut is how many more places match in the kind of match returned (as LocateToken counts the rest), so a caller can tell a list of 100 from one of 1,000. No match is no locations and 0 cut: not a verdict on the policy.

Locate takes no context and is bounded by construction. It makes one pass over the elements of one section and over the members of each element, so its work is at most the size of the text Parse accepted (MaxBytes, MaxNodes values). Nothing is decoded that the length of the text cannot rule out (see litEquals), a member name is read only if it is short enough to be one of the six fields, and a location (its pointer and line) is built only while the list has room: at most MaxLocations of each kind.

func LocateToken

func LocateToken(ctx context.Context, d *Doc, token string) ([]Location, int, error)

LocateToken finds every place the text holds the token as a name or as a string: where a server message quotes a tag or a group ("tag:x"), the failing element is one of these. It lists at most MaxLocations in document order and counts the rest.

type Match

type Match string

Match says how a location matched what was asked for.

const (
	// MatchSrcAndDestination: a test whose source and one of whose destinations are the ones
	// the failure names.
	MatchSrcAndDestination Match = "src_and_destination"
	// MatchSrcOnly: a test with the failure's source. It is every candidate when the failure
	// names no destination, or none of the tests with that source lists the destination (the
	// server may quote an address where the test wrote a name).
	MatchSrcOnly Match = "src_only"
	// MatchKey and MatchValue: a name or a string in the text that is the token asked for.
	MatchKey   Match = "key"
	MatchValue Match = "value"
)

type NormalForm

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

NormalForm is a policy reduced to what the server enforces: access rules as atoms, tests as assertions, entries keyed, and every field kept. Two policies with equal normal forms mean the same to the server; a pair the reduction cannot prove equal is "changed".

func Normalise

func Normalise(ctx context.Context, d *Doc) (*NormalForm, error)

Normalise reduces d to its normal form. It spends the work budget and honours ctx.

func (*NormalForm) AccessState

func (n *NormalForm) AccessState() State

AccessState is the state of the access rules as the server reads them together: acls and grants, which the admin console rewrites into each other, are non-empty if either is, empty if neither is non-empty and one is [], and absent if neither is in the text. An absent acls section is the default allow-all, and "acls": [] and "grants": [] are both deny-all (probe A-1, 20 Oct 2026). Whether a non-empty grants section alone cancels the default was not measured, so a move between these states is always reported (the defaults class of a diff). The pooled state hides which of the two sections is written as [], and that is not a rewrite: see writtenEmpty.

func (*NormalForm) Bytes

func (n *NormalForm) Bytes() []byte

Bytes is the canonical bytes of the normal form (see WriteTo).

func (*NormalForm) Ops

func (n *NormalForm) Ops() int64

Ops is the work spent building the normal form, in the units of MaxWork.

func (*NormalForm) WriteTo

func (n *NormalForm) WriteTo(w io.Writer) (int64, error)

WriteTo writes the canonical bytes of the normal form: one line for each section, entry, atom and shared source set, in a fixed order, every text JSON-quoted so none holds a line feed or a tab. Two policies with the same meaning write the same bytes whatever their comments, whitespace, key order, key case, acls or grants spelling or rule splitting, so a keyed digest of them says whether the policy now means what was validated. The state of a section is written; whether it was spelled null, and what case its name had, is not.

type Op

type Op string

Op is what happened to the thing a change names.

const (
	OpRemoved Op = "removed"
	OpAdded   Op = "added"
	OpChanged Op = "changed"
)

type Order

type Order uint8

Order says what a section's order means.

const (
	// OrderNone: there is no order to speak of (a scalar).
	OrderNone Order = iota + 1
	// OrderInsensitive: the arrays are sets. Reordering them never changes what the server
	// enforces (SSH evaluates "check" before "accept" whatever the order), so a reorder is
	// counted as a format change.
	OrderInsensitive
	// OrderSensitive: the arrays are lists processed in order, so a reorder is a change.
	// Today that is the operation lists inside ipsets values ("add" and "remove" are
	// processed in order) and the node lists of derpMap, which are compared in order
	// (conservative: only the ipsets operation lists need an order). The readers apply this
	// rule through keyedOpts, structOpts and the shapes, not through this field, and
	// TestSectionOrderIsWhatTheReadersDo compares the two for every section.
	OrderSensitive
)

type Section

type Section struct {
	// Name is the spelling Tailscale documents; the server reads names without regard to
	// case.
	Name  string
	Shape Shape
	Order Order
	// StructElements says what a nested object in the section looks like to the server's
	// decoder: the rules of an array are structs, whose field names fold case; the entries
	// of a map are exact.
	StructElements bool
}

Section is one entry of the table of sections the engine knows.

func SectionByName

func SectionByName(name string) (Section, bool)

SectionByName finds a known section by a name spelled in any case.

func Sections

func Sections() []Section

Sections returns the known sections in the order a normal form lists them. It is a copy: the table itself is private, so a caller cannot make it disagree with the index built from it.

type SectionSummary

type SectionSummary struct {
	// Name is the section's name as the table spells it, or as an unknown section was first
	// written (cut at 1 KiB).
	Name  string
	Known bool
	State State
	// Count is the elements of an array section, the entries of a map, or 1 for a value.
	Count int
	// Scalar is the canonical value of a section that holds one (disableIPv4,
	// OneCGNATRoute, randomizeClientPort), cut at 1 KiB.
	Scalar string
}

SectionSummary is one section of a policy at a glance.

type Shape

type Shape uint8

Shape says how a section is read and compared.

const (
	// ShapeRules is an array of access rules, reduced to atoms: acls and grants, which
	// the admin console rewrites into each other, share one pool of atoms.
	ShapeRules Shape = iota + 1
	// ShapeSet is an array of objects compared as a multiset of canonical elements:
	// ssh and extraDNSRecords.
	ShapeSet
	// ShapeTests is an array of test objects reduced to assertions: tests and sshTests.
	ShapeTests
	// ShapeNodeAttrs is nodeAttrs, whose entries are additive.
	ShapeNodeAttrs
	// ShapeMap is an object of named entries compared key by key.
	ShapeMap
	// ShapeStruct is an object of fields compared field by field, and key by key inside
	// a field that is a map: autoApprovers and derpMap.
	ShapeStruct
	// ShapeScalar is a single value.
	ShapeScalar
	// ShapeList is an array read as one set of strings: defaultSrcPosture.
	ShapeList
)

type Show

type Show func(s string, need int) string

Show is how a caller sees a piece of a policy's text: masked, with the markers it must not forge rewritten. It returns the first need bytes of what the caller sees of s, or all of it when that is shorter; it may return more. A result cuts what it quotes only after Show has run, because masking changes the length of a text and a cut made before it would fall where the real text says (a login a caller never saw, and how long it is) and not where the shown text does.

type State

type State uint8

State is whether a section is in the text and whether it holds anything. A section spelled null is absent: Go decodes null and a missing key to the same nil.

const (
	StateAbsent   State = iota // not in the text, or null
	StateEmpty                 // [] or {}
	StateNonEmpty              // holds elements, entries or a value
)

func (State) String

func (s State) String() string

type Summary

type Summary struct {
	// Sections are the known sections in table order, then the unknown ones by name (at most
	// MaxNames of them, the first by name; a section whose value is null is absent and has no
	// row).
	Sections []SectionSummary
	// Defined names, sorted and exact (each cut at 1 KiB, at most MaxNames of each; when a
	// list is cut it holds the first MaxNames in sorted order).
	Groups, TagOwners, Hosts, Postures, IPSets []string
	// SyncedGroups are the group names the policy refers to that it does not define in
	// groups: groups synced from an identity provider are written group:name@domain, and
	// can hold spaces and capitals. When the list is cut it holds the first MaxNames found
	// in the text, sorted. Names are compared as cut at 1 KiB.
	SyncedGroups []string
	// NamesTruncated: a list above was cut at MaxNames (the unknown sections too), or a
	// reference too long to read (over 4 KiB) was passed over.
	NamesTruncated bool
	// Tests and SSHTests are the numbers of elements of those sections.
	Tests, SSHTests int
	// Unknown are the sections the table does not know, in the order they were first written:
	// the unknown sections that have a row above, so at most MaxNames and none whose value is
	// null (Doc.UnknownSections lists every name, null or not).
	Unknown []string
}

Summary is what a caller needs to know about a policy without reading it: the state and size of every section, the names it defines, the synced groups it refers to, the number of tests and what the table does not know.

func Summarise

func Summarise(ctx context.Context, d *Doc) (*Summary, error)

Summarise reads the shape of a policy. Its work is metered with the normal form's: one budget covers the reduction, the rows, the names and the walk for synced groups.

type Text

type Text struct {
	Text      string
	Len       int
	Truncated bool
}

Text is a piece of a policy shown to a reader, cut at 1 KiB; Len is its full length.

Jump to

Keyboard shortcuts

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