Documentation
¶
Overview ¶
Package abuse is the per-app ingress abuse defense: a policy, a sharded per-client token bucket, deny lists and a replay of past traffic.
Index ¶
- Constants
- func MonotonicClock() int64
- func ParseDenyEntry(s string) (netip.Prefix, error)
- func ParseTrusted(raw string) ([]netip.Prefix, error)
- func PrivateRanges() []netip.Prefix
- func ReasonNames() []string
- func ResolveClientIP(remoteAddr string, forwardedFor []string, trusted []netip.Prefix) netip.Addr
- type Action
- type ClientImpact
- type Clock
- type Counters
- type Decision
- type Engine
- type FieldError
- type Limiter
- type Limits
- type Mode
- type Observation
- type Policy
- type Reason
- type ReasonTotal
- type Registry
- type ReplayResult
- type Snapshot
- type State
- type Suggestion
- type ValidationError
Constants ¶
const ( EnvMaxRPS = "APP_ABUSE_MAX_RPS" EnvMaxBurst = "APP_ABUSE_MAX_BURST" EnvMaxDenyEntries = "APP_ABUSE_MAX_DENY_ENTRIES" EnvMaxAgentEntries = "APP_ABUSE_MAX_AGENT_ENTRIES" EnvDefaultRPS = "APP_ABUSE_DEFAULT_RPS" EnvDefaultBurst = "APP_ABUSE_DEFAULT_BURST" EnvMaxTrackedClients = "APP_ABUSE_MAX_TRACKED_CLIENTS" EnvShards = "APP_ABUSE_SHARDS" EnvIdleEvict = "APP_ABUSE_IDLE_EVICT" EnvMaxRetryAfter = "APP_ABUSE_MAX_RETRY_AFTER" EnvDryRunDefault = "APP_ABUSE_DRYRUN_DEFAULT_WINDOW" EnvDryRunMax = "APP_ABUSE_DRYRUN_MAX_WINDOW" EnvDryRunTop = "APP_ABUSE_DRYRUN_TOP_CLIENTS" EnvSuggestBurstSecs = "APP_ABUSE_SUGGEST_BURST_SECONDS" )
Environment variables that tune the abuse defense. Every one is optional.
Variables ¶
This section is empty.
Functions ¶
func ParseDenyEntry ¶
ParseDenyEntry parses an IP address or CIDR range into a prefix.
func ParseTrusted ¶
ParseTrusted parses a comma or space separated list of IPs and CIDRs into the proxy ranges forwarded headers are believed from.
func PrivateRanges ¶
PrivateRanges are the loopback, link-local and RFC 1918 / ULA ranges.
func ReasonNames ¶
func ReasonNames() []string
ReasonNames lists every reason name in counter order.
func ResolveClientIP ¶
ResolveClientIP picks the client address for a request. Forwarded headers are believed only when the TCP peer is itself a trusted proxy, and then the chain is walked from the right, skipping trusted hops, so a value the client put at the front of X-Forwarded-For is never reached. Without a trusted peer the peer address is the client, whatever the headers say.
Types ¶
type ClientImpact ¶
type ClientImpact struct {
Client string `json:"client"`
Requests int `json:"requests"`
WouldBlock int `json:"would_block"`
// TopReason is the reason that blocked this client most often.
TopReason string `json:"top_reason"`
}
ClientImpact is one client's share of a replay.
type Counters ¶
type Counters struct {
// contains filtered or unexported fields
}
Counters tallies one app's decisions without taking a lock.
func (*Counters) NoteFailOpen ¶
func (c *Counters) NoteFailOpen()
NoteFailOpen counts one request served because the defense failed.
type Decision ¶
type Decision struct {
Action Action
Reason Reason
// RetryAfter is whole seconds, at least 1, set only for rate limit blocks.
RetryAfter int
}
Decision is the engine's verdict on one request.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine evaluates one compiled policy. It is immutable after NewEngine and safe for concurrent use; the limiter and counters it points at are shared across policy reloads so a reload never hands clients a fresh bucket.
type FieldError ¶
FieldError is one problem with a policy field.
type Limiter ¶
type Limiter struct {
// contains filtered or unexported fields
}
Limiter is a sharded per-client token bucket. Each shard has its own lock and its own bounded map, so there is no global lock and memory is capped at the configured client count. A shard drops buckets idle for longer than idle on a timed sweep, and evicts an arbitrary bucket when it is full.
func NewLimiter ¶
NewLimiter tracks at most maxClients addresses across shards shards (rounded up to a power of two). A nil clock uses MonotonicClock.
func (*Limiter) Allow ¶
func (l *Limiter) Allow(addr netip.Addr, rps float64, burst int) (ok bool, retryAfter time.Duration)
Allow takes one token for addr from a bucket refilling at rps up to burst. When it refuses, retryAfter is how long until a token exists.
type Limits ¶
type Limits struct {
MaxRPS float64
MaxBurst int
MaxDenyEntries int
MaxAgentEntries int
DefaultRPS float64
DefaultBurst int
MaxTrackedClients int
Shards int
IdleEvict time.Duration
MaxRetryAfter time.Duration
DryRunDefault time.Duration
DryRunMax time.Duration
DryRunTop int
// SuggestShare is the fraction of the app's peak request rate one client
// may use before the suggested limit bites; SuggestBurstSeconds sizes the
// suggested burst as that many seconds of the suggested rate.
SuggestBurstSeconds float64
}
Limits caps every policy field and sizes the limiter. Built from the environment with LimitsFromEnv.
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits are the values used when no environment override is set.
func LimitsFromEnv ¶
LimitsFromEnv reads the overrides through getenv; bad or non-positive values keep the default.
type Mode ¶
type Mode string
Mode says what the policy does with a request it would turn away.
The three modes. Off is the default for an app with no policy.
type Observation ¶
Observation is one past request as the replay needs it.
type Policy ¶
type Policy struct {
Mode Mode `json:"mode"`
// RPS is the sustained request rate allowed per client IP; Burst is the
// bucket size (at least 1 whenever RPS is set).
RPS float64 `json:"rps,omitempty"`
Burst int `json:"burst,omitempty"`
// DenyCIDRs are addresses and ranges turned away outright.
DenyCIDRs []string `json:"deny_cidrs,omitempty"`
// DenyUserAgents are case-insensitive substrings of the User-Agent header.
DenyUserAgents []string `json:"deny_user_agents,omitempty"`
RequireUserAgent bool `json:"require_user_agent,omitempty"`
}
Policy is one app's ingress abuse defense. RPS 0 turns the rate limit off, leaving only the deny lists and the User-Agent requirement.
type Reason ¶
type Reason int
Reason names why a request would be, or was, turned away.
type ReasonTotal ¶
ReasonTotal pairs a reason name with how many requests it covers.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry hands out one State per app. It is touched on config load and on each metrics drain, never per request.
func DefaultRegistry ¶
func DefaultRegistry() *Registry
DefaultRegistry is the process-wide registry the ingress handler uses; Caddy modules are built from JSON and cannot be handed a live value.
func NewRegistry ¶
NewRegistry returns an empty registry. A nil clock uses MonotonicClock.
func (*Registry) DrainAll ¶
DrainAll returns each app's counters since the last call, omitting apps with nothing to report.
func (*Registry) NoteFailOpen ¶
NoteFailOpen counts one request served unchecked for app, because the policy could not be loaded or applied.
type ReplayResult ¶
type ReplayResult struct {
Requests int `json:"requests"`
WouldBlock int `json:"would_block"`
Percent float64 `json:"percent"`
ByReason []ReasonTotal `json:"by_reason"`
TopClients []ClientImpact `json:"top_clients"`
From time.Time `json:"from"`
To time.Time `json:"to"`
}
ReplayResult is what a policy would have done to a stretch of past traffic. Nothing was blocked or changed to produce it.
func Replay ¶
func Replay(p Policy, obs []Observation, l Limits, top int) (ReplayResult, error)
Replay runs obs, oldest first, through a fresh engine for p on a clock that follows the recorded times, and reports what it would have blocked. It uses private state, so the live limiter and counters are untouched.
type Snapshot ¶
type Snapshot struct {
Allowed uint64
WouldBlock [ReasonCount]uint64
Blocked [ReasonCount]uint64
FailOpen uint64
}
Snapshot is a drained copy of the counters.
type State ¶
State is what survives a policy reload for one app: the limiter buckets and the counters.
type Suggestion ¶
type Suggestion struct {
PeakRPS float64 `json:"peak_rps"`
RPS float64 `json:"rps"`
Burst int `json:"burst"`
// FromTraffic is false when there was no traffic and the instance
// defaults were used.
FromTraffic bool `json:"from_traffic"`
}
Suggestion is a starting policy derived from the app's recent traffic.
func Suggest ¶
func Suggest(peakRPS float64, l Limits) Suggestion
Suggest sizes a per-client limit from the app's peak request rate: one client may use a configured share of the peak, never less than the default rate, so ordinary visitors are not caught by a quiet-hour measurement.
type ValidationError ¶
type ValidationError struct{ Fields []FieldError }
ValidationError lists every problem found, so a form can show them at once.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string