Network lists: always allowed, exempt from rate limits, always refused (closes #19)
check / check (push) Successful in 3m47s
check / check (push) Successful in 3m47s
Adds SWWAF_ALLOW_NETS, SWWAF_RATE_LIMIT_EXEMPT_NETS and SWWAF_DENY_NETS, read like SWWAF_TRUSTED_PROXIES and empty by default, and checked against the client's own address before its country is looked up. A client in SWWAF_ALLOW_NETS skips the country lists and the rate limits and is not looked up. One in SWWAF_DENY_NETS is refused with 403, logged as denied and not counted. One in SWWAF_RATE_LIMIT_EXEMPT_NETS is neither counted nor refused by the rate limits. SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES now refuses a private, loopback or link-local client unless SWWAF_ALLOW_NETS lists it. Judgement call: an address in both SWWAF_ALLOW_NETS and SWWAF_DENY_NETS is let through. Judgement call: the size and time limits still apply to SWWAF_ALLOW_NETS. Model: opus-5-5
This commit was merged in pull request #66.
This commit is contained in:
@@ -13,14 +13,15 @@ JSON log line for every request.
|
||||
|
||||
Status: the first two milestones are built
|
||||
(https://git.eeqj.de/sneak/smallwebwaf/issues/13 and
|
||||
https://git.eeqj.de/sneak/smallwebwaf/issues/14). `smallwebwaf` passes each
|
||||
request to the app and the app's answer back, unchanged, within its timeouts and
|
||||
size limits, works out each client's address, refuses a client that sends too
|
||||
many requests or comes from a country you refuse, and writes a JSON log line for
|
||||
every request. It comes as the image the app's own image is built on. The rest
|
||||
of the design comes after that, in the order of the build order in
|
||||
[`SPEC.md`](SPEC.md). The survey of existing tools that led to the design is in
|
||||
[`EVALUATION.md`](EVALUATION.md).
|
||||
https://git.eeqj.de/sneak/smallwebwaf/issues/14), and so are the static lists,
|
||||
which come next in the build order. `smallwebwaf` passes each request to the app
|
||||
and the app's answer back, unchanged, within its timeouts and size limits, works
|
||||
out each client's address, refuses a client that sends too many requests, comes
|
||||
from a country you refuse or from a network you refuse, lets the networks you
|
||||
choose through, and writes a JSON log line for every request. It comes as the
|
||||
image the app's own image is built on. The rest of the design comes after that,
|
||||
in the order of the build order in [`SPEC.md`](SPEC.md). The survey of existing
|
||||
tools that led to the design is in [`EVALUATION.md`](EVALUATION.md).
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -82,8 +83,17 @@ and `make run` builds and runs it, listening on port 8080 in front of an app at
|
||||
counted for the rate limits. While one of the country lists below is set, each
|
||||
client's country is looked up through GeoJS (see "Country and AS number
|
||||
lookup" below); with neither set, no visitor's address leaves the host. A
|
||||
client on a private, loopback or link-local address has no country, and
|
||||
neither list checks it.
|
||||
client on a private, loopback or link-local address has no country and is
|
||||
never looked up: `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` refuses it unless it is
|
||||
in `SWWAF_ALLOW_NETS`, and `SWWAF_DENIED_COUNTRIES` does not refuse it.
|
||||
- Checks the client's own address against the static lists, the three netblock
|
||||
settings below, before anything else, its country included. A client in
|
||||
`SWWAF_ALLOW_NETS` skips the country lists and the rate limits, and is not
|
||||
looked up; the timeouts and size limits still apply. A client in
|
||||
`SWWAF_DENY_NETS` is refused with `403` before its body is read, and the
|
||||
request is not counted for the rate limits; an address in `SWWAF_ALLOW_NETS`
|
||||
too is let through. A client in `SWWAF_RATE_LIMIT_EXEMPT_NETS` is neither
|
||||
counted nor refused by the rate limits; the country lists still apply to it.
|
||||
- Answers `GET /_smallwebwaf/healthz` itself with `200` and `ok`, before any
|
||||
check and without asking the app, for the image's health check.
|
||||
- Writes a line in the request log for each request (see "Request log" below).
|
||||
@@ -111,6 +121,11 @@ it, and the effective settings are logged at start.
|
||||
to send its whole answer, from the end of the request to the last byte.
|
||||
- `SWWAF_REQUEST_MAX_BYTES` (default `100M`): the largest request body.
|
||||
- `SWWAF_RESPONSE_MAX_BYTES` (default `5G`): the largest response body.
|
||||
- `SWWAF_ALLOW_NETS` (default empty): netblocks whose clients skip the country
|
||||
lists and the rate limits, such as your monitoring or your own networks.
|
||||
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients the
|
||||
rate limits do not apply to, such as a machine that talks to the app all day.
|
||||
- `SWWAF_DENY_NETS` (default empty): netblocks whose clients are always refused.
|
||||
- `SWWAF_RATE_LIMIT_PER_MINUTE` (default `1000`), `SWWAF_RATE_LIMIT_PER_HOUR`
|
||||
(default `10000`) and `SWWAF_RATE_LIMIT_PER_DAY` (default `50000`): the most
|
||||
requests a client may make in a minute, an hour and a day. The defaults are
|
||||
@@ -153,18 +168,19 @@ refused ones included:
|
||||
- `time` is when the request arrived, in UTC. `peer_ip` is the TCP peer,
|
||||
normally traefik. `path` and `query` are as the client sent them.
|
||||
- `country` is the client's country as GeoJS places it, and empty when it is not
|
||||
known: with neither country list set, for a client on a private, loopback or
|
||||
link-local address, and when GeoJS cannot place the client or has not answered
|
||||
in time.
|
||||
known: with neither country list set, for a client in `SWWAF_ALLOW_NETS` or
|
||||
`SWWAF_DENY_NETS`, for a client on a private, loopback or link-local address,
|
||||
and when GeoJS cannot place the client or has not answered in time.
|
||||
- `status` is what the client was sent, `0` if nothing was; `upstream_status` is
|
||||
what the app answered, and is left out when the app did not answer.
|
||||
- `request_bytes` and `response_bytes` count body bytes.
|
||||
- `action` is `forward` for a request passed to the app, `country_denied` for
|
||||
one refused for its client's country, `rate_limited` for one refused for a
|
||||
rate limit, `too_large` for a request or response over its size limit,
|
||||
`timed_out` for one that ran out of time, `upstream_error` when the app could
|
||||
not be reached or its answer broke off, and `admin` for one `smallwebwaf`
|
||||
answered at its own endpoint.
|
||||
- `action` is `forward` for a request passed to the app, `denied` for one
|
||||
refused because its client is in `SWWAF_DENY_NETS`, `country_denied` for one
|
||||
refused for its client's country, `rate_limited` for one refused for a rate
|
||||
limit, `too_large` for a request or response over its size limit, `timed_out`
|
||||
for one that ran out of time, `upstream_error` when the app could not be
|
||||
reached or its answer broke off, and `admin` for one `smallwebwaf` answered at
|
||||
its own endpoint.
|
||||
- `limit_hit` is there for a request refused for a rate limit, and names the
|
||||
window whose limit it went over: `minute`, `hour` or `day`, the shortest if it
|
||||
went over several.
|
||||
@@ -402,16 +418,17 @@ the metrics, failure behaviour and the build order.
|
||||
|
||||
So far `smallwebwaf` looks up only the country, only through GeoJS, and only
|
||||
while `SWWAF_DENIED_COUNTRIES` or `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` is set:
|
||||
then the address of every new visitor is sent to GeoJS, and with neither set,
|
||||
none is. An IPv6 visitor is asked about by the first address of its /64. A new
|
||||
visitor waits at most a second for its answer, and without one counts as coming
|
||||
from an unknown country until the answer arrives. The addresses waiting are
|
||||
asked about together, up to 200 in one request, one request at a time; at most
|
||||
10,000 visitors wait, and one more counts as coming from an unknown country
|
||||
until there is room. While GeoJS fails, visitors with a kept answer are
|
||||
unaffected and new ones count as coming from an unknown country. GeoJS is then
|
||||
left alone for a second, twice as long after each further failure up to five
|
||||
minutes, and asked again by the next request that needs it.
|
||||
then the address of every new visitor outside `SWWAF_ALLOW_NETS` and
|
||||
`SWWAF_DENY_NETS` is sent to GeoJS, and with neither set, none is. An IPv6
|
||||
visitor is asked about by the first address of its /64. A new visitor waits at
|
||||
most a second for its answer, and without one counts as coming from an unknown
|
||||
country until the answer arrives. The addresses waiting are asked about
|
||||
together, up to 200 in one request, one request at a time; at most 10,000
|
||||
visitors wait, and one more counts as coming from an unknown country until there
|
||||
is room. While GeoJS fails, visitors with a kept answer are unaffected and new
|
||||
ones count as coming from an unknown country. GeoJS is then left alone for a
|
||||
second, twice as long after each further failure up to five minutes, and asked
|
||||
again by the next request that needs it.
|
||||
|
||||
In the full design, `smallwebwaf` looks up the AS number and country of every
|
||||
client, for the request log, the metrics and the ban notes, and for the country
|
||||
@@ -447,9 +464,8 @@ data is powered by IPinfo". A service that uses the database through
|
||||
Neither source can place a private address, so a client on one, such as a
|
||||
visitor on your local network, another container or your monitoring, has no
|
||||
country: `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` refuses it unless you list it in
|
||||
`SWWAF_ALLOW_NETS`. Such addresses are never sent to GeoJS. In milestone 2,
|
||||
which has no `SWWAF_ALLOW_NETS`, neither country list checks such a client; the
|
||||
refusal comes with `SWWAF_ALLOW_NETS` in milestone 3 or later.
|
||||
`SWWAF_ALLOW_NETS`, and `SWWAF_DENIED_COUNTRIES` does not refuse it. Such
|
||||
addresses are never sent to GeoJS.
|
||||
|
||||
## How the code is laid out
|
||||
|
||||
@@ -462,8 +478,9 @@ refusal comes with `SWWAF_ALLOW_NETS` in milestone 3 or later.
|
||||
the checks, passes the request to the app and the answer back with the
|
||||
standard library's `httputil.ReverseProxy` within the timeouts and size
|
||||
limits, and writes the request's log line. Its `check` method is where a
|
||||
request is refused before anything reaches the app: for the country lists, for
|
||||
a rate limit, and for an announced body over the size limit.
|
||||
request is refused before anything reaches the app: for `SWWAF_DENY_NETS`, for
|
||||
the country lists, for a rate limit, and for an announced body over the size
|
||||
limit.
|
||||
- `internal/lookup`: looks up each client's country through GeoJS, and keeps the
|
||||
answers.
|
||||
- `internal/ratelimit`: counts each client's requests and tells when one takes
|
||||
@@ -518,8 +535,8 @@ so that they run in minimal containers.
|
||||
|
||||
## TODO
|
||||
|
||||
- Milestone 3 and the rest of the design, in the order of the build order in
|
||||
[`SPEC.md`](SPEC.md).
|
||||
- The rest of milestone 3, after the static lists, and the rest of the design,
|
||||
in the order of the build order in [`SPEC.md`](SPEC.md).
|
||||
|
||||
## Documents
|
||||
|
||||
|
||||
Reference in New Issue
Block a user