Compare commits
1
Commits
9a5782de68
..
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
719691d1e8 |
No files matched your search
+1
-1
@@ -159,7 +159,7 @@ Every one of these lines has:
|
|||||||
- `limit_hit`: the limit broken:
|
- `limit_hit`: the limit broken:
|
||||||
- `minute`, `hour` or `day` for a rate limit;
|
- `minute`, `hour` or `day` for a rate limit;
|
||||||
- `minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit;
|
- `minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit;
|
||||||
- `minute_refusals`.
|
- `minute_refusals` for the error burst (`SWW_ERROR_BURST_THRESHOLD`).
|
||||||
|
|
||||||
When the bytes counted for a request and its response break a limit, the
|
When the bytes counted for a request and its response break a limit, the
|
||||||
request has been passed on whole, so its `action` stays `forward`. Only its
|
request has been passed on whole, so its `action` stays `forward`. Only its
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ type State struct {
|
|||||||
// requests to it failed, and how many alerts were dropped from its full
|
// requests to it failed, and how many alerts were dropped from its full
|
||||||
// queue or given up as it refused them.
|
// queue or given up as it refused them.
|
||||||
type Counts struct {
|
type Counts struct {
|
||||||
Sent, Failed, Dropped int64
|
Sent, SendFailures, Dropped int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue takes the alerts raised, holds back those it must, and sends the
|
// Queue takes the alerts raised, holds back those it must, and sends the
|
||||||
@@ -264,7 +264,7 @@ type destination struct {
|
|||||||
// waiting are the alerts waiting to be sent, oldest first.
|
// waiting are the alerts waiting to be sent, oldest first.
|
||||||
waiting []*Alert
|
waiting []*Alert
|
||||||
|
|
||||||
sent, failed, dropped atomic.Int64
|
sent, sendFailures, dropped atomic.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// cooldownKey is what makes an alert a repeat of another: the same event
|
// cooldownKey is what makes an alert a repeat of another: the same event
|
||||||
@@ -426,7 +426,9 @@ func (q *Queue) Counts(name string) Counts {
|
|||||||
for _, d := range q.destinations {
|
for _, d := range q.destinations {
|
||||||
if d.name == name {
|
if d.name == name {
|
||||||
return Counts{
|
return Counts{
|
||||||
Sent: d.sent.Load(), Failed: d.failed.Load(), Dropped: d.dropped.Load(),
|
Sent: d.sent.Load(),
|
||||||
|
SendFailures: d.sendFailures.Load(),
|
||||||
|
Dropped: d.dropped.Load(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -675,7 +677,7 @@ func (d *destination) run(ctx context.Context) {
|
|||||||
retryAt = time.Time{}
|
retryAt = time.Time{}
|
||||||
case errors.Is(err, errRefused):
|
case errors.Is(err, errRefused):
|
||||||
d.remove(alert)
|
d.remove(alert)
|
||||||
d.failed.Add(1)
|
d.sendFailures.Add(1)
|
||||||
d.dropped.Add(1)
|
d.dropped.Add(1)
|
||||||
|
|
||||||
retryDelay = 0
|
retryDelay = 0
|
||||||
@@ -684,7 +686,7 @@ func (d *destination) run(ctx context.Context) {
|
|||||||
d.processLog.Warn("gave up an alert "+d.setting+" refused",
|
d.processLog.Warn("gave up an alert "+d.setting+" refused",
|
||||||
"event", alert.Event, "error", err.Error())
|
"event", alert.Event, "error", err.Error())
|
||||||
case ctx.Err() == nil: // not cut off as sww stops
|
case ctx.Err() == nil: // not cut off as sww stops
|
||||||
d.failed.Add(1)
|
d.sendFailures.Add(1)
|
||||||
|
|
||||||
retryDelay = min(max(retryDelayFactor*retryDelay, firstRetryDelay),
|
retryDelay = min(max(retryDelayFactor*retryDelay, firstRetryDelay),
|
||||||
maxRetryDelay)
|
maxRetryDelay)
|
||||||
|
|||||||
@@ -897,7 +897,7 @@ func TestDestinationThatDoesNotAnswerHoldsUpNeitherOther(t *testing.T) {
|
|||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
wantDestinationCounts(t, q, alerts.DestinationSlack,
|
wantDestinationCounts(t, q, alerts.DestinationSlack,
|
||||||
alerts.Counts{Sent: 3, Failed: 1})
|
alerts.Counts{Sent: 3, SendFailures: 1})
|
||||||
|
|
||||||
logged := log.String()
|
logged := log.String()
|
||||||
if !strings.Contains(logged,
|
if !strings.Contains(logged,
|
||||||
@@ -934,7 +934,7 @@ func TestDropsAreCountedForEachDestination(t *testing.T) {
|
|||||||
for destination, want := range map[string]alerts.Counts{
|
for destination, want := range map[string]alerts.Counts{
|
||||||
alerts.DestinationWebhook: {Sent: all},
|
alerts.DestinationWebhook: {Sent: all},
|
||||||
alerts.DestinationSlack: {Dropped: 1},
|
alerts.DestinationSlack: {Dropped: 1},
|
||||||
alerts.DestinationNtfy: {Failed: all, Dropped: all},
|
alerts.DestinationNtfy: {SendFailures: all, Dropped: all},
|
||||||
} {
|
} {
|
||||||
wantDestinationCounts(t, q, destination, want)
|
wantDestinationCounts(t, q, destination, want)
|
||||||
}
|
}
|
||||||
@@ -1223,12 +1223,12 @@ func wantEvents(t *testing.T, webhook *standIn, want ...string) {
|
|||||||
// requests to it that it counts as failed, the alerts it counts as held
|
// requests to it that it counts as failed, the alerts it counts as held
|
||||||
// back, and those it counts as dropped for the webhook.
|
// back, and those it counts as dropped for the webhook.
|
||||||
func wantCounts(
|
func wantCounts(
|
||||||
t *testing.T, q *alerts.Queue, sent, failed, suppressed, dropped int64,
|
t *testing.T, q *alerts.Queue, sent, sendFailures, suppressed, dropped int64,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
wantDestinationCounts(t, q, alerts.DestinationWebhook,
|
wantDestinationCounts(t, q, alerts.DestinationWebhook,
|
||||||
alerts.Counts{Sent: sent, Failed: failed, Dropped: dropped})
|
alerts.Counts{Sent: sent, SendFailures: sendFailures, Dropped: dropped})
|
||||||
|
|
||||||
if q.Suppressed() != suppressed {
|
if q.Suppressed() != suppressed {
|
||||||
t.Errorf("%d alerts held back, want %d", q.Suppressed(), suppressed)
|
t.Errorf("%d alerts held back, want %d", q.Suppressed(), suppressed)
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ func alertFor(r Request, k key, o overThreshold) alerts.Alert {
|
|||||||
detail["asn"] = k.asn
|
detail["asn"] = k.asn
|
||||||
case ScopeTotal:
|
case ScopeTotal:
|
||||||
counted = "the whole service"
|
counted = "the whole service"
|
||||||
default: // watch
|
default: // watched_net
|
||||||
counted = "the named netblock " + k.name + ", " + k.netblock.String()
|
counted = "the named netblock " + k.name + ", " + k.netblock.String()
|
||||||
detail["name"] = k.name
|
detail["name"] = k.name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ func TestBanWithoutACauseIsAnAdmins(t *testing.T) {
|
|||||||
func TestAdminsBansAreNeverDroppedAndDoNotCountTowardMaxBans(t *testing.T) {
|
func TestAdminsBansAreNeverDroppedAndDoNotCountTowardMaxBans(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
rules := defaultParams()
|
params := defaultParams()
|
||||||
rules.MaxBans = 1
|
params.MaxBans = 1
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(params)
|
||||||
adminsOnly := netip.MustParsePrefix("198.51.100.0/24")
|
adminsOnly := netip.MustParsePrefix("198.51.100.0/24")
|
||||||
both := netip.MustParsePrefix("203.0.113.1/32")
|
both := netip.MustParsePrefix("203.0.113.1/32")
|
||||||
second := netip.MustParsePrefix("203.0.113.2/32")
|
second := netip.MustParsePrefix("203.0.113.2/32")
|
||||||
@@ -64,9 +64,9 @@ func TestAdminsBansAreNeverDroppedAndDoNotCountTowardMaxBans(t *testing.T) {
|
|||||||
func TestEarlierBansCountABanDroppedAfterAnAdminsBan(t *testing.T) {
|
func TestEarlierBansCountABanDroppedAfterAnAdminsBan(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
rules := defaultParams()
|
params := defaultParams()
|
||||||
rules.MaxBans = 2
|
params.MaxBans = 2
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(params)
|
||||||
netblock := netip.MustParsePrefix("203.0.113.1/32")
|
netblock := netip.MustParsePrefix("203.0.113.1/32")
|
||||||
other := netip.MustParsePrefix("203.0.113.2/32")
|
other := netip.MustParsePrefix("203.0.113.2/32")
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -80,9 +80,9 @@ func TestRepeatWindowRunsOut(t *testing.T) {
|
|||||||
func TestFirstBanLongerThanTheMaximumIsPermanent(t *testing.T) {
|
func TestFirstBanLongerThanTheMaximumIsPermanent(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
rules := defaultParams()
|
params := defaultParams()
|
||||||
rules.LimitBanDuration = rules.MaxBanDuration + time.Hour
|
params.LimitBanDuration = params.MaxBanDuration + time.Hour
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(params)
|
||||||
|
|
||||||
ban, _ := ledger.BanForLimit(netip.MustParsePrefix("203.0.113.9/32"), midnight(),
|
ban, _ := ledger.BanForLimit(netip.MustParsePrefix("203.0.113.9/32"), midnight(),
|
||||||
bans.Notes{})
|
bans.Notes{})
|
||||||
@@ -97,9 +97,9 @@ func TestLongestBanSetFarOffDoesNotOverflow(t *testing.T) {
|
|||||||
// With bans of up to 100,000 days, the 14th ban in a row, of 3^13
|
// With bans of up to 100,000 days, the 14th ban in a row, of 3^13
|
||||||
// hours, is within the maximum, and three times as long would not fit
|
// hours, is within the maximum, and three times as long would not fit
|
||||||
// in a time.Duration. The 15th is permanent.
|
// in a time.Duration. The 15th is permanent.
|
||||||
rules := defaultParams()
|
params := defaultParams()
|
||||||
rules.MaxBanDuration = 100000 * day
|
params.MaxBanDuration = 100000 * day
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(params)
|
||||||
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
now := midnight()
|
now := midnight()
|
||||||
|
|
||||||
@@ -200,9 +200,9 @@ func TestFindCountsNothing(t *testing.T) {
|
|||||||
func TestMaxBansDropsTheEarliestBanOfTheNetblockSeenLongestAgo(t *testing.T) {
|
func TestMaxBansDropsTheEarliestBanOfTheNetblockSeenLongestAgo(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
rules := defaultParams()
|
params := defaultParams()
|
||||||
rules.MaxBans = 3
|
params.MaxBans = 3
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(params)
|
||||||
a := netip.MustParsePrefix("203.0.113.1/32")
|
a := netip.MustParsePrefix("203.0.113.1/32")
|
||||||
b := netip.MustParsePrefix("203.0.113.2/32")
|
b := netip.MustParsePrefix("203.0.113.2/32")
|
||||||
c := netip.MustParsePrefix("203.0.113.3/32")
|
c := netip.MustParsePrefix("203.0.113.3/32")
|
||||||
@@ -239,9 +239,9 @@ func TestFullLedgerDropsTheEarlierBanOfTheNetblockBannedAgain(t *testing.T) {
|
|||||||
|
|
||||||
// With room for one ban, the netblock's ended ban goes to make room for
|
// With room for one ban, the netblock's ended ban goes to make room for
|
||||||
// its new one, whose notes still count it.
|
// its new one, whose notes still count it.
|
||||||
rules := defaultParams()
|
params := defaultParams()
|
||||||
rules.MaxBans = 1
|
params.MaxBans = 1
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(params)
|
||||||
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
|
|
||||||
first, _ := ledger.BanForLimit(netblock, midnight(), bans.Notes{})
|
first, _ := ledger.BanForLimit(netblock, midnight(), bans.Notes{})
|
||||||
|
|||||||
@@ -219,9 +219,9 @@ func TestLoadKeepsAtMostMaxBansDroppingTheEarliest(t *testing.T) {
|
|||||||
Cause: bans.CauseLimit,
|
Cause: bans.CauseLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
rules := defaultParams()
|
params := defaultParams()
|
||||||
rules.MaxBans = 1
|
params.MaxBans = 1
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(params)
|
||||||
ledger.Load([]bans.Ban{later, earlier})
|
ledger.Load([]bans.Ban{later, earlier})
|
||||||
|
|
||||||
held := ledger.Snapshot()
|
held := ledger.Snapshot()
|
||||||
@@ -235,9 +235,9 @@ func TestLoadReplacesTheBansHeld(t *testing.T) {
|
|||||||
|
|
||||||
// Room for three bans, so that the second load, were it added to the
|
// Room for three bans, so that the second load, were it added to the
|
||||||
// two bans held, would drop none of them to make room.
|
// two bans held, would drop none of them to make room.
|
||||||
rules := defaultParams()
|
params := defaultParams()
|
||||||
rules.MaxBans = 3
|
params.MaxBans = 3
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(params)
|
||||||
kept := bans.Ban{
|
kept := bans.Ban{
|
||||||
Netblock: netip.MustParsePrefix("2001:db8::/64"),
|
Netblock: netip.MustParsePrefix("2001:db8::/64"),
|
||||||
Start: midnight(),
|
Start: midnight(),
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ func checkExclusions(exclusions string) error {
|
|||||||
// CoreRuleSet is the Core Rule Set, ready to inspect requests. It is safe
|
// CoreRuleSet is the Core Rule Set, ready to inspect requests. It is safe
|
||||||
// for concurrent use.
|
// for concurrent use.
|
||||||
type CoreRuleSet struct {
|
type CoreRuleSet struct {
|
||||||
waf coraza.WAF
|
engine coraza.WAF
|
||||||
bodyLimit int64
|
bodyLimit int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,14 +327,14 @@ func New(params Params) (*CoreRuleSet, error) {
|
|||||||
text += "SecRuleRemoveById " + strings.Join(ids, " ") + "\n"
|
text += "SecRuleRemoveById " + strings.Join(ids, " ") + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
waf, err := coraza.NewWAF(coraza.NewWAFConfig().
|
engine, err := coraza.NewWAF(coraza.NewWAFConfig().
|
||||||
WithRootFS(coreruleset.FS).
|
WithRootFS(coreruleset.FS).
|
||||||
WithDirectives(text))
|
WithDirectives(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load the Core Rule Set: %w", err)
|
return nil, fmt.Errorf("load the Core Rule Set: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CoreRuleSet{waf: waf, bodyLimit: params.BodyLimit}, nil
|
return &CoreRuleSet{engine: engine, bodyLimit: params.BodyLimit}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result is what the Core Rule Set found in a request.
|
// Result is what the Core Rule Set found in a request.
|
||||||
@@ -354,7 +354,7 @@ type Result struct {
|
|||||||
func (c *CoreRuleSet) Inspect(
|
func (c *CoreRuleSet) Inspect(
|
||||||
r *http.Request, client netip.Addr, body io.Reader,
|
r *http.Request, client netip.Addr, body io.Reader,
|
||||||
) (Result, []byte, error) {
|
) (Result, []byte, error) {
|
||||||
tx := c.waf.NewTransaction()
|
tx := c.engine.NewTransaction()
|
||||||
// Closing would remove the files Coraza wrote, and it writes none.
|
// Closing would remove the files Coraza wrote, and it writes none.
|
||||||
defer func() { _ = tx.Close() }()
|
defer func() { _ = tx.Close() }()
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func TestAdminEndpointsAreOffWhileTheTokenIsUnset(t *testing.T) {
|
|||||||
// An empty token does not match the unset one either.
|
// An empty token does not match the unset one either.
|
||||||
for _, authorization := range []string{adminBearer, bearer, "Bearer ", ""} {
|
for _, authorization := range []string{adminBearer, bearer, "Bearer ", ""} {
|
||||||
for _, e := range adminEndpoints() {
|
for _, e := range adminEndpoints() {
|
||||||
s.adminRequest(authorization, e.method, e.path, e.body, http.StatusNotFound)
|
s.ownEndpointRequest(authorization, e.method, e.path, e.body, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ func TestAdminEndpointsNeedTheAdminToken(t *testing.T) {
|
|||||||
for _, authorization := range []string{
|
for _, authorization := range []string{
|
||||||
"", bearer, "Bearer " + strings.ToUpper(adminSecret), "Basic " + adminSecret,
|
"", bearer, "Bearer " + strings.ToUpper(adminSecret), "Basic " + adminSecret,
|
||||||
} {
|
} {
|
||||||
got := s.adminRequest(authorization, e.method, e.path, e.body,
|
got := s.ownEndpointRequest(authorization, e.method, e.path, e.body,
|
||||||
http.StatusUnauthorized)
|
http.StatusUnauthorized)
|
||||||
if got.header.Get("WWW-Authenticate") != "Bearer" {
|
if got.header.Get("WWW-Authenticate") != "Bearer" {
|
||||||
t.Errorf("%s %s with %q was answered without WWW-Authenticate: Bearer",
|
t.Errorf("%s %s with %q was answered without WWW-Authenticate: Bearer",
|
||||||
@@ -329,7 +329,7 @@ func TestAdminEndpointsNeedTheTokenInObserveMode(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for _, e := range adminEndpoints() {
|
for _, e := range adminEndpoints() {
|
||||||
s.adminRequest("", e.method, e.path, e.body, http.StatusUnauthorized)
|
s.ownEndpointRequest("", e.method, e.path, e.body, http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
if held := server.Ledger.Snapshot(); len(held) != 0 {
|
if held := server.Ledger.Snapshot(); len(held) != 0 {
|
||||||
@@ -360,12 +360,12 @@ func adminEndpoints() []adminEndpoint {
|
|||||||
func (s *sender) admin(method, path, body string, status int) answer {
|
func (s *sender) admin(method, path, body string, status int) answer {
|
||||||
s.t.Helper()
|
s.t.Helper()
|
||||||
|
|
||||||
return s.adminRequest(adminBearer, method, path, body, status)
|
return s.ownEndpointRequest(adminBearer, method, path, body, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// adminRequest is admin with authorization as the request's Authorization header
|
// ownEndpointRequest is admin with authorization as the request's Authorization
|
||||||
// unless it is "".
|
// header unless it is "".
|
||||||
func (s *sender) adminRequest(
|
func (s *sender) ownEndpointRequest(
|
||||||
authorization, method, path, body string, status int,
|
authorization, method, path, body string, status int,
|
||||||
) answer {
|
) answer {
|
||||||
s.t.Helper()
|
s.t.Helper()
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ const (
|
|||||||
// tooLarge is how discriminator refuses a request over the header limits.
|
// tooLarge is how discriminator refuses a request over the header limits.
|
||||||
tooLarge = http.StatusRequestHeaderFieldsTooLarge
|
tooLarge = http.StatusRequestHeaderFieldsTooLarge
|
||||||
// silentAbuseIPDBURL is where AbuseIPDB never answers: at the stand-in
|
// silentAbuseIPDBURL is where AbuseIPDB never answers: at the stand-in
|
||||||
// that unansweredGeoJSURL names too.
|
// that silentGeoJSURL names too.
|
||||||
silentAbuseIPDBURL = "unanswered://abuseipdb/api/v2/check"
|
silentAbuseIPDBURL = "silent://abuseipdb/api/v2/check"
|
||||||
// wholeLimit is the percentage of a rate limit that lowers it not at all.
|
// wholeLimit is the percentage of a rate limit that lowers it not at all.
|
||||||
wholeLimit = 100
|
wholeLimit = 100
|
||||||
// clientsDecision is the CrowdSec decision list holding client alone.
|
// clientsDecision is the CrowdSec decision list holding client alone.
|
||||||
@@ -340,7 +340,7 @@ func newDiscriminatorInBubble(
|
|||||||
settings := map[string]string{tagDeny: "country=KP"}
|
settings := map[string]string{tagDeny: "country=KP"}
|
||||||
maps.Copy(settings, env)
|
maps.Copy(settings, env)
|
||||||
|
|
||||||
server, out, _ := newDiscriminator(t, unansweredGeoJSURL, time.Now, settings)
|
server, out, _ := newDiscriminator(t, silentGeoJSURL, time.Now, settings)
|
||||||
|
|
||||||
return server, out
|
return server, out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ func (rq *request) banForLimit(now time.Time, hit ratelimit.Hit, status int) {
|
|||||||
|
|
||||||
switch hit.Kind {
|
switch hit.Kind {
|
||||||
case ratelimit.KindRequests:
|
case ratelimit.KindRequests:
|
||||||
notes.LimitPercent, notes.LimitPercentSetting = rq.limitPercent.logged()
|
notes.LimitPercent, notes.LimitPercentSetting = rq.rateLimitPercent.logged()
|
||||||
case ratelimit.KindBytes:
|
case ratelimit.KindBytes:
|
||||||
notes.LimitPercent, notes.LimitPercentSetting = rq.bytesPercent.logged()
|
notes.LimitPercent, notes.LimitPercentSetting = rq.bytesLimitPercent.logged()
|
||||||
}
|
}
|
||||||
|
|
||||||
if rq.h.config.Observe {
|
if rq.h.config.Observe {
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ func startWithClock(
|
|||||||
|
|
||||||
// sender sends requests to rproxy one after another, each on a connection of its
|
// sender sends requests to rproxy one after another, each on a connection of its
|
||||||
// own, and checks each one's answer and log line. discriminatorAddr is where
|
// own, and checks each one's answer and log line. discriminatorAddr is where
|
||||||
// discriminator's own endpoints are, which adminRequest asks. Every request
|
// discriminator's own endpoints are, which ownEndpointRequest asks. Every request
|
||||||
// discriminator gets must come through the sender, since the log lines are
|
// discriminator gets must come through the sender, since the log lines are
|
||||||
// matched to them in order.
|
// matched to them in order.
|
||||||
type sender struct {
|
type sender struct {
|
||||||
|
|||||||
@@ -143,9 +143,9 @@ func TestBytesPercentSettingsTakeThePlaceOfTheOthersForByteLimits(t *testing.T)
|
|||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
env map[string]string
|
env map[string]string
|
||||||
// limitPercent and bytesPercent are the log line's, as percentText
|
// rateLimitPercent and bytesLimitPercent are the log line's, as
|
||||||
// gives them, and limitHit is its limit_hit.
|
// percentText gives them, and limitHit is its limit_hit.
|
||||||
limitPercent, bytesPercent, limitHit string
|
rateLimitPercent, bytesLimitPercent, limitHit string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"lowering the byte limits alone",
|
"lowering the byte limits alone",
|
||||||
@@ -183,10 +183,10 @@ func TestBytesPercentSettingsTakeThePlaceOfTheOthersForByteLimits(t *testing.T)
|
|||||||
|
|
||||||
wantPercent(t, "rate_limit_percent",
|
wantPercent(t, "rate_limit_percent",
|
||||||
line.RateLimitPercent, line.RateLimitPercentSetting,
|
line.RateLimitPercent, line.RateLimitPercentSetting,
|
||||||
tc.limitPercent)
|
tc.rateLimitPercent)
|
||||||
wantPercent(t, "bytes_limit_percent",
|
wantPercent(t, "bytes_limit_percent",
|
||||||
line.BytesLimitPercent, line.BytesLimitPercentSetting,
|
line.BytesLimitPercent, line.BytesLimitPercentSetting,
|
||||||
tc.bytesPercent)
|
tc.bytesLimitPercent)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ func TestClientWithoutAnAnswerInTimeHasTheUnknownLimitPercent(t *testing.T) {
|
|||||||
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
||||||
// says, with a GeoJS that never answers.
|
// says, with a GeoJS that never answers.
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
server, out, _ := newDiscriminator(t, unansweredGeoJSURL, time.Now,
|
server, out, _ := newDiscriminator(t, silentGeoJSURL, time.Now,
|
||||||
map[string]string{unknownLimitPercent: "0"})
|
map[string]string{unknownLimitPercent: "0"})
|
||||||
|
|
||||||
// Once the time the request waits for its answer is up, the client
|
// Once the time the request waits for its answer is up, the client
|
||||||
@@ -335,7 +335,7 @@ func TestRequestWaitsForItsLookupWhileABiasedThresholdIsSet(t *testing.T) {
|
|||||||
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
||||||
// says, with a GeoJS that never answers.
|
// says, with a GeoJS that never answers.
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
server, out, _ := newDiscriminator(t, unansweredGeoJSURL, time.Now,
|
server, out, _ := newDiscriminator(t, silentGeoJSURL, time.Now,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
lookupTimeout: timeout.String(), tagLists: "vpn=" + vpnList, tc.setting: tc.value,
|
lookupTimeout: timeout.String(), tagLists: "vpn=" + vpnList, tc.setting: tc.value,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (byteLimits) check(_ context.Context, rq *request, now time.Time) *verdict
|
|||||||
}
|
}
|
||||||
|
|
||||||
counts, hit, over := rq.h.limiter.CountBytes(rq.h.clientGroup(rq.client), now,
|
counts, hit, over := rq.h.limiter.CountBytes(rq.h.clientGroup(rq.client), now,
|
||||||
rq.countedBytes(), rq.bytesPercent.percent)
|
rq.countedBytes(), rq.bytesLimitPercent.percent)
|
||||||
rq.line.Counts.MinuteBytes = counts.MinuteBytes
|
rq.line.Counts.MinuteBytes = counts.MinuteBytes
|
||||||
rq.line.Counts.HourBytes = counts.HourBytes
|
rq.line.Counts.HourBytes = counts.HourBytes
|
||||||
rq.line.Counts.DayBytes = counts.DayBytes
|
rq.line.Counts.DayBytes = counts.DayBytes
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ func (h *handler) answerQuestion(w http.ResponseWriter, r *http.Request) {
|
|||||||
rq.line.DiscriminatorLine = requestlog.DiscriminatorLineDecision
|
rq.line.DiscriminatorLine = requestlog.DiscriminatorLineDecision
|
||||||
refused := rq.check(r.Context())
|
refused := rq.check(r.Context())
|
||||||
rq.checked = time.Now()
|
rq.checked = time.Now()
|
||||||
underOwnPrefix := strings.HasPrefix(r.URL.Path, adminPrefix)
|
underOwnPrefix := strings.HasPrefix(r.URL.Path, ownEndpointPrefix)
|
||||||
|
|
||||||
if refused == nil && !underOwnPrefix {
|
if refused == nil && !underOwnPrefix {
|
||||||
// In observe mode, what enforce mode would have refused counts here.
|
// In observe mode, what enforce mode would have refused counts here.
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ func TestHealthEndpointCountsTowardNoLimit(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
for range healthChecks {
|
for range healthChecks {
|
||||||
got := s.adminRequest("", http.MethodGet, discriminator.HealthPath, "", http.StatusOK)
|
got := s.ownEndpointRequest("", http.MethodGet, discriminator.HealthPath, "",
|
||||||
|
http.StatusOK)
|
||||||
|
|
||||||
if string(got.body) != "ok\n" || got.header.Get("Content-Type") != contentType {
|
if string(got.body) != "ok\n" || got.header.Get("Content-Type") != contentType {
|
||||||
t.Errorf("health endpoint answered %q with Content-Type %q, want ok "+
|
t.Errorf("health endpoint answered %q with Content-Type %q, want ok "+
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ func TestOwnEndpointsAreNotInTheHistory(t *testing.T) {
|
|||||||
|
|
||||||
s, _, server := startWithClock(t, "", map[string]string{metricsToken: token})
|
s, _, server := startWithClock(t, "", map[string]string{metricsToken: token})
|
||||||
|
|
||||||
s.adminRequest("", http.MethodGet, discriminator.HealthPath, "", http.StatusOK)
|
s.ownEndpointRequest("", http.MethodGet, discriminator.HealthPath, "", http.StatusOK)
|
||||||
s.adminRequest("", http.MethodGet, discriminator.MetricsPath, "",
|
s.ownEndpointRequest("", http.MethodGet, discriminator.MetricsPath, "",
|
||||||
http.StatusUnauthorized)
|
http.StatusUnauthorized)
|
||||||
s.scrape()
|
s.scrape()
|
||||||
|
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ func TestRequestWaitsAsLongAsTheLookupTimeoutSays(t *testing.T) {
|
|||||||
// is never reached.
|
// is never reached.
|
||||||
const timeout = 3 * time.Second
|
const timeout = 3 * time.Second
|
||||||
|
|
||||||
server, out, _ := newDiscriminator(t, unansweredGeoJSURL, time.Now,
|
server, out, _ := newDiscriminator(t, silentGeoJSURL, time.Now,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
lookupTimeout: timeout.String(),
|
lookupTimeout: timeout.String(),
|
||||||
allowedCountries: "DE",
|
allowedCountries: "DE",
|
||||||
@@ -405,25 +405,25 @@ func TestRequestWaitsAsLongAsTheLookupTimeoutSays(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// unansweredGeoJSURL is where a GeoJS that never answers is asked: a
|
// silentGeoJSURL is where a GeoJS that never answers is asked: a
|
||||||
// request to it waits, without the network, until it is abandoned.
|
// request to it waits, without the network, until it is abandoned.
|
||||||
// TestMain registers it with Go's default transport, through which GeoJS
|
// TestMain registers it with Go's default transport, through which GeoJS
|
||||||
// is asked.
|
// is asked.
|
||||||
const unansweredGeoJSURL = "unanswered://geojs/v1/ip/geo.json"
|
const silentGeoJSURL = "silent://geojs/v1/ip/geo.json"
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
transport, _ := http.DefaultTransport.(*http.Transport)
|
transport, _ := http.DefaultTransport.(*http.Transport)
|
||||||
transport.RegisterProtocol("unanswered", unansweredGeoJS{})
|
transport.RegisterProtocol("silent", silentGeoJS{})
|
||||||
transport.RegisterProtocol("abuseipdb", abuseIPDBStandIn{})
|
transport.RegisterProtocol("abuseipdb", abuseIPDBStandIn{})
|
||||||
|
|
||||||
m.Run()
|
m.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// unansweredGeoJS is the GeoJS at unansweredGeoJSURL.
|
// silentGeoJS is the GeoJS at silentGeoJSURL.
|
||||||
type unansweredGeoJS struct{}
|
type silentGeoJS struct{}
|
||||||
|
|
||||||
// RoundTrip waits until req is abandoned.
|
// RoundTrip waits until req is abandoned.
|
||||||
func (unansweredGeoJS) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (silentGeoJS) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
<-req.Context().Done()
|
<-req.Context().Done()
|
||||||
|
|
||||||
return nil, req.Context().Err()
|
return nil, req.Context().Err()
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func TestMetricsAreOffWhileTheTokenIsUnset(t *testing.T) {
|
|||||||
|
|
||||||
// An empty token does not match the unset one either.
|
// An empty token does not match the unset one either.
|
||||||
for _, authorization := range []string{bearer, "Bearer ", ""} {
|
for _, authorization := range []string{bearer, "Bearer ", ""} {
|
||||||
s.adminRequest(authorization, http.MethodGet, discriminator.MetricsPath, "",
|
s.ownEndpointRequest(authorization, http.MethodGet, discriminator.MetricsPath, "",
|
||||||
http.StatusNotFound)
|
http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ func TestMetricsNeedTheTokenAndOtherPathsAreNotFound(t *testing.T) {
|
|||||||
{http.MethodGet, "/_sww/bans", bearer, http.StatusNotFound},
|
{http.MethodGet, "/_sww/bans", bearer, http.StatusNotFound},
|
||||||
{http.MethodPost, discriminator.HealthPath, "", http.StatusNotFound},
|
{http.MethodPost, discriminator.HealthPath, "", http.StatusNotFound},
|
||||||
} {
|
} {
|
||||||
got := s.adminRequest(tc.authorization, tc.method, tc.path, "", tc.status)
|
got := s.ownEndpointRequest(tc.authorization, tc.method, tc.path, "", tc.status)
|
||||||
|
|
||||||
if tc.status == http.StatusUnauthorized &&
|
if tc.status == http.StatusUnauthorized &&
|
||||||
got.header.Get("WWW-Authenticate") != "Bearer" {
|
got.header.Get("WWW-Authenticate") != "Bearer" {
|
||||||
@@ -381,8 +381,8 @@ func scrape(t *testing.T, addr string) string {
|
|||||||
func (s *sender) scrape() string {
|
func (s *sender) scrape() string {
|
||||||
s.t.Helper()
|
s.t.Helper()
|
||||||
|
|
||||||
return string(s.adminRequest(bearer, http.MethodGet, discriminator.MetricsPath, "",
|
return string(s.ownEndpointRequest(bearer, http.MethodGet, discriminator.MetricsPath,
|
||||||
http.StatusOK).body)
|
"", http.StatusOK).body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// metric returns the value of series in metrics, which are in the
|
// metric returns the value of series in metrics, which are in the
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/rules"
|
"sneak.berlin/go/smallwebwaf/internal/rules"
|
||||||
)
|
)
|
||||||
|
|
||||||
// adminPrefix starts the path of each of discriminator's own endpoints. The app never
|
// ownEndpointPrefix starts the path of each of discriminator's own endpoints. The app
|
||||||
// sees a path under it.
|
// never sees a path under it.
|
||||||
const adminPrefix = "/_sww/"
|
const ownEndpointPrefix = "/_sww/"
|
||||||
|
|
||||||
// HealthPath is the health endpoint of discriminator and of rproxy, which the
|
// HealthPath is the health endpoint of discriminator and of rproxy, which the
|
||||||
// container's health check asks.
|
// container's health check asks.
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ func (rateLimits) check(_ context.Context, rq *request, now time.Time) *verdict
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rq.limitPercent, rq.bytesPercent = rq.limitPercentages()
|
rq.rateLimitPercent, rq.bytesLimitPercent = rq.limitPercentages()
|
||||||
rq.line.RateLimitPercent, rq.line.RateLimitPercentSetting = rq.limitPercent.logged()
|
rq.line.RateLimitPercent, rq.line.RateLimitPercentSetting =
|
||||||
rq.line.BytesLimitPercent, rq.line.BytesLimitPercentSetting = rq.bytesPercent.logged()
|
rq.rateLimitPercent.logged()
|
||||||
|
rq.line.BytesLimitPercent, rq.line.BytesLimitPercentSetting =
|
||||||
|
rq.bytesLimitPercent.logged()
|
||||||
|
|
||||||
counts, hit, over := rq.h.limiter.Count(rq.h.clientGroup(rq.client), now,
|
counts, hit, over := rq.h.limiter.Count(rq.h.clientGroup(rq.client), now,
|
||||||
rq.limitPercent.percent)
|
rq.rateLimitPercent.percent)
|
||||||
rq.line.Counts = counts
|
rq.line.Counts = counts
|
||||||
|
|
||||||
if !over {
|
if !over {
|
||||||
|
|||||||
@@ -256,9 +256,9 @@ func TestASNLimitPercentFileCountsAsTheSettingDoesTheLowerWinning(t *testing.T)
|
|||||||
name string
|
name string
|
||||||
env map[string]string
|
env map[string]string
|
||||||
file string
|
file string
|
||||||
// limitPercent and bytesPercent are the upload's, as percentText
|
// rateLimitPercent and bytesLimitPercent are the upload's, as
|
||||||
// gives them.
|
// percentText gives them.
|
||||||
limitPercent, bytesPercent string
|
rateLimitPercent, bytesLimitPercent string
|
||||||
}{
|
}{
|
||||||
{"the file's alone", nil, asnDEQuarter, fromURL, fromURL},
|
{"the file's alone", nil, asnDEQuarter, fromURL, fromURL},
|
||||||
{
|
{
|
||||||
@@ -293,10 +293,10 @@ func TestASNLimitPercentFileCountsAsTheSettingDoesTheLowerWinning(t *testing.T)
|
|||||||
line := s.uploadFrom(fromDE)
|
line := s.uploadFrom(fromDE)
|
||||||
wantPercent(t, "rate_limit_percent",
|
wantPercent(t, "rate_limit_percent",
|
||||||
line.RateLimitPercent, line.RateLimitPercentSetting,
|
line.RateLimitPercent, line.RateLimitPercentSetting,
|
||||||
tc.limitPercent)
|
tc.rateLimitPercent)
|
||||||
wantPercent(t, "bytes_limit_percent",
|
wantPercent(t, "bytes_limit_percent",
|
||||||
line.BytesLimitPercent, line.BytesLimitPercentSetting,
|
line.BytesLimitPercent, line.BytesLimitPercentSetting,
|
||||||
tc.bytesPercent)
|
tc.bytesLimitPercent)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ type decision struct {
|
|||||||
// so that not waiting never lowers its client's limits.
|
// so that not waiting never lowers its client's limits.
|
||||||
skippedLookupWait bool
|
skippedLookupWait bool
|
||||||
// counted is true for a request the rate limits counted, whose bytes
|
// counted is true for a request the rate limits counted, whose bytes
|
||||||
// the byte limits count once it has ended. limitPercent and
|
// the byte limits count once it has ended. rateLimitPercent and
|
||||||
// bytesPercent are then its client's limit percentages for the rate
|
// bytesLimitPercent are then its client's limit percentages for the rate
|
||||||
// limits and for the byte limits.
|
// limits and for the byte limits.
|
||||||
counted bool
|
counted bool
|
||||||
limitPercent, bytesPercent percentage
|
rateLimitPercent, bytesLimitPercent percentage
|
||||||
// attack is true for a request that matched a ban rule or asked for a
|
// attack is true for a request that matched a ban rule or asked for a
|
||||||
// trap path, blockedByBlockRule for one a block rule refused, and
|
// trap path, blockedByBlockRule for one a block rule refused, and
|
||||||
// blockedByCoreRuleSet for one the Core Rule Set refused, each an offence
|
// blockedByCoreRuleSet for one the Core Rule Set refused, each an offence
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ func TestWithoutWaitTheRequestGoesOnAtOnceAndTheAnswerIsGivenWhenItComes(
|
|||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
||||||
wantCountry(t, g, client, germany)
|
wantCountry(t, g, client, germany)
|
||||||
wantUnanswered(t, m, 0)
|
wantRequestsWithoutGeoJSAnswer(t, m, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,7 +596,7 @@ func TestClientsWithoutAnAnswerAreCounted(t *testing.T) {
|
|||||||
// GeoJS fails, so the first client goes without an answer, and GeoJS
|
// GeoJS fails, so the first client goes without an answer, and GeoJS
|
||||||
// is left alone for a second, which does not pass in this test.
|
// is left alone for a second, which does not pass in this test.
|
||||||
wantCountry(t, g, clients(), "")
|
wantCountry(t, g, clients(), "")
|
||||||
wantUnanswered(t, m, 1)
|
wantRequestsWithoutGeoJSAnswer(t, m, 1)
|
||||||
|
|
||||||
// Meanwhile each new client goes without one at once, while there is
|
// Meanwhile each new client goes without one at once, while there is
|
||||||
// room for it among the 10,000 that may wait.
|
// room for it among the 10,000 that may wait.
|
||||||
@@ -604,11 +604,11 @@ func TestClientsWithoutAnAnswerAreCounted(t *testing.T) {
|
|||||||
wantCountry(t, g, clients(), "")
|
wantCountry(t, g, clients(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
wantUnanswered(t, m, 10000)
|
wantRequestsWithoutGeoJSAnswer(t, m, 10000)
|
||||||
|
|
||||||
// One more, for which there is no room, goes without one too.
|
// One more, for which there is no room, goes without one too.
|
||||||
wantCountry(t, g, clients(), "")
|
wantCountry(t, g, clients(), "")
|
||||||
wantUnanswered(t, m, 10001)
|
wantRequestsWithoutGeoJSAnswer(t, m, 10001)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,9 +841,9 @@ func wantAsked(t *testing.T, geojs *standIn, i int, want ...string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wantUnanswered checks how many requests m counts as having gone without
|
// wantRequestsWithoutGeoJSAnswer checks how many requests m counts as having
|
||||||
// an answer from GeoJS.
|
// gone without an answer from GeoJS.
|
||||||
func wantUnanswered(t *testing.T, m *metrics.Metrics, want float64) {
|
func wantRequestsWithoutGeoJSAnswer(t *testing.T, m *metrics.Metrics, want float64) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got := testutil.ToFloat64(m.RequestsWithoutGeoJSAnswer)
|
got := testutil.ToFloat64(m.RequestsWithoutGeoJSAnswer)
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ func (m *Metrics) AddAlerts(queue *alerts.Queue) {
|
|||||||
Help: "Requests to the destination that failed.",
|
Help: "Requests to the destination that failed.",
|
||||||
ConstLabels: destination,
|
ConstLabels: destination,
|
||||||
}, func() float64 {
|
}, func() float64 {
|
||||||
return float64(queue.Counts(name).Failed)
|
return float64(queue.Counts(name).SendFailures)
|
||||||
}),
|
}),
|
||||||
prometheus.NewCounterFunc(prometheus.CounterOpts{
|
prometheus.NewCounterFunc(prometheus.CounterOpts{
|
||||||
Name: "sww_alerts_suppressed_total",
|
Name: "sww_alerts_suppressed_total",
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ const (
|
|||||||
idleTimeout = 120 * time.Second
|
idleTimeout = 120 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// adminPrefix starts the path of every request for sww itself, which
|
// ownEndpointPrefix starts the path of every request for sww itself, which
|
||||||
// never reaches the app.
|
// never reaches the app.
|
||||||
const adminPrefix = "/_sww/"
|
const ownEndpointPrefix = "/_sww/"
|
||||||
|
|
||||||
// allowedMethods lists the methods rproxy passes on, as an Allow header gives
|
// allowedMethods lists the methods rproxy passes on, as an Allow header gives
|
||||||
// them. It answers any other method 405 itself.
|
// them. It answers any other method 405 itself.
|
||||||
@@ -211,8 +211,8 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// passToApp passes the request on to the app, unless a limit or its path keeps
|
// passToApp passes the request on to the app, unless a limit or its path keeps
|
||||||
// it from the app. discriminator answers a path under adminPrefix itself, so such a
|
// it from the app. discriminator answers a path under ownEndpointPrefix itself, so
|
||||||
// request gets here only when SWRP_DEFAULT_DISPOSITION allowed it. ctx is the
|
// such a request gets here only when SWRP_DEFAULT_DISPOSITION allowed it. ctx is the
|
||||||
// request's own context.
|
// request's own context.
|
||||||
func (rq *request) passToApp(ctx context.Context) {
|
func (rq *request) passToApp(ctx context.Context) {
|
||||||
maxBytes := rq.h.config.MaxRequestSize
|
maxBytes := rq.h.config.MaxRequestSize
|
||||||
@@ -220,7 +220,7 @@ func (rq *request) passToApp(ctx context.Context) {
|
|||||||
switch {
|
switch {
|
||||||
case rq.refused.Load() != 0 || (maxBytes > 0 && rq.in.ContentLength > maxBytes):
|
case rq.refused.Load() != 0 || (maxBytes > 0 && rq.in.ContentLength > maxBytes):
|
||||||
rq.answer(http.StatusRequestEntityTooLarge)
|
rq.answer(http.StatusRequestEntityTooLarge)
|
||||||
case strings.HasPrefix(rq.in.URL.Path, adminPrefix):
|
case strings.HasPrefix(rq.in.URL.Path, ownEndpointPrefix):
|
||||||
rq.answer(http.StatusNotFound)
|
rq.answer(http.StatusNotFound)
|
||||||
default:
|
default:
|
||||||
rq.forward(ctx)
|
rq.forward(ctx)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ var (
|
|||||||
errCause = errors.New("is not limit, attack, admin or crowdsec")
|
errCause = errors.New("is not limit, attack, admin or crowdsec")
|
||||||
errExpires = errors.New("is not after its start")
|
errExpires = errors.New("is not after its start")
|
||||||
errDestination = errors.New("is not webhook, slack or ntfy")
|
errDestination = errors.New("is not webhook, slack or ntfy")
|
||||||
errScope = errors.New("is not client, net, asn, total or watch")
|
errScope = errors.New("is not client, net, asn, total or watched_net")
|
||||||
errWaitingList = errors.New(`waiting is a list, but now lists the alerts by ` +
|
errWaitingList = errors.New(`waiting is a list, but now lists the alerts by ` +
|
||||||
`destination: put the list under "webhook", as "waiting": {"webhook": [...]}, ` +
|
`destination: put the list under "webhook", as "waiting": {"webhook": [...]}, ` +
|
||||||
`or remove the file`)
|
`or remove the file`)
|
||||||
@@ -858,8 +858,8 @@ func (f *alertsFile) check([]byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkAnomalyCounters refuses an anomaly counter whose scope is not
|
// checkAnomalyCounters refuses an anomaly counter whose scope is not
|
||||||
// client, net, asn, total or watch, most likely misspelt, and one without
|
// client, net, asn, total or watched_net, most likely misspelt, and one
|
||||||
// a field it needs, as missingFromCounter tells.
|
// without a field it needs, as missingFromCounter tells.
|
||||||
func checkAnomalyCounters(counters []anomaly.Counter) error {
|
func checkAnomalyCounters(counters []anomaly.Counter) error {
|
||||||
for i, counter := range counters {
|
for i, counter := range counters {
|
||||||
if !slices.Contains(anomaly.Scopes(), counter.Scope) {
|
if !slices.Contains(anomaly.Scopes(), counter.Scope) {
|
||||||
@@ -879,9 +879,9 @@ func checkAnomalyCounters(counters []anomaly.Counter) error {
|
|||||||
// missingFromCounter returns the first field counter, an anomaly counter,
|
// missingFromCounter returns the first field counter, an anomaly counter,
|
||||||
// needs and has not, or "" when it has them all: what tells it from the
|
// needs and has not, or "" when it has them all: what tells it from the
|
||||||
// others in its scope, without which it would never be counted again, the
|
// others in its scope, without which it would never be counted again, the
|
||||||
// netblock of a client, net or watch counter, the AS number of an asn one
|
// netblock of a client, net or watched_net counter, the AS number of an
|
||||||
// and the name of a watch one; and the start of a window in which it has
|
// asn one and the name of a watched_net one; and the start of a window in
|
||||||
// requests or bytes, without which they would be dropped.
|
// which it has requests or bytes, without which they would be dropped.
|
||||||
func missingFromCounter(counter anomaly.Counter) string {
|
func missingFromCounter(counter anomaly.Counter) string {
|
||||||
scope := counter.Scope
|
scope := counter.Scope
|
||||||
|
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ func TestFileThatDoesNotParseStopsTheStart(t *testing.T) {
|
|||||||
`{"version": 1, "anomaly_counters": [{"scope": "total"}, ` +
|
`{"version": 1, "anomaly_counters": [{"scope": "total"}, ` +
|
||||||
`{"scope": "nett", "netblock": "203.0.113.0/24"}]}`,
|
`{"scope": "nett", "netblock": "203.0.113.0/24"}]}`,
|
||||||
`: anomaly_counters entry 2's scope "nett" is not client, net, asn, total ` +
|
`: anomaly_counters entry 2's scope "nett" is not client, net, asn, total ` +
|
||||||
`or watch`,
|
`or watched_net`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"a copy of a list with a line that does not read", reputationJSON,
|
"a copy of a list with a line that does not read", reputationJSON,
|
||||||
|
|||||||
Reference in new issue
Block a user