Documentation
¶
Overview ¶
Package netmon is the offline core of Phase 6: network flow observability and egress analysis. It consumes recorded connection/DNS events (from a fixture today, from Phase-5 eBPF telemetry tomorrow — see Source), attributes each flow to a process+container+workload, aggregates per-workload flow logs, and runs deterministic detection heuristics (C2 beaconing, data exfiltration, lateral movement, cloud-metadata access, DNS tunnelling/DGA).
It deliberately knows nothing about the CLI or HTTP frontends. The engine module in internal/modules/netpolicy adapts the anomalies and generated policies produced here onto the unified Finding model. The only shared dependency is engine.Severity, so an anomaly's risk speaks the same language as every other finding in the tool.
Determinism is a hard requirement: nothing here reads the wall clock or a random source. Any notion of "now" is injected through Options, and every slice the package returns is sorted by a stable key. Same capture in, byte identical anomalies and policies out — that is what makes the golden tests meaningful and what lets an agent trust a generated allowlist.
Index ¶
- Variables
- func IsExternal(ip string) bool
- func IsIMDS(ip string) bool
- func IsInternal(ip string) bool
- type Anomaly
- type AnomalyKind
- type Capture
- type DNSEvent
- type DestStat
- type Direction
- type EgressClass
- type EgressIntent
- type Endpoint
- type Flow
- type FlowLog
- type LiveSource
- type Options
- type Protocol
- type RecordedSource
- type Report
- type Source
- type Verdict
- type Workload
Constants ¶
This section is empty.
Variables ¶
ErrLiveCaptureUnavailable is returned by the live Source until real capture is wired. It is a sentinel so callers can distinguish "no capture backend" from a genuine runtime failure and fall back to a recorded Source.
Functions ¶
func IsExternal ¶
IsExternal reports whether an IP is a parseable, routable address outside the private/reserved ranges. An empty or unparseable string is not a valid external IP, so it returns false (the FQDN-only case is handled separately).
func IsInternal ¶
IsInternal reports whether an IP falls in a private/reserved range. An unparseable or empty IP is treated as external so we never under-report egress to an unknown host.
Types ¶
type Anomaly ¶
type Anomaly struct {
Kind AnomalyKind
Severity engine.Severity
Workload string
Dest string // human destination summary, "" for workload-scoped anomalies
Title string // one human line
Detail string // why it fired
Score float64 // 0..1 confidence, deterministic
Evidence map[string]string
}
Anomaly is one detection result, scoped to a workload. Severity speaks the engine's language so the module adapter is a straight projection. Evidence is a small, machine-consumable map an agent can reason over — the "explain" half of the AI-age mandate lives here.
type AnomalyKind ¶
type AnomalyKind string
AnomalyKind names a detected behaviour. Kinds are stable strings so the netpolicy module can map each to a DS-RAT-NET rule id and the right references.
const ( KindIMDS AnomalyKind = "imds_access" KindBeacon AnomalyKind = "c2_beacon" KindExfil AnomalyKind = "exfil_volume" KindLowAndSlow AnomalyKind = "exfil_low_and_slow" KindLateral AnomalyKind = "lateral_movement" KindDNSTunnel AnomalyKind = "dns_tunnel" KindDGA AnomalyKind = "dns_dga" KindBlockedEg AnomalyKind = "blocked_egress" KindAgentEgress AnomalyKind = "agent_egress_unknown" // AI-age feature (off by default) KindAnomalousEg AnomalyKind = "anomalous_egress" // AI-age feature (off by default) KindHostNetwork AnomalyKind = "host_network" KindFreeForAll AnomalyKind = "unrestricted_east_west" )
type Capture ¶
type Capture struct {
// PolicyMode describes what egress enforcement was in place when the capture
// was taken ("none", "audit", "enforce"). Absent means unknown/none, which is
// itself worth flagging.
PolicyMode string `json:"policy_mode,omitempty"`
Workloads []Workload `json:"workloads"`
Flows []Flow `json:"flows"`
DNS []DNSEvent `json:"dns,omitempty"`
}
Capture is a recorded window of network telemetry: the workloads seen and the flow and DNS events attributed to them. It is the unit a Source yields and the shape of every fixture under testdata/.
func DecodeCapture ¶
DecodeCapture parses a JSON capture from r with the same size bound as LoadCapture. It rejects trailing garbage so a truncated-then-padded file is caught rather than silently half-read.
func LoadCapture ¶
LoadCapture reads a JSON capture from a file path, enforcing the size bound, and returns it normalized and ready to analyse.
type DNSEvent ¶
type DNSEvent struct {
WorkloadID string `json:"workload_id"`
TSUnix int64 `json:"ts_unix"`
QName string `json:"qname"`
QType string `json:"qtype"` // A, AAAA, TXT, NULL, ...
RCode string `json:"rcode,omitempty"` // NOERROR, NXDOMAIN, ...
Answers []string `json:"answers,omitempty"`
}
DNSEvent is a single resolver query/response, attributed to a workload. It is the raw material for tunnelling and DGA detection and for turning IP flows into FQDN egress allowlists.
type DestStat ¶
type DestStat struct {
Key string // FQDN:port when known, else IP:port
Host string // FQDN if known else IP
FQDN string
IP string
Port uint16
Proto Protocol
Internal bool
IMDS bool
Count int
BytesTx int64
BytesRx int64
FirstTS int64
LastTS int64
Denied int // flows with a deny verdict
Timestamps []int64 // per-connection timestamps, ascending — beaconing input
}
DestStat rolls up every egress flow to a single logical destination. It is the unit beaconing/exfil reason over and the unit a policy allow-rule maps to.
type Direction ¶
type Direction string
Direction is the flow direction relative to the attributed workload.
type EgressClass ¶
type EgressClass string
EgressClass labels a destination's inferred intent.
const ( // ClassIntended is a destination the workload contacts consistently — the // baseline that a least-privilege allowlist should permit. ClassIntended EgressClass = "intended" // ClassAnomalous is a rare, one-off, or otherwise unexpected destination that // warrants review before it enters an allowlist. ClassAnomalous EgressClass = "anomalous" )
type EgressIntent ¶
type EgressIntent struct {
Dest string
Class EgressClass
Reasons []string
Count int
BytesTx int64
FQDN string
Confidence float64
}
EgressIntent is the classification of one destination, with a rationale that is both human-readable and machine-parseable (the reasons slice).
func ClassifyEgress ¶
func ClassifyEgress(fl *FlowLog, o Options) []EgressIntent
ClassifyEgress buckets a workload's external destinations into intended vs anomalous using deterministic, explainable rules (no model, no randomness). It is exported so both the intent detector and policy generation can reuse the same reasoning — an agent applying a policy sees exactly why each entry is on (or off) the allowlist. Results are sorted by destination for stability.
type Endpoint ¶
type Endpoint struct {
IP string `json:"ip,omitempty"`
Port uint16 `json:"port,omitempty"`
FQDN string `json:"fqdn,omitempty"`
Workload string `json:"workload,omitempty"`
Namespace string `json:"namespace,omitempty"`
}
Endpoint is one side of a flow. For an external destination IP and Port are set and FQDN is filled in when DNS correlation resolved the name; for an in-cluster peer Workload/Namespace identify it independently of its ephemeral IP (the whole point of identity-based policy).
type Flow ¶
type Flow struct {
// WorkloadID links the flow to a Workload in the same Capture.
WorkloadID string `json:"workload_id"`
TSUnix int64 `json:"ts_unix"`
Proto Protocol `json:"proto"`
Direction Direction `json:"direction"`
Src Endpoint `json:"src"`
Dst Endpoint `json:"dst"`
BytesTx int64 `json:"bytes_tx"`
BytesRx int64 `json:"bytes_rx"`
Verdict Verdict `json:"verdict,omitempty"`
// Process attribution (the eBPF forensic correlation we get from Phase 5).
Process string `json:"process,omitempty"`
PID int `json:"pid,omitempty"`
}
Flow is a single observed connection record, already attributed to the workload that opened it and the process inside that workload.
type FlowLog ¶
type FlowLog struct {
Workload Workload
Egress []Flow
Ingress []Flow
DNS []DNSEvent
// Dests aggregates egress by destination (FQDN-preferred key) in stable order.
Dests []*DestStat
}
FlowLog is one workload's attributed traffic, split into destinations for egress analysis. It is the searchable per-flow view the market-parity checklist asks for (src/dst identity, port, verdict), grouped for reuse.
func BuildFlowLogs ¶
BuildFlowLogs groups a normalized capture into per-workload flow logs, sorted by workload id. A flow whose WorkloadID has no matching Workload descriptor is still attributed under a synthesized identity so telemetry gaps never drop coverage.
func (*FlowLog) ExternalDests ¶
ExternalDests returns the routable-internet destinations in stable order — the set egress policy and exfil detection care about.
type LiveSource ¶
type LiveSource struct {
// Iface optionally names the interface to attach to; unused until wired.
Iface string
}
LiveSource is the placeholder for the Linux eBPF flow/DNS probe. On Linux the real implementation will attach kprobes/tracepoints (or consume Phase-5 telemetry) and stream Flow/DNSEvent records. Loading an eBPF program requires a loader dependency and CAP_BPF, which are out of scope for the deterministic offline core, so this returns the sentinel today. The interface is identical to the non-Linux stub so the rest of the package is platform-agnostic.
func NewLiveSource ¶
func NewLiveSource(iface string) *LiveSource
NewLiveSource returns a live capture source for the given interface.
type Options ¶
type Options struct {
// Now is the reference time. Zero means "derive from the capture window",
// which keeps analysis deterministic without any wall-clock read.
Now time.Time
// Beaconing.
BeaconMinSamples int // minimum connections to a dest before periodicity is meaningful
BeaconMaxCV float64 // max coefficient of variation of intervals to call it regular
BeaconMaxBytes int64 // per-connection byte ceiling; beacons are small
// Exfiltration.
ExfilMinBytes int64 // total egress bytes to one external dest to flag volume exfil
ExfilTxRxRatio float64 // tx:rx ratio above which traffic is upload-heavy
LowSlowMinBytes int64 // cumulative bytes for low-and-slow
LowSlowMinSpanSec int64 // minimum duration for low-and-slow
// Lateral movement.
LateralMinPeers int // distinct internal peers before fan-out is suspicious
// DNS.
DNSTunnelMinQueries int // queries under one parent before tunnelling is considered
DNSEntropyThreshold float64 // Shannon entropy (bits/char) above which a label looks generated
DGAMinNXDomain int // NXDOMAIN responses before a DGA is considered
// AI-age features (off by default).
EnableAgentEgress bool // flag AI-agent workloads reaching unknown model/inference hosts
EnableIntent bool // cluster destinations into intended vs anomalous
IntentMinCount int // connection count at/above which a dest is "intended" (baseline)
}
Options tunes detection. The zero value is a safe, deterministic default with the AI-age features OFF — callers opt in explicitly. Thresholds are exposed so they can be tightened per environment without editing the heuristics.
type RecordedSource ¶
type RecordedSource struct {
// contains filtered or unexported fields
}
RecordedSource replays a Capture that was loaded from a fixture or an upstream recorder. It is the deterministic Source used in tests and in the offline `dsecrat net` path.
func NewRecordedSource ¶
func NewRecordedSource(c *Capture) *RecordedSource
NewRecordedSource wraps an already-loaded capture as a Source.
type Report ¶
Report is the full deterministic result of analysing a capture: the derived per-workload flow logs plus every anomaly, sorted stably.
type Source ¶
type Source interface {
// Capture collects the currently available events. It must respect ctx and
// must not block indefinitely.
Capture(ctx context.Context) (*Capture, error)
}
Source yields a window of observed network telemetry. Implementations must return a Capture whose events are attributed to workloads; callers Normalize before analysis.
type Verdict ¶
type Verdict string
Verdict records whether the dataplane allowed or dropped a flow. A capture taken in policy "audit" mode carries verdicts even when nothing is enforced, which is what lets us alert on unexpected drops (or unexpected allows).
type Workload ¶
type Workload struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Kind string `json:"kind,omitempty"`
HostNetwork bool `json:"host_network,omitempty"`
}
Workload is the identity a flow is attributed to. Kind carries an optional classification ("agent" marks an AI-agent workload, which the agent-egress governance feature keys off).