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
110 lines
3.4 KiB
Go
110 lines
3.4 KiB
Go
package discriminator
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/netip"
|
|
"strings"
|
|
"time"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/config"
|
|
"sneak.berlin/go/smallwebwaf/internal/lookup"
|
|
)
|
|
|
|
// The headers in which an answer of allow gives rproxy the client's AS number
|
|
// and country, for the app, while SWW_ADD_LOOKUP_HEADERS is set. Go writes
|
|
// every header name in this form, as it sends it and as it receives it, so
|
|
// X-Client-ASN arrives as X-Client-Asn; header names are not case-sensitive.
|
|
const (
|
|
asnHeader = "X-Client-Asn"
|
|
countryHeader = "X-Client-Country"
|
|
)
|
|
|
|
// lookUp looks up the client's AS number and country, in the lookup
|
|
// database or through GeoJS, and notes them for the log line and as the
|
|
// request's asn= and country= tags, unless
|
|
// SWW_LOOKUP_SOURCE is off or the client is on a private, loopback or
|
|
// link-local address, which no lookup can place. The lookup database
|
|
// answers at once. With GeoJS, while a setting needs the answer, such as a
|
|
// country list or a biased threshold, a new client's request waits for it,
|
|
// unless refusedWhateverTheAnswer reports true. ctx is the request's own
|
|
// context.
|
|
func (rq *request) lookUp(ctx context.Context, now time.Time) {
|
|
if rq.h.config.LookupSource == "off" || !canBePlaced(rq.client) {
|
|
return
|
|
}
|
|
|
|
client := rq.h.clientGroup(rq.client)
|
|
|
|
switch {
|
|
case rq.h.config.LookupSource == "file":
|
|
rq.lookupAnswer = rq.h.lookupFile.LookUp(client)
|
|
case rq.refusedWhateverTheAnswer(now):
|
|
rq.lookupAnswer = rq.h.geojs.LookUpWithoutWaiting(ctx, client)
|
|
rq.skippedLookupWait = rq.lookupAnswer.Answered.IsZero()
|
|
default:
|
|
rq.lookupAnswer = rq.h.geojs.LookUp(ctx, client)
|
|
}
|
|
|
|
rq.lookedUp = true
|
|
rq.line.ASN = rq.lookupAnswer.ASN
|
|
rq.line.ASName = rq.lookupAnswer.ASName
|
|
rq.line.Country = rq.lookupAnswer.Country
|
|
|
|
if tag := asnTag(rq.line.ASN); tag != "" {
|
|
rq.addTag(tag)
|
|
}
|
|
|
|
if tag := countryTag(rq.line.Country); tag != "" {
|
|
rq.addTag(tag)
|
|
}
|
|
}
|
|
|
|
// asnTag returns the tag of asn, an AS number as the lookup gives it, such as
|
|
// asn=64496 for AS64496, or "" for no AS number.
|
|
func asnTag(asn string) string {
|
|
if asn == "" {
|
|
return ""
|
|
}
|
|
|
|
return config.ASNTagPrefix + strings.TrimPrefix(asn, "AS")
|
|
}
|
|
|
|
// countryTag returns the tag of country, a code as the lookup gives it, such
|
|
// as country=DE for DE, or "" for no country.
|
|
func countryTag(country string) string {
|
|
if country == "" {
|
|
return ""
|
|
}
|
|
|
|
return config.CountryTagPrefix + country
|
|
}
|
|
|
|
// addLookup adds answer, an answer about a client from the lookup
|
|
// database or GeoJS, to the client's history, and to the notes of the bans
|
|
// on its netblock that have no AS number, AS name or country yet.
|
|
func (h *handler) addLookup(answer lookup.Answer) {
|
|
h.limiter.AddLookup(answer.Client, answer.Answered,
|
|
answer.ASN, answer.ASName, answer.Country)
|
|
h.ledger.AddLookup(h.netblock(answer.Client.Addr()),
|
|
answer.ASN, answer.ASName, answer.Country)
|
|
}
|
|
|
|
// setLookupHeaders sets the headers that give the client's AS number and
|
|
// country, leaving out one that is unknown.
|
|
func setLookupHeaders(header http.Header, asn, country string) {
|
|
if asn != "" {
|
|
header.Set(asnHeader, asn)
|
|
}
|
|
|
|
if country != "" {
|
|
header.Set(countryHeader, country)
|
|
}
|
|
}
|
|
|
|
// canBePlaced reports whether a lookup can place addr: private, loopback
|
|
// and link-local addresses have no AS number or country.
|
|
func canBePlaced(addr netip.Addr) bool {
|
|
return !addr.IsPrivate() && !addr.IsLoopback() && !addr.IsLinkLocalUnicast()
|
|
}
|