Documentation
¶
Overview ¶
Package egress is the console's only way out to a remote log backend: an HTTP client whose dialer is pinned to the configured host and port, that uses no proxy and follows no redirect, sends only the requests of an allowlist (method and path, segment by segment), caps every response, and checks the server's certificate as the profile's tls mode says. It also probes whether a backend answers a data read without credentials, which a production profile refuses.
The pin is the configured host name and port: the name is resolved at each dial, so a resolver that changes its answer can point the console at another address (never a link-local or metadata one). On production the certificate check (verify-full) binds the connection to the name.
The console reaches a backend only through the Docker network the human names with LOCKLOG_NETWORK (no network otherwise). Whether agents can also reach that network, or the backend's unauthenticated ports, is a deployment precondition, not something this package can see. The pin is in-process: a parser exploit in the console could still use the network the console is on.
Index ¶
- Constants
- Variables
- func CheckElasticUnauthenticated(ctx context.Context, c *Client, production bool, indices []string) error
- func CheckUnauthenticated(ctx context.Context, c *Client, production bool) error
- func CheckUnauthenticatedAt(ctx context.Context, c *Client, production bool, path string, ...) error
- func ElasticData(body []byte) bool
- func ElasticProbePath(indices []string) (string, error)
- func ExtraInterfaces(sysClassNet string) ([]string, error)
- func IndexSegment(indices []string) (string, error)
- func LokiData(body []byte) bool
- func ValidIndex(name string) bool
- type Allowlist
- type Client
- func (c *Client) Close()
- func (c *Client) Get(ctx context.Context, path string, q url.Values) (Response, error)
- func (c *Client) GetWith(ctx context.Context, path string, q url.Values, hdr map[string]string) (Response, error)
- func (c *Client) Probe(ctx context.Context, path string, recognize Recognizer) (bool, error)
- func (c *Client) Send(ctx context.Context, method, path string, q url.Values, body []byte) (Response, error)
- type Credentials
- type Options
- type Recognizer
- type Response
- type Route
Constants ¶
const ( ElasticUserPrivilegesPath = "/_security/user/_privileges" OpenSearchAuthInfoPath = "/_plugins/_security/authinfo" )
The privilege checks: Elasticsearch's grants of the user, the OpenSearch security plugin's authinfo.
const IdleConnTimeout = 30 * time.Second
IdleConnTimeout closes a pooled connection left idle that long (some carry the source's credentials or client certificate).
const LokiProbePath = "/loki/api/v1/labels"
LokiProbePath is the data endpoint the unauthenticated-read check reads.
const MaxResponseBytes = 64 << 20
MaxResponseBytes caps one response body (design: 64 MiB per response).
Variables ¶
var ErrClosed = errors.New("egress: the client is closed")
ErrClosed is the error of a request on a closed client.
var ErrNotAllowed = errors.New("egress: request not in the allowlist")
ErrNotAllowed is returned for a request outside the allowlist.
var ErrResponseTooLarge = errors.New("egress: response larger than the cap")
ErrResponseTooLarge is returned when a body passes MaxResponseBytes.
var LokiHeaders = map[string]string{"X-Loki-Response-Encoding-Flags": "categorize-labels"}
LokiHeaders are the fixed headers of a Loki source: structured metadata comes back apart from the stream labels (without the flag, Loki 3 merges it into each stream's label set). The Loki source also sends it on each request itself, and refuses a streams answer that did not apply it.
var LokiRoutes = NewAllowlist( Route{http.MethodGet, "/loki/api/v1/query_range", map[string]string{"query": "", "start": "", "end": "", "limit": "", "direction": "forward", "step": ""}}, Route{http.MethodGet, "/loki/api/v1/query", map[string]string{"query": "", "time": ""}}, Route{http.MethodGet, "/loki/api/v1/labels", map[string]string{"query": "", "start": "", "end": ""}}, Route{http.MethodGet, "/loki/api/v1/label/{name}/values", map[string]string{"query": "", "start": "", "end": ""}}, Route{http.MethodGet, "/loki/api/v1/series", map[string]string{"match[]": "", "start": "", "end": ""}}, Route{http.MethodGet, "/loki/api/v1/index/stats", map[string]string{"query": "", "start": "", "end": ""}}, Route{http.MethodGet, "/loki/api/v1/index/volume", map[string]string{"query": "", "start": "", "end": ""}}, Route{http.MethodGet, "/ready", nil}, Route{http.MethodGet, "/loki/api/v1/status/buildinfo", nil}, )
LokiRoutes are the only Loki requests locklog sends (read endpoints). Each carries the parameters the Loki source sends (log queries: forward only).
Functions ¶
func CheckElasticUnauthenticated ¶
func CheckElasticUnauthenticated(ctx context.Context, c *Client, production bool, indices []string) error
CheckElasticUnauthenticated is the production check of an Elasticsearch or OpenSearch source: it refuses the source when its mappings read without credentials.
func CheckUnauthenticated ¶
CheckUnauthenticated is the production check of a Loki source: it refuses the source when the probe reads data without credentials.
func CheckUnauthenticatedAt ¶
func CheckUnauthenticatedAt(ctx context.Context, c *Client, production bool, path string, recognize Recognizer) error
CheckUnauthenticatedAt is the production check of a remote source whose data endpoint is path and whose data answer recognize recognizes.
func ElasticData ¶
ElasticData recognizes a mapping answer: a JSON object of at least one index, each an object holding "mappings". An empty object (no index matched the pattern) is data too: the read was allowed.
func ElasticProbePath ¶
ElasticProbePath is the data endpoint the unauthenticated-read check reads: the mappings of the source's indices (field names: data).
func ExtraInterfaces ¶
ExtraInterfaces lists the network interfaces of sysClassNet (/sys/class/net) other than the loopback: none in a console started with --network none.
func IndexSegment ¶
IndexSegment validates indices and returns the path segment that names them (sorted, comma-separated).
func LokiData ¶
LokiData recognizes a Loki data answer: a JSON object whose status is "success" (whatever its data).
func ValidIndex ¶
ValidIndex reports whether name is an index name or trailing-'*' pattern a source may name (config.ValidIndex: lower-case letters, digits, '.', '_' and '-', starting with a letter or digit, so no hidden or system index, no "_all", no remote cluster "a:b"; at most one '*', as its last character, a pattern over the open, non-hidden indices).
Types ¶
type Allowlist ¶
type Allowlist struct {
// contains filtered or unexported fields
}
Allowlist is a compiled set of routes.
func ElasticRoutes ¶
ElasticRoutes are the only Elasticsearch / OpenSearch requests locklog sends: searches, counts, field capabilities, mappings and settings of the source's own indices (the segment is literal: no other index is reachable), the root (flavour and version), and the read-only privilege checks of each flavour: Elasticsearch's grants of the user and what the source's indices resolve to, the OpenSearch security plugin's roles of the user (authinfo: any authenticated user may read it).
func NewAllowlist ¶
NewAllowlist compiles routes. It panics on a malformed route: the lists are constants of the code.
func (*Allowlist) Allows ¶
Allows reports whether method and path (relative to the base path, as the caller builds it; request re-escapes it, so that a "%2F" in a segment stays inside that segment) are in the list, without query parameters.
func (*Allowlist) AllowsQuery ¶
AllowsQuery reports whether method, path and the query parameters q are in the list: a route matching the path whose parameter allowlist holds every parameter of q, each given once, with its fixed value if it has one.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client sends the allowed requests of one remote source.
func New ¶
New builds the client of a remote source. creds are the source's secret; they are never sent by Probe.
func (*Client) Close ¶ added in v0.2.0
func (c *Client) Close()
Close closes the client's pooled connections, the credentialed and the probe's; a request after it fails with ErrClosed. A request still in flight completes, and its connection is closed as it returns. Close does not wait for it.
func (*Client) Get ¶
Get sends an allowed GET with the source's credentials and returns the capped body. A non-2xx status is an error whose text never quotes the body.
func (*Client) GetWith ¶
func (c *Client) GetWith(ctx context.Context, path string, q url.Values, hdr map[string]string) (Response, error)
GetWith is Get with extra request headers (e.g. Loki's response-encoding flags, set by the source itself). Like Options.Headers, each must be on the extra-header allowlist (checkHeaders).
func (*Client) Probe ¶
Probe reads a data endpoint WITHOUT credentials: no Authorization, no client certificate. An X-Scope-OrgID is not a credential (anyone who reaches the backend can set it), so it reads once with no tenant and once with one (the configured org_id, else a made-up tenant): data on either is an open backend. A production profile refuses it: anyone on its network reads the logs without the console.
func (*Client) Send ¶
func (c *Client) Send(ctx context.Context, method, path string, q url.Values, body []byte) (Response, error)
Send sends an allowed GET or POST with the source's credentials and a JSON body (nil: none), and returns the capped body. A non-2xx status is an error whose text never quotes either body.
type Credentials ¶
Credentials are what the console obtained for a source (asked or read from Vault). The zero value sends none.
type Options ¶
type Options struct {
// Resolver resolves host names (default net.DefaultResolver).
Resolver *net.Resolver
// RootCAs, when set, replaces the system roots (tests).
RootCAs *x509.CertPool
// Timeout bounds one request (default 60 s).
Timeout time.Duration
// AllowLinkLocal lets the dialer reach link-local addresses (tests
// only: the cloud metadata service lives there).
AllowLinkLocal bool
// MaxResponseBytes overrides MaxResponseBytes (tests).
MaxResponseBytes int64
// Headers are fixed request headers of the source (e.g. Loki's
// response-encoding flags), each on the extra-header allowlist
// (checkHeaders). They never carry credentials: those go through the
// auth mode.
Headers map[string]string
}
Options tune a client; tests set them.
type Recognizer ¶
Recognizer reports whether a 2xx body is the backend's data answer (and not, say, a login page).
type Route ¶
type Route struct {
Method string
Path string
// Params are the query parameters the route may carry, each once:
// name to fixed value, or to "" for any value. Any other parameter,
// or one repeated, is refused.
Params map[string]string
}
Route is one allowed request: a method and a path pattern relative to the source URL's path, its segments either literal or "{name}" (one non-empty segment, no "." or "..").