Request tags, with refusing and limits per tag (closes #175)
check / check (push) Failing after 1m22s
check / check (push) Failing after 1m22s
Each request gets sorted tags from the lookup, new address lists, the reputation sources and the rule matches, logged as `tags`. New settings: `SWW_TAG_LISTS`, `SWW_TAG_DENY`, `SWW_TAG_LIMIT_PERCENT`. They replace `SWW_ASN_LIMIT_PERCENT`, `SWW_COUNTRY_LIMIT_PERCENT` and `SWW_DENIED_COUNTRIES`. New heuristics `tag_lists` and `tag_deny`; the latter runs before the rate limits and again after the Core Rule Set. Model: opus-5-5
This commit is contained in:
1 parent
f6435b1a73
commit
de9c71a796
42 files changed
+1420
-362
No files matched your search
+315
-28
@@ -93,29 +93,30 @@ type Config struct {
|
||||
LookupDBPath string
|
||||
LookupTimeout time.Duration
|
||||
AddLookupHeaders bool
|
||||
// DeniedCountries are the countries whose clients are refused
|
||||
// (SWW_DENIED_COUNTRIES). ExclusivelyAllowedCountries, when not
|
||||
// empty, are the only countries whose clients are let through
|
||||
// (SWW_EXCLUSIVELY_ALLOWED_COUNTRIES). Both hold two-letter codes in
|
||||
// capitals, as GeoJS gives them.
|
||||
DeniedCountries []string
|
||||
// ExclusivelyAllowedCountries, when not empty, are the only countries
|
||||
// whose clients are let through (SWW_EXCLUSIVELY_ALLOWED_COUNTRIES),
|
||||
// two-letter codes in capitals, as GeoJS gives them.
|
||||
ExclusivelyAllowedCountries []string
|
||||
// The biased thresholds. ASNLimitPercent and CountryLimitPercent give
|
||||
// the clients of the AS numbers and the countries they list that
|
||||
// percentage of every rate limit and byte limit
|
||||
// (SWW_ASN_LIMIT_PERCENT and SWW_COUNTRY_LIMIT_PERCENT).
|
||||
// ASNBytesPercent and CountryBytesPercent give those they list a
|
||||
// percentage of the byte limits in place of that one
|
||||
// TagLists are the address lists that each add a tag to the requests of
|
||||
// the clients they hold (SWW_TAG_LISTS). TagDeny are the tags whose
|
||||
// requests are refused (SWW_TAG_DENY). TagLimitPercent gives the clients
|
||||
// with a tag that percentage of every rate limit and byte limit, from 1
|
||||
// to 99 (SWW_TAG_LIMIT_PERCENT). All hold tags as requests carry them: in
|
||||
// lower case, a country code in capitals.
|
||||
TagLists []TagList
|
||||
TagDeny []string
|
||||
TagLimitPercent map[string]int64
|
||||
// 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_ASN_BYTES_PERCENT and SWW_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_UNKNOWN_LIMIT_PERCENT). ASNLimitPercentURL is
|
||||
// where a file of AS:percent lines is fetched from, whose percentages
|
||||
// count as those of ASNLimitPercent do (SWW_ASN_LIMIT_PERCENT_URL), ""
|
||||
// while it is unset.
|
||||
ASNLimitPercent map[string]int64
|
||||
CountryLimitPercent map[string]int64
|
||||
// count like asn= tags in TagLimitPercent (SWW_ASN_LIMIT_PERCENT_URL),
|
||||
// "" while it is unset.
|
||||
ASNBytesPercent map[string]int64
|
||||
CountryBytesPercent map[string]int64
|
||||
UnknownLimitPercent int64
|
||||
@@ -164,8 +165,8 @@ type Config struct {
|
||||
// BanResponse is the status a refused client is answered with, 403
|
||||
// or 429, or 0 to close the connection without an answer
|
||||
// (SWW_BAN_RESPONSE). It answers a banned client, a request that
|
||||
// breaks a rate limit or matches a ban rule, SWW_DENY_NETS and the
|
||||
// country lists.
|
||||
// breaks a rate limit or matches a ban rule, SWW_DENY_NETS,
|
||||
// SWW_EXCLUSIVELY_ALLOWED_COUNTRIES and SWW_TAG_DENY.
|
||||
BanResponse int
|
||||
// LimitBanDuration is the ban for a first broken rate limit
|
||||
// (SWW_LIMIT_BAN_DURATION). A limit broken again within
|
||||
@@ -367,7 +368,7 @@ var (
|
||||
errHeaderTakenOut = errors.New(
|
||||
"is taken out of every request by Go's HTTP server, so it can never " +
|
||||
"be logged")
|
||||
errOnBothLists = errors.New("is in SWW_DENIED_COUNTRIES too")
|
||||
errOnBothLists = errors.New("is in SWW_TAG_DENY too")
|
||||
errNotLookupSource = errors.New("is not geojs, file or off")
|
||||
errNeedsLookups = errors.New("it needs each client looked up")
|
||||
errNeedsDBPath = errors.New("it names the file to look clients up in")
|
||||
@@ -465,6 +466,25 @@ var (
|
||||
errLAPIKeyUnused = errors.New("it is sent only to the engine at that URL")
|
||||
errAnotherList = errors.New(
|
||||
"is in SWW_BLOCKLIST_URLS or is SWW_ASN_LIMIT_PERCENT_URL too")
|
||||
errFetchedOtherwise = errors.New("is in SWW_BLOCKLIST_URLS, is " +
|
||||
"SWW_ASN_LIMIT_PERCENT_URL or is the CrowdSec decision list too")
|
||||
errNotTagList = errors.New(
|
||||
"is not a name, = and an http or https URL, such as " +
|
||||
"torexit=https://example.com/tor.txt")
|
||||
errNotTagPercentItem = errors.New(
|
||||
"is not a tag, : and a percentage, such as vpn:50 or asn=14061:25")
|
||||
errNotTagPercent = errors.New("is not a percentage from 1 to 99")
|
||||
errNotASNTag = errors.New("is not asn= and a number, such as asn=14061")
|
||||
errExploitTag = errors.New(
|
||||
"cannot lower limits: the rate limits count a request before its " +
|
||||
"rules are matched")
|
||||
errCrowdSecTag = errors.New(
|
||||
"cannot be used: a CrowdSec decision refuses the request first")
|
||||
errNoRequestGetsTag = errors.New(
|
||||
"is a tag no request can get: it is not a name in SWW_TAG_LISTS, an " +
|
||||
"asn= or country= tag, a dnsbl= tag of a zone in SWW_DNSBL_ZONES, " +
|
||||
"abuseipdb with SWW_ABUSEIPDB_KEY set, or exploitsignature with " +
|
||||
"rule files or the Core Rule Set on")
|
||||
)
|
||||
|
||||
// FromEnvironment reads the settings with lookupEnv, normally
|
||||
@@ -499,11 +519,11 @@ func FromEnvironment(
|
||||
LookupDBPath: env.value("SWW_LOOKUP_DB_PATH", ""),
|
||||
LookupTimeout: env.durationNotOff("SWW_LOOKUP_TIMEOUT", "800ms"),
|
||||
AddLookupHeaders: env.boolean("SWW_ADD_LOOKUP_HEADERS", "false"),
|
||||
DeniedCountries: env.countries("SWW_DENIED_COUNTRIES", ""),
|
||||
ExclusivelyAllowedCountries: env.countries(
|
||||
"SWW_EXCLUSIVELY_ALLOWED_COUNTRIES", ""),
|
||||
ASNLimitPercent: env.percents("SWW_ASN_LIMIT_PERCENT", ParseASN),
|
||||
CountryLimitPercent: env.percents("SWW_COUNTRY_LIMIT_PERCENT", parseCountry),
|
||||
TagLists: env.tagLists("SWW_TAG_LISTS"),
|
||||
TagDeny: env.tags("SWW_TAG_DENY"),
|
||||
TagLimitPercent: env.tagPercents("SWW_TAG_LIMIT_PERCENT"),
|
||||
ASNBytesPercent: env.percents("SWW_ASN_BYTES_PERCENT", ParseASN),
|
||||
CountryBytesPercent: env.percents("SWW_COUNTRY_BYTES_PERCENT", parseCountry),
|
||||
UnknownLimitPercent: env.percent("SWW_UNKNOWN_LIMIT_PERCENT", "100"),
|
||||
@@ -583,6 +603,8 @@ func FromEnvironment(
|
||||
env.checkCountriesAndLookups(cfg)
|
||||
env.checkASNLimitPercentURL(cfg)
|
||||
env.checkCrowdSec(cfg)
|
||||
env.checkTagLists(cfg)
|
||||
env.checkTags(cfg)
|
||||
|
||||
if env.err != nil {
|
||||
return nil, env.err
|
||||
@@ -860,6 +882,31 @@ func (e *environment) percents(
|
||||
return percents
|
||||
}
|
||||
|
||||
// tagLists reads SWW_TAG_LISTS, which is empty by default.
|
||||
func (e *environment) tagLists(name string) []TagList {
|
||||
lists, err := parseTagLists(e.value(name, ""))
|
||||
e.check(name, err)
|
||||
|
||||
return lists
|
||||
}
|
||||
|
||||
// tags reads a setting that is a list of tags. It is empty by default.
|
||||
func (e *environment) tags(name string) []string {
|
||||
tags, err := parseTags(e.value(name, ""))
|
||||
e.check(name, err)
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
// tagPercents reads a setting that is a list of tags, each with a
|
||||
// percentage. It is empty by default.
|
||||
func (e *environment) tagPercents(name string) map[string]int64 {
|
||||
percents, err := parseTagPercents(e.value(name, ""))
|
||||
e.check(name, err)
|
||||
|
||||
return percents
|
||||
}
|
||||
|
||||
// percent reads a setting that is a percentage, from 0 to 100.
|
||||
func (e *environment) percent(name, defaultValue string) int64 {
|
||||
percent, err := ParsePercent(e.value(name, defaultValue))
|
||||
@@ -997,15 +1044,17 @@ func (e *environment) checkLookupDBPath(cfg *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
// checkCountriesAndLookups refuses a country on both country lists, and,
|
||||
// checkCountriesAndLookups refuses a country in
|
||||
// SWW_EXCLUSIVELY_ALLOWED_COUNTRIES whose tag is in SWW_TAG_DENY, and,
|
||||
// while SWW_LOOKUP_SOURCE is off, each setting that needs clients looked
|
||||
// up: the country lists, SWW_ADD_LOOKUP_HEADERS, the biased thresholds,
|
||||
// up: the country list, SWW_ADD_LOOKUP_HEADERS, the tag settings that name
|
||||
// an asn= or country= tag, the biased thresholds,
|
||||
// SWW_ASN_LIMIT_PERCENT_URL among them, of which
|
||||
// SWW_UNKNOWN_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) {
|
||||
for _, country := range cfg.ExclusivelyAllowedCountries {
|
||||
if slices.Contains(cfg.DeniedCountries, country) {
|
||||
if slices.Contains(cfg.TagDeny, CountryTagPrefix+country) {
|
||||
e.check("SWW_EXCLUSIVELY_ALLOWED_COUNTRIES",
|
||||
fmt.Errorf("%q %w", country, errOnBothLists))
|
||||
}
|
||||
@@ -1019,11 +1068,10 @@ func (e *environment) checkCountriesAndLookups(cfg *Config) {
|
||||
name string
|
||||
set bool
|
||||
}{
|
||||
{"SWW_DENIED_COUNTRIES", len(cfg.DeniedCountries) > 0},
|
||||
{"SWW_EXCLUSIVELY_ALLOWED_COUNTRIES", len(cfg.ExclusivelyAllowedCountries) > 0},
|
||||
{"SWW_ADD_LOOKUP_HEADERS", cfg.AddLookupHeaders},
|
||||
{"SWW_ASN_LIMIT_PERCENT", len(cfg.ASNLimitPercent) > 0},
|
||||
{"SWW_COUNTRY_LIMIT_PERCENT", len(cfg.CountryLimitPercent) > 0},
|
||||
{"SWW_TAG_DENY", slices.ContainsFunc(cfg.TagDeny, isLookupTag)},
|
||||
{"SWW_TAG_LIMIT_PERCENT", tagLimitNeedsLookups(cfg)},
|
||||
{"SWW_ASN_BYTES_PERCENT", len(cfg.ASNBytesPercent) > 0},
|
||||
{"SWW_COUNTRY_BYTES_PERCENT", len(cfg.CountryBytesPercent) > 0},
|
||||
{"SWW_UNKNOWN_LIMIT_PERCENT", cfg.UnknownLimitPercent < 100},
|
||||
@@ -1069,6 +1117,90 @@ func (e *environment) checkCrowdSec(cfg *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
// checkTagLists refuses a list of SWW_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) {
|
||||
for _, list := range cfg.TagLists {
|
||||
if slices.Contains(cfg.BlocklistURLs, list.URL) ||
|
||||
list.URL == cfg.ASNLimitPercentURL || list.URL == cfg.CrowdSecDecisionsURL {
|
||||
e.check("SWW_TAG_LISTS", fmt.Errorf("%q %w", list.URL, errFetchedOtherwise))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkTags refuses a tag of SWW_TAG_DENY or SWW_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) {
|
||||
for _, tag := range cfg.TagDeny {
|
||||
e.check("SWW_TAG_DENY", checkTag(cfg, tag))
|
||||
}
|
||||
|
||||
for tag := range cfg.TagLimitPercent {
|
||||
e.check("SWW_TAG_LIMIT_PERCENT", checkTag(cfg, tag))
|
||||
}
|
||||
}
|
||||
|
||||
// checkTag returns an error when tag would never apply, as checkTags says.
|
||||
func checkTag(cfg *Config, tag string) error {
|
||||
if tag == CrowdSecTag {
|
||||
return fmt.Errorf("%q %w", tag, errCrowdSecTag)
|
||||
}
|
||||
|
||||
if !requestCanGetTag(cfg, tag) {
|
||||
return fmt.Errorf("%q %w", MaskZoneKey(tag), errNoRequestGetsTag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// requestCanGetTag reports whether anything cfg sets up can give a request tag.
|
||||
func requestCanGetTag(cfg *Config, 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
|
||||
|
||||
return cfg.RulesEnabled || coreRuleSetOn
|
||||
}
|
||||
|
||||
if tag == AbuseIPDBTag {
|
||||
return cfg.AbuseIPDBKey != ""
|
||||
}
|
||||
|
||||
zone, isZoneTag := strings.CutPrefix(tag, DNSBLTagPrefix)
|
||||
|
||||
return isZoneTag && slices.ContainsFunc(cfg.DNSBLZones, func(each string) bool {
|
||||
return strings.ToLower(MaskZoneKey(each)) == zone
|
||||
})
|
||||
}
|
||||
|
||||
// TagsNeedLookup reports whether SWW_TAG_DENY or SWW_TAG_LIMIT_PERCENT
|
||||
// names an asn= or country= tag, which only the lookup can give a request.
|
||||
func (c *Config) TagsNeedLookup() bool {
|
||||
return slices.ContainsFunc(c.TagDeny, isLookupTag) || tagLimitNeedsLookups(c)
|
||||
}
|
||||
|
||||
// tagLimitNeedsLookups reports whether SWW_TAG_LIMIT_PERCENT names an asn=
|
||||
// or country= tag.
|
||||
func tagLimitNeedsLookups(cfg *Config) bool {
|
||||
for tag := range cfg.TagLimitPercent {
|
||||
if isLookupTag(tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isLookupTag reports whether tag is an asn= or country= tag.
|
||||
func isLookupTag(tag string) bool {
|
||||
return strings.HasPrefix(tag, ASNTagPrefix) || strings.HasPrefix(tag, CountryTagPrefix)
|
||||
}
|
||||
|
||||
// headerNames reads a setting that is a list of header names, and
|
||||
// returns them in lower case.
|
||||
func (e *environment) headerNames(name, defaultValue string) []string {
|
||||
@@ -1785,6 +1917,161 @@ func ParseASN(value string) (string, error) {
|
||||
return "AS" + strconv.FormatUint(number, 10), nil
|
||||
}
|
||||
|
||||
// The tags a request can get that the settings can name, as discriminator
|
||||
// writes them. The prefixes are followed by a value.
|
||||
const (
|
||||
ASNTagPrefix = "asn="
|
||||
CountryTagPrefix = "country="
|
||||
DNSBLTagPrefix = "dnsbl="
|
||||
AbuseIPDBTag = "abuseipdb"
|
||||
CrowdSecTag = "crowdsec"
|
||||
// ExploitSignatureTag is the tag of a request a rule file or the Core
|
||||
// Rule Set matched.
|
||||
ExploitSignatureTag = "exploitsignature"
|
||||
)
|
||||
|
||||
// TagList is an address list of SWW_TAG_LISTS: the tag it adds to the
|
||||
// requests of the clients it holds, and the URL it is fetched from.
|
||||
type TagList struct {
|
||||
Name string
|
||||
URL string
|
||||
}
|
||||
|
||||
// parseTagLists reads a comma-separated list of name=URL pairs, such as
|
||||
// torexit=https://example.com/tor.txt, and returns each list with its name
|
||||
// in lower case. A name can hold a =, as vpn=mullvad does, and ends at the
|
||||
// = before http. A name or a URL listed twice is an error.
|
||||
func parseTagLists(value string) ([]TagList, error) {
|
||||
items, err := parseList(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lists := make([]TagList, 0, len(items))
|
||||
|
||||
for _, item := range items {
|
||||
end := strings.Index(item, "=http")
|
||||
if end <= 0 || !isListURL(item[end+1:]) {
|
||||
return nil, fmt.Errorf("%q %w", item, errNotTagList)
|
||||
}
|
||||
|
||||
list := TagList{Name: strings.ToLower(item[:end]), URL: item[end+1:]}
|
||||
|
||||
if slices.ContainsFunc(lists, func(earlier TagList) bool {
|
||||
return earlier.Name == list.Name || earlier.URL == list.URL
|
||||
}) {
|
||||
return nil, fmt.Errorf("%q %w", item, errListedTwice)
|
||||
}
|
||||
|
||||
lists = append(lists, list)
|
||||
}
|
||||
|
||||
return lists, nil
|
||||
}
|
||||
|
||||
// parseTags reads a comma-separated list of tags, which parseTag reads. A
|
||||
// tag listed twice is an error, which shows the tag as MaskZoneKey does.
|
||||
func parseTags(value string) ([]string, error) {
|
||||
items, err := parseList(value)
|
||||
if err != nil {
|
||||
// parseList's error, for an empty item, shows the whole value, keys
|
||||
// included.
|
||||
return nil, errEmptyItem
|
||||
}
|
||||
|
||||
tags := make([]string, 0, len(items))
|
||||
|
||||
for _, item := range items {
|
||||
tag, err := parseTag(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if slices.Contains(tags, tag) {
|
||||
return nil, fmt.Errorf("%q %w", MaskZoneKey(item), errListedTwice)
|
||||
}
|
||||
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// parseTag reads a tag in either case, with spaces around it, and returns it
|
||||
// as a request carries it: in lower case, but for a country code, which is in
|
||||
// capitals, and an asn= tag's number without leading zeros. An asn= or
|
||||
// country= tag must name a number or a country, so that a mistyped one stops
|
||||
// the start instead of never matching.
|
||||
func parseTag(value string) (string, error) {
|
||||
tag := strings.ToLower(strings.TrimSpace(value))
|
||||
|
||||
if digits, isASN := strings.CutPrefix(tag, ASNTagPrefix); isASN {
|
||||
number, err := strconv.ParseUint(digits, 10, 32)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%q %w", value, errNotASNTag)
|
||||
}
|
||||
|
||||
return ASNTagPrefix + strconv.FormatUint(number, 10), nil
|
||||
}
|
||||
|
||||
if code, isCountry := strings.CutPrefix(tag, CountryTagPrefix); isCountry {
|
||||
country, err := parseCountry(code)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return CountryTagPrefix + country, nil
|
||||
}
|
||||
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
// parseTagPercents reads a comma-separated list of tags, each with : and a
|
||||
// percentage from 1 to 99, such as vpn:50 or asn=14061:25. A tag listed
|
||||
// twice is an error, and so is ExploitSignatureTag, which a request gets
|
||||
// only after the rate limits have counted it.
|
||||
func parseTagPercents(value string) (map[string]int64, error) {
|
||||
items, err := parseList(value)
|
||||
if err != nil {
|
||||
// parseList's error, for an empty item, shows the whole value, keys
|
||||
// included.
|
||||
return nil, errEmptyItem
|
||||
}
|
||||
|
||||
percents := make(map[string]int64, len(items))
|
||||
|
||||
for _, item := range items {
|
||||
colon := strings.LastIndex(item, ":")
|
||||
if colon <= 0 {
|
||||
return nil, fmt.Errorf("%q %w", MaskZoneKey(item), errNotTagPercentItem)
|
||||
}
|
||||
|
||||
tag, err := parseTag(item[:colon])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
percent, err := strconv.ParseInt(item[colon+1:], 10, 64)
|
||||
if err != nil || percent < 1 || percent > 99 {
|
||||
return nil, fmt.Errorf("%q %w", MaskZoneKey(item[colon+1:]), errNotTagPercent)
|
||||
}
|
||||
|
||||
if tag == ExploitSignatureTag {
|
||||
return nil, fmt.Errorf("%q %w", tag, errExploitTag)
|
||||
}
|
||||
|
||||
if _, listed := percents[tag]; listed {
|
||||
// tag, since item[:colon] may end in a space, which MaskZoneKey
|
||||
// would not mask.
|
||||
return nil, fmt.Errorf("%q %w", MaskZoneKey(tag), errListedTwice)
|
||||
}
|
||||
|
||||
percents[tag] = percent
|
||||
}
|
||||
|
||||
return percents, nil
|
||||
}
|
||||
|
||||
// parsePercents reads a comma-separated list of items, each an AS number
|
||||
// or a country, which parseCode reads, then : and a percentage, such as
|
||||
// AS64496:50 or cn:25, and returns each one's percentage. An empty value
|
||||
|
||||
+251
-53
@@ -42,10 +42,10 @@ const (
|
||||
lookupDBPath = "SWW_LOOKUP_DB_PATH"
|
||||
lookupTimeout = "SWW_LOOKUP_TIMEOUT"
|
||||
addLookupHeaders = "SWW_ADD_LOOKUP_HEADERS"
|
||||
deniedCountries = "SWW_DENIED_COUNTRIES"
|
||||
allowedCountries = "SWW_EXCLUSIVELY_ALLOWED_COUNTRIES"
|
||||
asnLimitPercent = "SWW_ASN_LIMIT_PERCENT"
|
||||
countryLimitPercent = "SWW_COUNTRY_LIMIT_PERCENT"
|
||||
tagLists = "SWW_TAG_LISTS"
|
||||
tagDeny = "SWW_TAG_DENY"
|
||||
tagLimitPercent = "SWW_TAG_LIMIT_PERCENT"
|
||||
asnBytesPercent = "SWW_ASN_BYTES_PERCENT"
|
||||
countryBytesPercent = "SWW_COUNTRY_BYTES_PERCENT"
|
||||
unknownLimitPercent = "SWW_UNKNOWN_LIMIT_PERCENT"
|
||||
@@ -208,6 +208,9 @@ const off = "off"
|
||||
// enabled is true, as a setting's value.
|
||||
const enabled = "true"
|
||||
|
||||
// disabled is false, as a setting's value.
|
||||
const disabled = "false"
|
||||
|
||||
// defaultLookupSource is the default of SWW_LOOKUP_SOURCE, and
|
||||
// fileSource the source that is the lookup database.
|
||||
const (
|
||||
@@ -274,9 +277,13 @@ func TestDefaults(t *testing.T) {
|
||||
wantNetblocks(t, cfg.AllowNets)
|
||||
wantNetblocks(t, cfg.RateLimitExemptNets)
|
||||
wantNetblocks(t, cfg.DenyNets)
|
||||
wantCountries(t, deniedCountries, cfg.DeniedCountries)
|
||||
wantCountries(t, allowedCountries, cfg.ExclusivelyAllowedCountries)
|
||||
|
||||
if len(cfg.TagLists) != 0 || len(cfg.TagDeny) != 0 || len(cfg.TagLimitPercent) != 0 {
|
||||
t.Errorf("tag settings %v, %v and %v by default, want all empty",
|
||||
cfg.TagLists, cfg.TagDeny, cfg.TagLimitPercent)
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil || hostname == "" || cfg.InstanceName != hostname {
|
||||
t.Errorf("%s is %q, want the host's name %q (%v)", instanceName,
|
||||
@@ -327,7 +334,6 @@ func TestValuesAsSet(t *testing.T) {
|
||||
rateLimitPerMinute: "60",
|
||||
rateLimitPerHour: "600",
|
||||
rateLimitPerDay: "6000",
|
||||
deniedCountries: "cn, RU,kp,Xk",
|
||||
allowedCountries: "de",
|
||||
banResponse: "429",
|
||||
limitBanDuration: "15m",
|
||||
@@ -342,7 +348,7 @@ func TestValuesAsSet(t *testing.T) {
|
||||
metricsToken: token,
|
||||
metricsTopN: "10",
|
||||
rulesDir: "/srv/waf-rules",
|
||||
rulesEnabled: "false",
|
||||
rulesEnabled: disabled,
|
||||
})
|
||||
|
||||
wantSettings(t, cfg, config.Config{
|
||||
@@ -370,7 +376,6 @@ func TestValuesAsSet(t *testing.T) {
|
||||
wantNetblocks(t, cfg.AllowNets, "192.0.2.7/32")
|
||||
wantNetblocks(t, cfg.RateLimitExemptNets, "2001:db8::/48", "10.9.8.7/32")
|
||||
wantNetblocks(t, cfg.DenyNets, "198.51.100.0/24")
|
||||
wantCountries(t, deniedCountries, cfg.DeniedCountries, "CN", "RU", "KP", "XK")
|
||||
wantCountries(t, allowedCountries, cfg.ExclusivelyAllowedCountries, "DE")
|
||||
}
|
||||
|
||||
@@ -1097,15 +1102,15 @@ func TestInstanceNameNotUTF8StopsTheStart(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodeOnBothCountryListsStopsTheStart(t *testing.T) {
|
||||
func TestCodeInAllowedCountriesAndTagDenyStopsTheStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := config.FromEnvironment(environment{
|
||||
deniedCountries: "cn,ru",
|
||||
tagDeny: "country=cn,country=ru",
|
||||
allowedCountries: "de,RU",
|
||||
}.lookupEnv)
|
||||
if err == nil {
|
||||
t.Fatal("ru on both country lists was accepted")
|
||||
t.Fatal("ru in SWW_EXCLUSIVELY_ALLOWED_COUNTRIES and SWW_TAG_DENY was accepted")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(err.Error(), allowedCountries+": ") ||
|
||||
@@ -1176,12 +1181,11 @@ func TestSettingNeedingLookupsStopsTheStartWhileTheyAreOff(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for name, value := range map[string]string{
|
||||
deniedCountries: "kp",
|
||||
allowedCountries: "de",
|
||||
addLookupHeaders: enabled,
|
||||
tagDeny: "country=KP",
|
||||
tagLimitPercent: "asn=64496:50",
|
||||
asnLimitPercentURL: asnURL,
|
||||
asnLimitPercent: "AS64496:50",
|
||||
countryLimitPercent: "cn:25",
|
||||
asnBytesPercent: "AS64496:50",
|
||||
countryBytesPercent: "cn:25",
|
||||
unknownLimitPercent: "99",
|
||||
@@ -1204,12 +1208,13 @@ func TestSettingNeedingLookupsStopsTheStartWhileTheyAreOff(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set empty, the lists need nothing looked up, and nor does
|
||||
// SWW_UNKNOWN_LIMIT_PERCENT at 100, which lowers no limit, an anomaly
|
||||
// threshold per AS number that is off, any other anomaly threshold, or
|
||||
// a blocklist.
|
||||
// SWW_UNKNOWN_LIMIT_PERCENT at 100, which lowers no limit, a tag the
|
||||
// lookup does not give, an anomaly threshold per AS number that is off,
|
||||
// any other anomaly threshold, or a blocklist.
|
||||
env := environment{
|
||||
lookupSource: off, deniedCountries: "", allowedCountries: "",
|
||||
asnLimitPercent: "", countryLimitPercent: "", asnBytesPercent: "",
|
||||
lookupSource: off, allowedCountries: "", asnBytesPercent: "",
|
||||
tagLists: "torexit=https://example.com/tor.txt,vpn=https://example.com/vpn.txt",
|
||||
tagDeny: torExitTag, tagLimitPercent: "vpn:50",
|
||||
countryBytesPercent: "", unknownLimitPercent: "100", asnLimitPercentURL: "",
|
||||
blocklistURLs: dropURL,
|
||||
}
|
||||
@@ -1355,34 +1360,194 @@ func TestInvalidAnomalySettingStopsTheStartSayingWhatIsWrong(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// torExitTag, zenTag and exploitTag are tags these tests name.
|
||||
const (
|
||||
torExitTag = "torexit"
|
||||
zenTag = "dnsbl=zen.example"
|
||||
exploitTag = "exploitsignature"
|
||||
)
|
||||
|
||||
func TestTagSettingsAsSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const torURL = "https://example.com/tor.txt"
|
||||
|
||||
// Names and tags in either case, an AS number with leading zeros, and a
|
||||
// name holding a =.
|
||||
cfg := fromEnvironment(t, environment{
|
||||
tagLists: "TorExit=" + torURL +
|
||||
", vpn=mullvad=https://example.com/mullvad.txt",
|
||||
dnsblZones: "Zen.example",
|
||||
tagDeny: "TorExit, Country=kp, asn=014061",
|
||||
tagLimitPercent: "vpn=mullvad :50, asn=014061:25, COUNTRY=cn:1, dnsbl=zen.example:99",
|
||||
})
|
||||
|
||||
wantLists := []config.TagList{
|
||||
{Name: torExitTag, URL: torURL},
|
||||
{Name: "vpn=mullvad", URL: "https://example.com/mullvad.txt"},
|
||||
}
|
||||
if !slices.Equal(cfg.TagLists, wantLists) {
|
||||
t.Errorf("%s gave %+v, want %+v", tagLists, cfg.TagLists, wantLists)
|
||||
}
|
||||
|
||||
wantDeny := []string{torExitTag, "country=KP", "asn=14061"}
|
||||
if !slices.Equal(cfg.TagDeny, wantDeny) {
|
||||
t.Errorf("%s gave %v, want %v", tagDeny, cfg.TagDeny, wantDeny)
|
||||
}
|
||||
|
||||
wantPercents := map[string]int64{
|
||||
"vpn=mullvad": 50, "asn=14061": 25, "country=CN": 1, zenTag: 99,
|
||||
}
|
||||
if !maps.Equal(cfg.TagLimitPercent, wantPercents) {
|
||||
t.Errorf("%s gave %v, want %v", tagLimitPercent, cfg.TagLimitPercent, wantPercents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagThatNoRequestCanGetIsToldApartFromOneItCan(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
list = "torexit=https://example.com/tor.txt"
|
||||
key = "abc123"
|
||||
zone = key + ".xbl.dq.spamhaus.net"
|
||||
masked = "********.xbl.dq.spamhaus.net"
|
||||
)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
env environment
|
||||
want string // "" is accepted
|
||||
}{
|
||||
{"a list's name", environment{tagLists: list, tagDeny: torExitTag}, ""},
|
||||
{
|
||||
"a mistyped list's name", environment{tagLists: list, tagDeny: "torexti"},
|
||||
`"torexti"`,
|
||||
},
|
||||
{"a masked zone", environment{dnsblZones: zone, tagDeny: "dnsbl=" + masked}, ""},
|
||||
{
|
||||
"a zone with its key", environment{dnsblZones: zone, tagDeny: "dnsbl=" + zone},
|
||||
masked,
|
||||
},
|
||||
{"another zone", environment{dnsblZones: zone, tagDeny: zenTag}, zenTag},
|
||||
{
|
||||
"abuseipdb with a key",
|
||||
environment{abuseIPDBKey: "k", tagLimitPercent: "abuseipdb:50"}, "",
|
||||
},
|
||||
{"crowdsec, though a list has its name", environment{
|
||||
tagLists: "crowdsec=https://example.com/c.txt", tagDeny: "crowdsec",
|
||||
}, `"crowdsec" cannot be used`},
|
||||
{exploitTag, environment{tagDeny: exploitTag}, ""},
|
||||
{
|
||||
exploitTag + " with rule files off",
|
||||
environment{tagDeny: exploitTag, rulesEnabled: disabled}, "",
|
||||
},
|
||||
{
|
||||
exploitTag + " with the Core Rule Set off",
|
||||
environment{tagDeny: exploitTag, wafMode: off}, "",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := config.FromEnvironment(tc.env.lookupEnv)
|
||||
|
||||
switch {
|
||||
case tc.want == "" && err != nil:
|
||||
t.Errorf("error %v, want none", err)
|
||||
case tc.want != "" && (err == nil || !strings.Contains(err.Error(), tc.want)):
|
||||
t.Errorf("error %v, want one holding %s", err, tc.want)
|
||||
case err != nil && strings.Contains(err.Error(), key):
|
||||
t.Errorf("error %v shows the zone's key", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorOfATagSettingLeavesOutAZoneKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
tag = "dnsbl=" + spamhaus
|
||||
emptyItem = "has an empty item in its list"
|
||||
twice = `"` + spamhausMasked + `" is listed twice`
|
||||
)
|
||||
|
||||
for _, tc := range []struct{ name, value, want string }{
|
||||
{tagDeny, tag + "," + tag, twice},
|
||||
{tagDeny, tag + ",,", emptyItem},
|
||||
{
|
||||
tagLimitPercent, tag,
|
||||
`"` + spamhausMasked + `" is not a tag, : and a percentage, ` +
|
||||
"such as vpn:50 or asn=14061:25",
|
||||
},
|
||||
{
|
||||
tagLimitPercent, "50:" + tag,
|
||||
`"` + spamhausMasked + `" is not a percentage from 1 to 99`,
|
||||
},
|
||||
{tagLimitPercent, tag + ":50," + tag + " :25", twice},
|
||||
{tagLimitPercent, tag + ":50,,", emptyItem},
|
||||
} {
|
||||
_, err := config.FromEnvironment(environment{tc.name: tc.value}.lookupEnv)
|
||||
|
||||
want := tc.name + ": " + tc.want
|
||||
if err == nil || err.Error() != want {
|
||||
t.Errorf("%s=%s gave the error %v, want %s", tc.name, tc.value, err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagListURLFetchedAsAnotherListStopsTheStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const lapi = "http://172.17.0.1:8080"
|
||||
|
||||
for _, tc := range []struct {
|
||||
name, listURL string
|
||||
env environment
|
||||
}{
|
||||
{"a blocklist", dropURL, environment{blocklistURLs: dropURL}},
|
||||
{"the file of AS:percent lines", asnURL, environment{asnLimitPercentURL: asnURL}},
|
||||
{
|
||||
"the CrowdSec decision list", lapi + "/v1/decisions",
|
||||
environment{"SWW_CROWDSEC_LAPI_URL": lapi, "SWW_CROWDSEC_LAPI_KEY": "key"},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tc.env[tagLists] = "dup=" + tc.listURL
|
||||
|
||||
want := tagLists + `: "` + tc.listURL + `" is in SWW_BLOCKLIST_URLS, is ` +
|
||||
"SWW_ASN_LIMIT_PERCENT_URL or is the CrowdSec decision list too"
|
||||
|
||||
_, err := config.FromEnvironment(tc.env.lookupEnv)
|
||||
if err == nil || err.Error() != want {
|
||||
t.Errorf("error %v, want %s", err, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBiasedThresholdsAsSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := fromEnvironment(t, environment{})
|
||||
if len(cfg.ASNLimitPercent) != 0 || len(cfg.CountryLimitPercent) != 0 ||
|
||||
len(cfg.ASNBytesPercent) != 0 || len(cfg.CountryBytesPercent) != 0 ||
|
||||
if len(cfg.ASNBytesPercent) != 0 || len(cfg.CountryBytesPercent) != 0 ||
|
||||
cfg.UnknownLimitPercent != 100 {
|
||||
t.Errorf("biased thresholds %v, %v, %v, %v and %d by default, "+
|
||||
"want four empty lists and 100", cfg.ASNLimitPercent, cfg.CountryLimitPercent,
|
||||
t.Errorf("biased thresholds %v, %v and %d by default, "+
|
||||
"want two empty lists and 100",
|
||||
cfg.ASNBytesPercent, cfg.CountryBytesPercent, cfg.UnknownLimitPercent)
|
||||
}
|
||||
|
||||
// AS numbers and countries in either case, an AS number with leading
|
||||
// zeros, 0 and 100.
|
||||
cfg = fromEnvironment(t, environment{
|
||||
asnLimitPercent: "AS14061:50, as16276:0,AS045102:100",
|
||||
countryLimitPercent: "cn:25,RU:50",
|
||||
asnBytesPercent: "as16276:75",
|
||||
countryBytesPercent: "ru:10",
|
||||
unknownLimitPercent: "0",
|
||||
})
|
||||
|
||||
for name, tc := range map[string]struct{ got, want map[string]int64 }{
|
||||
asnLimitPercent: {
|
||||
cfg.ASNLimitPercent,
|
||||
map[string]int64{"AS14061": 50, "AS16276": 0, "AS45102": 100},
|
||||
},
|
||||
countryLimitPercent: {cfg.CountryLimitPercent, map[string]int64{"CN": 25, "RU": 50}},
|
||||
asnBytesPercent: {cfg.ASNBytesPercent, map[string]int64{"AS16276": 75}},
|
||||
countryBytesPercent: {cfg.CountryBytesPercent, map[string]int64{"RU": 10}},
|
||||
} {
|
||||
@@ -1406,16 +1571,16 @@ func TestInvalidBiasedThresholdStopsTheStartSayingWhatIsWrong(t *testing.T) {
|
||||
)
|
||||
|
||||
for _, tc := range []struct{ name, value, want string }{
|
||||
{asnLimitPercent, "14061:50", `"14061"` + notASN},
|
||||
{asnLimitPercent, "AS4294967296:50", `"AS4294967296"` + notASN},
|
||||
{asnLimitPercent, "AS14061", `"AS14061"` + notItem},
|
||||
{asnLimitPercent, "AS14061:101", `"101"` + notPercent},
|
||||
{asnLimitPercent, "AS14061:50,as14061:25", `"as14061" is listed twice`},
|
||||
{asnBytesPercent, "14061:50", `"14061"` + notASN},
|
||||
{asnBytesPercent, "AS4294967296:50", `"AS4294967296"` + notASN},
|
||||
{asnBytesPercent, "AS14061", `"AS14061"` + notItem},
|
||||
{asnBytesPercent, "AS14061:101", `"101"` + notPercent},
|
||||
{asnBytesPercent, "AS14061:50,as14061:25", `"as14061" is listed twice`},
|
||||
{
|
||||
countryLimitPercent, "nk:25",
|
||||
countryBytesPercent, "nk:25",
|
||||
`"nk" is not a two-letter country code such as de or kp`,
|
||||
},
|
||||
{countryLimitPercent, "cn:25,CN:50", `"CN" is listed twice`},
|
||||
{countryBytesPercent, "cn:25,CN:50", `"CN" is listed twice`},
|
||||
{asnBytesPercent, "AS14061:-1", `"-1"` + notPercent},
|
||||
{countryBytesPercent, "cn:50%", `"50%"` + notPercent},
|
||||
{unknownLimitPercent, "101", `"101"` + notPercent},
|
||||
@@ -2003,23 +2168,37 @@ func TestInvalidValueStopsTheStart(t *testing.T) {
|
||||
{lookupSource, "ipinfo"}, {lookupSource, "GeoJS"}, {lookupSource, ""},
|
||||
{lookupTimeout, off}, {lookupTimeout, "0s"}, {lookupTimeout, "1"},
|
||||
{addLookupHeaders, "yes"},
|
||||
{deniedCountries, "nk"},
|
||||
{deniedCountries, "kp,,ir"},
|
||||
{deniedCountries, "prk"},
|
||||
{deniedCountries, "408"},
|
||||
{deniedCountries, "k"},
|
||||
{deniedCountries, "eu"},
|
||||
{deniedCountries, "un"},
|
||||
{deniedCountries, "su"},
|
||||
{allowedCountries, "nk"},
|
||||
{allowedCountries, "kp,,ir"},
|
||||
{allowedCountries, "prk"},
|
||||
{allowedCountries, "408"},
|
||||
{allowedCountries, "k"},
|
||||
{allowedCountries, "eu"},
|
||||
{allowedCountries, "un"},
|
||||
{allowedCountries, "su"},
|
||||
{allowedCountries, "ac"},
|
||||
{allowedCountries, "uk"},
|
||||
{allowedCountries, "zz"},
|
||||
{allowedCountries, "de,germany"},
|
||||
{asnLimitPercent, "AS14061:50,,AS16276:50"}, {asnLimitPercent, "ASX:50"},
|
||||
{asnLimitPercent, "AS14061:"}, {asnLimitPercent, "AS14061 :50"},
|
||||
{asnLimitPercent, "AS14061:1.5"}, {asnLimitPercent, "AS-1:50"},
|
||||
{countryLimitPercent, "cn"}, {countryLimitPercent, "cn:"},
|
||||
{countryLimitPercent, "cn:25:50"}, {countryLimitPercent, "china:25"},
|
||||
{asnBytesPercent, "AS14061:50,,AS16276:50"}, {asnBytesPercent, "ASX:50"},
|
||||
{asnBytesPercent, "AS14061:"}, {asnBytesPercent, "AS14061 :50"},
|
||||
{asnBytesPercent, "AS14061:1.5"}, {asnBytesPercent, "AS-1:50"},
|
||||
{countryBytesPercent, "cn"}, {countryBytesPercent, "cn:"},
|
||||
{countryBytesPercent, "cn:25:50"}, {countryBytesPercent, "china:25"},
|
||||
{tagDeny, "a,,b"}, {tagDeny, "country=nk"}, {tagDeny, "asn=abc"},
|
||||
{tagDeny, "asn="}, {tagDeny, "torexit,TOREXIT"}, {tagDeny, "torexti"},
|
||||
{tagDeny, zenTag}, {tagDeny, "abuseipdb"}, {tagDeny, "crowdsec"},
|
||||
{tagLimitPercent, "torexti:50"}, {tagLimitPercent, "dnsbl=zen.example:50"},
|
||||
{tagLimitPercent, "abuseipdb:50"}, {tagLimitPercent, "crowdsec:50"},
|
||||
{tagLimitPercent, "vpn :50"}, {tagLimitPercent, "vpn"}, {tagLimitPercent, ":50"},
|
||||
{tagLimitPercent, "vpn:"}, {tagLimitPercent, "vpn:0"}, {tagLimitPercent, "vpn:100"},
|
||||
{tagLimitPercent, "vpn:1.5"}, {tagLimitPercent, "vpn:50,VPN:25"},
|
||||
{tagLimitPercent, "exploitsignature:50"}, {tagLimitPercent, "asn=abc:50"},
|
||||
{tagLists, torExitTag}, {tagLists, "=https://example.com/a.txt"},
|
||||
{tagLists, "a,b=https://example.com/a.txt"},
|
||||
{tagLists, "a=ftp://example.com/a.txt"},
|
||||
{tagLists, "a=https://example.com/a.txt,A=https://example.com/b.txt"},
|
||||
{tagLists, "a=https://example.com/a.txt,b=https://example.com/a.txt"},
|
||||
{asnBytesPercent, "AS14061:101"}, {countryBytesPercent, "su:50"},
|
||||
{unknownLimitPercent, ""}, {unknownLimitPercent, "-1"},
|
||||
{unknownLimitPercent, "50%"},
|
||||
@@ -2031,6 +2210,25 @@ func TestInvalidValueStopsTheStart(t *testing.T) {
|
||||
{logLevel, "INFO"}, {logLevel, "warning"}, {logLevel, "trace"}, {logLevel, ""},
|
||||
{rulesEnabled, "yes"}, {rulesEnabled, "True"},
|
||||
})
|
||||
|
||||
wantExploitTagStopsTheStartWithRulesAndCoreRuleSetOff(t)
|
||||
}
|
||||
|
||||
// wantExploitTagStopsTheStartWithRulesAndCoreRuleSetOff checks that with rule
|
||||
// files off, and either setting turning off the Core Rule Set, the tag
|
||||
// exploitsignature in SWW_TAG_DENY stops the start.
|
||||
func wantExploitTagStopsTheStartWithRulesAndCoreRuleSetOff(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
for _, name := range []string{wafMode, wafAnomalyThreshold} {
|
||||
_, err := config.FromEnvironment(environment{
|
||||
tagDeny: exploitTag, rulesEnabled: disabled, name: off,
|
||||
}.lookupEnv)
|
||||
if err == nil || !strings.HasPrefix(err.Error(), tagDeny+": ") {
|
||||
t.Errorf("%s=off with rule files off gave the error %v, want one naming %s",
|
||||
name, err, tagDeny)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidBanOrStateValueStopsTheStart(t *testing.T) {
|
||||
@@ -2285,11 +2483,11 @@ func TestLogsEachSettingWithItsValue(t *testing.T) {
|
||||
lookupSource: defaultLookupSource,
|
||||
lookupDBPath: "",
|
||||
lookupTimeout: "800ms",
|
||||
addLookupHeaders: "false",
|
||||
deniedCountries: "",
|
||||
addLookupHeaders: disabled,
|
||||
allowedCountries: "",
|
||||
asnLimitPercent: "",
|
||||
countryLimitPercent: "",
|
||||
tagLists: "",
|
||||
tagDeny: "",
|
||||
tagLimitPercent: "",
|
||||
asnBytesPercent: "",
|
||||
countryBytesPercent: "",
|
||||
unknownLimitPercent: "100",
|
||||
|
||||
Reference in new issue
Block a user