Documentation
¶
Overview ¶
Package api is tailkeeper's Tailscale API client for reads (spec §4, §20).
Responses are decoded into tailkeeper's own allowlisted structs: any field a struct does not declare is dropped, so a credential the API returns in a field nobody listed can never reach an output. Tailscale's Go client library is used only for workload identity federation.
Index ¶
- Constants
- func ETagOf(body []byte) string
- func Path(format string, segments ...string) string
- type Client
- func (c *Client) BeginValidate(ctx context.Context, posts int) (*ValidateCall, error)
- func (c *Client) BeginValidateCharged(ctx context.Context, posts, tokens int) (*ValidateCall, error)
- func (c *Client) Get(ctx context.Context, path string, q url.Values, out any) error
- func (c *Client) GetOnce(ctx context.Context, path string, q url.Values) (RawResponse, error)
- func (c *Client) GetPolicy(ctx context.Context, g PolicyGet) (RawResponse, error)
- func (c *Client) GetPolicyDetails(ctx context.Context) (PolicyDetails, error)
- func (c *Client) GetRaw(ctx context.Context, path, accept string) (Raw, error)
- func (c *Client) GrantedScopes() (scopes []string, known bool)
- func (c *Client) LiveStamp(ctx context.Context) (PolicyStamp, error)
- func (c *Client) Tailnet() string
- func (c *Client) TailnetPath(suffix string) string
- func (c *Client) ValidatePolicy(ctx context.Context, body []byte, contentType string) (RawResponse, error)
- func (c *Client) ValidateTests(ctx context.Context, tests []PolicyTest) (Validation, error)
- func (c *Client) WithMaxBody(n int64) *Client
- type Options
- type PolicyDetails
- type PolicyGet
- type PolicyStamp
- type PolicyStatus
- type PolicyTest
- type PolicyValidation
- type Raw
- type RawResponse
- type TestResult
- type ValidateCall
- type Validation
Constants ¶
const DefaultBaseURL = "https://api.tailscale.com"
DefaultBaseURL is the only API host tailkeeper talks to by default.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client performs authenticated reads against one tailnet.
func New ¶
New builds a Client for tailnet using cred. It makes no network call. ctx must outlive the Client: token requests made on its behalf stop when ctx ends, which is how a signal reaches a hung token exchange.
func (*Client) BeginValidate ¶ added in v0.6.0
BeginValidate starts a call that will send posts policies (1, or 2 for a call with invariants). It waits for a free slot (or for ctx), then takes posts tokens at once or none: when fewer are left it frees the slot and returns validate_rate_limited, whose retryAfterSeconds is the whole seconds, rounded up, until posts tokens exist. A call that ends having sent fewer gets the rest back.
func (*Client) BeginValidateCharged ¶ added in v0.6.0
func (c *Client) BeginValidateCharged(ctx context.Context, posts, tokens int) (*ValidateCall, error)
BeginValidateCharged is BeginValidate for a call that sends posts policies and is charged tokens in all, tokens being at least posts. The tokens beyond the posts pay for what the call asks of Tailscale besides sending: they are taken with the rest, at once or not at all, and End does not give them back. A call that asks for more tokens than the bucket holds can never be granted: it is refused at once, with an error that says so and not one that tells its caller to wait.
func (*Client) Get ¶
Get performs a GET of path (already escaped, see TailnetPath and Path) and decodes the JSON body into out. Reads are retried on 429, 500, 502, 503, 504 and transport errors (client timeouts included) with bounded backoff; Retry-After is honoured when present (up to 30s). The caller's cancellation is never retried.
func (*Client) GetOnce ¶ added in v0.6.0
GetOnce performs a GET of path (already escaped, see TailnetPath) with the query q and returns the answer, whatever its status: one attempt, no retry on a 429 or a 5xx, no Accept header. Get and GetRaw retry; the probes must not, because a second attempt is a request they did not plan.
func (*Client) GetPolicy ¶ added in v0.6.0
GetPolicy performs GET .../acl as g says and returns the answer: the status, the ETag and the body unparsed. One attempt, like ValidatePolicy.
func (*Client) GetPolicyDetails ¶ added in v0.6.0
func (c *Client) GetPolicyDetails(ctx context.Context) (PolicyDetails, error)
GetPolicyDetails reads the live policy with its warnings and errors in one request: GET .../acl?details=true, retried like any read. The answer is read strictly: the acl field is required and standard base64, warnings and errors are null or lists of strings, and any other shape is an error, never a zero-value success. A decoded policy over guard.MaxPolicyBytes is refused with its size. Text is not parsed here: what it holds, an array say, is for acl.Parse to refuse.
func (*Client) GetRaw ¶
GetRaw performs a GET of path with the given Accept type and returns the body unparsed, for formats that are not JSON (the policy file's HuJSON). It is retried and bounded like Get.
func (*Client) GrantedScopes ¶
GrantedScopes returns the scopes the token endpoint reported, once a token has been fetched. known is false for API keys and federated identities.
func (*Client) LiveStamp ¶ added in v0.6.0
func (c *Client) LiveStamp(ctx context.Context) (PolicyStamp, error)
LiveStamp reads the live policy once more, as the plain text, and returns its ETag and the SHA-256 of its bytes. It is the re-check after a validation: the API never answered 304 to If-None-Match (probe A-8), so nothing sends that header, and the whole body is read again. A policy over guard.MaxPolicyBytes is refused with its size.
func (*Client) TailnetPath ¶
TailnetPath returns /api/v2/tailnet/<tailnet><suffix>.
func (*Client) ValidatePolicy ¶ added in v0.6.0
func (c *Client) ValidatePolicy(ctx context.Context, body []byte, contentType string) (RawResponse, error)
ValidatePolicy sends body to POST .../acl/validate with the given Content-Type and returns the answer. The endpoint chooses its mode from the body: a JSON array is a set of tests run against the live policy (send application/json), a JSON object is a policy to validate (send application/hujson, the policy's exact bytes). The call changes nothing on the tailnet. It makes one attempt: no retry on a 429 or a 5xx, and no check that the body suits the content type (the probes send mismatches on purpose). A transport failure, and a body over the client's limit, are errors.
func (*Client) ValidateTests ¶
func (c *Client) ValidateTests(ctx context.Context, tests []PolicyTest) (Validation, error)
ValidateTests runs tests against the live policy without changing it: POST .../acl/validate with a JSON array of tests (tests mode), the one request other than GET this client sends. The endpoint answers HTTP 200 for pass, fail and warnings alike, so a pass is read only from a recognised shape: a blank body, {}, or data holding at least one entry. An empty list, a null list and an empty message are errors, never a pass. It is retried like a read: the endpoint changes nothing. The call is metered like every send of the validate POST: it takes one token of the bucket and a slot among the calls in flight, and ends at the call's deadline (bucket.go).
func (*Client) WithMaxBody ¶
WithMaxBody returns a copy of c that refuses response bodies over n bytes, or over c's own limit if that is lower. The copy shares c's credential, token cache and observed scopes.
type Options ¶
type Options struct {
BaseURL string // default DefaultBaseURL
Transport http.RoundTripper // default http.DefaultTransport
Timeout time.Duration // per request; default 60s
Retries int // read retries; default 3; negative disables
MaxBody int64 // default 64 MiB
Sleep func(context.Context, time.Duration) error // default: timer honouring ctx
// The limits on validation requests (bucket.go). Zero values are the production defaults.
ValidatePerMinute int // tokens a minute, one for each validation request tailkeeper decides to send (a retry of a request takes none); the bucket holds max(this, 2); default config.DefaultValidatePerMinute (30)
Now func() time.Time // the bucket's clock; default time.Now
ValidateDeadline time.Duration // one validate call, all its sends; default 60s
ValidateInFlight int // validate calls at once; default 2
}
Options tune a Client. Zero values are production defaults.
type PolicyDetails ¶ added in v0.6.0
type PolicyDetails struct {
Text []byte // the policy as HuJSON, decoded from the base64 acl field; at most guard.MaxPolicyBytes
ETag string // the header verbatim, quotes included; "" when none
Warnings []string // the server's own warnings for the stored policy: the baseline
Errors []string // the stored policy's errors: it no longer parses or resolves
}
PolicyDetails is the live policy as GET .../acl?details=true answers it.
type PolicyGet ¶ added in v0.6.0
type PolicyGet struct {
Details bool // adds ?details=true: a JSON object with the policy, its warnings and errors
Accept string // "" sends no Accept header
IfNoneMatch string // "" sends no If-None-Match header; otherwise the value, verbatim
}
PolicyGet selects how GetPolicy asks for the live policy.
type PolicyStamp ¶ added in v0.6.0
type PolicyStamp struct {
ETag string // the header verbatim, quotes included; "" when none
SHA256 string // lower-case hex of the plain body's bytes
}
PolicyStamp identifies the live policy without carrying it.
type PolicyStatus ¶ added in v0.6.0
type PolicyStatus string
PolicyStatus is what an answer of the validate endpoint to a policy (policy mode) means. The reader trusts nothing it does not recognise: a pass is read only from a shape the documentation or a probe of the live API showed, and everything else is PolicyUnknown, which a caller must treat as "not validated".
const ( PolicyPass PolicyStatus = "pass" // HTTP 200 and a recognised pass; warnings may ride along PolicyFail PolicyStatus = "fail" // HTTP 200, at least one entry with errors PolicyRejected PolicyStatus = "rejected" // a bare {message}: the server refused the document PolicyUnknown PolicyStatus = "unknown" // anything else, including every Assumed shape )
The four things a policy-mode answer can mean.
type PolicyTest ¶
type PolicyTest struct {
Src string `json:"src"`
Proto string `json:"proto,omitempty"`
SrcPostureAttrs map[string]any `json:"srcPostureAttrs,omitempty"` // string, number or bool values, passed through as the caller gave them
Accept []string `json:"accept,omitempty"`
Deny []string `json:"deny,omitempty"`
}
PolicyTest is one test of the live policy, in the shape Tailscale's validate endpoint takes in tests mode.
type PolicyValidation ¶ added in v0.6.0
type PolicyValidation struct {
Status PolicyStatus
HTTP int
Message string // the server's message: untrusted text
Results []TestResult // entries with their errors and warnings, in order; set for pass and fail only
Why string // for PolicyUnknown: tailkeeper's words, quoting nothing but capped untrusted tokens
Hint string // set by the client's send, never by the reader: the size hint of a rejection that speaks of size
}
PolicyValidation is a policy-mode answer as the strict reader understood it.
func ReadPolicyValidation ¶ added in v0.6.0
func ReadPolicyValidation(status int, body []byte) PolicyValidation
ReadPolicyValidation reads the answer of the validate endpoint to a policy:
- 200 with an empty body or {} is a pass;
- 200 {message, data[]}: any entry with errors is a fail; entries with warnings only are a pass whose Results carry them, if the message reports warnings or is absent; an entry with neither is unknown;
- 200 or 400 with exactly the key message is a rejection (the live API rejects a well-formed document with a 200 and a bare message, and a body that is no policy at all with a 400), unless the message says "test(s) failed" or "warning(s) found": it then promises entries that are not there, which is unknown;
- anything else is unknown: a 400 that carries data, any other status, a field the documentation does not name, a field given twice, a non-object, trailing bytes, a comment, a byte order mark.
body is at most what the client read (the policy call reads 1 MiB); the message is not cut here.
func RemoveCanary ¶ added in v0.6.0
func RemoveCanary(v PolicyValidation, src string, grammar func(line string) bool) (PolicyValidation, bool)
RemoveCanary drops the one result whose User is src, which must carry exactly one error and no warning, and sets the status from what is left: a fail while an entry has errors, else a pass, which carries no message. It reports false, and returns v unchanged, when v is not a fail, when no result or more than one has that User (compared exactly), or when the one has anything but a single error. grammar, when not nil, is the caller's check of that one error: a canary that comes back in another shape is not a pass. The check lives in grammar, not in a second copy here.
type Raw ¶
Raw is a response body as the API sent it, and its ETag (verbatim, quotes included; "" when the API sent none).
type RawResponse ¶ added in v0.6.0
RawResponse is one answer of the API, whatever its status: the HTTP status, the ETag header verbatim (quotes included; "" when the API sent none) and the body unparsed. The low-level calls below return a non-2xx answer as data, not as an error, because the probes exist to see what the API says to a request that fails. A later, strict reader decides what each status means.
type TestResult ¶
type TestResult struct {
User string `json:"user"`
Errors []string `json:"errors"`
Warnings []string `json:"warnings"`
}
TestResult is the validate endpoint's answer for one source.
type ValidateCall ¶ added in v0.6.0
type ValidateCall struct {
// contains filtered or unexported fields
}
ValidateCall is one tool call's use of the validate endpoint: the tokens it reserved, its deadline and its slot among the calls in flight. One goroutine uses it. It must be ended.
func (*ValidateCall) Context ¶ added in v0.6.0
func (v *ValidateCall) Context() context.Context
Context is the context of the call: the caller's, with the call's deadline.
func (*ValidateCall) End ¶ added in v0.6.0
func (v *ValidateCall) End()
End frees the call's slot, stops its deadline and gives back the tokens it did not use. It may be called more than once.
func (*ValidateCall) Send ¶ added in v0.6.0
func (v *ValidateCall) Send(body []byte) (PolicyValidation, error)
Send validates a policy in policy mode. Each Send uses one of the tokens the call reserved; the retries inside it use none. A send past the call's deadline fails with upstream_unavailable.
type Validation ¶
type Validation struct {
Passed bool // no test failed
Results []TestResult // sources with errors or warnings
}
Validation is how the live policy answered a set of tests.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package apitest is a fake Tailscale API for tests.
|
Package apitest is a fake Tailscale API for tests. |
|
Package openapi pins the operations of Tailscale's OpenAPI document (spec §20).
|
Package openapi pins the operations of Tailscale's OpenAPI document (spec §20). |