Documentation
¶
Overview ¶
Package flowstore defines the FlowStore interface and its Postgres implementation. All flow reads and writes go through the interface; no other package issues SQL against flow tables. The schema is column-shaped and time-partitioned so a columnar backend can be added behind the same interface when a real estate's scale demands it (ADR-0009).
Package flowstore defines the FlowStore interface and its Postgres implementation. All flow reads and writes go through the interface; no other package issues SQL against flow tables. The schema is column-shaped so a columnar backend can be added behind the same interface when a real estate's scale demands it (ADR-0009, ADR-0019).
Index ¶
- Constants
- Variables
- func GroupByNames() string
- type Endpoint
- type FlowStore
- type Gap
- type GapQuery
- type GapRow
- type GapWriter
- type Group
- type GroupBy
- type GroupOrder
- type GroupQuery
- type GroupResult
- type Peer
- type PeerKind
- type Postgres
- func (p *Postgres) CountWindows(ctx context.Context) (int64, error)
- func (p *Postgres) ListGaps(ctx context.Context, q GapQuery) ([]GapRow, error)
- func (p *Postgres) ListTotals(ctx context.Context, id identity.WorkloadID, ...) ([]Total, error)
- func (p *Postgres) ListWindowPage(ctx context.Context, q WindowPageQuery) ([]WindowRow, error)
- func (p *Postgres) ListWindows(ctx context.Context, q WindowQuery) ([]WindowRow, error)
- func (p *Postgres) PruneWindows(ctx context.Context, horizon time.Time) (int64, bool, error)
- func (p *Postgres) Rollup(ctx context.Context, q RollupQuery) ([]RollupRow, error)
- func (p *Postgres) RollupGroups(ctx context.Context, q GroupQuery) (*GroupResult, error)
- func (p *Postgres) Within(tx pgx.Tx) GapWriter
- func (p *Postgres) WriteGaps(ctx context.Context, id identity.WorkloadID, gaps []Gap) error
- func (p *Postgres) WriteWindow(ctx context.Context, w Window) (int, error)
- type Pruner
- type PrunerFunc
- type Record
- type Retention
- type RollupQuery
- type RollupRow
- type Total
- type TotalsKey
- type Window
- type WindowCursor
- type WindowPageQuery
- type WindowQuery
- type WindowRow
Constants ¶
const ( DefaultGroupLimit = 200 MaxGroupLimit = 1000 )
DefaultGroupLimit is the number of groups a grouped rollup returns when the query sets none; MaxGroupLimit is the most it ever returns. A rollup with more groups than its limit returns the top groups in its order and says so; the screens show top-N with totals, never an unbounded list (ADR-0007 as amended).
const ( DefaultPageLimit = 100 MaxPageLimit = 500 )
DefaultPageLimit is the number of windows a page holds when the query sets none; MaxPageLimit is the most a page ever holds.
const DefaultRetention = 30 * 24 * time.Hour
DefaultRetention is how long windows are kept unless configured otherwise. Totals are never pruned.
const DefaultRetentionInterval = time.Hour
DefaultRetentionInterval is how often a replica attempts a pruning run.
Variables ¶
var ErrUnknownEndpoint = errors.New("flowstore: unknown endpoint")
ErrUnknownEndpoint is returned for an endpoint other than dst or either.
var ErrUnknownGroupBy = errors.New("flowstore: unknown grouping")
ErrUnknownGroupBy is returned for a grouping outside GroupBys.
var GroupBys = []GroupBy{GroupByRule, GroupByRulePeer, GroupBySrcDst, GroupByDstService, GroupByPeerService}
GroupBys lists every grouping, in the order a document names them.
Functions ¶
func GroupByNames ¶
func GroupByNames() string
GroupByNames joins the allowed groupings for a message.
Types ¶
type Endpoint ¶ added in v0.2.0
type Endpoint string
Endpoint says which end of a stored record a grouped rollup's workload set selects. A record is reported by its destination workload and names its source as the peer ingestion resolved (ADR-0019 decision 2), so a workload set can select either end without resolving anything at read time but the set itself.
const ( // EndpointDst selects the records the workloads in the set reported: // the traffic that reached them. The default. EndpointDst Endpoint = "dst" // EndpointEither also selects the records whose source resolved to a // workload in the set: the traffic they sent as well as received. A // record with both ends in the set is selected once. EndpointEither Endpoint = "either" )
Endpoints of a grouped rollup's workload set.
func ParseEndpoint ¶ added in v0.2.0
ParseEndpoint resolves an endpoint name; empty is the default, dst.
type FlowStore ¶
type FlowStore interface {
// WriteWindow stores every record of w, folds each into the totals,
// and stores its gaps, atomically; a gap already stored is not stored
// again. It returns the number of records stored.
WriteWindow(ctx context.Context, w Window) (int, error)
// WriteGaps stores gaps the control plane derived itself for one
// workload, outside any reported window; a gap already stored is not
// stored again. The agent-reported gaps ride WriteWindow.
WriteGaps(ctx context.Context, id identity.WorkloadID, gaps []Gap) error
// ListGaps returns the gaps of a workload set intersecting a range.
ListGaps(ctx context.Context, q GapQuery) ([]GapRow, error)
// ListWindows returns a workload's stored windows, newest first.
ListWindows(ctx context.Context, q WindowQuery) ([]WindowRow, error)
// Rollup groups the windows of a workload set by peer and service.
Rollup(ctx context.Context, q RollupQuery) ([]RollupRow, error)
// RollupGroups aggregates windows under one of the named groupings,
// ordered and bounded as the query says. It returns ErrUnknownGroupBy
// for a grouping outside GroupBys.
RollupGroups(ctx context.Context, q GroupQuery) (*GroupResult, error)
// ListWindowPage returns one page of a workload's windows, newest
// first, continuing after the query's cursor.
ListWindowPage(ctx context.Context, q WindowPageQuery) ([]WindowRow, error)
// ListTotals returns a workload's cumulative flow keys, most recently
// seen first. A zero decision means every decision.
ListTotals(ctx context.Context, id identity.WorkloadID, decision innerwallv1.PolicyDecision) ([]Total, error)
// PruneWindows deletes windows that started before horizon, and gaps
// that ended before it, in bounded batches. It reports how many window
// rows it deleted and whether it ran at all: only one replica prunes at
// a time, and a replica that finds the retention lock held by another
// returns ran == false and deleted == 0.
PruneWindows(ctx context.Context, horizon time.Time) (deleted int64, ran bool, err error)
}
FlowStore is the only path to flow data (ADR-0009). Every method is implemented with hand-written SQL in internal/store/queries (ADR-0006).
type Gap ¶
type Gap struct {
Kind innerwallv1.EvidenceGapKind
Source innerwallv1.EvidenceSource
From time.Time
To time.Time
Count *uint64
}
Gap is one interval [From, To) in which a workload's flow evidence is known to be incomplete: as its agent reported it, or, for the one server-derived kind (agent silence), as flow ingest derived it. Count is the records or table entries lost, nil when unknown (ADR-0019 as amended).
type GapQuery ¶
type GapQuery struct {
WorkloadIDs []identity.WorkloadID
Since time.Time
Until time.Time
Limit int
}
GapQuery selects the gaps of a set of workloads that intersect [Since, Until), newest first, at most Limit of them. An empty set means every workload.
type GapWriter ¶ added in v0.5.0
type GapWriter interface {
WriteGaps(ctx context.Context, id identity.WorkloadID, gaps []Gap) error
}
GapWriter stores the gaps the control plane derived itself for one workload, as FlowStore.WriteGaps does; a gap already stored is not stored again. The Postgres store hands one out bound to a caller's transaction (Postgres.Within).
type Group ¶
type Group struct {
RuleID string
Peer Peer
WorkloadID identity.WorkloadID
DstPort uint16
Protocol innerwallv1.Protocol
// WorkloadCount is the number of distinct workloads that saw the
// group. Only rule and peer,service count it; it is zero for the
// others: src,dst and dst,service name their one workload in their
// keys, and rule,peer, whose key names none, is not counted.
WorkloadCount int64
// FlowCount is the number of stored records in the group.
FlowCount int64
ConnectionCount uint64
ByteCount uint64
FirstSeen time.Time
LastSeen time.Time
}
Group is one group of a grouped rollup. Which key fields are set depends on the grouping: RuleID for rule and rule,peer; Peer for rule,peer, src,dst, and peer,service; WorkloadID for src,dst and dst,service; DstPort and Protocol for dst,service and peer,service. The peer's labels are the snapshot stored with its most recently seen record in the group (ADR-0019).
type GroupBy ¶
type GroupBy string
GroupBy names one grouping of the windows the store serves. The set is closed: each member maps to one named statement in the store, chosen by the caller, and there is no free-form grouping (ADR-0007 as amended, ADR-0019). The names are the ones the operator surface accepts.
const ( // GroupByRule groups by the resolved rule that admitted the traffic; // records with no matched rule form one group with the empty rule id. // Rule hit counters, with the distinct workloads that reported each. GroupByRule GroupBy = "rule" // GroupByRulePeer groups by rule and the resolved peer that hit it. GroupByRulePeer GroupBy = "rule,peer" // GroupBySrcDst groups by resolved peer and reporting workload: the // edges of the dependency map, and the rows of a would-block review. GroupBySrcDst GroupBy = "src,dst" // GroupByDstService groups by reporting workload and the service // (destination port and protocol) reached on it: the matrix cells. GroupByDstService GroupBy = "dst,service" // GroupByPeerService groups by resolved peer and the service it // reached across every workload in scope, counting the distinct // workloads that saw it: the rows of a simulation review (ADR-0019 // decision 4(b)). GroupByPeerService GroupBy = "peer,service" )
The groupings the store serves. Inbound only in this version (ADR-0010), so the source of a flow is its resolved peer and the destination is the reporting workload; "peer" and "src" name that same resolved-peer dimension, each under the name the screen that uses it speaks.
func ParseGroupBy ¶
ParseGroupBy resolves a comma-joined grouping, tolerating spaces around the names. It accepts the members of GroupBys and nothing else.
type GroupOrder ¶
type GroupOrder string
GroupOrder says how a grouped rollup is ordered before it is bounded.
const ( // OrderByConnections puts the busiest groups first. The default. OrderByConnections GroupOrder = "connections" // OrderByRecency puts the most recently seen groups first. OrderByRecency GroupOrder = "recent" )
Orders of a grouped rollup.
type GroupQuery ¶
type GroupQuery struct {
GroupBy GroupBy
WorkloadIDs []identity.WorkloadID
Endpoint Endpoint
Since time.Time
Until time.Time
Decision innerwallv1.PolicyDecision
Direction innerwallv1.Direction
Protocol innerwallv1.Protocol
DstPort uint16
Order GroupOrder
// Limit bounds the groups returned; DefaultGroupLimit when zero,
// never more than MaxGroupLimit.
Limit int
}
GroupQuery is a grouped rollup over the windows in [Since, Until). An empty WorkloadIDs means every workload; Endpoint says which end of a record a non-empty WorkloadIDs selects, the destination when zero; a zero Decision or Direction means any; a zero Protocol means every service, otherwise exactly the service (Protocol, DstPort).
type GroupResult ¶
type GroupResult struct {
Groups []Group
EffectiveFrom time.Time
EffectiveTo time.Time
GroupCount int64
FlowCount int64
ConnectionCount uint64
ByteCount uint64
}
GroupResult is a grouped rollup. EffectiveFrom and EffectiveTo are the bounds of the windows actually covered, which quantize the requested range to window boundaries; both are zero when no window matched. GroupCount and the totals cover every group, including those beyond the limit.
func (*GroupResult) Truncated ¶
func (r *GroupResult) Truncated() bool
Truncated reports whether groups beyond those returned exist.
type Peer ¶
Peer is the resolved remote end of a flow as captured at ingest. Labels is the workload's label set at that moment and is empty for the other kinds; it is stored with the row because labels and addresses change and a query-time join would rewrite what the operator saw (ADR-0019).
type PeerKind ¶
type PeerKind int32
PeerKind says what a flow record's source address resolved to at ingest.
const ( // PeerUnknown is an address that matched no workload and no address // group; the peer key is the address itself. PeerUnknown PeerKind = 0 // PeerWorkload is a managed workload; the peer key is its id. PeerWorkload PeerKind = 1 // PeerAddressGroup is a named address group; the peer key is its id. PeerAddressGroup PeerKind = 2 )
Peer kinds, in the order the schema encodes them.
type Postgres ¶
type Postgres struct {
// contains filtered or unexported fields
}
Postgres is the FlowStore over the control plane's database.
func (*Postgres) CountWindows ¶
CountWindows returns the number of stored windows. Exposed for tests and diagnostics.
func (*Postgres) ListTotals ¶
func (p *Postgres) ListTotals(ctx context.Context, id identity.WorkloadID, decision innerwallv1.PolicyDecision) ([]Total, error)
ListTotals implements FlowStore.
func (*Postgres) ListWindowPage ¶
ListWindowPage implements FlowStore.
func (*Postgres) ListWindows ¶
ListWindows implements FlowStore.
func (*Postgres) PruneWindows ¶
PruneWindows implements FlowStore. Each batch is its own transaction holding the retention lock, so a run never holds row locks for the whole backlog and another replica can take over between batches. Gaps ride the same batches, and a run continues while either table fills its batch.
func (*Postgres) RollupGroups ¶
func (p *Postgres) RollupGroups(ctx context.Context, q GroupQuery) (*GroupResult, error)
RollupGroups implements FlowStore. Each grouping is its own statement; the query's grouping chooses which one runs and nothing is assembled.
func (*Postgres) Within ¶ added in v0.5.0
Within returns a writer of control-plane-derived gaps bound to tx, for a gap that must commit or roll back with the caller's transaction: the silence a workload's retirement closes is written with the retirement itself (ADR-0023). The SQL stays here, behind the interface.
type Pruner ¶
Pruner is a bounded deletion that rides the retention schedule instead of having a schedule of its own: expired operator sessions are the first (ADR-0021). It reports how many rows it removed. A pruner must be safe to run on every replica at once, because unlike window pruning it is not serialized by the retention lock.
type PrunerFunc ¶
PrunerFunc adapts a function to Pruner.
type Record ¶
type Record struct {
Peer Peer
SrcAddress netip.Addr
DstAddress netip.Addr
DstPort uint16
Protocol innerwallv1.Protocol
Direction innerwallv1.Direction
Decision innerwallv1.PolicyDecision
MatchedRuleID string
ConnectionCount uint64
ByteCount uint64
FirstSeen time.Time
LastSeen time.Time
ProcessName string
}
Record is one aggregated flow observation as the store keeps it: what the agent reported plus the resolved peer.
func AggregateTotals ¶
AggregateTotals folds the records of one window into one record per totals key, which is the shape the upsert needs: a statement may touch a given totals row once, and several records in a window can share a key when two source addresses resolve to the same peer. Counts add, the first-seen instant is the earliest, the last-seen the latest, and the label snapshot, matched rule, and addresses come from the latest-seen record. The result is in a deterministic order.
type Retention ¶
type Retention struct {
Store FlowStore
// Pruners run after the windows on every round, each in turn; one
// failing does not stop the others.
Pruners []Pruner
// Horizon is the age beyond which windows are deleted;
// DefaultRetention when zero.
Horizon time.Duration
// Interval is the period between runs; DefaultRetentionInterval when
// zero.
Interval time.Duration
Log *slog.Logger
// Now is the clock; time.Now if nil.
Now func() time.Time
}
Retention prunes aged windows on a schedule. Every replica runs one; the store's advisory lock makes sure only one prunes at a time, and a replica that finds the lock held skips the round (ADR-0017). This is a scheduled maintenance job with a fixed period, not a poll for work: it runs whether or not there is anything to delete, and nothing waits on it.
type RollupQuery ¶
type RollupQuery struct {
WorkloadIDs []identity.WorkloadID
Since time.Time
Until time.Time
Decision innerwallv1.PolicyDecision
}
RollupQuery aggregates the windows of a set of workloads (a label scope, resolved by the caller) in [Since, Until) by resolved peer and service. A zero Decision means every decision.
type RollupRow ¶
type RollupRow struct {
Peer Peer
DstPort uint16
Protocol innerwallv1.Protocol
Decision innerwallv1.PolicyDecision
Workloads int64
ConnectionCount uint64
ByteCount uint64
FirstSeen time.Time
LastSeen time.Time
}
RollupRow is one group of a rollup: a resolved peer talking to one service (port and protocol) with one decision, across every workload in scope. Workloads counts the distinct workloads that saw it.
type Total ¶
type Total struct {
WorkloadID identity.WorkloadID
Peer Peer
DstPort uint16
Protocol innerwallv1.Protocol
Direction innerwallv1.Direction
Decision innerwallv1.PolicyDecision
MatchedRuleID string
FirstSeen time.Time
LastSeen time.Time
ConnectionCount uint64
ByteCount uint64
WindowCount uint64
}
Total is the cumulative record of one flow key since it was first seen. It is upserted at ingest and never pruned, so "since first seen" verdicts come from one row rather than a scan of the windows (ADR-0019).
type TotalsKey ¶
type TotalsKey struct {
PeerKind PeerKind
PeerKey string
DstPort uint16
Protocol innerwallv1.Protocol
Direction innerwallv1.Direction
Decision innerwallv1.PolicyDecision
}
TotalsKey identifies one flow_totals row.
type Window ¶
type Window struct {
WorkloadID identity.WorkloadID
Start time.Time
End time.Time
Records []Record
Gaps []Gap
}
Window is one reporting window from one workload, resolved and ready to store, with the evidence gaps that rode it.
type WindowCursor ¶
WindowCursor is the position after which a page continues: the (window start, id) of the last row served, since pages run newest first.
type WindowPageQuery ¶
type WindowPageQuery struct {
WorkloadID identity.WorkloadID
Since time.Time
Until time.Time
Decision innerwallv1.PolicyDecision
Direction innerwallv1.Direction
PeerKey string
Protocol innerwallv1.Protocol
DstPort uint16
Before *WindowCursor
Limit int
}
WindowPageQuery selects one page of a workload's windows in [Since, Until), newest first, after Before. A nil Before is the first page. A zero Decision or Direction means any; an empty PeerKey means every peer; a zero Protocol means every service.
type WindowQuery ¶
type WindowQuery struct {
WorkloadID identity.WorkloadID
Since time.Time
Until time.Time
Decision innerwallv1.PolicyDecision
Limit int
}
WindowQuery selects a workload's windows in [Since, Until). A zero Decision means every decision; Limit bounds the rows returned, newest window first.