Documentation
¶
Overview ¶
Package sanction decides what a trigger becomes (SPEC §5.4): the local checks — exemptions first, trust bounds next — then the policy ladder, escalation and port scope. The trust threshold multiplier acts at detection, not here: CountScale plugs into the evaluator so that a trusted origin's rules simply need more evidence before they fire.
What this agent cannot resolve locally is skipped and reported, never silently applied wrong: trust and exemption entries for countries and ASNs need the enrichment databases and are listed in Skipped until those arrive.
Index ¶
- Constants
- type Decider
- func (d *Decider) Active() int
- func (d *Decider) ActiveFor(value string, now time.Time) (store.Sanction, bool)
- func (d *Decider) Commit(dec Decision)
- func (d *Decider) CommitRecord(key string, id int64)
- func (d *Decider) CountScale(group string, count int) int
- func (d *Decider) Decide(trg eval.Trigger, now time.Time) (Decision, error)
- func (d *Decider) Exempt(prefix netip.Prefix, until time.Time, note string)
- func (d *Decider) ExemptSelf(addrs []netip.Addr)
- func (d *Decider) Forget(value string)
- func (d *Decider) Knows(value netip.Prefix) bool
- func (d *Decider) Misses() int64
- func (d *Decider) Note(sn store.Sanction)
- func (d *Decider) SetMaxActive(n int)
- type Decision
- type History
- type Outcome
Constants ¶
const ReasonAlreadyRecorded = "an active record already answers this trigger"
ReasonAlreadyRecorded marks the deduplicated detect-only decision, so the caller can log it quietly: a flood of them is one fact, not one line each.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Decider ¶
type Decider struct {
// Skipped lists config entries this agent cannot resolve without the
// enrichment databases (countries, ASNs). Surface them; never let
// them look enforced.
Skipped []string
// contains filtered or unexported fields
}
Decider turns triggers into decisions. Compile once per config load.
func NewDecider ¶
NewDecider compiles the decision side of the configuration.
func (*Decider) ActiveFor ¶
ActiveFor returns a copy of the sanction the index holds in force against a value — what the restore's finish re-asserts to the data plane, so the freshest deadline is the one that lands last.
func (*Decider) Commit ¶
Commit records the result of a decision that was successfully enforced, so the next trigger on the same value is answered from memory. The caller invokes it after the data plane accepted the change.
func (*Decider) CommitRecord ¶ added in v0.0.3
CommitRecord teaches a fresh detect-only dedup entry its store row id, so later returns can be counted against the row. A key the map no longer holds (cleared under flood) is simply gone: the next trigger makes a new row.
func (*Decider) CountScale ¶
CountScale is the evaluator hook (eval.Options.CountScale): a trusted origin's threshold is multiplied before any rule fires, so no evidence is wasted on triggers that trust would refuse.
func (*Decider) Decide ¶
Decide runs the local checks in order (SPEC §5.4): exemptions, trust bounds, then the policy — ladder rung from escalation, duration start, port scope.
func (*Decider) Exempt ¶
Exempt adds an exemption while the agent runs, so that an operator who has just locked themselves out is back in now rather than after an edit and a restart they may no longer be able to perform. The durable copy is a file the CLI writes; this is the same thing in memory, applied at once.
func (*Decider) ExemptSelf ¶ added in v0.0.3
ExemptSelf marks the machine's own addresses as never sanctionable. Each address counts as itself (/32 or /128), never its subnet: the machine is exempt, the neighbourhood is the operator's decision.
func (*Decider) Forget ¶
Forget drops a value from the index — used when a sanction is lifted. It must also leave the recency list, or its element lingers there as an orphan: trimActive pops from the front, and popping an orphan would evict whatever fresher entry now answers to the same value.
func (*Decider) Knows ¶
Knows reports whether the index already holds a sanction for a value — how a boot-time restore tells its snapshot from a decision made since.
func (*Decider) Misses ¶
Misses is how often a decision had to read a sanction back from the store because the index no longer held it — the price of the cap, and the number that says whether it is set too low.
func (*Decider) Note ¶
Note puts one sanction into the index. The boot pass calls it for every active sanction as it streams them into the data plane, so the millions of rows a fleet comes back to are read once and not twice.
func (*Decider) SetMaxActive ¶
SetMaxActive caps the index. Zero leaves it unbounded.
type Decision ¶
type Decision struct {
Outcome Outcome
Reason string
Sanction *store.Sanction // set for enforce, record, expired — and the updated state for absorbed/expanded
Previous *store.Sanction // absorbed/expanded: the state being replaced
// Changed reports whether Sanction differs from Previous — an
// absorption that extends nothing needs no data-plane touch.
Changed bool
// Span is the rung duration the sanction was (re)armed at, carried so
// the caller can Commit it to the in-memory index without a re-query.
Span time.Duration
// RecordKey is set on a fresh detect-only record: once the caller has
// persisted the row, CommitRecord(key, id) teaches the dedup entry
// its row id. RecordID is that id coming back on every deduplicated
// return, so the caller can count the return against the row.
RecordKey string
RecordID int64
}
Decision is the outcome of one trigger.
type History ¶
type History interface {
SanctionCountSince(value string, since time.Time) (int, error)
ActiveSanctionFor(value string, now time.Time) (*store.Sanction, error)
}
History is the durable store behind the decision layer. The active sanction of any value is answered from an in-memory index (below), not from here — a flood must never mean a database round-trip per line. The store is consulted only on the cold paths: the escalation count when a new episode begins, and a value the capped index no longer holds.
type Outcome ¶
type Outcome string
Outcome is what a trigger became.
const ( // OutcomeEnforce: sanction to persist and enforce. OutcomeEnforce Outcome = "enforce" // OutcomeRecord: a detect-only rule — the sanction is computed and // recorded so observed-versus-enforced comparison works, but the data // plane is never touched. OutcomeRecord Outcome = "record" // OutcomeExempt: an exemption removed the sanction. The trigger and // this decision remain observable — never the observation is removed. OutcomeExempt Outcome = "exempt" // OutcomeTrustRefused: a trust bound blocked the sanction (a range // ban inside an origin whose trust forbids it). OutcomeTrustRefused Outcome = "trust-refused" // OutcomeExpired: an evidence-started duration is already over — // history and escalation, no enforcement. OutcomeExpired Outcome = "expired" // OutcomeAbsorbed: an active sanction already answers this trigger // (same service, SPEC §5.4 check 5) — at most its end is extended, // never a new episode, never a rung climbed. OutcomeAbsorbed Outcome = "absorbed" // OutcomeExpanded: the trigger proves the offender attacks a service // the active sanction does not cover — the sanction re-arms at its // current rung and widens its ports. Escalated or expanded, never // duplicated. OutcomeExpanded Outcome = "expanded" )