egress

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

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

View Source
const (
	ElasticUserPrivilegesPath = "/_security/user/_privileges"
	OpenSearchAuthInfoPath    = "/_plugins/_security/authinfo"
)

The privilege checks: Elasticsearch's grants of the user, the OpenSearch security plugin's authinfo.

View Source
const LokiProbePath = "/loki/api/v1/labels"

LokiProbePath is the data endpoint the unauthenticated-read check reads.

View Source
const MaxResponseBytes = 64 << 20

MaxResponseBytes caps one response body (design: 64 MiB per response).

Variables

View Source
var ErrNotAllowed = errors.New("egress: request not in the allowlist")

ErrNotAllowed is returned for a request outside the allowlist.

View Source
var ErrResponseTooLarge = errors.New("egress: response larger than the cap")

ErrResponseTooLarge is returned when a body passes MaxResponseBytes.

View Source
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.

View Source
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

func CheckUnauthenticated(ctx context.Context, c *Client, production bool) error

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

func ElasticData(body []byte) bool

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

func ElasticProbePath(indices []string) (string, error)

ElasticProbePath is the data endpoint the unauthenticated-read check reads: the mappings of the source's indices (field names: data).

func ExtraInterfaces

func ExtraInterfaces(sysClassNet string) ([]string, error)

ExtraInterfaces lists the network interfaces of sysClassNet (/sys/class/net) other than the loopback: none in a console started with --network none.

func IndexSegment

func IndexSegment(indices []string) (string, error)

IndexSegment validates indices and returns the path segment that names them (sorted, comma-separated).

func LokiData

func LokiData(body []byte) bool

LokiData recognizes a Loki data answer: a JSON object whose status is "success" (whatever its data).

func ValidIndex

func ValidIndex(name string) bool

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

func ElasticRoutes(indices []string) (*Allowlist, error)

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

func NewAllowlist(routes ...Route) *Allowlist

NewAllowlist compiles routes. It panics on a malformed route: the lists are constants of the code.

func (*Allowlist) Allows

func (a *Allowlist) Allows(method, path string) bool

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

func (a *Allowlist) AllowsQuery(method, path string, q url.Values) bool

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

func New(r *config.Remote, allow *Allowlist, creds Credentials, opt Options) (*Client, error)

New builds the client of a remote source. creds are the source's secret; they are never sent by Probe.

func (*Client) Get

func (c *Client) Get(ctx context.Context, path string, q url.Values) (Response, error)

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). They follow the fixed-header rule: no credentials, tenant or host.

func (*Client) Probe

func (c *Client) Probe(ctx context.Context, path string, recognize Recognizer) (bool, error)

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

type Credentials struct {
	User   string
	Secret string
}

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). They never carry credentials: those go
	// through the auth mode.
	Headers map[string]string
}

Options tune a client; tests set them.

type Recognizer

type Recognizer func(body []byte) bool

Recognizer reports whether a 2xx body is the backend's data answer (and not, say, a login page).

type Response

type Response struct {
	Status int
	Body   []byte
}

Response is a capped response: the status and the body.

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 "..").

Jump to

Keyboard shortcuts

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