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

probe

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

Documentation

Overview

Package probe is the harness behind `make probe-acl`: the read-only live probes of the probe table (docs/design.md §22), each with its request and its expectation written down before any run, and a printer that shows what the real API said without showing a value from the tailnet.

Everything a probe sends is a synthetic document (tag:p, example.com, documentation addresses) or a document built in memory from the live policy; everything it records goes through internal/skeleton, which keeps a status, key paths with types, error texts reduced to templates, and booleans and counts the probe computed. Nothing here sends anything but GET and POST .../acl/validate: the client's guard sees to that.

Index

Constants

View Source
const DefaultBudget = 80

DefaultBudget is the most requests one run sends when Options.Budget is not set. A full run sends 61 at most (A-10's fallback and three windows of the audit log); the ceiling is there to stop a probe that loops, not to be reached.

View Source
const DefaultGap = 2 * time.Second

DefaultGap is the pause before every request after the first: about 50 validate calls and ten reads, spaced 2 s apart, stay well under the API's rate limit.

Variables

View Source
var (
	// ErrRateLimited means the API answered 429: no more requests are sent.
	ErrRateLimited = errors.New("the API answered 429, so the run stopped")
	// ErrBudget means the run reached its request ceiling.
	ErrBudget = errors.New("the request budget is spent, so the run stopped")
)

Errors that stop the whole run.

Functions

func Command

func Command(ctx context.Context, c *api.Client, args []string, stdout, stderr io.Writer) int

Command is `tailkeeper probe-acl`, built into the binary only with -tags probe (cmd/tailkeeper/probe_on.go). The caller has checked TAILKEEPER_LIVE=1 and resolved the read credential; c is the client built from it, whose guard allows GET and the validate POST only. It prints the report to stdout and, with --out DIR, writes one skeleton file per probe into DIR. The exit code is 0 when every probe ran, 1 when a probe ended in an error, was not run, was refused (every request a 401 or 403), or a file could not be written, and 2 for a usage error. A check that differs from the probe table (docs/design.md §22) is a finding, not a failure.

func Compare

func Compare(committed, now Observation) []string

Compare reports how two observations differ in status and in the set of (path, type) pairs. Texts are templates and are not compared, and nothing returned is a text from an answer: a status difference is the word "status", and a path difference is the path.

func Render

func Render(w io.Writer, rs []Result)

Render prints the results for a person: for each probe what the probe table (docs/design.md §22) expected, then for each request what it expected of it beside what was observed, then the checks with MATCH or DIFFERS. Every dynamic piece is a skeleton field, a count, a boolean or a fixed sentence from probes.go; no value from the tailnet reaches it.

func SelfCheckCount

func SelfCheckCount() int

SelfCheckCount is how many checks a full self-check makes: the policy checks and one shape check for each shape probe. A run that prints fewer dropped some.

func Tally

func Tally(rs []Result) (match, differ, errored, notRun int)

Tally counts the checks that match and differ, the probes that ended in an error, and the probes that did not run.

func WriteFiles

func WriteFiles(dir string, rs []Result, day string) ([]string, error)

WriteFiles writes one <probe>.json per result into dir, creating it, and returns the names it wrote. day is the run's date (YYYY-MM-DD). A probe that did not run, that stopped on an error (a 429, the budget, a cancellation, a transport error), or that the API refused (every observation a 401 or 403: a revoked key or a missing scope) is not written: its file from an earlier complete run stays as it was. A result whose ID is not of the form A-n or H-n is refused before anything is written.

Types

type API

type API interface {
	TailnetPath(suffix string) string
	ValidatePolicy(ctx context.Context, body []byte, contentType string) (api.RawResponse, error)
	GetPolicy(ctx context.Context, g api.PolicyGet) (api.RawResponse, error)
	GetOnce(ctx context.Context, path string, q url.Values) (api.RawResponse, error)
}

API is what the probes call; *api.Client satisfies it.

type Check

type Check struct {
	Name string `json:"name"`
	Want bool   `json:"want"`
	Got  bool   `json:"got"`
}

Check is an expectation the harness can test: the answer to a yes-or-no question the table's "Expected" column implies, as the table expects it (Want) and as the API behaved (Got).

func (Check) Matches

func (c Check) Matches() bool

Matches reports whether the API behaved as the probe table expected.

type Entry

type Entry struct {
	User     string   `json:"user"`
	Errors   []string `json:"errors"`
	Warnings []string `json:"warnings"`
}

Entry is one element of the data array of an answer.

type Exec

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

Exec is what a probe runs against. It paces and counts requests, records every one, and holds the live policy in memory for the probes that need data from it.

func (*Exec) Check

func (x *Exec) Check(name string, want, got bool)

Check records an expectation and how the API behaved against it.

func (*Exec) Count

func (x *Exec) Count(name string, n int)

Count records a number the probe computed.

func (*Exec) Fact

func (x *Exec) Fact(name string, v bool)

Fact records a yes-or-no the probe table makes no promise about.

func (*Exec) GetJSON

func (x *Exec) GetJSON(ctx context.Context, s Step, suffix string, q url.Values) (Seen, error)

GetJSON performs a GET of a path below the tailnet and records the answer, whatever its status. It makes one attempt: the client's retrying reader is not used, so a 429 is seen and stops the run, and every request is paced and counted.

func (*Exec) GetPolicy

func (x *Exec) GetPolicy(ctx context.Context, s Step, g api.PolicyGet) (Seen, error)

GetPolicy performs GET .../acl as g says and records the answer.

func (*Exec) Hex

func (x *Exec) Hex(n int) string

Hex returns n random lower-case hex digits.

func (*Exec) Live

func (x *Exec) Live(ctx context.Context) (*Live, error)

Live reads the live policy with GET .../acl?details=true once and keeps it for the rest of the run. The request is recorded in the probe that first asks. It sends no Accept header: the OpenAPI document says not to send one with details.

func (*Exec) Post

func (x *Exec) Post(ctx context.Context, s Step, contentType string, body []byte) (Seen, error)

Post sends body to POST .../acl/validate as contentType and records the answer. The request line shows the body's size: use it for a synthetic body only.

func (*Exec) PostLive

func (x *Exec) PostLive(ctx context.Context, s Step, contentType string, body []byte) (Seen, error)

PostLive is Post for a body built from the live policy: the request line shows no size, because the size of a body that holds the policy's names says something about the policy.

func (*Exec) PostPolicy

func (x *Exec) PostPolicy(ctx context.Context, s Step, doc string) (Seen, error)

PostPolicy is Post in policy mode: application/hujson.

func (*Exec) Skip

func (x *Exec) Skip(reason string)

Skip records why part of a probe did not run. The reason is a fixed sentence.

type LineKind

type LineKind int

LineKind says what a line of the self-check's output is.

const (
	LineUnknown LineKind = iota // not a line the self-check prints
	LineCheck                   // one check: a policy check or a shape
	LineClosing                 // the closing count
)

The kinds of line the self-check prints. Nothing but these is printed to stdout.

func ClassifyLine

func ClassifyLine(line string) (kind LineKind, checks, notHeld int)

ClassifyLine reports what a line printed by the self-check is. For the closing line it also returns how many checks the line counts and how many of them did not hold. Every line of a kind other than LineUnknown holds a fixed word, a probe ID or a count, so it is fit to log whatever the checks found.

type Live

type Live struct {
	Text     []byte   // the policy as the server stores it (HuJSON)
	ETag     string   // the ETag header of the details answer
	Warnings []string // the server's warnings for the stored policy
	Errors   []string // the server's errors for the stored policy
	Tags     []string // the tags tagOwners defines, sorted
	Hosts    []string // the host aliases, sorted
	Groups   []string // the group names, sorted
	Logins   []string // the members of groups that are written as an address, sorted
	Services []string // the svc: names the text mentions, sorted
	SSH      []SSHRule
	// StoredTests is how many entries the policy's tests section holds (0 when it has none).
	// A count only: A-12 needs to know whether a stored test could fail, never what it says.
	StoredTests int
	Parsed      bool // the sections above were read; false when the text would not parse as the probe expects
}

Live is the tailnet's own policy, held in memory for the probes that need a name from it (A-7, A-8, A-9, A-10, A-12, A-13). Nothing in it is ever printed or written: the probes print booleans and counts computed from it.

func (Live) GoString

func (Live) GoString() string

func (Live) String

func (Live) String() string

String and GoString keep a stray %v or %+v from printing the policy's names.

type Observation

type Observation struct {
	Label   string             `json:"label"`
	Expect  string             `json:"expect,omitempty"`
	Request string             `json:"request"`
	Result  *skeleton.Skeleton `json:"result,omitempty"`
	ETag    string             `json:"etag,omitempty"` // the form of the ETag header, never the value
	Error   string             `json:"error,omitempty"`
}

Observation is one request and the skeleton of its answer.

type Options

type Options struct {
	Gap    time.Duration                              // pause before every request after the first; the CLI uses 2s
	Sleep  func(context.Context, time.Duration) error // default: a timer that honours ctx
	Budget int                                        // most requests in one run; default DefaultBudget
	Only   []string                                   // probe IDs to run; empty runs all
	Hex    func(n int) string                         // n random lower-case hex digits; default crypto/rand
	// OnStart, when set, is called with a probe's ID as the probe starts, before its first
	// request. A probe that is not run because the run stopped is not announced.
	OnStart func(id string)
	// Against is the directory of committed captures a self-check compares with.
	// Empty means the run is not a self-check.
	Against string
}

Options tune a run. The zero value is a production run except for Gap.

type Probe

type Probe struct {
	ID       string
	Title    string
	Sends    string
	Expected string
	Branch   string
	Run      func(ctx context.Context, x *Exec) error
}

Probe is one row of the probe table. Expected and Branch are copied from the table before any run and printed beside what was observed.

func All

func All() []Probe

All returns the probes of the probe table in order: A-1 to A-14, then H-1. Every synthetic document uses tag:p, example.com and documentation addresses (192.0.2.0/24, 2001:db8::/32), plus the one 100.64.0.1 that A-1 names; documents built from the live policy exist only in memory.

type Result

type Result struct {
	ID           string          `json:"probe"`
	Title        string          `json:"title"`
	Sends        string          `json:"sends"`
	Expected     string          `json:"expected"`
	Branch       string          `json:"branch"`
	Observations []Observation   `json:"observations"`
	Checks       []Check         `json:"checks,omitempty"`
	Facts        map[string]bool `json:"facts,omitempty"`
	Counts       map[string]int  `json:"counts,omitempty"`
	Skipped      []string        `json:"skipped,omitempty"`
	Error        string          `json:"error,omitempty"`
	NotRun       bool            `json:"notRun,omitempty"`
}

Result is what one probe recorded.

func Run

func Run(ctx context.Context, a API, probes []Probe, o Options) []Result

type SSHRule

type SSHRule struct {
	Src, Dst, Users []string
}

SSHRule is the part of an ssh rule an sshTests entry can be built from.

type Seen

type Seen struct {
	Status int
	ETag   string
	Body   []byte
	// contains filtered or unexported fields
}

Seen is one answer, in memory: the status, the ETag and the body as sent. It is for the probe to compute booleans and counts from; none of it is printed.

func (Seen) Accepted

func (s Seen) Accepted() bool

Accepted reports a policy the server took: an HTTP 200 with a JSON or empty body, and not a bare message.

func (Seen) Bare

func (s Seen) Bare() bool

Bare reports a bare message: a JSON object with a message and no data array, which is how the API words a rejection of the policy itself.

func (Seen) BodyHas

func (s Seen) BodyHas(sub string) bool

BodyHas reports whether the answer's body, as received, contains sub. Unlike TextHas it reads the words the printer would template away (only a pinned list of phrases survives it), and like every Seen method it yields a boolean for the probe to record.

func (Seen) Entries

func (s Seen) Entries() []Entry

Entries decodes the answer's data array into its entries; nil when it has none.

func (Seen) ErrorLines

func (s Seen) ErrorLines() int

ErrorLines counts the error lines of the answer, in the grammar or not.

func (Seen) GoString

func (Seen) GoString() string

func (Seen) Grammar

func (s Seen) Grammar() int

Grammar counts the error lines of the answer that are in the verdict grammar (one per failed assertion), whatever their repetition.

func (Seen) HasPath

func (s Seen) HasPath(path string) bool

HasPath reports whether the answer, as JSON, holds a value at path (see skeleton.Path).

func (Seen) Passes

func (s Seen) Passes() bool

Passes reports an answer that found no fault: an HTTP 200 with a JSON or empty body (an HTML page from a proxy is not a pass), not a bare message, no error line.

func (Seen) String

func (Seen) String() string

String and GoString keep a stray %v or %+v from printing a body: it can hold names from the tailnet.

func (Seen) TextHas

func (s Seen) TextHas(sub string) bool

TextHas reports whether a template of the answer contains sub.

func (Seen) WarningLines

func (s Seen) WarningLines() []string

WarningLines returns the warning strings of the answer's data entries.

type SelfCheck

type SelfCheck struct {
	Name    string
	Held    bool
	Count   int
	Note    string
	Skipped []string
}

SelfCheck is one consistency or shape result. Name is a fixed word or a probe ID. Held is whether the check passed. Count is a number of changes, sections or differing paths, never a length of a policy text. Note is a fixed word for a check that could not be made (noteNotRead, noteNoCapture, noteNotRun, noteProbeFailed); Held is false then and Count is 0. Skipped names what the check left out because it depends on what the tailnet holds: fixed words only, plus a count of unknown sections, never a name from the tailnet.

func RunSelfCheck

func RunSelfCheck(ctx context.Context, x *Exec) []SelfCheck

RunSelfCheck reads the live policy through x and returns the policy checks, then the shape checks when x was built with Options.Against set. Each result is a boolean and a count. Nothing in it is a name or a text from the tailnet.

type Step

type Step struct {
	Label  string
	Expect string
}

Step names one request of a probe: what it is, and what the probe table expects of it.

Jump to

Keyboard shortcuts

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