Skip to content

Commit 4ea0add

Browse files
authored
feat(lib/policy/expressions): add system load average to bot expression inputs (#766)
* feat(lib/policy/expressions): add system load average to bot expression inputs This lets Anubis dynamically react to system load in order to increase and decrease the required level of scrutiny. High load? More scrutiny required. Low load? Less scrutiny required. * docs: spell system correctly Signed-off-by: Xe Iaso <[email protected]> * Update metadata check-spelling run (pull_request) for Xe/load-average Signed-off-by: check-spelling-bot <[email protected]> on-behalf-of: @check-spelling <[email protected]> * fix(default-config): don't enable low load average feature by default Signed-off-by: Xe Iaso <[email protected]> --------- Signed-off-by: Xe Iaso <[email protected]> Signed-off-by: check-spelling-bot <[email protected]> Signed-off-by: Xe Iaso <[email protected]>
1 parent 289c802 commit 4ea0add

File tree

9 files changed

+192
-31
lines changed

9 files changed

+192
-31
lines changed

.github/actions/spelling/expect.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ goland
120120
gomod
121121
goodbot
122122
googlebot
123+
gopsutil
123124
govulncheck
124125
goyaml
125126
GPG
@@ -193,6 +194,7 @@ Mojeek
193194
mojeekbot
194195
mozilla
195196
nbf
197+
nepeat
196198
netsurf
197199
nginx
198200
nicksnyder
@@ -259,6 +261,7 @@ Semrush
259261
Seo
260262
setsebool
261263
shellcheck
264+
shirou
262265
Sidetrade
263266
simprint
264267
sitemap

data/botPolicies.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,25 @@ bots:
7474
weight:
7575
adjust: 10
7676

77+
## System load based checks.
78+
# If the system is under high load, add weight.
79+
- name: high-load-average
80+
action: WEIGH
81+
expression: load_1m >= 10.0 # make sure to end the load comparison in a .0
82+
weight:
83+
adjust: 20
84+
85+
## If your backend service is running on the same operating system as Anubis,
86+
## you can uncomment this rule to make the challenge easier when the system is
87+
## under low load.
88+
##
89+
## If it is not, remove weight.
90+
# - name: low-load-average
91+
# action: WEIGH
92+
# expression: load_15m <= 4.0 # make sure to end the load comparison in a .0
93+
# weight:
94+
# adjust: -10
95+
7796
# Generic catchall rule
7897
- name: generic-browser
7998
user_agent_regex: >-

docs/docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2929
- Add translation for Turkish language ([#751](https://github.com/TecharoHQ/anubis/pull/751))
3030
- Allow [Common Crawl](https://commoncrawl.org/) by default so scrapers have less incentive to scrape
3131
- The [bbolt storage backend](./admin/policies.mdx#bbolt) now runs its cleanup every hour instead of every five minutes.
32+
- Added the ability for Anubis to dynamically take action [based on the system load average](./admin/configuration/expressions.mdx#using-the-system-load-average).
3233
- Add translation for Traditional Chinese ([#759](https://github.com/TecharoHQ/anubis/pull/759))
3334

3435
### Potentially breaking changes

docs/docs/admin/configuration/expressions.mdx

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,18 @@ For this rule, if a request comes in matching [the signature of the `go get` com
9999

100100
Anubis exposes the following variables to expressions:
101101

102-
| Name | Type | Explanation | Example |
103-
| :-------------- | :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
104-
| `headers` | `map[string, string]` | The [headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers) of the request being processed. | `{"User-Agent": "Mozilla/5.0 Gecko/20100101 Firefox/137.0"}` |
105-
| `host` | `string` | The [HTTP hostname](https://web.dev/articles/url-parts#host) the request is targeted to. | `anubis.techaro.lol` |
106-
| `method` | `string` | The [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) in the request being processed. | `GET`, `POST`, `DELETE`, etc. |
107-
| `path` | `string` | The [path](https://web.dev/articles/url-parts#pathname) of the request being processed. | `/`, `/api/memes/create` |
108-
| `query` | `map[string, string]` | The [query parameters](https://web.dev/articles/url-parts#query) of the request being processed. | `?foo=bar` -> `{"foo": "bar"}` |
109-
| `remoteAddress` | `string` | The IP address of the client. | `1.1.1.1` |
110-
| `userAgent` | `string` | The [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent) string in the request being processed. | `Mozilla/5.0 Gecko/20100101 Firefox/137.0` |
102+
| Name | Type | Explanation | Example |
103+
| :-------------- | :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
104+
| `headers` | `map[string, string]` | The [headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers) of the request being processed. | `{"User-Agent": "Mozilla/5.0 Gecko/20100101 Firefox/137.0"}` |
105+
| `host` | `string` | The [HTTP hostname](https://web.dev/articles/url-parts#host) the request is targeted to. | `anubis.techaro.lol` |
106+
| `load_1m` | `double` | The current system load average over the last one minute. This is useful for making [load-based checks](#using-the-system-load-average). |
107+
| `load_5m` | `double` | The current system load average over the last five minutes. This is useful for making [load-based checks](#using-the-system-load-average). |
108+
| `load_15m` | `double` | The current system load average over the last fifteen minutes. This is useful for making [load-based checks](#using-the-system-load-average). |
109+
| `method` | `string` | The [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) in the request being processed. | `GET`, `POST`, `DELETE`, etc. |
110+
| `path` | `string` | The [path](https://web.dev/articles/url-parts#pathname) of the request being processed. | `/`, `/api/memes/create` |
111+
| `query` | `map[string, string]` | The [query parameters](https://web.dev/articles/url-parts#query) of the request being processed. | `?foo=bar` -> `{"foo": "bar"}` |
112+
| `remoteAddress` | `string` | The IP address of the client. | `1.1.1.1` |
113+
| `userAgent` | `string` | The [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent) string in the request being processed. | `Mozilla/5.0 Gecko/20100101 Firefox/137.0` |
111114

112115
Of note: in many languages when you look up a key in a map and there is nothing there, the language will return some "falsy" value like `undefined` in JavaScript, `None` in Python, or the zero value of the type in Go. In CEL, if you try to look up a value that does not exist, execution of the expression will fail and Anubis will return an error.
113116

@@ -141,6 +144,44 @@ X-Real-Ip: 8.8.8.8
141144

142145
Anubis would return a challenge because all of those conditions are true.
143146

147+
### Using the system load average
148+
149+
In Unix-like systems (such as Linux), every process on the system has to wait its turn to be able to run. This means that as more processes on the system are running, they need to wait longer to be able to execute. The [load average](<https://en.wikipedia.org/wiki/Load_(computing)>) represents the number of processes that want to be able to run but can't run yet. This metric isn't the most reliable to identify a cause, but is great at helping to identify symptoms.
150+
151+
Anubis lets you use the system load average as an input to expressions so that you can make dynamic rules like "when the system is under a low amount of load, dial back the protection, but when it's under a lot of load, crank it up to the mix". This lets you get all of the blocking features of Anubis in the background but only really expose Anubis to users when the system is actively being attacked.
152+
153+
This is best combined with the [weight](../policies.mdx#request-weight) and [threshold](./thresholds.mdx) systems so that you can have Anubis dynamically respond to attacks. Consider these rules in the default configuration file:
154+
155+
```yaml
156+
## System load based checks.
157+
# If the system is under high load for the last minute, add weight.
158+
- name: high-load-average
159+
action: WEIGH
160+
expression: load_1m >= 10.0 # make sure to end the load comparison in a .0
161+
weight:
162+
adjust: 20
163+
164+
# If it is not for the last 15 minutes, remove weight.
165+
- name: low-load-average
166+
action: WEIGH
167+
expression: load_15m <= 4.0 # make sure to end the load comparison in a .0
168+
weight:
169+
adjust: -10
170+
```
171+
172+
This combination of rules makes Anubis dynamically react to the system load and only kick in when the system is under attack.
173+
174+
Something to keep in mind about system load average is that it is not aware of the number of cores the system has. If you have a 16 core system that has 16 processes running but none of them is hogging the CPU, then you will get a load average below 16. If you are in doubt, make your "high load" metric at least two times the number of CPU cores and your "low load" metric at least half of the number of CPU cores. For example:
175+
176+
| Kind | Core count | Load threshold |
177+
| --------: | :--------- | :------------- |
178+
| high load | 4 | `8.0` |
179+
| low load | 4 | `2.0` |
180+
| high load | 16 | `32.0` |
181+
| low load | 16 | `8` |
182+
183+
Also keep in mind that this does not account for other kinds of latency like I/O latency. A system can have its web applications unresponsive due to high latency from a MySQL server but still have that web application server report a load near or at zero.
184+
144185
## Functions exposed to Anubis expressions
145186

146187
Anubis expressions can be augmented with the following functions:

go.mod

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ require (
1919
github.com/prometheus/client_golang v1.22.0
2020
github.com/redis/go-redis/v9 v9.11.0
2121
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
22+
github.com/shirou/gopsutil/v4 v4.25.1
23+
github.com/testcontainers/testcontainers-go v0.37.0
2224
go.etcd.io/bbolt v1.4.2
2325
golang.org/x/net v0.41.0
2426
golang.org/x/text v0.26.0
@@ -79,7 +81,7 @@ require (
7981
github.com/go-git/go-billy/v5 v5.6.2 // indirect
8082
github.com/go-git/go-git/v5 v5.14.0 // indirect
8183
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
82-
github.com/go-logr/logr v1.4.2 // indirect
84+
github.com/go-logr/logr v1.4.3 // indirect
8385
github.com/go-logr/stdr v1.2.2 // indirect
8486
github.com/go-ole/go-ole v1.2.6 // indirect
8587
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
@@ -127,7 +129,6 @@ require (
127129
github.com/prometheus/procfs v0.15.1 // indirect
128130
github.com/russross/blackfriday/v2 v2.1.0 // indirect
129131
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
130-
github.com/shirou/gopsutil/v4 v4.25.1 // indirect
131132
github.com/shopspring/decimal v1.4.0 // indirect
132133
github.com/sirupsen/logrus v1.9.3 // indirect
133134
github.com/skeema/knownhosts v1.3.1 // indirect
@@ -138,7 +139,6 @@ require (
138139
github.com/suzuki-shunsuke/logrus-error v0.1.4 // indirect
139140
github.com/suzuki-shunsuke/pinact v1.6.0 // indirect
140141
github.com/suzuki-shunsuke/urfave-cli-help-all v0.0.4 // indirect
141-
github.com/testcontainers/testcontainers-go v0.37.0 // indirect
142142
github.com/tklauser/go-sysconf v0.3.12 // indirect
143143
github.com/tklauser/numcpus v0.6.1 // indirect
144144
github.com/ulikunitz/xz v0.5.12 // indirect
@@ -149,9 +149,12 @@ require (
149149
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
150150
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
151151
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
152-
go.opentelemetry.io/otel v1.35.0 // indirect
153-
go.opentelemetry.io/otel/metric v1.35.0 // indirect
154-
go.opentelemetry.io/otel/trace v1.35.0 // indirect
152+
go.opentelemetry.io/otel v1.37.0 // indirect
153+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
154+
go.opentelemetry.io/otel/metric v1.37.0 // indirect
155+
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
156+
go.opentelemetry.io/otel/trace v1.37.0 // indirect
157+
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
155158
go.yaml.in/yaml/v2 v2.4.2 // indirect
156159
go.yaml.in/yaml/v3 v3.0.3 // indirect
157160
golang.org/x/crypto v0.39.0 // indirect
@@ -166,8 +169,8 @@ require (
166169
golang.org/x/tools v0.34.0 // indirect
167170
golang.org/x/vuln v1.1.4 // indirect
168171
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
169-
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
170-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
172+
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
173+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
171174
google.golang.org/protobuf v1.36.6 // indirect
172175
gopkg.in/warnings.v0 v0.1.2 // indirect
173176
honnef.co/go/tools v0.6.1 // indirect

0 commit comments

Comments
 (0)