Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a030557f3 |
No files matched your search
@@ -7,8 +7,8 @@ requests of scrapers, scanners and other abusers before they reach an app.
|
|||||||
## What it does
|
## What it does
|
||||||
|
|
||||||
A web application firewall reads each request on its way to a web app and
|
A web application firewall reads each request on its way to a web app and
|
||||||
refuses the ones that look like abuse. `smallwebwaf` is one binary, shipped in
|
refuses the ones that look like abuse. `smallwebwaf` is one program, `sww`,
|
||||||
one image, with two commands that do this together:
|
shipped in one image, with two commands that do this together:
|
||||||
|
|
||||||
- `sww rproxy` is a small reverse proxy in front of one app. traefik routes to
|
- `sww rproxy` is a small reverse proxy in front of one app. traefik routes to
|
||||||
it. It asks `discriminator` about each request, passes the allowed ones to the
|
it. It asks `discriminator` about each request, passes the allowed ones to the
|
||||||
@@ -194,7 +194,8 @@ account, a key, a token or an alert destination stays off until you give one.
|
|||||||
contents are the value, less one newline at the end. Setting both forms stops
|
contents are the value, less one newline at the end. Setting both forms stops
|
||||||
the start. `SWW_LOG_REMOTE_TLS_CA_FILE` and
|
the start. `SWW_LOG_REMOTE_TLS_CA_FILE` and
|
||||||
`SWW_CORE_RULE_SET_EXCLUSIONS_FILE` already name a file, so they have no
|
`SWW_CORE_RULE_SET_EXCLUSIONS_FILE` already name a file, so they have no
|
||||||
`_FILE` form. No `SWRP_` setting has one, since `rproxy` never touches disk.
|
`_FILE` form. `SWW_COMMAND` cannot be given as a file. No `SWRP_` setting has
|
||||||
|
one, since `rproxy` never touches disk.
|
||||||
- A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`.
|
- A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`.
|
||||||
- A size is a number of bytes with an optional `K`, `M` or `G`, in powers of
|
- A size is a number of bytes with an optional `K`, `M` or `G`, in powers of
|
||||||
1024: `512K`, `100M`, `5G`.
|
1024: `512K`, `100M`, `5G`.
|
||||||
@@ -581,10 +582,10 @@ is set aside as `<name>.bad`, and never stops the running process (see
|
|||||||
come from Ubuntu's archive, and nixpkgs is fixed at one release. An app's
|
come from Ubuntu's archive, and nixpkgs is fixed at one release. An app's
|
||||||
`Dockerfile` installs a package with `nix-env -iA nixpkgs.<name>` and gets the
|
`Dockerfile` installs a package with `nix-env -iA nixpkgs.<name>` and gets the
|
||||||
same package each time it is built (see [`docs/image.md`](docs/image.md)).
|
same package each time it is built (see [`docs/image.md`](docs/image.md)).
|
||||||
- runit, a service supervisor, runs `smallwebwaf` as the `sww` user (uid and
|
- runit, a service supervisor, runs `sww` as the `sww` user (uid and gid 65532)
|
||||||
gid 65532) and starts it again when it exits. The `run` script of
|
and starts it again when it exits. The `run` script of `discriminator` gives
|
||||||
`discriminator` gives the state directory to the `sww` user at each start, so
|
the state directory to the `sww` user at each start, so a volume needs no
|
||||||
a volume needs no change of owner.
|
change of owner.
|
||||||
- `rproxy` and `discriminator` use no token between them. `discriminator`'s own
|
- `rproxy` and `discriminator` use no token between them. `discriminator`'s own
|
||||||
endpoints, under `/_sww/`, are for your admin tools and your metrics
|
endpoints, under `/_sww/`, are for your admin tools and your metrics
|
||||||
collector, at its own address on the private network; nothing routes the
|
collector, at its own address on the private network; nothing routes the
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# smallwebwaf specification
|
# smallwebwaf specification
|
||||||
|
|
||||||
`smallwebwaf` is a web application firewall for self-hosted apps. It is one
|
`smallwebwaf` is a web application firewall for self-hosted apps. It is one
|
||||||
binary, and two of its commands do the work:
|
binary, `sww`, and two of its commands do the work:
|
||||||
|
|
||||||
- `sww rproxy` is a reverse proxy between traefik and one app.
|
- `sww rproxy` is a reverse proxy between traefik and one app.
|
||||||
- `sww discriminator` decides what happens to each request, for many sites.
|
- `sww discriminator` decides what happens to each request, for many sites.
|
||||||
@@ -326,7 +326,8 @@ Conventions:
|
|||||||
the value itself would be. Setting both forms stops the start, and so does
|
the value itself would be. Setting both forms stops the start, and so does
|
||||||
naming a file that cannot be read. `SWW_LOG_REMOTE_TLS_CA_FILE` and
|
naming a file that cannot be read. `SWW_LOG_REMOTE_TLS_CA_FILE` and
|
||||||
`SWW_CORE_RULE_SET_EXCLUSIONS_FILE` already hold a file name, so they have no
|
`SWW_CORE_RULE_SET_EXCLUSIONS_FILE` already hold a file name, so they have no
|
||||||
`_FILE` form. No `SWRP_` setting has one, since `rproxy` never touches disk.
|
`_FILE` form. `SWW_COMMAND` cannot be given as a file. No `SWRP_` setting has
|
||||||
|
one, since `rproxy` never touches disk.
|
||||||
- Settings, files included, are read once at start. Changing one means
|
- Settings, files included, are read once at start. Changing one means
|
||||||
restarting the container. The files `discriminator` watches while it runs are
|
restarting the container. The files `discriminator` watches while it runs are
|
||||||
its state files, its rule files and the lookup database.
|
its state files, its rule files and the lookup database.
|
||||||
@@ -584,8 +585,8 @@ files, the limits and the bans still apply to the client that sends it.
|
|||||||
Coraza directives. An exclusion switches one rule off for some fields or paths
|
Coraza directives. An exclusion switches one rule off for some fields or paths
|
||||||
only, where `SWW_WAF_DISABLED_RULES` switches it off everywhere. The file
|
only, where `SWW_WAF_DISABLED_RULES` switches it off everywhere. The file
|
||||||
holds an app's own exclusions; the eighth change below holds gitea's.
|
holds an app's own exclusions; the eighth change below holds gitea's.
|
||||||
- `discriminator` loads the file after `sww`'s own setup rules and before
|
- `discriminator` loads the file after `smallwebwaf`'s own setup rules and
|
||||||
the Core Rule Set's rules. The Core Rule Set loads its own
|
before the Core Rule Set's rules. The Core Rule Set loads its own
|
||||||
`REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf` at the same point.
|
`REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf` at the same point.
|
||||||
- So the file holds only `SecAction` and `SecRule` lines that exclude with
|
- So the file holds only `SecAction` and `SecRule` lines that exclude with
|
||||||
`ctl:ruleRemoveTargetById` or `ctl:ruleRemoveById`. These run with each
|
`ctl:ruleRemoveTargetById` or `ctl:ruleRemoveById`. These run with each
|
||||||
@@ -595,7 +596,8 @@ files, the limits and the bans still apply to the client that sends it.
|
|||||||
- The start stops on a file that cannot be read or does not parse, whatever
|
- The start stops on a file that cannot be read or does not parse, whatever
|
||||||
`SWW_WAF_MODE` is.
|
`SWW_WAF_MODE` is.
|
||||||
- It stops too on a file that names a number from 900000 to 900999, the ids
|
- It stops too on a file that names a number from 900000 to 900999, the ids
|
||||||
of `sww`'s own rules, alone or in a range. Comment lines are not checked.
|
of `smallwebwaf`'s own rules, alone or in a range. Comment lines are not
|
||||||
|
checked.
|
||||||
- It stops too on a directive that removes or changes rules as the file
|
- It stops too on a directive that removes or changes rules as the file
|
||||||
loads, such as `SecRuleRemoveById`, since none of the Core Rule Set's
|
loads, such as `SecRuleRemoveById`, since none of the Core Rule Set's
|
||||||
rules is loaded yet.
|
rules is loaded yet.
|
||||||
|
|||||||
+5
-5
@@ -1,8 +1,8 @@
|
|||||||
// Command sww is a web application firewall. `sww rproxy` is
|
// Command sww is a web application firewall. `sww rproxy` is the reverse
|
||||||
// the reverse proxy in front of one app, which asks discriminator about each request.
|
// proxy in front of one app, which asks discriminator about each request.
|
||||||
// `sww discriminator` decides, for many sites, and keeps what lasts.
|
// `sww discriminator` decides, for many sites, and keeps what lasts. `sww
|
||||||
// `sww healthcheck` is the container's health check. Everything it does
|
// healthcheck` is the container's health check. Everything it does is in
|
||||||
// is in internal/sww.
|
// internal/sww.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -306,10 +306,9 @@ func (h *handler) refuseQuestion(w http.ResponseWriter, reason string) {
|
|||||||
|
|
||||||
// asTheVisitorSentIt returns a copy of r, a question, as the checks and the
|
// asTheVisitorSentIt returns a copy of r, a question, as the checks and the
|
||||||
// log line are to see it: without the headers rproxy adds for discriminator,
|
// log line are to see it: without the headers rproxy adds for discriminator,
|
||||||
// with
|
// with body, which has been read of r, and with the length the visitor
|
||||||
// body, which has been read of r, and with the length the visitor announced,
|
// announced, as -1 when it announced none. The question's own length covers
|
||||||
// as -1 when it announced none. The question's own length covers only the part
|
// only the part of the body it carries.
|
||||||
// of the body it carries.
|
|
||||||
func asTheVisitorSentIt(r *http.Request, body []byte) *http.Request {
|
func asTheVisitorSentIt(r *http.Request, body []byte) *http.Request {
|
||||||
sent := r.Clone(r.Context())
|
sent := r.Clone(r.Context())
|
||||||
announced := sent.Header.Get(ContentLengthHeader)
|
announced := sent.Header.Get(ContentLengthHeader)
|
||||||
|
|||||||
@@ -391,9 +391,9 @@ func startRProxy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newDiscriminator makes discriminator's server, without starting it, and
|
// newDiscriminator makes discriminator's server, without starting it, and
|
||||||
// returns it, what
|
// returns it, what it writes, and the queue of the alerts it raises, as the
|
||||||
// it writes, and the queue of the alerts it raises, as the settings in env make
|
// settings in env make it. No alert is sent from the queue: they wait in it,
|
||||||
// it. No alert is sent from the queue: they wait in it, for the test to look at.
|
// for the test to look at.
|
||||||
// With no geojsURL, there is no stand-in for GeoJS to look clients up at, and
|
// With no geojsURL, there is no stand-in for GeoJS to look clients up at, and
|
||||||
// SWW_LOOKUP_SOURCE is off unless env sets it. While it is file, the lookup
|
// SWW_LOOKUP_SOURCE is off unless env sets it. While it is file, the lookup
|
||||||
// database SWW_LOOKUP_DB_PATH names is read. Clients are checked with AbuseIPDB
|
// database SWW_LOOKUP_DB_PATH names is read. Clients are checked with AbuseIPDB
|
||||||
|
|||||||
@@ -146,10 +146,9 @@ func newTransport() *http.Transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newDiscriminatorTransport returns what carries questions and reports to
|
// newDiscriminatorTransport returns what carries questions and reports to
|
||||||
// discriminator.
|
// discriminator. It speaks HTTP/1.1 only. Go's HTTP/2 client refuses to send
|
||||||
// It speaks HTTP/1.1 only. Go's HTTP/2 client refuses to send a visitor's Upgrade
|
// a visitor's Upgrade header, or a Connection header other than close or
|
||||||
// header, or a Connection header other than close or keep-alive, and that would
|
// keep-alive, and that would read as discriminator giving no answer.
|
||||||
// read as discriminator giving no answer.
|
|
||||||
func newDiscriminatorTransport() *http.Transport {
|
func newDiscriminatorTransport() *http.Transport {
|
||||||
transport := newTransport()
|
transport := newTransport()
|
||||||
transport.Protocols = new(http.Protocols)
|
transport.Protocols = new(http.Protocols)
|
||||||
|
|||||||
@@ -268,8 +268,8 @@ func (s *Discriminator) question(t *testing.T) question {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startDiscriminatorAnsweringReports starts a stand-in for discriminator that
|
// startDiscriminatorAnsweringReports starts a stand-in for discriminator that
|
||||||
// answers each
|
// answers each question as answer does and each report as report does, and
|
||||||
// question as answer does and each report as report does, and returns its URL.
|
// returns its URL.
|
||||||
func startDiscriminatorAnsweringReports(
|
func startDiscriminatorAnsweringReports(
|
||||||
t *testing.T, answer, report http.HandlerFunc,
|
t *testing.T, answer, report http.HandlerFunc,
|
||||||
) string {
|
) string {
|
||||||
@@ -1033,9 +1033,8 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startDiscriminatorWriting starts a stand-in for discriminator that reads
|
// startDiscriminatorWriting starts a stand-in for discriminator that reads
|
||||||
// each question and
|
// each question and answers it with text, as it is, and returns its URL. It
|
||||||
// answers it with text, as it is, and returns its URL. It can send what Go's
|
// can send what Go's server would not.
|
||||||
// server would not.
|
|
||||||
func startDiscriminatorWriting(t *testing.T, text string) string {
|
func startDiscriminatorWriting(t *testing.T, text string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user