Documentation
¶
Overview ¶
Package edgefw is the ingress firewall: ordered rules, automatic bans, bot modes, a proof-of-work challenge and bounded in-memory visibility.
Index ¶
- Constants
- Variables
- func ChallengePage(title, token string, difficulty int) (page []byte, csp string)
- func DefaultAIBotAgents() []string
- func LeadingZeroBits(sum [32]byte) int
- func Normalize(r *Rule)
- func ValidateRule(r *Rule) error
- type Action
- type Ban
- type Bans
- func (b *Bans) Active(ip netip.Addr, now time.Time) bool
- func (b *Bans) Add(ip netip.Addr, reason string, d time.Duration, now time.Time)
- func (b *Bans) Lift(ip netip.Addr) bool
- func (b *Bans) List(now time.Time) []Ban
- func (b *Bans) SetLimits(l Limits)
- func (b *Bans) Strike(ip netip.Addr, reason string, now time.Time) bool
- type Condition
- type Dimension
- type Engine
- func (e *Engine) CurrentSettings() Settings
- func (e *Engine) Evaluate(req *Request, now time.Time) Verdict
- func (e *Engine) Issue(req *Request, now time.Time) string
- func (e *Engine) Limits() Limits
- func (e *Engine) NeedsCountry() bool
- func (e *Engine) Record(req *Request, o Outcome, r Reason, now time.Time)
- func (e *Engine) SetRules(rules []Rule)
- func (e *Engine) SetSettings(s Settings)
- func (e *Engine) SetTitle(name string)
- func (e *Engine) Solve(ip netip.Addr, token, counter string, now time.Time) (string, bool)
- func (e *Engine) Title() string
- type Field
- type FieldError
- type Limits
- type Mode
- type Observation
- type Op
- type Outcome
- type Reason
- type ReasonCountRow
- type Request
- type Rule
- type RuleHit
- type SeriesPoint
- type Settings
- type Signer
- func (s *Signer) NewChallenge(ip netip.Addr, difficulty int, ttl time.Duration, now time.Time) string
- func (s *Signer) NewPass(ip netip.Addr, ttl time.Duration, now time.Time) string
- func (s *Signer) PassValid(cookie string, ip netip.Addr, now time.Time) bool
- func (s *Signer) SetKey(key []byte)
- func (s *Signer) VerifyProof(token, counter string, ip netip.Addr, now time.Time) bool
- type Stats
- func (s *Stats) Observe(o Observation)
- func (s *Stats) PruneRules(keep map[string]bool)
- func (s *Stats) Reasons(host string, from, to time.Time) []ReasonCountRow
- func (s *Stats) RuleHits(id string, now time.Time) RuleHit
- func (s *Stats) RuleMatched(id string, at time.Time)
- func (s *Stats) Series(host string, from, to time.Time, points int) []SeriesPoint
- func (s *Stats) SetLimits(l Limits)
- func (s *Stats) Top(host string, dim Dimension, now time.Time, n int) []TopRow
- func (s *Stats) Totals(host string, from, to time.Time) [OutcomeCount]uint64
- type TopRow
- type ValidationError
- type Verdict
Constants ¶
const ( EnvMaxRules = "APP_FIREWALL_MAX_RULES" EnvMaxRateEntries = "APP_FIREWALL_MAX_RATE_ENTRIES" EnvBanStrikes = "APP_FIREWALL_BAN_STRIKES" EnvBanWindow = "APP_FIREWALL_BAN_WINDOW" EnvBanDuration = "APP_FIREWALL_BAN_DURATION" EnvMaxBans = "APP_FIREWALL_MAX_BANS" EnvChallengeBits = "APP_FIREWALL_CHALLENGE_BITS" EnvChallengeTTL = "APP_FIREWALL_CHALLENGE_TTL" EnvPassTTL = "APP_FIREWALL_PASS_TTL" //nolint:gosec // env var name EnvTopCapacity = "APP_FIREWALL_TOP_CAPACITY" EnvMaxStatScopes = "APP_FIREWALL_MAX_STAT_HOSTS" EnvTopSample = "APP_FIREWALL_TOP_SAMPLE" EnvSuggestRate = "APP_FIREWALL_SUGGEST_ATTACK_PER_MINUTE" EnvSuggestMinutes = "APP_FIREWALL_SUGGEST_ATTACK_MINUTES" EnvMaxBotAgents = "APP_FIREWALL_MAX_AI_BOT_AGENTS" )
Environment variables that tune the firewall. All are optional.
const ChallengePath = "/.well-known/lr-challenge"
ChallengePath is the reserved path the challenge page posts its proof to.
const PassCookie = "__lr_pass"
PassCookie is the name of the short-lived cookie a solved challenge earns.
Variables ¶
var FieldOps = map[Field][]Op{ FieldPath: {OpEquals, OpStartsWith, OpContains, OpGlob}, FieldHost: {OpEquals, OpContains, OpGlob}, FieldMethod: {OpEquals}, FieldHeader: {OpEquals, OpStartsWith, OpContains, OpGlob, OpExists}, FieldQuery: {OpEquals, OpStartsWith, OpContains, OpExists}, FieldUserAgent: {OpEquals, OpStartsWith, OpContains, OpGlob}, FieldIP: {OpInCIDR}, FieldCountry: {OpEquals}, }
FieldOps lists the operators each field accepts; it is the closed catalog the dashboard builder and the validator share.
Functions ¶
func ChallengePage ¶
ChallengePage renders the self-contained proof-of-work page and the Content-Security-Policy header value that allows only its own script.
func DefaultAIBotAgents ¶
func DefaultAIBotAgents() []string
DefaultAIBotAgents returns the shipped AI crawler list.
func LeadingZeroBits ¶
LeadingZeroBits counts the zero bits at the start of sum.
func Normalize ¶
func Normalize(r *Rule)
Normalize trims and canonicalizes a rule in place before validation.
func ValidateRule ¶
ValidateRule checks a normalized rule against the limits.
Types ¶
type Action ¶
type Action string
Action is what a matching rule does.
const ( ActionAllow Action = "allow" ActionDeny Action = "deny" ActionChallenge Action = "challenge" ActionLog Action = "log" ActionRateLimit Action = "rate_limit" )
Rule actions. Allow ends evaluation and skips every later check, deny and challenge end it, log and rate_limit let the request continue unless the rate limit trips.
type Ban ¶
type Ban struct {
IP netip.Addr `json:"ip"`
Reason string `json:"reason"`
Started time.Time `json:"started"`
Ends time.Time `json:"ends"`
Hits uint64 `json:"hits"`
}
Ban is one automatic temporary block ("persistent action"). Bans live in memory only and are gone after a restart.
type Bans ¶
type Bans struct {
// contains filtered or unexported fields
}
Bans tracks denials per client and bans a client that crosses the threshold. Both tables are capped.
type Condition ¶
type Condition struct {
Field Field `json:"field"`
Op Op `json:"op"`
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
}
Condition is one comparison; a rule matches when all its conditions do.
type Engine ¶
type Engine struct {
Stats *Stats
Bans *Bans
Signer *Signer
// contains filtered or unexported fields
}
Engine evaluates requests against the current rules and settings. All state it keeps is bounded; see Stats and Bans.
func Default ¶
func Default() *Engine
Default is the process-wide engine the ingress handler reads; Caddy modules are built from JSON and cannot receive a live value any other way.
func New ¶
func New(l Limits, shield trafficpolicy.Limits) *Engine
New builds an engine with empty rules and default settings.
func (*Engine) CurrentSettings ¶
CurrentSettings returns the installed settings.
func (*Engine) Evaluate ¶
Evaluate runs the ordered rules, then automatic bans, attack-mode challenge and the bot modes. It never allocates per rule.
func (*Engine) NeedsCountry ¶
NeedsCountry reports whether any active rule reads the country, so the ingress only pays for a geo lookup when one does.
func (*Engine) Record ¶
Record classifies a finished decision: it feeds the stats and, for denials and rate limits, counts a strike toward an automatic ban.
func (*Engine) SetSettings ¶
SetSettings installs the settings and rebuilds the bot matchers.
type FieldError ¶
FieldError names one invalid field with plain text.
type Limits ¶
type Limits struct {
MaxRules int
MaxRateEntries int
BanStrikes int
BanWindow time.Duration
BanDuration time.Duration
MaxBans int
ChallengeBits int
ChallengeTTL time.Duration
PassTTL time.Duration
TopCapacity int
MaxStatScopes int
TopSample int
SuggestRate int
SuggestMinutes int
MaxBotAgents int
}
Limits bounds every table the firewall keeps and sets its defaults.
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits are the values used when no variable is set.
func LimitsFromEnv ¶
LimitsFromEnv overlays set variables on DefaultLimits; unparseable or non-positive values keep the default.
type Observation ¶
type Observation struct {
At time.Time
Outcome Outcome
Reason Reason
IP netip.Addr
UA string
Path string
Host string
}
Observation is one request's final classification.
type Outcome ¶
type Outcome int
Outcome classifies what the firewall did with one request.
type Reason ¶
type Reason int
Reason says which mechanism produced an outcome.
type ReasonCountRow ¶
ReasonCountRow is the number of requests a reason produced.
type Request ¶
type Request struct {
Method string
Host string
Path string
RawQuery string
UserAgent string
Header http.Header
IP netip.Addr
Country string
PassCookie string
}
Request is the slice of an HTTP request the firewall reads. Host carries no port; Country is empty when no geo source is configured.
type Rule ¶
type Rule struct {
ID string `json:"id"`
Name string `json:"name"`
Note string `json:"note,omitempty"`
Scope string `json:"scope,omitempty"`
Enabled bool `json:"enabled"`
Position int `json:"position"`
Conditions []Condition `json:"conditions"`
Action Action `json:"action"`
RateCount int `json:"rate_count,omitempty"`
RateWindowSeconds int `json:"rate_window_seconds,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
Rule is one ordered firewall rule. Scope is a host or empty for every host; Position orders rules, lowest first.
type SeriesPoint ¶
type SeriesPoint struct {
Time time.Time `json:"time"`
Counts [OutcomeCount]uint64 `json:"counts"`
}
SeriesPoint is one chart bucket.
type Settings ¶
type Settings struct {
BotMode Mode `json:"bot_mode"`
AIBotMode Mode `json:"ai_bot_mode"`
AIBotAgents []string `json:"ai_bot_agents"`
BansEnabled bool `json:"bans_enabled"`
AttackUntil *time.Time `json:"attack_until,omitempty"`
AttackChallenge bool `json:"attack_challenge"`
}
Settings is the instance-wide firewall configuration beside the rules.
func DefaultSettings ¶
func DefaultSettings() Settings
DefaultSettings leaves both bot lists off and automatic bans on.
func (Settings) AttackActive ¶
AttackActive reports whether attack mode is on at now.
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer issues and checks the challenge token and the pass cookie. Both are HMAC-SHA256 signed, bound to the client's network prefix and short lived.
func NewSigner ¶
func NewSigner() *Signer
NewSigner starts with a random per-process key; SetKey replaces it with one derived from the master key so cookies survive a restart.
func (*Signer) NewChallenge ¶
func (s *Signer) NewChallenge(ip netip.Addr, difficulty int, ttl time.Duration, now time.Time) string
NewChallenge returns a signed token for ip that the page must solve with the given difficulty.
type Stats ¶
type Stats struct {
// contains filtered or unexported fields
}
Stats keeps the bounded, in-memory firewall visibility: per-minute outcome counters for 24 hours, per-hour reason counters, per-rule hit rings and a rolling hour of approximate top-N tables. No URL, query string or client address is kept outside the capped top tables.
func (*Stats) Observe ¶
func (s *Stats) Observe(o Observation)
Observe records one request under the global scope and, within the host cap, under its own host.
func (*Stats) PruneRules ¶
PruneRules drops counters of rules that no longer exist.
func (*Stats) Reasons ¶
func (s *Stats) Reasons(host string, from, to time.Time) []ReasonCountRow
Reasons sums reason counters over whole hours overlapping from..to.
func (*Stats) RuleHits ¶
RuleHits returns the counters for id: total since start, last hit and a sparkline of the last hour in 5 minute steps (oldest first).
func (*Stats) RuleMatched ¶
RuleMatched counts one match of rule id.
func (*Stats) Series ¶
Series returns outcome counts between from and to in at most points buckets, each a whole number of minutes.
type TopRow ¶
type TopRow struct {
Key string `json:"key"`
Count uint64 `json:"count"`
Spark [sparkSlots]uint32 `json:"spark"`
}
TopRow is one approximate top-N entry with a six point sparkline.
type ValidationError ¶
type ValidationError struct{ Fields []FieldError }
ValidationError carries every problem found in a rule or setting.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string