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
- Variables
- func Command(ctx context.Context, c *api.Client, args []string, stdout, stderr io.Writer) int
- func Compare(committed, now Observation) []string
- func Render(w io.Writer, rs []Result)
- func SelfCheckCount() int
- func Tally(rs []Result) (match, differ, errored, notRun int)
- func WriteFiles(dir string, rs []Result, day string) ([]string, error)
- type API
- type Check
- type Entry
- type Exec
- func (x *Exec) Check(name string, want, got bool)
- func (x *Exec) Count(name string, n int)
- func (x *Exec) Fact(name string, v bool)
- func (x *Exec) GetJSON(ctx context.Context, s Step, suffix string, q url.Values) (Seen, error)
- func (x *Exec) GetPolicy(ctx context.Context, s Step, g api.PolicyGet) (Seen, error)
- func (x *Exec) Hex(n int) string
- func (x *Exec) Live(ctx context.Context) (*Live, error)
- func (x *Exec) Post(ctx context.Context, s Step, contentType string, body []byte) (Seen, error)
- func (x *Exec) PostLive(ctx context.Context, s Step, contentType string, body []byte) (Seen, error)
- func (x *Exec) PostPolicy(ctx context.Context, s Step, doc string) (Seen, error)
- func (x *Exec) Skip(reason string)
- type LineKind
- type Live
- type Observation
- type Options
- type Probe
- type Result
- type SSHRule
- type Seen
- func (s Seen) Accepted() bool
- func (s Seen) Bare() bool
- func (s Seen) BodyHas(sub string) bool
- func (s Seen) Entries() []Entry
- func (s Seen) ErrorLines() int
- func (Seen) GoString() string
- func (s Seen) Grammar() int
- func (s Seen) HasPath(path string) bool
- func (s Seen) Passes() bool
- func (Seen) String() string
- func (s Seen) TextHas(sub string) bool
- func (s Seen) WarningLines() []string
- type SelfCheck
- type Step
Constants ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
Tally counts the checks that match and differ, the probes that ended in an error, and the probes that did not run.
func WriteFiles ¶
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 ¶
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).
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) GetJSON ¶
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) Live ¶
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 ¶
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 ¶
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 ¶
PostPolicy is Post in policy mode: application/hujson.
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 ¶
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.
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.
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 ¶
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 ¶
Accepted reports a policy the server took: an HTTP 200 with a JSON or empty body, and not a bare message.
func (Seen) Bare ¶
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 ¶
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 ¶
Entries decodes the answer's data array into its entries; nil when it has none.
func (Seen) ErrorLines ¶
ErrorLines counts the error lines of the answer, in the grammar or not.
func (Seen) Grammar ¶
Grammar counts the error lines of the answer that are in the verdict grammar (one per failed assertion), whatever their repetition.
func (Seen) HasPath ¶
HasPath reports whether the answer, as JSON, holds a value at path (see skeleton.Path).
func (Seen) Passes ¶
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 ¶
String and GoString keep a stray %v or %+v from printing a body: it can hold names from the tailnet.
func (Seen) WarningLines ¶
WarningLines returns the warning strings of the answer's data entries.
type SelfCheck ¶
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 ¶
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.