Rename the settings whose names do not say what they control (closes #234)
check / check (push) Waiting to run
check / check (push) Waiting to run
Applies the setting renames of the naming audit under the SWW_WAF_ and SWW_RPROXY_ prefixes, with sneak's names for the IP reputation cache time and the two time limits, which now take milliseconds. The rproxy log field default_disposition becomes waf_backend_no_answer, and config.Config and config.FromEnvironment become config.WAF and config.WAFFromEnvironment. Go fields, test names and prose follow. No aliases; nothing reads the old names. Model: opus-5-5
This commit is contained in:
1 parent
101cc0aba5
commit
4be0b6e7fc
97 files changed
+2030
-1810
No files matched your search
+227
-192
@@ -27,9 +27,9 @@ import (
|
||||
"sneak.berlin/go/smallwebwaf/internal/remotelog"
|
||||
)
|
||||
|
||||
// Config is the settings of `sww waf`. A time or size limit of
|
||||
// WAF is the settings of `sww waf`. A time or size limit of
|
||||
// zero is off.
|
||||
type Config struct {
|
||||
type WAF struct {
|
||||
// ListenAddr is where waf listens (SWW_WAF_LISTEN_ADDR).
|
||||
ListenAddr string
|
||||
// InstanceName is the name every log line and alert gives as instance,
|
||||
@@ -70,25 +70,27 @@ type Config struct {
|
||||
// BytesLimitPerMinute, BytesLimitPerHour and BytesLimitPerDay are the
|
||||
// most bytes a client's requests may carry in a minute, an hour and a
|
||||
// day (SWW_WAF_BYTES_LIMIT_PER_MINUTE, SWW_WAF_BYTES_LIMIT_PER_HOUR and
|
||||
// SWW_WAF_BYTES_LIMIT_PER_DAY). BytesCount is which body bytes count
|
||||
// toward them (SWW_WAF_BYTES_COUNT): response, request or both.
|
||||
// SWW_WAF_BYTES_LIMIT_PER_DAY). BytesCounted is which body bytes count
|
||||
// toward them and the byte anomaly thresholds (SWW_WAF_BYTES_COUNTED):
|
||||
// response, request or both.
|
||||
BytesLimitPerMinute int64
|
||||
BytesLimitPerHour int64
|
||||
BytesLimitPerDay int64
|
||||
BytesCount string
|
||||
BytesCounted string
|
||||
// LookupSource is where each client's AS number and country are
|
||||
// looked up (SWW_WAF_LOOKUP_SOURCE): geojs, file, or off for nowhere.
|
||||
// LookupDBPath is the lookup database, the IPinfo Lite file looked up
|
||||
// in while LookupSource is file (SWW_WAF_LOOKUP_DB_PATH), and "" for any
|
||||
// other source. A request waits up to LookupTimeout for its client's
|
||||
// first answer from GeoJS while a setting needs it
|
||||
// (SWW_WAF_LOOKUP_TIMEOUT), which cannot be off. AddLookupHeaders is true
|
||||
// when the app is passed the client's AS number and country in headers
|
||||
// other source. A request waits up to ASNAndCountryLookupTimeout for
|
||||
// its client's first answer from GeoJS while a setting needs it
|
||||
// (SWW_WAF_ASN_AND_COUNTRY_LOOKUP_TIMEOUT_MS), which cannot be off.
|
||||
// AddLookupHeaders is true when the app is passed the client's AS
|
||||
// number and country in headers
|
||||
// (SWW_WAF_ADD_LOOKUP_HEADERS).
|
||||
LookupSource string
|
||||
LookupDBPath string
|
||||
LookupTimeout time.Duration
|
||||
AddLookupHeaders bool
|
||||
LookupSource string
|
||||
LookupDBPath string
|
||||
ASNAndCountryLookupTimeout time.Duration
|
||||
AddLookupHeaders bool
|
||||
// ExclusivelyAllowedCountries, when not empty, are the only countries
|
||||
// whose clients are let through (SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES),
|
||||
// two-letter codes in capitals, as GeoJS gives them.
|
||||
@@ -105,21 +107,22 @@ type Config struct {
|
||||
// RouteWatchURL is where the routewatch instance is that adds the asn=
|
||||
// and prefix= tags, "" for none (SWW_WAF_ROUTEWATCH_URL).
|
||||
RouteWatchURL string
|
||||
// The biased thresholds. ASNBytesPercent and CountryBytesPercent give
|
||||
// those they list a percentage of the byte limits in place of the one
|
||||
// TagLimitPercent gives their asn= or country= tag
|
||||
// (SWW_WAF_ASN_BYTES_PERCENT and SWW_WAF_COUNTRY_BYTES_PERCENT). Each holds
|
||||
// percentages from 0 to 100, by AS number, written as AS64496, or by
|
||||
// country, a two-letter code in capitals, as the lookup gives them.
|
||||
// UnknownLimitPercent is the percentage of every limit a client without
|
||||
// a country gets (SWW_WAF_UNKNOWN_LIMIT_PERCENT). ASNLimitPercentURL is
|
||||
// The biased thresholds. ASNBytesLimitPercent and
|
||||
// CountryBytesLimitPercent give those they list a percentage of the byte
|
||||
// limits in place of the one TagLimitPercent gives their asn= or
|
||||
// country= tag (SWW_WAF_ASN_BYTES_LIMIT_PERCENT and
|
||||
// SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT). Each holds percentages from 0 to
|
||||
// 100, by AS number, written as AS64496, or by country, a two-letter
|
||||
// code in capitals, as the lookup gives them. UnknownCountryLimitPercent
|
||||
// is the percentage of every limit a client without a country gets
|
||||
// (SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT). ASNLimitPercentURL is
|
||||
// where a file of AS:percent lines is fetched from, whose percentages
|
||||
// count like asn= tags in TagLimitPercent (SWW_WAF_ASN_LIMIT_PERCENT_URL),
|
||||
// "" while it is unset.
|
||||
ASNBytesPercent map[string]int64
|
||||
CountryBytesPercent map[string]int64
|
||||
UnknownLimitPercent int64
|
||||
ASNLimitPercentURL string
|
||||
ASNBytesLimitPercent map[string]int64
|
||||
CountryBytesLimitPercent map[string]int64
|
||||
UnknownCountryLimitPercent int64
|
||||
ASNLimitPercentURL string
|
||||
// BlocklistURLs are where the blocklists are fetched from
|
||||
// (SWW_WAF_BLOCKLIST_URLS). Each list, and ASNLimitPercentURL's, is
|
||||
// fetched again BlocklistRefresh after it was last fetched or tried
|
||||
@@ -139,21 +142,23 @@ type Config struct {
|
||||
// of AbuseIPDBMinScore or more is a hit (SWW_WAF_ABUSEIPDB_MIN_SCORE), and
|
||||
// at most AbuseIPDBDailyBudget checks are made a day
|
||||
// (SWW_WAF_ABUSEIPDB_DAILY_BUDGET).
|
||||
// ReputationAction is what is done with a client a zone's verdict lists,
|
||||
// or whose score is a hit (SWW_WAF_REPUTATION_ACTION): deny, limit or log;
|
||||
// for limit, ReputationLimitPercent is the percentage of every limit it
|
||||
// gets. A verdict or a score is used for ReputationCacheTTL after it was
|
||||
// fetched (SWW_WAF_REPUTATION_CACHE_TTL), and a query or a check may take
|
||||
// ReputationTimeout (SWW_WAF_REPUTATION_TIMEOUT). Neither can be off.
|
||||
DNSBLZones []string
|
||||
DNSBLResolver netip.AddrPort
|
||||
AbuseIPDBKey string
|
||||
AbuseIPDBMinScore int64
|
||||
AbuseIPDBDailyBudget int
|
||||
ReputationAction string
|
||||
ReputationLimitPercent int64
|
||||
ReputationCacheTTL time.Duration
|
||||
ReputationTimeout time.Duration
|
||||
// DNSBLAbuseIPDBAction is what is done with a client a zone's verdict
|
||||
// lists, or whose score is a hit (SWW_WAF_DNSBL_ABUSEIPDB_ACTION): deny,
|
||||
// limit or log; for limit, DNSBLAbuseIPDBLimitPercent is the percentage
|
||||
// of every limit it gets. A verdict or a score is used for
|
||||
// IPReputationCacheTTL after it was fetched
|
||||
// (SWW_WAF_IP_REPUTATION_CACHE_TTL), and a query or a check may take
|
||||
// DNSBLAbuseIPDBTimeout (SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS). Neither
|
||||
// can be off.
|
||||
DNSBLZones []string
|
||||
DNSBLResolver netip.AddrPort
|
||||
AbuseIPDBKey string
|
||||
AbuseIPDBMinScore int64
|
||||
AbuseIPDBDailyBudget int
|
||||
DNSBLAbuseIPDBAction string
|
||||
DNSBLAbuseIPDBLimitPercent int64
|
||||
IPReputationCacheTTL time.Duration
|
||||
DNSBLAbuseIPDBTimeout time.Duration
|
||||
// CrowdSecDecisionsURL is where the decision list of the CrowdSec
|
||||
// engine whose local API SWW_WAF_CROWDSEC_LAPI_URL names is fetched from:
|
||||
// that URL with v1/decisions added to its path, "" while it is unset and
|
||||
@@ -171,11 +176,11 @@ type Config struct {
|
||||
// (SWW_WAF_LIMIT_BAN_DURATION). A limit broken again within
|
||||
// LimitBanRepeatWindow after the last ban ended bans for three times
|
||||
// as long as that ban (SWW_WAF_LIMIT_BAN_REPEAT_WINDOW), and a ban that
|
||||
// would be longer than MaxBanDuration is permanent instead
|
||||
// (SWW_WAF_MAX_BAN_DURATION). None of them can be off.
|
||||
LimitBanDuration time.Duration
|
||||
LimitBanRepeatWindow time.Duration
|
||||
MaxBanDuration time.Duration
|
||||
// would be longer than LimitBanPermanentAbove is permanent instead
|
||||
// (SWW_WAF_LIMIT_BAN_PERMANENT_ABOVE). None of them can be off.
|
||||
LimitBanDuration time.Duration
|
||||
LimitBanRepeatWindow time.Duration
|
||||
LimitBanPermanentAbove time.Duration
|
||||
// AttackBanDuration is the ban for a first clear sign of attack
|
||||
// (SWW_WAF_ATTACK_BAN_DURATION). It cannot be off.
|
||||
AttackBanDuration time.Duration
|
||||
@@ -185,13 +190,13 @@ type Config struct {
|
||||
// client that a ban covers (SWW_WAF_BAN_SCOPE_V4_PREFIX).
|
||||
BanScopeV4Prefix int
|
||||
// StateDir is the directory of the state files, an absolute path
|
||||
// (SWW_WAF_STATE_DIR). bans.json is written StateWriteDelay after a ban
|
||||
// is made (SWW_WAF_STATE_WRITE_DELAY), and every state and cache file
|
||||
// every StateCounterInterval (SWW_WAF_STATE_COUNTER_INTERVAL). Neither
|
||||
// (SWW_WAF_STATE_DIR). bans.json is written BansWriteDelay after a ban
|
||||
// is made (SWW_WAF_BANS_WRITE_DELAY), and every state and cache file
|
||||
// every StateWriteInterval (SWW_WAF_STATE_WRITE_INTERVAL). Neither
|
||||
// can be off.
|
||||
StateDir string
|
||||
StateWriteDelay time.Duration
|
||||
StateCounterInterval time.Duration
|
||||
StateDir string
|
||||
BansWriteDelay time.Duration
|
||||
StateWriteInterval time.Duration
|
||||
// CacheDir is the directory of the cache files, which keep answers
|
||||
// from outside services, an absolute path (SWW_WAF_CACHE_DIR).
|
||||
CacheDir string
|
||||
@@ -207,32 +212,38 @@ type Config struct {
|
||||
AdminToken string
|
||||
// MetricsToken is the bearer token a scraper sends for the metrics
|
||||
// (SWW_WAF_METRICS_TOKEN), "" while it is unset and the metrics are off.
|
||||
// MetricsTopN is how many AS numbers and how many countries get series
|
||||
// of their own in the metrics (SWW_WAF_METRICS_TOP_N).
|
||||
MetricsToken string
|
||||
MetricsTopN int
|
||||
// RulesDir is the directory of the rule files (SWW_WAF_RULES_DIR), read
|
||||
// unless RulesEnabled is false (SWW_WAF_RULES_ENABLED).
|
||||
RulesDir string
|
||||
RulesEnabled bool
|
||||
// WAFMode is what the Core Rule Set does (SWW_WAF_CORE_RULE_SET_MODE): WAFModeOff,
|
||||
// WAFModeDetect or WAFModeBlock. WAFParanoiaLevel is its paranoia
|
||||
// level, from 1 to 4 (SWW_WAF_CORE_RULE_SET_PARANOIA_LEVEL), and
|
||||
// WAFAnomalyThreshold the anomaly score at which a request is a match
|
||||
// (SWW_WAF_CORE_RULE_SET_ANOMALY_THRESHOLD), 0 while it is off. WAFDisabledRules
|
||||
// are the ids of its rules switched off (SWW_WAF_CORE_RULE_SET_DISABLED_RULES),
|
||||
// WAFExemptPaths the path prefixes it does not inspect
|
||||
// (SWW_WAF_CORE_RULE_SET_EXEMPT_PATHS), and WAFBodyLimit the most of a request body
|
||||
// it reads (SWW_WAF_CORE_RULE_SET_BODY_LIMIT), 0 while it is off and it reads none.
|
||||
// MetricsBusiestCountriesAndASNs is how many AS numbers and how many
|
||||
// countries get series of their own in the metrics
|
||||
// (SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS).
|
||||
MetricsToken string
|
||||
MetricsBusiestCountriesAndASNs int
|
||||
// RuleFilesDir is the directory of the rule files
|
||||
// (SWW_WAF_RULE_FILES_DIR), read unless RuleFilesEnabled is false
|
||||
// (SWW_WAF_RULE_FILES_ENABLED).
|
||||
RuleFilesDir string
|
||||
RuleFilesEnabled bool
|
||||
// CoreRuleSetMode is what the Core Rule Set does
|
||||
// (SWW_WAF_CORE_RULE_SET_MODE): CoreRuleSetModeOff,
|
||||
// CoreRuleSetModeDetect or CoreRuleSetModeBlock.
|
||||
// CoreRuleSetParanoiaLevel is its paranoia level, from 1 to 4
|
||||
// (SWW_WAF_CORE_RULE_SET_PARANOIA_LEVEL), and CoreRuleSetScoreThreshold
|
||||
// the anomaly score at which a request is a match
|
||||
// (SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD), 0 while it is off.
|
||||
// CoreRuleSetDisabledRules are the ids of its rules switched off
|
||||
// (SWW_WAF_CORE_RULE_SET_DISABLED_RULES), CoreRuleSetExemptPaths the
|
||||
// path prefixes it does not inspect (SWW_WAF_CORE_RULE_SET_EXEMPT_PATHS),
|
||||
// and CoreRuleSetBodyInspectionSize the most of a request body it reads
|
||||
// (SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE), 0 while it is off and it
|
||||
// reads none.
|
||||
// CoreRuleSetExclusionsFile is the path of the file of its exclusions
|
||||
// (SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE), "" while there is none.
|
||||
WAFMode string
|
||||
WAFParanoiaLevel int
|
||||
WAFAnomalyThreshold int
|
||||
WAFDisabledRules []int
|
||||
WAFExemptPaths []string
|
||||
WAFBodyLimit int64
|
||||
CoreRuleSetExclusionsFile string
|
||||
CoreRuleSetMode string
|
||||
CoreRuleSetParanoiaLevel int
|
||||
CoreRuleSetScoreThreshold int
|
||||
CoreRuleSetDisabledRules []int
|
||||
CoreRuleSetExemptPaths []string
|
||||
CoreRuleSetBodyInspectionSize int64
|
||||
CoreRuleSetExclusionsFile string
|
||||
// Apps are the names of the apps each hostname serves, by hostname in
|
||||
// lowercase (SWW_WAF_APPS): a request gets only its hostname's apps' fixes
|
||||
// to the Core Rule Set. Apps is nil while SWW_WAF_APPS is unset, and every
|
||||
@@ -241,25 +252,26 @@ type Config struct {
|
||||
// TrapPaths are the paths a request for which is a clear sign of
|
||||
// attack (SWW_WAF_TRAP_PATHS), each starting with / and without a ?.
|
||||
TrapPaths []string
|
||||
// ErrorBurstThreshold is the most requests of a client within a minute
|
||||
// RefusalsLimitPerMinute is the most requests of a client within a minute
|
||||
// that waf may refuse after a rule file or Core Rule Set match; one
|
||||
// more breaks a limit (SWW_WAF_ERROR_BURST_THRESHOLD). 0 is off.
|
||||
ErrorBurstThreshold int64
|
||||
// more breaks a limit (SWW_WAF_REFUSALS_LIMIT_PER_MINUTE). 0 is off.
|
||||
RefusalsLimitPerMinute int64
|
||||
// LogRemoteURL is where every line on stdout is also sent
|
||||
// (SWW_WAF_LOG_REMOTE_URL), nil while it is unset and nothing is sent.
|
||||
// LogRemoteTLSCAs are the certificates a syslog+tls endpoint's
|
||||
// certificate must chain to (SWW_WAF_LOG_REMOTE_TLS_CA_FILE), nil while
|
||||
// it is unset and the host's own are used. LogRemoteBuffer is the most
|
||||
// lines held while they wait to be sent (SWW_WAF_LOG_REMOTE_BUFFER).
|
||||
// it is unset and the host's own are used. LogRemoteBufferLines is the
|
||||
// most lines held while they wait to be sent
|
||||
// (SWW_WAF_LOG_REMOTE_BUFFER_LINES).
|
||||
// LogRemoteFacility is the number of the syslog facility
|
||||
// (SWW_WAF_LOG_REMOTE_FACILITY), and LogRemoteAppName the APP-NAME
|
||||
// (SWW_WAF_LOG_REMOTE_APP_NAME, by default InstanceName), of the records
|
||||
// the lines are sent in.
|
||||
LogRemoteURL *url.URL
|
||||
LogRemoteTLSCAs *x509.CertPool
|
||||
LogRemoteBuffer int
|
||||
LogRemoteFacility int
|
||||
LogRemoteAppName string
|
||||
LogRemoteURL *url.URL
|
||||
LogRemoteTLSCAs *x509.CertPool
|
||||
LogRemoteBufferLines int
|
||||
LogRemoteFacility int
|
||||
LogRemoteAppName string
|
||||
// AlertWebhookURL is where each alert is posted as JSON
|
||||
// (SWW_WAF_ALERT_WEBHOOK_URL), nil while it is unset. AlertWebhookHeaders
|
||||
// are sent with each (SWW_WAF_ALERT_WEBHOOK_HEADERS).
|
||||
@@ -285,19 +297,19 @@ type Config struct {
|
||||
// and bytes a minute and an hour per client (SWW_WAF_ANOMALY_CLIENT_*),
|
||||
// per netblock around a client (SWW_WAF_ANOMALY_NET_*), per AS number
|
||||
// (SWW_WAF_ANOMALY_ASN_*), for the whole service (SWW_WAF_ANOMALY_TOTAL_*)
|
||||
// and per named netblock (SWW_WAF_WATCH_*), each 0 while it is off.
|
||||
// AnomalyNetV4Prefix and AnomalyNetV6Prefix are the lengths of the
|
||||
// netblock around a client (SWW_WAF_ANOMALY_NET_V4_PREFIX and
|
||||
// SWW_WAF_ANOMALY_NET_V6_PREFIX), and WatchNets the named netblocks
|
||||
// (SWW_WAF_WATCH_NETS).
|
||||
// and per watched netblock (SWW_WAF_ANOMALY_WATCHED_NET_*), each 0 while
|
||||
// it is off. AnomalyNetV4Prefix and AnomalyNetV6Prefix are the lengths
|
||||
// of the netblock around a client (SWW_WAF_ANOMALY_NET_V4_PREFIX and
|
||||
// SWW_WAF_ANOMALY_NET_V6_PREFIX), and AnomalyWatchedNets the watched
|
||||
// netblocks (SWW_WAF_ANOMALY_WATCHED_NETS).
|
||||
AnomalyClient anomaly.Thresholds
|
||||
AnomalyNet anomaly.Thresholds
|
||||
AnomalyASN anomaly.Thresholds
|
||||
AnomalyTotal anomaly.Thresholds
|
||||
AnomalyWatch anomaly.Thresholds
|
||||
AnomalyWatchedNet anomaly.Thresholds
|
||||
AnomalyNetV4Prefix int
|
||||
AnomalyNetV6Prefix int
|
||||
WatchNets []anomaly.NamedNetblock
|
||||
AnomalyWatchedNets []anomaly.NamedNetblock
|
||||
|
||||
// settings are the values read, as given or by default, and the
|
||||
// files they were read from, for the log line at start.
|
||||
@@ -306,12 +318,12 @@ type Config struct {
|
||||
|
||||
// The values of SWW_WAF_CORE_RULE_SET_MODE.
|
||||
const (
|
||||
// WAFModeOff runs no request through the Core Rule Set.
|
||||
WAFModeOff = off
|
||||
// WAFModeDetect logs and alerts a match, and refuses nothing.
|
||||
WAFModeDetect = "detect"
|
||||
// WAFModeBlock refuses a match with 403.
|
||||
WAFModeBlock = "block"
|
||||
// CoreRuleSetModeOff runs no request through the Core Rule Set.
|
||||
CoreRuleSetModeOff = off
|
||||
// CoreRuleSetModeDetect logs and alerts a match, and refuses nothing.
|
||||
CoreRuleSetModeDetect = "detect"
|
||||
// CoreRuleSetModeBlock refuses a match with 403.
|
||||
CoreRuleSetModeBlock = "block"
|
||||
)
|
||||
|
||||
// fileSource is the SWW_WAF_LOOKUP_SOURCE that looks clients up in the
|
||||
@@ -379,10 +391,10 @@ var (
|
||||
"is not a name, = and a netblock, such as office=203.0.113.0/24")
|
||||
errNotAbsolutePath = errors.New(
|
||||
"is not an absolute path, such as /var/lib/sww")
|
||||
errShortToken = errors.New("is shorter than 32 characters")
|
||||
errNotMode = errors.New("is not enforce or observe")
|
||||
errNotBytesCount = errors.New("is not response, request or both")
|
||||
errNotPathPrefix = errors.New(
|
||||
errShortToken = errors.New("is shorter than 32 characters")
|
||||
errNotMode = errors.New("is not enforce or observe")
|
||||
errNotBytesCounted = errors.New("is not response, request or both")
|
||||
errNotPathPrefix = errors.New(
|
||||
"is not a path prefix starting with /, such as /assets/")
|
||||
errNotTrapPath = errors.New(
|
||||
"is not a path starting with / and without a ?, such as /wp-login.php",
|
||||
@@ -396,9 +408,11 @@ var (
|
||||
errNotApp = errors.New(
|
||||
"is not an app sww has fixes for; the apps are",
|
||||
)
|
||||
errNotWAFMode = errors.New("is not off, detect or block")
|
||||
errNotParanoiaLevel = errors.New("is not a paranoia level, from 1 to 4")
|
||||
errNotRuleID = errors.New(
|
||||
errNotCoreRuleSetMode = errors.New("is not off, detect or block")
|
||||
errNotParanoiaLevel = errors.New(
|
||||
"is not a paranoia level, from 1 to 4",
|
||||
)
|
||||
errNotRuleID = errors.New(
|
||||
"is not the id of a Core Rule Set rule, a whole number such as 942100",
|
||||
)
|
||||
errSetupRuleID = errors.New(
|
||||
@@ -501,7 +515,7 @@ var (
|
||||
)
|
||||
)
|
||||
|
||||
// FromEnvironment reads the settings through v, normally the viper
|
||||
// WAFFromEnvironment reads the settings through v, normally the viper
|
||||
// NewViper returns, bound to the process's environment. A setting may
|
||||
// instead be given as a file: the variable named by the setting's name
|
||||
// with _FILE added names the file, which is read now (see lookup). A
|
||||
@@ -509,9 +523,9 @@ var (
|
||||
// invalid is an error that names it.
|
||||
//
|
||||
//nolint:funlen // one line for each setting, a list that grows with them
|
||||
func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
func WAFFromEnvironment(v *viper.Viper) (*WAF, error) {
|
||||
env := &environment{v: v}
|
||||
cfg := &Config{
|
||||
cfg := &WAF{
|
||||
ListenAddr: env.address("SWW_WAF_LISTEN_ADDR"),
|
||||
InstanceName: env.instanceName(),
|
||||
Observe: env.observe("SWW_WAF_MODE", "enforce"),
|
||||
@@ -545,15 +559,15 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
),
|
||||
BytesLimitPerHour: env.size("SWW_WAF_BYTES_LIMIT_PER_HOUR", "50G"),
|
||||
BytesLimitPerDay: env.size("SWW_WAF_BYTES_LIMIT_PER_DAY", "100G"),
|
||||
BytesCount: env.bytesCount("SWW_WAF_BYTES_COUNT", "both"),
|
||||
BytesCounted: env.bytesCounted("SWW_WAF_BYTES_COUNTED", "both"),
|
||||
LookupSource: env.lookupSource(
|
||||
"SWW_WAF_LOOKUP_SOURCE",
|
||||
"geojs",
|
||||
),
|
||||
LookupDBPath: env.value("SWW_WAF_LOOKUP_DB_PATH", ""),
|
||||
LookupTimeout: env.durationNotOff(
|
||||
"SWW_WAF_LOOKUP_TIMEOUT",
|
||||
"800ms",
|
||||
ASNAndCountryLookupTimeout: env.milliseconds(
|
||||
"SWW_WAF_ASN_AND_COUNTRY_LOOKUP_TIMEOUT_MS",
|
||||
"800",
|
||||
),
|
||||
AddLookupHeaders: env.boolean("SWW_WAF_ADD_LOOKUP_HEADERS", "false"),
|
||||
ExclusivelyAllowedCountries: env.countries(
|
||||
@@ -562,16 +576,16 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
TagDeny: env.tags("SWW_WAF_TAG_DENY"),
|
||||
TagLimitPercent: env.tagPercents("SWW_WAF_TAG_LIMIT_PERCENT"),
|
||||
RouteWatchURL: env.routeWatchURL("SWW_WAF_ROUTEWATCH_URL"),
|
||||
ASNBytesPercent: env.percents(
|
||||
"SWW_WAF_ASN_BYTES_PERCENT",
|
||||
ASNBytesLimitPercent: env.percents(
|
||||
"SWW_WAF_ASN_BYTES_LIMIT_PERCENT",
|
||||
ParseASN,
|
||||
),
|
||||
CountryBytesPercent: env.percents(
|
||||
"SWW_WAF_COUNTRY_BYTES_PERCENT",
|
||||
CountryBytesLimitPercent: env.percents(
|
||||
"SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT",
|
||||
parseCountry,
|
||||
),
|
||||
UnknownLimitPercent: env.percent(
|
||||
"SWW_WAF_UNKNOWN_LIMIT_PERCENT",
|
||||
UnknownCountryLimitPercent: env.percent(
|
||||
"SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT",
|
||||
"100",
|
||||
),
|
||||
ASNLimitPercentURL: env.listURL("SWW_WAF_ASN_LIMIT_PERCENT_URL"),
|
||||
@@ -585,13 +599,13 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
"SWW_WAF_ABUSEIPDB_DAILY_BUDGET",
|
||||
"900",
|
||||
),
|
||||
ReputationCacheTTL: env.durationNotOff(
|
||||
"SWW_WAF_REPUTATION_CACHE_TTL",
|
||||
IPReputationCacheTTL: env.durationNotOff(
|
||||
"SWW_WAF_IP_REPUTATION_CACHE_TTL",
|
||||
"24h",
|
||||
),
|
||||
ReputationTimeout: env.durationNotOff(
|
||||
"SWW_WAF_REPUTATION_TIMEOUT",
|
||||
"2s",
|
||||
DNSBLAbuseIPDBTimeout: env.milliseconds(
|
||||
"SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS",
|
||||
"2000",
|
||||
),
|
||||
CrowdSecDecisionsURL: env.crowdSecDecisionsURL(
|
||||
"SWW_WAF_CROWDSEC_LAPI_URL",
|
||||
@@ -606,8 +620,8 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
"SWW_WAF_LIMIT_BAN_REPEAT_WINDOW",
|
||||
"24h",
|
||||
),
|
||||
MaxBanDuration: env.durationNotOff(
|
||||
"SWW_WAF_MAX_BAN_DURATION",
|
||||
LimitBanPermanentAbove: env.durationNotOff(
|
||||
"SWW_WAF_LIMIT_BAN_PERMANENT_ABOVE",
|
||||
"7d",
|
||||
),
|
||||
AttackBanDuration: env.durationNotOff(
|
||||
@@ -620,12 +634,12 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
"SWW_WAF_STATE_DIR",
|
||||
"/var/lib/sww",
|
||||
),
|
||||
StateWriteDelay: env.durationNotOff(
|
||||
"SWW_WAF_STATE_WRITE_DELAY",
|
||||
BansWriteDelay: env.durationNotOff(
|
||||
"SWW_WAF_BANS_WRITE_DELAY",
|
||||
"10s",
|
||||
),
|
||||
StateCounterInterval: env.durationNotOff(
|
||||
"SWW_WAF_STATE_COUNTER_INTERVAL",
|
||||
StateWriteInterval: env.durationNotOff(
|
||||
"SWW_WAF_STATE_WRITE_INTERVAL",
|
||||
"15m",
|
||||
),
|
||||
CacheDir: env.absolutePath(
|
||||
@@ -636,48 +650,62 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
"SWW_WAF_LOG_REQUEST_HEADERS",
|
||||
"accept,accept-language,accept-encoding,content-type,origin,range",
|
||||
),
|
||||
LogLevel: env.logLevel("SWW_WAF_LOG_LEVEL", "info"),
|
||||
LogLevel: env.logLevel(
|
||||
"SWW_WAF_LOG_LEVEL",
|
||||
"info",
|
||||
),
|
||||
AdminToken: env.token("SWW_WAF_ADMIN_TOKEN"),
|
||||
MetricsToken: env.token("SWW_WAF_METRICS_TOKEN"),
|
||||
MetricsTopN: env.numberNotOff("SWW_WAF_METRICS_TOP_N", "50"),
|
||||
RulesDir: env.value("SWW_WAF_RULES_DIR", "/etc/sww/rules.d"),
|
||||
RulesEnabled: env.boolean("SWW_WAF_RULES_ENABLED", "true"),
|
||||
WAFMode: env.wafMode(
|
||||
"SWW_WAF_CORE_RULE_SET_MODE",
|
||||
WAFModeBlock,
|
||||
MetricsBusiestCountriesAndASNs: env.numberNotOff(
|
||||
"SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS",
|
||||
"50",
|
||||
),
|
||||
WAFParanoiaLevel: env.paranoiaLevel(
|
||||
RuleFilesDir: env.value(
|
||||
"SWW_WAF_RULE_FILES_DIR",
|
||||
"/etc/sww/rules.d",
|
||||
),
|
||||
RuleFilesEnabled: env.boolean(
|
||||
"SWW_WAF_RULE_FILES_ENABLED",
|
||||
"true",
|
||||
),
|
||||
CoreRuleSetMode: env.coreRuleSetMode(
|
||||
"SWW_WAF_CORE_RULE_SET_MODE",
|
||||
CoreRuleSetModeBlock,
|
||||
),
|
||||
CoreRuleSetParanoiaLevel: env.paranoiaLevel(
|
||||
"SWW_WAF_CORE_RULE_SET_PARANOIA_LEVEL",
|
||||
"1",
|
||||
),
|
||||
WAFAnomalyThreshold: env.numberOrOff(
|
||||
"SWW_WAF_CORE_RULE_SET_ANOMALY_THRESHOLD",
|
||||
CoreRuleSetScoreThreshold: env.numberOrOff(
|
||||
"SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD",
|
||||
"5",
|
||||
),
|
||||
WAFDisabledRules: env.ruleIDs("SWW_WAF_CORE_RULE_SET_DISABLED_RULES",
|
||||
"920340,920420,920440,920640,930130,930140"),
|
||||
WAFExemptPaths: env.pathPrefixes(
|
||||
CoreRuleSetDisabledRules: env.ruleIDs(
|
||||
"SWW_WAF_CORE_RULE_SET_DISABLED_RULES",
|
||||
"920340,920420,920440,920640,930130,930140",
|
||||
),
|
||||
CoreRuleSetExemptPaths: env.pathPrefixes(
|
||||
"SWW_WAF_CORE_RULE_SET_EXEMPT_PATHS",
|
||||
"",
|
||||
),
|
||||
WAFBodyLimit: env.wafBodyLimit(
|
||||
"SWW_WAF_CORE_RULE_SET_BODY_LIMIT",
|
||||
CoreRuleSetBodyInspectionSize: env.coreRuleSetBodyInspectionSize(
|
||||
"SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE",
|
||||
off,
|
||||
),
|
||||
CoreRuleSetExclusionsFile: env.filePath(
|
||||
"SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE"),
|
||||
Apps: env.apps("SWW_WAF_APPS"),
|
||||
TrapPaths: env.trapPaths("SWW_WAF_TRAP_PATHS"),
|
||||
ErrorBurstThreshold: env.count(
|
||||
"SWW_WAF_ERROR_BURST_THRESHOLD",
|
||||
RefusalsLimitPerMinute: env.count(
|
||||
"SWW_WAF_REFUSALS_LIMIT_PER_MINUTE",
|
||||
"30",
|
||||
),
|
||||
LogRemoteURL: env.logRemoteURL("SWW_WAF_LOG_REMOTE_URL"),
|
||||
LogRemoteTLSCAs: env.certificates(
|
||||
"SWW_WAF_LOG_REMOTE_TLS_CA_FILE",
|
||||
),
|
||||
LogRemoteBuffer: env.numberNotOff(
|
||||
"SWW_WAF_LOG_REMOTE_BUFFER",
|
||||
LogRemoteBufferLines: env.numberNotOff(
|
||||
"SWW_WAF_LOG_REMOTE_BUFFER_LINES",
|
||||
"10000",
|
||||
),
|
||||
LogRemoteFacility: env.facility(
|
||||
@@ -700,11 +728,11 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
"SWW_WAF_ALERT_MAX_PER_HOUR",
|
||||
"60",
|
||||
),
|
||||
AnomalyClient: env.thresholds("SWW_WAF_ANOMALY_CLIENT_"),
|
||||
AnomalyNet: env.thresholds("SWW_WAF_ANOMALY_NET_"),
|
||||
AnomalyASN: env.thresholds("SWW_WAF_ANOMALY_ASN_"),
|
||||
AnomalyTotal: env.thresholds("SWW_WAF_ANOMALY_TOTAL_"),
|
||||
AnomalyWatch: env.thresholds("SWW_WAF_WATCH_"),
|
||||
AnomalyClient: env.thresholds("SWW_WAF_ANOMALY_CLIENT_"),
|
||||
AnomalyNet: env.thresholds("SWW_WAF_ANOMALY_NET_"),
|
||||
AnomalyASN: env.thresholds("SWW_WAF_ANOMALY_ASN_"),
|
||||
AnomalyTotal: env.thresholds("SWW_WAF_ANOMALY_TOTAL_"),
|
||||
AnomalyWatchedNet: env.thresholds("SWW_WAF_ANOMALY_WATCHED_NET_"),
|
||||
AnomalyNetV4Prefix: env.v4Prefix(
|
||||
"SWW_WAF_ANOMALY_NET_V4_PREFIX",
|
||||
"24",
|
||||
@@ -713,15 +741,17 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
"SWW_WAF_ANOMALY_NET_V6_PREFIX",
|
||||
"48",
|
||||
),
|
||||
WatchNets: env.namedNetblocks("SWW_WAF_WATCH_NETS"),
|
||||
AnomalyWatchedNets: env.namedNetblocks(
|
||||
"SWW_WAF_ANOMALY_WATCHED_NETS",
|
||||
),
|
||||
}
|
||||
|
||||
cfg.LogRemoteAppName = env.appName("SWW_WAF_LOG_REMOTE_APP_NAME",
|
||||
cfg.InstanceName, cfg.LogRemoteURL != nil)
|
||||
cfg.BlocklistAction, cfg.BlocklistLimitPercent = env.action(
|
||||
"SWW_WAF_BLOCKLIST_ACTION", "deny")
|
||||
cfg.ReputationAction, cfg.ReputationLimitPercent = env.action(
|
||||
"SWW_WAF_REPUTATION_ACTION", "limit:25")
|
||||
cfg.DNSBLAbuseIPDBAction, cfg.DNSBLAbuseIPDBLimitPercent = env.action(
|
||||
"SWW_WAF_DNSBL_ABUSEIPDB_ACTION", "limit:25")
|
||||
|
||||
env.checkInstanceNameForNtfy(cfg.InstanceName, cfg.AlertNtfyURL != nil)
|
||||
env.checkLookupDBPath(cfg)
|
||||
@@ -741,7 +771,7 @@ func FromEnvironment(v *viper.Viper) (*Config, error) {
|
||||
}
|
||||
|
||||
// WAFListenAddr reads only SWW_WAF_LISTEN_ADDR, which may be given as a
|
||||
// file, as FromEnvironment does. The health check in waf's container needs
|
||||
// file, as WAFFromEnvironment does. The health check in waf's container needs
|
||||
// no other setting, so it reads no other, nor a file that another names.
|
||||
func WAFListenAddr(v *viper.Viper) (string, error) {
|
||||
env := &environment{v: v}
|
||||
@@ -751,18 +781,18 @@ func WAFListenAddr(v *viper.Viper) (string, error) {
|
||||
}
|
||||
|
||||
// InstanceName reads only SWW_WAF_INSTANCE_NAME, which may be given as a
|
||||
// file, as FromEnvironment does, so that the line saying a setting is
|
||||
// file, as WAFFromEnvironment does, so that the line saying a setting is
|
||||
// invalid carries it too. A file that cannot be read gives the default
|
||||
// here, and FromEnvironment then stops the start over it.
|
||||
// here, and WAFFromEnvironment then stops the start over it.
|
||||
func InstanceName(v *viper.Viper) string {
|
||||
env := &environment{v: v}
|
||||
|
||||
return env.instanceName()
|
||||
}
|
||||
|
||||
// LogValue makes a Config log as each setting's name with the value it was
|
||||
// LogValue makes a WAF log as each setting's name with the value it was
|
||||
// given, or its default.
|
||||
func (c *Config) LogValue() slog.Value {
|
||||
func (c *WAF) LogValue() slog.Value {
|
||||
return slog.GroupValue(c.settings...)
|
||||
}
|
||||
|
||||
@@ -795,10 +825,14 @@ func (e *environment) duration(name, defaultValue string) time.Duration {
|
||||
return duration
|
||||
}
|
||||
|
||||
// wafBodyLimit reads the setting that is the most of a request body the
|
||||
// Core Rule Set reads.
|
||||
func (e *environment) wafBodyLimit(name, defaultValue string) int64 {
|
||||
limit, err := parseWAFBodyLimit(e.value(name, defaultValue))
|
||||
// coreRuleSetBodyInspectionSize reads the setting that is the most of a
|
||||
// request body the Core Rule Set reads.
|
||||
func (e *environment) coreRuleSetBodyInspectionSize(
|
||||
name, defaultValue string,
|
||||
) int64 {
|
||||
limit, err := parseCoreRuleSetBodyInspectionSize(
|
||||
e.value(name, defaultValue),
|
||||
)
|
||||
e.check(name, err)
|
||||
|
||||
return limit
|
||||
@@ -812,12 +846,12 @@ func (e *environment) count(name, defaultValue string) int64 {
|
||||
return count
|
||||
}
|
||||
|
||||
// bytesCount reads the setting that is which body bytes count toward the
|
||||
// byte limits: response, request or both.
|
||||
func (e *environment) bytesCount(name, defaultValue string) string {
|
||||
// bytesCounted reads the setting that is which body bytes count toward the
|
||||
// byte limits and the byte anomaly thresholds: response, request or both.
|
||||
func (e *environment) bytesCounted(name, defaultValue string) string {
|
||||
value := e.value(name, defaultValue)
|
||||
if value != "response" && value != "request" && value != "both" {
|
||||
e.check(name, fmt.Errorf("%q %w", value, errNotBytesCount))
|
||||
e.check(name, fmt.Errorf("%q %w", value, errNotBytesCounted))
|
||||
}
|
||||
|
||||
return value
|
||||
@@ -859,12 +893,13 @@ func (e *environment) trapPaths(name string) []string {
|
||||
return paths
|
||||
}
|
||||
|
||||
// wafMode reads the setting that is what the Core Rule Set does: off,
|
||||
// coreRuleSetMode reads the setting that is what the Core Rule Set does: off,
|
||||
// detect or block.
|
||||
func (e *environment) wafMode(name, defaultValue string) string {
|
||||
func (e *environment) coreRuleSetMode(name, defaultValue string) string {
|
||||
mode := e.value(name, defaultValue)
|
||||
if mode != WAFModeOff && mode != WAFModeDetect && mode != WAFModeBlock {
|
||||
e.check(name, fmt.Errorf("%q %w", mode, errNotWAFMode))
|
||||
if mode != CoreRuleSetModeOff && mode != CoreRuleSetModeDetect &&
|
||||
mode != CoreRuleSetModeBlock {
|
||||
e.check(name, fmt.Errorf("%q %w", mode, errNotCoreRuleSetMode))
|
||||
}
|
||||
|
||||
return mode
|
||||
@@ -1078,7 +1113,7 @@ func (e *environment) lookupSource(name, defaultValue string) string {
|
||||
// checkLookupDBPath refuses SWW_WAF_LOOKUP_SOURCE=file without
|
||||
// SWW_WAF_LOOKUP_DB_PATH, and SWW_WAF_LOOKUP_DB_PATH with any other source:
|
||||
// one source at a time.
|
||||
func (e *environment) checkLookupDBPath(cfg *Config) {
|
||||
func (e *environment) checkLookupDBPath(cfg *WAF) {
|
||||
switch {
|
||||
case cfg.LookupSource == fileSource && cfg.LookupDBPath == "":
|
||||
e.check("SWW_WAF_LOOKUP_SOURCE", fmt.Errorf(
|
||||
@@ -1100,9 +1135,9 @@ func (e *environment) checkLookupDBPath(cfg *Config) {
|
||||
// up: the country list, SWW_WAF_ADD_LOOKUP_HEADERS, the tag settings that name
|
||||
// an asn= or country= tag, the biased thresholds,
|
||||
// SWW_WAF_ASN_LIMIT_PERCENT_URL among them, of which
|
||||
// SWW_WAF_UNKNOWN_LIMIT_PERCENT needs them only below 100, where it lowers a
|
||||
// SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT needs them only below 100, where it lowers a
|
||||
// limit, and the anomaly thresholds per AS number.
|
||||
func (e *environment) checkCountriesAndLookups(cfg *Config) {
|
||||
func (e *environment) checkCountriesAndLookups(cfg *WAF) {
|
||||
for _, country := range cfg.ExclusivelyAllowedCountries {
|
||||
if slices.Contains(cfg.TagDeny, CountryTagPrefix+country) {
|
||||
e.check("SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES",
|
||||
@@ -1129,9 +1164,9 @@ func (e *environment) checkCountriesAndLookups(cfg *Config) {
|
||||
{"SWW_WAF_TAG_DENY", slices.ContainsFunc(cfg.TagDeny, onlyLookupGives)},
|
||||
{"SWW_WAF_TAG_LIMIT_PERCENT", slices.ContainsFunc(
|
||||
slices.Collect(maps.Keys(cfg.TagLimitPercent)), onlyLookupGives)},
|
||||
{"SWW_WAF_ASN_BYTES_PERCENT", len(cfg.ASNBytesPercent) > 0},
|
||||
{"SWW_WAF_COUNTRY_BYTES_PERCENT", len(cfg.CountryBytesPercent) > 0},
|
||||
{"SWW_WAF_UNKNOWN_LIMIT_PERCENT", cfg.UnknownLimitPercent < 100},
|
||||
{"SWW_WAF_ASN_BYTES_LIMIT_PERCENT", len(cfg.ASNBytesLimitPercent) > 0},
|
||||
{"SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT", len(cfg.CountryBytesLimitPercent) > 0},
|
||||
{"SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT", cfg.UnknownCountryLimitPercent < 100},
|
||||
{"SWW_WAF_ASN_LIMIT_PERCENT_URL", cfg.ASNLimitPercentURL != ""},
|
||||
{"SWW_WAF_ANOMALY_ASN_REQUESTS_PER_MINUTE", cfg.AnomalyASN.RequestsPerMinute > 0},
|
||||
{"SWW_WAF_ANOMALY_ASN_REQUESTS_PER_HOUR", cfg.AnomalyASN.RequestsPerHour > 0},
|
||||
@@ -1150,7 +1185,7 @@ func (e *environment) checkCountriesAndLookups(cfg *Config) {
|
||||
|
||||
// checkASNLimitPercentURL refuses SWW_WAF_ASN_LIMIT_PERCENT_URL naming a
|
||||
// blocklist too: the file at a URL is fetched as one list or the other.
|
||||
func (e *environment) checkASNLimitPercentURL(cfg *Config) {
|
||||
func (e *environment) checkASNLimitPercentURL(cfg *WAF) {
|
||||
if slices.Contains(cfg.BlocklistURLs, cfg.ASNLimitPercentURL) {
|
||||
e.check("SWW_WAF_ASN_LIMIT_PERCENT_URL",
|
||||
fmt.Errorf("%q %w", cfg.ASNLimitPercentURL, errInBlocklistURLs))
|
||||
@@ -1160,7 +1195,7 @@ func (e *environment) checkASNLimitPercentURL(cfg *Config) {
|
||||
// checkCrowdSec refuses SWW_WAF_CROWDSEC_LAPI_URL without
|
||||
// SWW_WAF_CROWDSEC_LAPI_KEY, the key without the URL, and a decision list
|
||||
// that is fetched as another list too.
|
||||
func (e *environment) checkCrowdSec(cfg *Config) {
|
||||
func (e *environment) checkCrowdSec(cfg *WAF) {
|
||||
decisionsURL := cfg.CrowdSecDecisionsURL
|
||||
|
||||
switch {
|
||||
@@ -1186,7 +1221,7 @@ func (e *environment) checkCrowdSec(cfg *Config) {
|
||||
|
||||
// checkTagLists refuses a list of SWW_WAF_TAG_LISTS whose URL is fetched as
|
||||
// another list too: the file at a URL is read as one kind of list.
|
||||
func (e *environment) checkTagLists(cfg *Config) {
|
||||
func (e *environment) checkTagLists(cfg *WAF) {
|
||||
for _, list := range cfg.TagLists {
|
||||
if slices.Contains(cfg.BlocklistURLs, list.URL) ||
|
||||
list.URL == cfg.ASNLimitPercentURL || list.URL == cfg.CrowdSecDecisionsURL {
|
||||
@@ -1201,7 +1236,7 @@ func (e *environment) checkTagLists(cfg *Config) {
|
||||
// checkTags refuses a tag of SWW_WAF_TAG_DENY or SWW_WAF_TAG_LIMIT_PERCENT that would
|
||||
// never apply: crowdsec, whose request is refused before tags are acted on, and
|
||||
// any tag these settings cannot give a request.
|
||||
func (e *environment) checkTags(cfg *Config) {
|
||||
func (e *environment) checkTags(cfg *WAF) {
|
||||
for _, tag := range cfg.TagDeny {
|
||||
e.check("SWW_WAF_TAG_DENY", checkTag(cfg, tag))
|
||||
}
|
||||
@@ -1212,7 +1247,7 @@ func (e *environment) checkTags(cfg *Config) {
|
||||
}
|
||||
|
||||
// checkTag returns an error when tag would never apply, as checkTags says.
|
||||
func checkTag(cfg *Config, tag string) error {
|
||||
func checkTag(cfg *WAF, tag string) error {
|
||||
if tag == CrowdSecTag {
|
||||
return fmt.Errorf("%q %w", tag, errCrowdSecTag)
|
||||
}
|
||||
@@ -1225,17 +1260,17 @@ func checkTag(cfg *Config, tag string) error {
|
||||
}
|
||||
|
||||
// requestCanGetTag reports whether anything cfg sets up can give a request tag.
|
||||
func requestCanGetTag(cfg *Config, tag string) bool {
|
||||
func requestCanGetTag(cfg *WAF, tag string) bool {
|
||||
isListName := func(list TagList) bool { return list.Name == tag }
|
||||
if isLookupTag(tag) || slices.ContainsFunc(cfg.TagLists, isListName) {
|
||||
return true
|
||||
}
|
||||
|
||||
if tag == ExploitSignatureTag {
|
||||
coreRuleSetOn := cfg.WAFMode != WAFModeOff &&
|
||||
cfg.WAFAnomalyThreshold != 0
|
||||
coreRuleSetOn := cfg.CoreRuleSetMode != CoreRuleSetModeOff &&
|
||||
cfg.CoreRuleSetScoreThreshold != 0
|
||||
|
||||
return cfg.RulesEnabled || coreRuleSetOn
|
||||
return cfg.RuleFilesEnabled || coreRuleSetOn
|
||||
}
|
||||
|
||||
if tag == AbuseIPDBTag {
|
||||
@@ -1256,14 +1291,14 @@ func requestCanGetTag(cfg *Config, tag string) bool {
|
||||
|
||||
// TagsNeedLookup reports whether SWW_WAF_TAG_DENY or SWW_WAF_TAG_LIMIT_PERCENT
|
||||
// names an asn= or country= tag.
|
||||
func (c *Config) TagsNeedLookup() bool {
|
||||
func (c *WAF) TagsNeedLookup() bool {
|
||||
return slices.ContainsFunc(c.TagDeny, isLookupTag) ||
|
||||
tagLimitNeedsLookups(c)
|
||||
}
|
||||
|
||||
// tagLimitNeedsLookups reports whether SWW_WAF_TAG_LIMIT_PERCENT names an asn=
|
||||
// or country= tag.
|
||||
func tagLimitNeedsLookups(cfg *Config) bool {
|
||||
func tagLimitNeedsLookups(cfg *WAF) bool {
|
||||
for tag := range cfg.TagLimitPercent {
|
||||
if isLookupTag(tag) {
|
||||
return true
|
||||
@@ -1639,10 +1674,10 @@ func durationOrDays(value string) (time.Duration, error) {
|
||||
return time.Duration(n) * day, nil
|
||||
}
|
||||
|
||||
// parseWAFBodyLimit reads the most of a request body the Core Rule Set
|
||||
// parseCoreRuleSetBodyInspectionSize reads the most of a request body the Core Rule Set
|
||||
// reads: a size as parseSize reads it, or off, but at most 64K, since waf
|
||||
// gets no more of a body than that.
|
||||
func parseWAFBodyLimit(value string) (int64, error) {
|
||||
func parseCoreRuleSetBodyInspectionSize(value string) (int64, error) {
|
||||
limit, err := parseSize(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
||||
Reference in new issue
Block a user