Documentation
¶
Overview ¶
Package client is how a mesh component reads from its control plane. Node and router share it, so the body cap, the status handling and the signature check on /keys are a single code path. It depends on api/ and build metadata: importing it pulls in none of the control plane server.
Index ¶
- Constants
- Variables
- func DoWithChallenge(httpClient *http.Client, now func() time.Time, ...) (*http.Response, error)
- func DoWithChallengeRetry(ctx context.Context, httpClient *http.Client, now func() time.Time, b Backoff, ...) (*http.Response, error)
- func NewHTTPClient(timeout time.Duration, allowInsecure func() bool, component string) *http.Client
- func ReadBody(r io.Reader) ([]byte, error)
- func SetChallengeHeaders(req *http.Request, priv crypto.PrivKey, payload []byte, ts int64) error
- type Backoff
- type Client
- func (c *Client) ExchangeToken(ctx context.Context, biscuit []byte, req *api.TokenExchangeRequest) (*api.TokenExchangeResponse, error)
- func (c *Client) FetchEgress(ctx context.Context, biscuit []byte) (*api.EgressAssignmentsResponse, error)
- func (c *Client) FetchInfo(ctx context.Context) (*api.ControlPlaneInfoResponse, error)
- func (c *Client) FetchKeys(ctx context.Context, trusted []ed25519.PublicKey) ([]ed25519.PublicKey, error)
- func (c *Client) FetchPolicy(ctx context.Context, biscuit []byte) (*api.PolicyConfigGetResponse, error)
- func (c *Client) FetchRevocations(ctx context.Context, biscuit []byte) (*api.RevocationsResponse, error)
- func (c *Client) MintSTSToken(ctx context.Context, biscuit []byte, req *api.STSTokenRequest) (*api.STSTokenResponse, error)
- func (c *Client) ReportCatalog(ctx context.Context, biscuit []byte, services []*api.ServiceInfo) error
- func (c *Client) WithClock(now func() time.Time) *Client
- func (c *Client) WithIdentity(peerID string, priv crypto.PrivKey) *Client
- type FileTokenSource
- type StatusError
- type TokenSource
Constants ¶
const MaxBodyBytes = 8 << 20
MaxBodyBytes caps every response body read from a control plane: a misbehaving or impersonated server must not be able to make a client buffer arbitrary amounts of memory. It is sized for the largest legitimate answer, the ban set in /info at roughly 55 bytes per peer ID, so about 150k banned peers fit; the policy is bounded by the control plane's own 1 MiB cap on POST /policies, and /keys is a few hundred bytes.
Variables ¶
var DefaultEnrollBackoff = Backoff{Initial: time.Second, Max: 15 * time.Second, Budget: 3 * time.Minute}
DefaultEnrollBackoff keeps trying for three minutes, which admits a fleet of about 1500 members that start at once through the control plane's limiter of ten a second. A larger fleet starts in waves.
var ErrBodyTooLarge = errors.New("control plane answer exceeds the body cap")
ErrBodyTooLarge marks an answer over MaxBodyBytes. It is an error, never a prefix: a protobuf message cut at a field boundary still decodes, so a truncated ban set or router list would be read as a smaller, valid one.
var ErrNotFound = errors.New("control plane does not serve this endpoint")
ErrNotFound marks a 404: the control plane does not serve the endpoint, as one predating it does not. Callers of an endpoint added after the first release check for it, so a newer node works against an older control plane.
Functions ¶
func DoWithChallenge ¶
func DoWithChallenge(httpClient *http.Client, now func() time.Time, build func(ts int64) (*http.Request, error)) (*http.Response, error)
DoWithChallenge executes a request built with the local clock's millisecond timestamp. If the control plane answers 401 with api.ErrStaleChallengeTimestampMessage and a valid HTTP Date header, it recomputes the timestamp from Date and retries once. No other 401 is retried.
func DoWithChallengeRetry ¶
func DoWithChallengeRetry(ctx context.Context, httpClient *http.Client, now func() time.Time, b Backoff, build func(ts int64) (*http.Request, error)) (*http.Response, error)
DoWithChallengeRetry is DoWithChallenge for a request the control plane may answer with 429 or 503. Such an answer is retried after Retry-After when the control plane names a wait, and after the backoff's own delay when it does not, with jitter so a fleet told "not now" together does not come back together. Any other answer, and the last one within the budget, is returned to the caller.
func NewHTTPClient ¶
NewHTTPClient is the HTTP client for every request a mesh component makes to its control plane. A nil allowInsecure never allows plaintext.
Types ¶
type Backoff ¶
type Backoff struct {
// Initial is the first wait when the answer carries no Retry-After;
// each further wait doubles, up to Max.
Initial, Max time.Duration
// Budget is how long in all the caller keeps trying. The last answer
// before the budget runs out is returned to the caller as it came.
// Time, not a count of attempts: a control plane that names a short
// Retry-After is asking for many short waits, and a fleet of N members
// behind a limiter of R a second needs N/R seconds whatever it answers.
Budget time.Duration
}
Backoff is how a caller waits for a control plane that is busy. A 429 or 503 is the control plane saying "not now": its enrollment limiter admits a fixed number of members per second for the whole mesh, so a fleet that joins at once is told to come back, and a member that gave up at the first such answer never joined.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client reads the pull side of the mesh protocol from one control plane.
func New ¶
New normalizes baseURL, https:// when no scheme is given and no trailing slash, and speaks through httpClient, which the caller builds with NewHTTPClient so its own transport policy applies.
func (*Client) ExchangeToken ¶
func (c *Client) ExchangeToken(ctx context.Context, biscuit []byte, req *api.TokenExchangeRequest) (*api.TokenExchangeResponse, error)
ExchangeToken is POST /token/exchange, authenticated with the calling node's biscuit: verifies an external OIDC/K8s/SPIFFE JWT and mints a short-lived Delegated Session Biscuit bound to the calling node.
func (*Client) FetchEgress ¶
func (c *Client) FetchEgress(ctx context.Context, biscuit []byte) (*api.EgressAssignmentsResponse, error)
FetchEgress is GET /egress, authenticated with the caller's biscuit and signed challenge: the egress destinations the control plane assigned to this node.
func (*Client) FetchInfo ¶
FetchInfo is GET /info: the router addresses, the ban set and the OIDC details a node needs to enroll.
func (*Client) FetchKeys ¶
func (c *Client) FetchKeys(ctx context.Context, trusted []ed25519.PublicKey) ([]ed25519.PublicKey, error)
FetchKeys is GET /keys: the control plane's currently valid signing keys. The set is accepted only if signed by a key in trusted (api.VerifyKeysResponse): whoever answers the URL must already be the control plane, not become it.
func (*Client) FetchPolicy ¶
func (c *Client) FetchPolicy(ctx context.Context, biscuit []byte) (*api.PolicyConfigGetResponse, error)
FetchPolicy is GET /policies, authenticated with the caller's biscuit and signed challenge: the mesh policy as the Datalog rules a member adds to its authorizer.
func (*Client) FetchRevocations ¶
func (c *Client) FetchRevocations(ctx context.Context, biscuit []byte) (*api.RevocationsResponse, error)
FetchRevocations is GET /revocations, authenticated with the caller's biscuit and signed challenge: the revoked Biscuit IDs and banned peer IDs currently tracked by the control plane.
func (*Client) MintSTSToken ¶
func (c *Client) MintSTSToken(ctx context.Context, biscuit []byte, req *api.STSTokenRequest) (*api.STSTokenResponse, error)
MintSTSToken is POST /sts/token, authenticated with the egress node's biscuit: verifies a caller Biscuit for an egress destination and mints a short-lived ES256 JWT for cloud STS federation.
func (*Client) ReportCatalog ¶
func (c *Client) ReportCatalog(ctx context.Context, biscuit []byte, services []*api.ServiceInfo) error
ReportCatalog is POST /nodes/catalog, authenticated with the calling node's biscuit and signed challenge: reports the services currently registered on the node.
type FileTokenSource ¶
type FileTokenSource struct {
// contains filtered or unexported fields
}
FileTokenSource reads a JWT from a file path (e.g. a Kubernetes projected ServiceAccount token volume or a SPIRE spiffe-helper JWT-SVID file).
func NewFileTokenSource ¶
func NewFileTokenSource(path string) *FileTokenSource
NewFileTokenSource creates a TokenSource that reads path on every FetchToken call.
func (*FileTokenSource) FetchToken ¶
func (s *FileTokenSource) FetchToken(_ context.Context) (string, error)
FetchToken reads and trims the JWT from disk.
type StatusError ¶ added in v0.1.1
StatusError is a control plane answer other than success, with the status code a caller can act on (a 401 on an authenticated endpoint says the control plane does not know this node any more) and the body it sent.
func (*StatusError) Error ¶ added in v0.1.1
func (e *StatusError) Error() string