ingressdefense

package
v0.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package ingressdefense owns provider- and protocol-neutral ingress self-defense policy plus the bounded, process-local, exact-address hostile source state it drives. It is default kernel security for the standard HTTP data plane and stays required when optional feature plugins are absent, so it is not a feature plugin and not infrastructure policy.

The package knows nothing about HTTP paths, headers, auth providers, frontends, routing, providers, storage, or clocks. Request classification and responses belong to internal/stdhttp; configuration decoding and defaults belong to internal/core/config; process and generation composition belongs to internal/infra/runtimebundle.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Policy

type Policy struct {
	// Enabled reports whether self-defense request enforcement runs.
	Enabled bool
	// AuthFailures is the counted unauthenticated-failure threshold that starts
	// or escalates a quarantine when reached inside FailureWindow.
	AuthFailures int
	// FailureWindow bounds the counted authentication failures of one source.
	FailureWindow time.Duration
	// InitialQuarantine is the first-offense duration that later offenses grow
	// from exponentially.
	InitialQuarantine time.Duration
	// MaxQuarantine is the ceiling that the exponential growth saturates at.
	MaxQuarantine time.Duration
	// AdaptiveExemptCIDRs is the read-only allowlist of sources exempt from
	// adaptive state mutation and quarantine. It is never a ban list, it never
	// overrides fixed network policy, and it never creates prefix-keyed state.
	AdaptiveExemptCIDRs []netip.Prefix
}

Policy is the immutable, provider- and protocol-neutral request policy of one generation. It carries only bounded scalars plus the read-only adaptive exemption allowlist, and holds no source state, no credential, no request content, and no aggregate network policy. Callers must treat a compiled value as read-only; the state machine never mutates it.

func (Policy) AdaptiveExempt

func (p Policy) AdaptiveExempt(addr netip.Addr) bool

AdaptiveExempt reports whether addr falls inside the adaptive exemption allowlist. Exemption suppresses adaptive state mutation and quarantine only: it never overrides fixed network policy and never makes an impossible-path request valid.

func (Policy) Validate

func (p Policy) Validate() error

Validate reports whether the policy satisfies the bounded domain invariants the state machine depends on: a finite positive threshold and window, a positive first-offense duration, a ceiling that is not below it, and a normalized exemption allowlist. It applies to a filled, enabled policy: the zero value a nil CompiledSelfDefense projects is not a valid request policy and the state machine records nothing for a disabled one.

type Reason

type Reason string

Reason is a finite denial or transition classification suitable for bounded metric labels. The set is closed: a reason never carries a request path, source address, prefix text, credential, principal, header, User-Agent, or any other attacker-controlled value.

const (
	// ReasonImpossiblePath classifies a deterministic impossible-path rejection.
	ReasonImpossiblePath Reason = "impossible_path"
	// ReasonAuthFailureThreshold classifies quarantine started or escalated by
	// the authentication-failure threshold being reached inside the window.
	ReasonAuthFailureThreshold Reason = "auth_failure_threshold"
	// ReasonActiveQuarantine classifies refusal while a quarantine is active.
	ReasonActiveQuarantine Reason = "active_quarantine"
	// ReasonClientIPError classifies a request whose source address could not be
	// resolved into a usable identity.
	ReasonClientIPError Reason = "client_ip_error"
)

func AllReasons

func AllReasons() []Reason

AllReasons returns the closed reason set in stable metric-label order.

type State

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

State is the bounded, process-local, exact-address adaptive hostile-source state. It is process-owned and shared by every immutable generation: request policy arrives per call, capacity and inactivity TTL are fixed at construction, and nothing is persisted. The state owns no clock, no goroutine, no timer and no I/O: every operation takes the current instant from its caller so expiry is lazy on lookup and mutation and shutdown needs no cleanup.

A lock is held only for one map/ring mutation. No lock is ever held while calling authentication, HTTP handlers, logging, metrics or any other code.

Caller obligations, because the process-owned store outlives every generation and only the two recorders can see a per-generation policy: a caller must check Policy.Enabled before consulting this state at all, since the process store survives a reload that disables self-defense, and it must apply Policy.AdaptiveExempt before IsQuarantined, before RecordProbe, and before RecordAuthFailure.

func NewState

func NewState(limits StateLimits) (*State, error)

NewState constructs the bounded process state for the given limits. Shard count is min(stateShardCount, MaxEntries) and each shard's capacity is MaxEntries divided across the shards, so the sum of shard capacities equals MaxEntries exactly and no address can be tracked beyond it.

func (*State) Clear

func (s *State) Clear(addr netip.Addr) int

Clear deletes the exact-address entry after a successful full authentication chain, discarding its accumulated offense level, and reports the number of tracked source entries afterwards. It is a reset, never a trust cache: a later hostile event starts a fresh offense level and a prior success never exempts a later impossible-path request.

func (*State) IsQuarantined

func (s *State) IsQuarantined(addr netip.Addr, now time.Time) bool

IsQuarantined reports whether addr is quarantined at now. A read is not hostile activity: it never refreshes lastHostileAt, so a source cannot keep alive state it never attacks again. It does expire an entry that has been inactive for StateTTL, so a read path is also a lazy expiry path.

func (*State) Len

func (s *State) Len() int

Len reports the number of tracked source entries. It is an eventually exact gauge: every entry change is applied under a shard lock, so a quiescent state reports exactly and a concurrent caller may observe a neighbouring instant.

func (*State) RecordAuthFailure

func (s *State) RecordAuthFailure(addr netip.Addr, now time.Time, p Policy) Transition

RecordAuthFailure records one counted unauthenticated-failure offense for addr and starts or escalates a quarantine when the configured threshold is reached inside the failure window. The window begins with the first counted failure.

This is the only auth-evidence entry point, so a 403, a 5xx, a provider error or a request that already established a principal cannot be recorded here: the classification of an authentication outcome is the auth adapter's decision, not this package's. Adaptive exemption is likewise the gate's decision. A counted failure below the threshold reports the zero Transition so the closed reason vocabulary stays reserved for quarantine transitions.

func (*State) RecordProbe

func (s *State) RecordProbe(addr netip.Addr, now time.Time, p Policy) Transition

RecordProbe records one strong hostile offense for addr: a matched impossible-path target is a deterministic rejection, so it counts immediately as a single offense on the same offense level the thresholded auth-failure window escalates. There is no weighted scoring engine behind it. The authentication window is left untouched because a path offense is not authentication evidence.

type StateLimits

type StateLimits struct {
	// MaxEntries is the hard cap on tracked source entries. The state must stay
	// bounded at it through deterministic eviction or replacement.
	MaxEntries int
	// StateTTL is the hostile-inactivity lifetime after which one source entry
	// and its accumulated offense level expire.
	StateTTL time.Duration
}

StateLimits size the process-owned adaptive state. They are restart-required in v1 because one store is shared by every generation, so they are kept out of Policy and must never be projected into a request generation.

func (StateLimits) Validate

func (l StateLimits) Validate() error

Validate reports whether the limits bound the process state: a positive capacity and a positive inactivity TTL.

type Transition

type Transition struct {
	// QuarantineStarted reports whether the event started or extended an active
	// quarantine for the source.
	QuarantineStarted bool
	// QuarantineUntil is the absolute quarantine deadline, zero when no
	// quarantine is active for the source.
	QuarantineUntil time.Time
	// Reason is the closed classification of the recorded event.
	Reason Reason
	// EntryCount is the number of tracked source entries after the event.
	EntryCount int
}

Transition is the result of one recorded hostile event for one exact source address. It is the only value that crosses into HTTP/auth adapters and metrics, so it deliberately omits the offense level and any score: a quarantine denial must not disclose them.

Jump to

Keyboard shortcuts

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