engine

package
v0.2.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: 34 Imported by: 0

Documentation

Overview

Package engine answers the agent's log queries over masked records: it validates a query, builds a byte plan from the range and trusted selectors only, admits it against the per-call budgets, masks every record of each planned granule before any predicate runs, and stops at plan-chunk boundaries at its deadline, with a sealed cursor to continue.

Admission, charges, stopping points, cursors, `scanned` and `covered_until` are functions of the plan, never of matches (design §3.3, §4.4): the work of a call depends on the bytes and their detector hits, not on the agent's predicates.

Index

Constants

View Source
const (
	MaxBy      = 2
	MaxBuckets = 1000
	// MaxGroups bounds distinct (bucket, by) groups; beyond, records
	// count under an "⟦other⟧" key. Distinct counts are exact up to it.
	MaxGroups = 100000
	// MaxGroupBytes bounds the memory of one count's groups (keys are
	// masked values of any length): beyond, records count under
	// "⟦other⟧" as beyond MaxGroups.
	MaxGroupBytes = 8 << 20
)

Count limits.

View Source
const (
	DefaultScanTTL      = 30 * time.Minute
	DefaultScanDeadline = 240 * time.Second
	DefaultScanBytes    = 64 << 30
	DefaultScanRange    = 400 * 24 * time.Hour
)

Scan defaults (design §3.3).

View Source
const (
	MethodSearch   = "log.search"
	MethodCount    = "log.count"
	MethodPatterns = "log.patterns"
	MethodContext  = "log.context"
	MethodTrace    = "log.trace"
	MethodTail     = "log.tail"
)

Agent methods served by the engine (IPC log.*).

View Source
const (
	DefaultTop = 20
	MaxTop     = 100
	// MaxTemplateTokens and MaxTemplateBytes bound a template: beyond, it
	// ends with TemplateCut.
	MaxTemplateTokens = 64
	MaxTemplateBytes  = 512
	// Variable replaces a variable part of a template.
	Variable = "<*>"
	// TemplateCut ends a template cut at its bounds.
	TemplateCut = "…"
)

Pattern limits.

View Source
const (
	EBadQuery     = "E_BAD_QUERY"
	EUnknownField = "E_UNKNOWN_FIELD"
	EPIIInQuery   = "E_PII_IN_QUERY"
	EOverBudget   = "E_OVER_BUDGET"
	ESource       = "E_SOURCE"
	// EMask is reserved: a record masking fails on is withheld and
	// counted in masked_failures, never refused (see run).
	EMask          = "E_MASK"
	EPolicyChanged = "E_POLICY_CHANGED"
	EStale         = "E_STALE"
	// EApproval: the call needs a human approval this console cannot ask
	// (no approver on the call).
	EApproval = "E_APPROVAL"
	// EUnmaskNever: the profile's unmask = "never" (the production
	// default) refuses every unmask.
	EUnmaskNever = "E_UNMASK_NEVER"
	// EBusy: the console runs its most calls at once (limits.max_calls);
	// the call did no work and can be retried.
	EBusy = "E_BUSY"
)

Refusal codes (stable, documented for agents).

View Source
const (
	OpEq       = "="
	OpNe       = "!="
	OpIn       = "in"
	OpExists   = "exists"
	OpLt       = "<"
	OpLe       = "<="
	OpGt       = ">"
	OpGe       = ">="
	OpContains = "contains"
	OpMatches  = "matches"
)

Operators.

View Source
const (
	MaxPattern    = 256 // bytes of a regex
	MaxNeedle     = 256 // bytes of a contains/text needle
	MaxPredicates = 32
	MaxFields     = 64
	MaxInValues   = 64
	MaxSources    = 32
	DefaultLimit  = 100
)

Limits of a query.

View Source
const (
	ScanSearch   = "search"
	ScanCount    = "count"
	ScanPatterns = "patterns"
	ScanTrace    = "trace"
)

The tools an approved scan runs.

View Source
const (
	MaxTailDuration = 50 * time.Second
	DefaultTailMax  = 100
	// DefaultTailPoll is the interval between polls of a tail.
	DefaultTailPoll = time.Second
)

Tail bounds (design §3.1).

View Source
const (
	MethodUnmask = "log.unmask"
	MethodLookup = "log.lookup"
)

Methods of the approved and rate-limited value tools.

View Source
const (
	UnmaskNever    = "never"
	UnmaskApproved = "approved"
)

Unmask modes (config: unmask).

View Source
const (
	MaxUnmask      = 8
	MaxLookup      = 10
	MaxLookupValue = 256
)

MaxUnmask bounds the pseudonyms of one unmask, MaxLookup the values of one lookup and MaxLookupValue the bytes of one looked-up value.

View Source
const DefaultMaxCalls = 4

DefaultMaxCalls is the number of agent calls an engine runs at once when Options.MaxCalls is not set.

View Source
const DefaultNsPerByte = 5000

DefaultNsPerByte is the worst-case cost per byte and core before the first calibration (S4: the slowest corpus, encoded text, 0.2 MB/s/core).

View Source
const MaxAround = 50

MaxAround bounds before and after of a context call.

View Source
const MaxReason = 300

MaxReason bounds the reason an agent gives for an approval.

View Source
const MaxScanTTL = 4 * time.Hour

MaxScanTTL bounds the lifetime of an approved scan however long its plan (lockkit's grant: one approval for a bounded read scope, 4 h).

View Source
const MaxScans = 8

MaxScans bounds the approved scans in progress on a console.

View Source
const MethodScan = "log.scan"

MethodScan is an approved scan (locklog_scan).

View Source
const Other = mask.Open + "other" + mask.Close

Other is the key of groups beyond MaxGroups or MaxGroupBytes, or of index overflow.

Variables

View Source
var ErrSealed = errors.New("engine: invalid or expired reference")

ErrSealed is returned for a ref or cursor that does not open: wrong type, forged, from another key (console reinstalled), corrupt.

Functions

func Template

func Template(masked string) string

Template is the pattern of a masked text: a pseudonym keeps its kind and loses its id (⟦email:…⟧), other placeholders stay as they are, and every part of a word holding a digit (parts are split at = : , ; ( ) [ ] { } " ' / and @) becomes Variable. Words are joined by one space. It reads masked text only: a template carries less than the text it comes from. A bare placeholder equal to Other is written ⟦other:…⟧, so that a template is never the overflow key. It runs in time linear in its input and stops reading once the template is over MaxTemplateBytes.

func WithClearSink

func WithClearSink(ctx context.Context, s *ClearSink) context.Context

WithClearSink attaches a call's clear-view sink to ctx.

func WithExposureCount added in v0.2.0

func WithExposureCount(ctx context.Context, f ExposureCount) context.Context

WithExposureCount attaches the console's exposure count to ctx: the unmask screen shows it.

func WithPeer

func WithPeer(ctx context.Context, p Peer) context.Context

WithPeer attaches the caller of a call to ctx.

func WithSourceLog added in v0.2.0

func WithSourceLog(ctx context.Context, l func(line string)) context.Context

WithSourceLog attaches the operator's log to ctx: the engine reports the failures of sources through it, one line each, for the console's terminal only (never the agent, never the audit).

Types

type Approval

type Approval struct {
	// Class is "read" or "read-pii".
	Class  string
	Header string
	// Note is the agent's reason (untrusted: the console escapes it).
	Note     string
	Body     []string
	Warnings []string
	// NeverAuto marks the console's own approval screen as one no auto
	// mode takes. In hub mode the ticket's never_auto flag is the
	// profile's auto rule's (auto.read, auto.unmask, auto.lookup:
	// logconsole), not this field.
	NeverAuto bool
	// Clear is the clear view of the screen's record (secrets and
	// redactions kept), for the console's own terminal only: the
	// console shows it when its clear-results switch (Ctrl-O) is on.
	// It never reaches the agent, nor a hub's masked screen: in hub mode
	// it goes sealed to the pinned devices only (the clear screen).
	Clear []string
	// HubBody, when set, replaces Body on the hub's masked screen: the
	// lines of Body that hold a clear value are left out of it (an
	// approved lookup's values). Body stays the console's screen and the
	// clear screen sealed to the devices.
	HubBody []string
	// Hash is the keyed plan hash the approval binds (HMAC under K_ref),
	// and Inputs its keyed input digests by name: what a hub ticket is
	// bound to (plans.Binding). Moving names the inputs a renewal cannot
	// check without reading the record again. None of them ever reaches
	// the agent.
	Hash   string
	Inputs map[string]string
	Moving []string
}

Approval is what the engine asks the human to approve (an approval screen of the console): a scan (class read), an unmask or an approved lookup (class read-pii).

type Approver

type Approver func(ctx context.Context, a Approval) error

Approver asks the human. It returns nil once approved; any other error is the console's answer (a denial, a timeout, a pending ticket), passed back unchanged as the engine's error.

type BudgetStatus

type BudgetStatus struct {
	Local  *ClassBudget `json:"local,omitempty"`
	Remote *ClassBudget `json:"remote,omitempty"`
	// Sources is each source's own budget left.
	Sources []SourceBudget `json:"sources"`
	// LookupsLeft is the distinct values the console may still look up
	// this hour, for every peer together (-1: no limit).
	LookupsLeft int `json:"lookups_left"`
	// LookupsLeftPeer is what the calling peer (its uid) may still look
	// up this hour: its own limit within the console's (-1: no limit).
	// Absent outside an agent's call (the console's :status).
	LookupsLeftPeer *int `json:"lookups_left_peer,omitempty"`
	// Scans is the approved scans in progress on the console, every
	// peer's (at most MaxScans).
	Scans int `json:"scans"`
}

BudgetStatus is what is left of the rolling budgets (log.status): no content, only the console's own counters.

type Budgets

type Budgets struct {
	// LocalBytesPerHour and RemoteBytesPerHour bound the bytes processed
	// (plan-based, granule-rounded; remote: the bytes fetched) per rolling
	// hour, by each source and by the console's sources of that kind.
	LocalBytesPerHour, RemoteBytesPerHour int64
	// RemoteRequestsPerMinute paces the requests of free calls to a remote
	// backend (per source and per console).
	RemoteRequestsPerMinute int
	// MetricQueriesPerHour bounds the server-side metric queries of
	// counts answered by a remote backend.
	MetricQueriesPerHour int
	// LookupsPerHour and LookupsPerHourPerUID bound the distinct values
	// log.lookup resolves per rolling hour, for the console and for one
	// peer uid.
	LookupsPerHour, LookupsPerHourPerUID int
}

Budgets are the rolling budgets (design §3.3): what the agent's free calls may cost per hour (bytes processed, server-side metric queries) and per minute (remote backend requests), per source and for the whole console. Search, count, patterns, trace, context re-reads, tail polls and continuations all draw on them; an approved scan draws on its own plan instead.

func DefaultBudgets

func DefaultBudgets() Budgets

DefaultBudgets are the design defaults (non-production).

type ClassBudget

type ClassBudget struct {
	BytesPerHour      int64 `json:"bytes_per_hour"`
	BytesLeft         int64 `json:"bytes_left"`
	RequestsLeft      *int  `json:"requests_left_this_minute,omitempty"`
	MetricLeft        *int  `json:"metric_queries_left,omitempty"`
	RequestsPerMinute int   `json:"requests_per_minute,omitempty"`
}

ClassBudget is one kind of source's rolling budget for the console.

type ClearMasker

type ClearMasker interface {
	MaskClear(source string, ev parse.Event) (mask.Record, map[string]string)
}

ClearMasker is what unmask needs of the masker: the clear value of each pseudonym of a record (mask.Masker.MaskClear).

type ClearSink

type ClearSink struct {
	// contains filtered or unexported fields
}

ClearSink keeps the raw records one call returned, for its clear view.

func NewClearSink

func NewClearSink() *ClearSink

NewClearSink returns an empty sink.

type ContextQuery

type ContextQuery struct {
	Ref    string   `json:"ref"`
	Before int      `json:"before,omitempty"`
	After  int      `json:"after,omitempty"`
	Fields []string `json:"fields,omitempty"`
}

ContextQuery is a context call (locklog_context): the records around one returned record, from its stream, in stream order.

type ContextResult

type ContextResult struct {
	Epoch   string            `json:"epoch"`
	Records []json.RawMessage `json:"records"`
	Target  int               `json:"target"`
	// Complete is false when the output cap dropped the records farthest
	// from the target.
	Complete       bool    `json:"complete"`
	Scanned        Scanned `json:"scanned"`
	Masking        Masking `json:"masking"`
	MaskedFailures int     `json:"masked_failures,omitempty"`
}

ContextResult is the answer of a context call. Target is the index in Records of the record the ref names. Records are masked as search results are, each with its own ref; a record masking failed on is withheld (masked_failure).

type CostModel

type CostModel struct {
	// contains filtered or unexported fields
}

CostModel is the calibrated worst-case cost.

func NewCostModel

func NewCostModel(p *parse.Parser, m MaskFunc, workers int, now func() time.Time) *CostModel

NewCostModel calibrates once; workers is the number of masking workers of a call (the per-call cost is the per-core cost divided by it).

func (*CostModel) Budget

func (c *CostModel) Budget(d time.Duration) int64

Budget returns how many bytes fit in d.

func (*CostModel) Calibrate

func (c *CostModel) Calibrate()

Calibrate measures the worst-case cost per byte on the fixed corpus, synchronously (at start; queries use Recalibrate).

func (*CostModel) NsPerByte

func (c *CostModel) NsPerByte() float64

NsPerByte returns the cost per byte, recalibrating hourly in the background (the current value serves until the new one is measured).

func (*CostModel) Recalibrate

func (c *CostModel) Recalibrate()

Recalibrate starts a calibration in the background unless one is already running, and returns at once: a query never waits for one (it may take up to calibrationBudget), and concurrent queries start at most one.

func (*CostModel) String

func (c *CostModel) String() string

String reports the model for status screens.

func (*CostModel) Time

func (c *CostModel) Time(n int64) time.Duration

Time returns the worst-case time of n bytes.

type CountQuery

type CountQuery struct {
	Query
	// Bucket is a duration ("5m"); empty: one bucket for the range.
	Bucket string `json:"bucket,omitempty"`
	// By groups by up to two trusted or pseudonymised line fields, and
	// ByLabels by source labels (Loki stream labels, journald and docker
	// attributes); at most MaxBy in all. A group's key holds the By
	// values, then the ByLabels values.
	By       []string `json:"by,omitempty"`
	ByLabels []string `json:"by_labels,omitempty"`
	// ByPattern also groups by the Template of the masked message (the
	// last key; null without a message). It counts as one of MaxBy and is
	// never served from the index.
	ByPattern bool `json:"by_pattern,omitempty"`
	// FirstLast adds each group's first and last timestamp.
	FirstLast bool `json:"first_last,omitempty"`
	// contains filtered or unexported fields
}

CountQuery is a count (locklog_count).

type CountResult

type CountResult struct {
	Epoch        string     `json:"epoch"`
	Groups       []Group    `json:"groups"`
	Untimed      int64      `json:"untimed,omitempty"`
	Complete     bool       `json:"complete"`
	CoveredUntil *time.Time `json:"covered_until,omitempty"`
	Cursor       string     `json:"cursor,omitempty"`
	Scanned      Scanned    `json:"scanned"`
	// FromIndex is set when the local index answered (part of) the
	// count; IndexedFrom is where the index starts.
	FromIndex      bool       `json:"from_index,omitempty"`
	IndexedFrom    *time.Time `json:"indexed_from,omitempty"`
	IndexedUntil   *time.Time `json:"indexed_until,omitempty"`
	MaskedFailures int        `json:"masked_failures,omitempty"`
	// A count whose groups do not fit one answer (Limits.MaxOutput) is
	// paged once it is whole: each page holds the next groups, in order,
	// from GroupsFrom of GroupsTotal, and a cursor to the rest (complete
	// on the last). A partial count (its scan goes on) shows the first
	// groups that fit, Truncated set, and OmittedGroups and OmittedN
	// what it left out for now.
	GroupsFrom    int   `json:"groups_from,omitempty"`
	GroupsTotal   int   `json:"groups_total,omitempty"`
	Truncated     bool  `json:"truncated,omitempty"`
	OmittedGroups int   `json:"omitted_groups,omitempty"`
	OmittedN      int64 `json:"omitted_n,omitempty"`
}

CountResult is the answer of a count.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine runs queries. It is safe for concurrent use.

func New

func New(o Options) (*Engine, error)

New returns an engine.

func (*Engine) BudgetStatus

func (e *Engine) BudgetStatus() BudgetStatus

BudgetStatus reports what is left of the rolling budgets.

func (*Engine) BudgetStatusFor added in v0.2.0

func (e *Engine) BudgetStatusFor(ctx context.Context) BudgetStatus

BudgetStatusFor is BudgetStatus with the lookups left to the peer of ctx, when one is attached.

func (*Engine) ClearRecords

func (e *Engine) ClearRecords(ctx context.Context, sink *ClearSink, refs []string) []string

ClearRecords renders the records of refs in clear for the console's terminal, one line each, in order, from sink (the call's own records) or, for a local source, by reading the record again; everything under one deadline. A record it cannot show is named, not shown.

func (*Engine) Context

func (e *Engine) Context(ctx context.Context, cq ContextQuery) (ContextResult, error)

Context re-reads the records around a ref (locklog_context). The record must still be the one the ref was made on (its keyed digest): a stream rewritten since gives E_STALE. Every record is masked before it is rendered; the work depends on the stream around the ref only, and scanned reports it in granules (charged to the rolling budget).

func (*Engine) Count

func (e *Engine) Count(ctx context.Context, q CountQuery) (_ CountResult, retErr error)

Count runs a count. A trusted-field-only count is answered from the local index where it holds the range, and from a plan-based scan of the rest (continued free) that masks only the records holding a trusted value the masker would change (countTrusted); every other count scans its plan, masking every record first. A record masking fails on is reported in masked_failures; a trusted-only count still counts it by its trusted values, as the index does, while any other count leaves it out, since its masked predicates cannot be evaluated on it.

func (*Engine) Handle

func (e *Engine) Handle(ctx context.Context, method string, params json.RawMessage) (any, *Error, error)

Handle decodes the params of an agent method and runs it. A refusal is the agent's error (structured, no log content); err is the engine's own failure. Unknown fields in params are refused.

A call takes one of the engine's call slots (Options.MaxCalls) for its whole run, or is refused at once with E_BUSY. Calls a human approves take none while they wait: a scan takes its slot once approved (run); an unmask or a lookup reads one record and takes none.

func (*Engine) Lookup

func (e *Engine) Lookup(ctx context.Context, q LookupQuery) (LookupResult, error)

Lookup gives the pseudonyms of clear values of a detector kind, as the masker shows them in this epoch (the equality the agent then searches or counts by). It is free and rate-limited (distinct values per hour, per peer uid and per console), or a read-pii approval with lookup = "approved". Refused: a kind no value detector finds (field names, template tokens, rule kinds), a value that reads as a secret, and a value that is not wholly of the kind. Documented residual: membership probing (design §4.4 item 5).

func (*Engine) Paced

func (e *Engine) Paced(ctx context.Context, src string) (context.Context, context.CancelFunc)

Paced returns ctx with the request limiter of a free call to the remote source src and the call deadline: its requests outside a query (log.sources describing it) are paced and charged to the rolling request budget like a query's, and a request the budget leaves no room for before the deadline fails at once instead of waiting for the window. The caller calls cancel when done.

func (*Engine) Patterns

func (e *Engine) Patterns(ctx context.Context, q PatternsQuery) (_ PatternsResult, retErr error)

Patterns runs a patterns query: it scans the plan of the window (and of the baseline), masking every record first, and groups the matching records' masked messages by Template. Admission, stopping points and cursors are those of a count: functions of the plan, never of matches.

func (*Engine) RecordDigest

func (e *Engine) RecordDigest(raw string) string

RecordDigest is the keyed digest of a raw record bound into its ref.

func (*Engine) Ref

func (e *Engine) Ref(ref string) (source.RawRef, string, error)

Ref opens a sealed ref (for context, unmask, reveal).

func (*Engine) RefRecord added in v0.2.0

func (e *Engine) RefRecord(ref string) (key, epoch string, ok bool)

RefRecord opens a sealed ref of a returned record: a key of the record (keyed: the same record at the same place, the same key; it says nothing of its text) and the epoch of its pseudonyms. ok is false for a ref this engine cannot open.

func (*Engine) Scan

func (e *Engine) Scan(ctx context.Context, q ScanQuery) (ScanResult, error)

Scan runs an approved scan (log.scan). The first call (no cursor in the query) plans the tool's call with the scan's budgets, re-attaches to the same plan approved earlier by the same peer within its TTL, or asks the human to approve it; every later call continues the approved plan with the cursor, free. The plan hash, its inputs and the policy fingerprint never reach the agent: it gets the scan's random id.

func (*Engine) Search

func (e *Engine) Search(ctx context.Context, q Query) (SearchResult, error)

Search runs a search (locklog_search).

func (*Engine) String

func (e *Engine) String() string

String describes an engine for status screens (no content).

func (*Engine) Tail

func (e *Engine) Tail(ctx context.Context, tq TailQuery) (TailResult, error)

Tail polls the sources until Duration ends or Max match, for the records timestamped from the call's start (to the second): new records are told by their time, which every source has; records without one are never tailed. Each record is evaluated and returned once: identical records are told apart by their position. While the set of local streams stays the same (no stream appears, goes or shrinks: which streams exist and their ends come from a plan of every stream whole, unfiltered, at each poll), complete:true means every new record was evaluated once. When it changes (rotation, truncation, a new file), the tail is complete:false: a copy made during the tail (copytruncate) is recognised by a record of the original at the same offset with the same keyed digest and the records it shares with the original are not returned again, but two streams of their own can share such records. The duration ends with a poll planned after it.

Work follows the new data: a plan of every local stream at the start (no record read) gives each stream's end in its granules, and a poll scans only granules past what was there or already scanned whole (the growing last one again, from its start); within a stream, records up to the last one evaluated are skipped. These granule ends are used for pruning only; they are compared with record positions only for byte streams (file, docker), whose granules and positions are both byte offsets: there the granule of a stream's held record is read again once the stream grows. Journal entries are atomic and never held. A stream first seen during the tail is scanned from its start (its records still need a time from the start on). A record that was already there at the start (for a byte stream, its end at the first listing, milliseconds after the call starts; a compressed stream not measured then, whole) is never new, whatever its date. Residuals: a journal entry already there and dated from the start on (a clock stepped back) is returned; a record written after the start with a time before it (its second, for a byte stream) is not.

Every record of a scanned granule is masked before evaluation, as in a search; a poll's matches are kept only when it completes (stopping points are group boundaries), its work always. Each poll's plan, once pruned, is admitted against the call's byte budgets, and the polls share the call's bytes; everything runs under the duration plus a grace.

func (*Engine) Trace

func (e *Engine) Trace(ctx context.Context, tq TraceQuery) (SearchResult, error)

Trace runs a trace (locklog_trace). It is a search for the id over the range, with the same validation, plan, budgets, stopping points and cursors; each page is in time order across the sources of the call (untimed records last), and continuations follow the plan.

func (*Engine) Unmask

func (e *Engine) Unmask(ctx context.Context, q UnmaskQuery) (UnmaskResult, error)

Unmask reveals the clear values of exact pseudonyms of one record to the agent, once, on a read-pii approval the human gives at the console (never auto-approved). The plan hash binds the ref, the record's keyed digest (HMAC under K_ref), the epoch and the exact (kind, ID, span offsets); the record is read again after the approval and anything that moved is refused (E_STALE). A secret span is never revealed, nor a value that held one.

type Error

type Error struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	// Span is the JSON path of the offending part of the query.
	Span string `json:"span,omitempty"`
	// Estimate is the plan estimate of an over-budget call.
	Estimate *Estimate `json:"estimate,omitempty"`
	// contains filtered or unexported fields
}

Error is a structured refusal. Its text never holds log content: query values are echoed only through the masker's text path by the caller.

func (*Error) Error

func (e *Error) Error() string

type Estimate

type Estimate struct {
	Bytes    int64         `json:"bytes"`
	Granules int           `json:"granules"`
	Range    time.Duration `json:"range_ns"`
}

Estimate is what a plan would cost.

type ExposureCount added in v0.2.0

type ExposureCount func(epoch string, pseudonyms []string) (counts []int, exact bool, since time.Time)

ExposureCount counts, for each pseudonym, the distinct records holding it returned to agents in epoch (the console's count, from the calls it answered and audited): whether the counts are exact (false: lower bounds) and since when it counts.

type Group

type Group struct {
	Bucket time.Time  `json:"bucket"`
	Key    []*string  `json:"key,omitempty"` // masked values; null when absent
	N      int64      `json:"n"`
	First  *time.Time `json:"first,omitempty"`
	Last   *time.Time `json:"last,omitempty"`
}

Group is one counted group.

type Limits

type Limits struct {
	// Range of a query with text, regex or masked-field predicates.
	LocalRange, RemoteRange time.Duration
	// CountRange of a trusted-field-only count, RemoteCountRange of one a
	// remote source answers with its own counts (0: CountRange).
	CountRange, RemoteCountRange time.Duration
	// Bytes a call's plan may cost (admission).
	LocalBytes, RemoteBytes int64
	// Records per page (ceiling of limit) and output bytes per page.
	MaxRecords int
	MaxOutput  int
	// Deadline of one call.
	Deadline time.Duration
}

Limits are the per-call budgets (design §3.3).

func DefaultLimits

func DefaultLimits() Limits

DefaultLimits are the design defaults.

type LookupQuery

type LookupQuery struct {
	Kind   string   `json:"kind"`
	Values []string `json:"values"`
	Reason string   `json:"reason,omitempty"`
}

LookupQuery asks for the pseudonyms of clear values of a detector kind (log.lookup): equality only.

type LookupResult

type LookupResult struct {
	Epoch      string   `json:"epoch"`
	Kind       string   `json:"kind"`
	Pseudonyms []string `json:"pseudonyms"`
}

LookupResult is the pseudonym of each value, in order (the values are not echoed).

func (LookupResult) AuditPseudonyms

func (r LookupResult) AuditPseudonyms() []string

AuditPseudonyms names the lookup's answer by pseudonym only.

type Lookuper

type Lookuper interface {
	Lookup(kind, value string) (string, error)
	DetectorKinds() []string
}

Lookuper is what lookup needs of the masker (mask.Masker.Lookup).

type MaskFunc

type MaskFunc func(ev parse.Event)

MaskFunc masks one parsed event (the console passes its masker); the result is ignored by calibration.

type Masker

type Masker interface {
	Mask(source string, ev parse.Event) mask.Record
	Text(s string) string
	// TrustedValue masks a trusted value read outside a record (index
	// values, values compared with trusted fields) as Mask masks a
	// trusted leaf at that path.
	TrustedValue(source, path, v string) string
	Epoch() string
}

Masker is what the engine needs of the masker.

type Masking

type Masking struct {
	Kinds []string `json:"kinds"`
}

Masking reports what was masked in the returned records.

type Options

type Options struct {
	Sources map[string]source.Source
	// Trusted reports a trusted line field (label unset) or a trusted
	// label (label set) of a source. They are separate namespaces: a
	// trusted label never makes a line field of the same name trusted.
	Trusted func(src, name string, label bool) bool
	Masker  Masker
	Sealer  *Sealer
	Cost    *CostModel
	// PolicyFP is the fingerprint of the approved policy in force.
	PolicyFP func() string
	// Detect reports a query value a detector flags.
	Detect func(string) bool
	// NameMasked reports a clear path the masker shows masked.
	NameMasked func(src, path string) bool
	Limits     Limits
	// Prefilter (opt-in, refused on production by the config) skips
	// masking a record whose clear text cannot contain a needle.
	Prefilter bool
	// Workers mask in parallel (the console runs with --cpus min(2,
	// nproc)); the cost model is per call, so it is calibrated with the
	// same count.
	Workers int
	Now     func() time.Time
	// TailPoll is the interval between a tail's polls (DefaultTailPoll).
	TailPoll time.Duration
	// Budgets are the rolling budgets (zero: DefaultBudgets).
	Budgets Budgets
	// ScanTTL, ScanDeadline, ScanBytes and ScanRange bound an approved
	// scan: its lifetime from the approval (DefaultScanTTL), the
	// deadline of each of its calls (DefaultScanDeadline, and never past
	// scanCallBudget from the request), the bytes and the range of its
	// whole plan (DefaultScanBytes, DefaultScanRange). The deadline is a
	// fixed bound, not calibrated: what a call does within it is (run
	// admits each group of granules on the calibrated cost model).
	ScanTTL      time.Duration
	ScanDeadline time.Duration
	ScanBytes    int64
	ScanRange    time.Duration
	// ScanRequestsPerMinute paces an approved scan's remote requests (0:
	// none beyond the source's own in-flight cap; design R6-P2).
	ScanRequestsPerMinute int
	// Unmask is "approved" to serve log.unmask on a read-pii approval;
	// anything else ("never", the default) refuses it.
	Unmask string
	// LookupApproved makes every log.lookup a read-pii approval.
	LookupApproved bool
	// Production marks the approval screens of a production profile.
	Production bool
	// MaxCalls bounds the agent calls the engine runs at once
	// (DefaultMaxCalls when 0); a call over it is refused with E_BUSY.
	MaxCalls int
	// contains filtered or unexported fields
}

Options configure an Engine.

type PatternGroup

type PatternGroup struct {
	Pattern   string     `json:"pattern"`
	N         int64      `json:"n"`
	BaselineN *int64     `json:"baseline_n,omitempty"`
	First     *time.Time `json:"first,omitempty"`
	Last      *time.Time `json:"last,omitempty"`
}

PatternGroup is one pattern and its counts.

func (PatternGroup) String

func (g PatternGroup) String() string

String describes a pattern group (tests and status screens).

type PatternsQuery

type PatternsQuery struct {
	Query
	// Top is the number of patterns returned (DefaultTop, at most MaxTop).
	Top int `json:"top,omitempty"`
	// Baseline, when set, is an offset ("24h"): the same window that much
	// earlier is counted too, for each pattern's baseline count and the
	// new and gone lists. It is at least the window's length (the windows
	// never overlap).
	Baseline string `json:"baseline,omitempty"`
	// contains filtered or unexported fields
}

PatternsQuery asks for the top templates of the masked message text (locklog_patterns).

type PatternsResult

type PatternsResult struct {
	Epoch string `json:"epoch"`
	// Patterns are the top patterns of the window, most frequent first.
	Patterns []PatternGroup `json:"patterns"`
	// Distinct is the number of distinct patterns of the window, Records
	// the records counted (with a message), NoText the matching records
	// without one.
	Distinct int   `json:"distinct"`
	Records  int64 `json:"records"`
	NoText   int64 `json:"no_text,omitempty"`
	// Untimed counts the matching untimed records of the window's plan.
	Untimed int64 `json:"untimed,omitempty"`
	// Truncated is set when the window or the baseline had more distinct
	// patterns than MaxGroups (or MaxGroupBytes): the rest count under
	// Other, in their own group (counted in Distinct). New is then left
	// empty when the baseline is truncated, Gone when the window is.
	Truncated bool `json:"truncated,omitempty"`
	// With a baseline: New are the top patterns absent from it, Gone the
	// top patterns of the baseline absent from the window.
	BaselineFrom   *time.Time     `json:"baseline_from,omitempty"`
	BaselineTo     *time.Time     `json:"baseline_to,omitempty"`
	New            []PatternGroup `json:"new,omitempty"`
	Gone           []PatternGroup `json:"gone,omitempty"`
	Complete       bool           `json:"complete"`
	CoveredUntil   *time.Time     `json:"covered_until,omitempty"`
	Cursor         string         `json:"cursor,omitempty"`
	Scanned        Scanned        `json:"scanned"`
	MaskedFailures int            `json:"masked_failures,omitempty"`
}

PatternsResult is the answer of a patterns query.

type Peer

type Peer struct {
	UID int
	// Token is the hash of the agent's token (lea's requester token,
	// plans.HashToken), "" when the agent sends none. An approved scan is bound to
	// it as to the uid: another agent of the same uid, with another token,
	// neither re-attaches to it nor continues it.
	Token   string
	Approve Approver
}

Peer is the caller of a call: its kernel-verified uid, the agent's token it binds an approval to, and the console's approver.

type Pred

type Pred struct {
	Field string          `json:"field,omitempty"`
	Label string          `json:"label,omitempty"`
	Op    string          `json:"op"`
	Value json.RawMessage `json:"value,omitempty"`
	// contains filtered or unexported fields
}

Pred is one predicate of where. Field addresses a field by its shown path (clear, or with ⟦key:ID⟧ segments); Label a source label (Loki labels, container-frame attributes).

type Query

type Query struct {
	Sources []string  `json:"sources,omitempty"`
	From    time.Time `json:"from"`
	To      time.Time `json:"to"`
	// Last, when set, resolves the range at validation: [now-Last, now).
	Last   string   `json:"last,omitempty"`
	Where  []Pred   `json:"where,omitempty"`
	Text   string   `json:"text,omitempty"`
	Regex  string   `json:"regex,omitempty"`
	Fields []string `json:"fields,omitempty"`
	Limit  int      `json:"limit,omitempty"`
	// Order is "asc" (default) or "desc": pages follow it within each
	// stream; each call's matches are sorted, while streams of several
	// sources follow each other rather than being merged by time.
	Order  string `json:"order,omitempty"`
	Cursor string `json:"cursor,omitempty"`
	// contains filtered or unexported fields
}

Query is a search or count query (design §3.2).

type ScanInfo

type ScanInfo struct {
	ID         string    `json:"id"`
	ExpiresAt  time.Time `json:"expires_at"`
	Reattached bool      `json:"reattached,omitempty"`
	// Resumed: the re-attached scan went on after the last page it
	// answered. A page whose answer the agent did not get is not sent
	// again.
	Resumed bool `json:"resumed,omitempty"`
	// Done: the plan is exhausted and the scan ended.
	Done bool `json:"done"`
}

ScanInfo names the approved scan of a result: a random id (it carries nothing about the plan), when it expires and whether this call re-attached to a scan approved earlier.

type ScanQuery

type ScanQuery struct {
	Tool   string          `json:"tool"`
	Query  json.RawMessage `json:"query"`
	Reason string          `json:"reason,omitempty"`
}

ScanQuery is an approved scan (log.scan): a search, count, patterns or trace call over the free budgets, approved once (class read) for its whole plan. Query is that tool's parameters; with its cursor it continues the approved plan, free and with no new approval.

type ScanResult

type ScanResult struct {
	Scan   ScanInfo `json:"scan"`
	Tool   string   `json:"tool"`
	Result any      `json:"result"`
}

ScanResult is the answer of a scan: the tool's own result (its cursor continues the scan) and the scan.

type Scanned

type Scanned struct {
	Bytes    int64 `json:"bytes"`
	Records  int64 `json:"records"`
	Granules int   `json:"granules"`
}

Scanned is the plan-based work of a call.

type Sealer

type Sealer struct {
	// contains filtered or unexported fields
}

Sealer seals refs and cursors (AEAD) and computes the keyed digests plans are bound to (HMAC), each under its own subkey derived from K_ref with HKDF-SHA256 (info "locklog/aead/v1" and "locklog/hmac/v1"): one key is never used by two primitives. Nothing it produces can be opened, forged or guessed offline without K_ref, which never leaves the console's home volume: an agent holding a ref or a cursor learns nothing from it, and plan hashes and record digests are never returned on the agent socket at all.

func NewSealer

func NewSealer(key []byte) (*Sealer, error)

NewSealer uses a 32-byte key.

func (*Sealer) Digest

func (s *Sealer) Digest(domain string, parts ...[]byte) string

Digest is HMAC-SHA256(K_hmac, domain ‖ (0 ‖ len ‖ part)…), base64url (unpadded); K_hmac is derived from K_ref. It is the only hash used for plan binding and record identity (round-6 amendment: never an unkeyed hash of a record).

func (*Sealer) Open

func (s *Sealer) Open(prefix, sealed string, v any) error

Open decrypts a sealed value of the given prefix into v.

func (*Sealer) Seal

func (s *Sealer) Seal(prefix string, v any) (string, error)

Seal encrypts v (JSON) under a prefix that names its type ("Lr1", "Lc1").

type SearchResult

type SearchResult struct {
	Epoch        string            `json:"epoch"`
	Records      []json.RawMessage `json:"records"`
	Complete     bool              `json:"complete"`
	CoveredUntil *time.Time        `json:"covered_until,omitempty"`
	Cursor       string            `json:"cursor,omitempty"`
	Scanned      Scanned           `json:"scanned"`
	Masking      Masking           `json:"masking"`
	// MaskedFailures counts records withheld whole because masking
	// failed. With the opt-in pre-filter and a needle in the query (text
	// or contains), it is not counted and stays 0: only the records
	// holding the needles are masked, so the count would tell which
	// failing records hold them (prefilter.go, docs/usage.md).
	MaskedFailures int `json:"masked_failures,omitempty"`
}

SearchResult is the answer of a search (design §3.4).

type SourceBudget

type SourceBudget struct {
	Name      string `json:"name"`
	BytesLeft int64  `json:"bytes_left"`
}

SourceBudget is one source's rolling budget left.

type TailQuery

type TailQuery struct {
	Sources  []string `json:"sources,omitempty"`
	Where    []Pred   `json:"where,omitempty"`
	Text     string   `json:"text,omitempty"`
	Regex    string   `json:"regex,omitempty"`
	Fields   []string `json:"fields,omitempty"`
	Duration string   `json:"duration,omitempty"` // default and ceiling 50s
	Max      int      `json:"max,omitempty"`
}

TailQuery is a tail (locklog_tail): the new records matching a filter for up to Duration, at most Max of them.

type TailResult

type TailResult struct {
	Epoch          string            `json:"epoch"`
	Records        []json.RawMessage `json:"records"`
	Complete       bool              `json:"complete"`
	Scanned        Scanned           `json:"scanned"`
	Masking        Masking           `json:"masking"`
	MaskedFailures int               `json:"masked_failures,omitempty"`
}

TailResult is the answer of a tail: the new records in time order. Complete is false when the tail may have left records out: it stopped early (at max, a poll failed or could not cover what was written, the call's byte budget ran out), the output cap dropped records, a record was still being written at the end, or records without a time were seen (they are never tailed: search them).

type TraceQuery

type TraceQuery struct {
	Sources []string `json:"sources,omitempty"`
	// ID is a clear id (as the agent saw it in a result) or a pseudonym
	// ⟦kind:ID⟧.
	ID   string    `json:"id"`
	From time.Time `json:"from"`
	To   time.Time `json:"to"`
	// Last, when set, resolves the range at validation (see Query).
	Last   string   `json:"last,omitempty"`
	Fields []string `json:"fields,omitempty"`
	Limit  int      `json:"limit,omitempty"`
	Cursor string   `json:"cursor,omitempty"`
}

TraceQuery is a trace (locklog_trace): the records of a range that carry one id (a request or trace id shown in clear) or one pseudonym, across sources, as one timeline.

type UnmaskQuery

type UnmaskQuery struct {
	Ref        string   `json:"ref"`
	Pseudonyms []string `json:"pseudonyms"`
	Reason     string   `json:"reason"`
}

UnmaskQuery asks for the clear values of exact pseudonyms of one record (log.unmask), with the agent's reason for the human.

type UnmaskResult

type UnmaskResult struct {
	Epoch  string        `json:"epoch"`
	Values []UnmaskValue `json:"values"`
}

UnmaskResult is the answer of an approved unmask: those spans' clear values, once.

func (UnmaskResult) AuditPseudonyms

func (r UnmaskResult) AuditPseudonyms() []string

AuditPseudonyms names what was revealed by pseudonym (never the value).

type UnmaskValue

type UnmaskValue struct {
	Pseudonym string `json:"pseudonym"`
	Value     string `json:"value"`
}

UnmaskValue is one pseudonym's clear value.

Directories

Path Synopsis
Package session opens the log engine of an approved policy for the console (logconsole.Opener): the masker of each source, the local index in the console's state directory, the sources the policy names (given only the mount roots the launcher checked), the keys (K_ref, pseudonym epochs), the cost model and the budgets, and serves the agent's methods over it.
Package session opens the log engine of an approved policy for the console (logconsole.Opener): the masker of each source, the local index in the console's state directory, the sources the policy names (given only the mount roots the launcher checked), the keys (K_ref, pseudonym epochs), the cost model and the budgets, and serves the agent's methods over it.

Jump to

Keyboard shortcuts

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