Compare commits
2
Commits
42643be662
..
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42e06bf298 | ||
|
|
beb503b233 |
No files matched your search
+11
-2
@@ -136,10 +136,19 @@ RUN mkdir /etc/sysusers.d \
|
||||
# ca-certificates, nix-bin and runit, from Ubuntu's archive. apt fetches
|
||||
# them over plain HTTP and checks each one against the archive's signed
|
||||
# package lists, so no CA certificates are needed.
|
||||
RUN apt-get update \
|
||||
#
|
||||
# nix-bin depends on xz-utils, whose install links man pages with
|
||||
# update-alternatives. The image's dpkg config drops man pages, so each
|
||||
# link warns. z-man, read after /etc/dpkg/dpkg.cfg.d/excludes, re-includes
|
||||
# the man1 pages for this install alone; the same step deletes them again,
|
||||
# so no man page or package is added.
|
||||
RUN echo 'path-include=/usr/share/man/man1/*' > /etc/dpkg/dpkg.cfg.d/z-man \
|
||||
&& apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
|
||||
ca-certificates nix-bin runit \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& rm -rf /var/lib/apt/lists/* /etc/dpkg/dpkg.cfg.d/z-man \
|
||||
&& find /usr/share/man/man1 ! -type d -delete \
|
||||
&& find /etc/alternatives -type l -xtype l -delete
|
||||
|
||||
# Nix run by root expects a group of build users, which nix-bin does not
|
||||
# create; with the setting empty, root's builds run without them.
|
||||
|
||||
@@ -7,8 +7,8 @@ requests of scrapers, scanners and other abusers before they reach an app.
|
||||
## What it does
|
||||
|
||||
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
|
||||
one image, with two commands that do this together:
|
||||
refuses the ones that look like abuse. `smallwebwaf` is one program, `sww`,
|
||||
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
|
||||
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
|
||||
the start. `SWW_LOG_REMOTE_TLS_CA_FILE` and
|
||||
`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 size is a number of bytes with an optional `K`, `M` or `G`, in powers of
|
||||
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
|
||||
`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)).
|
||||
- runit, a service supervisor, runs `smallwebwaf` as the `sww` user (uid and
|
||||
gid 65532) and starts it again when it exits. The `run` script of
|
||||
`discriminator` gives the state directory to the `sww` user at each start, so
|
||||
a volume needs no change of owner.
|
||||
- runit, a service supervisor, runs `sww` as the `sww` user (uid and gid 65532)
|
||||
and starts it again when it exits. The `run` script of `discriminator` gives
|
||||
the state directory to the `sww` user at each start, so a volume needs no
|
||||
change of owner.
|
||||
- `rproxy` and `discriminator` use no token between them. `discriminator`'s own
|
||||
endpoints, under `/_sww/`, are for your admin tools and your metrics
|
||||
collector, at its own address on the private network; nothing routes the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# smallwebwaf specification
|
||||
|
||||
`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 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
|
||||
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
|
||||
`_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
|
||||
restarting the container. The files `discriminator` watches while it runs are
|
||||
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
|
||||
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.
|
||||
- `discriminator` loads the file after `sww`'s own setup rules and before
|
||||
the Core Rule Set's rules. The Core Rule Set loads its own
|
||||
- `discriminator` loads the file after `smallwebwaf`'s own setup rules and
|
||||
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.
|
||||
- So the file holds only `SecAction` and `SecRule` lines that exclude with
|
||||
`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
|
||||
`SWW_WAF_MODE` is.
|
||||
- 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
|
||||
loads, such as `SecRuleRemoveById`, since none of the Core Rule Set's
|
||||
rules is loaded yet.
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
// Command sww is a web application firewall. `sww rproxy` is
|
||||
// the reverse proxy in front of one app, which asks discriminator about each request.
|
||||
// `sww discriminator` decides, for many sites, and keeps what lasts.
|
||||
// `sww healthcheck` is the container's health check. Everything it does
|
||||
// is in internal/sww.
|
||||
// Command sww is a web application firewall. `sww rproxy` is the reverse
|
||||
// proxy in front of one app, which asks discriminator about each request.
|
||||
// `sww discriminator` decides, for many sites, and keeps what lasts. `sww
|
||||
// healthcheck` is the container's health check. Everything it does is in
|
||||
// internal/sww.
|
||||
package main
|
||||
|
||||
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
|
||||
// log line are to see it: without the headers rproxy adds for discriminator,
|
||||
// with
|
||||
// body, which has been read of r, and with the length the visitor announced,
|
||||
// as -1 when it announced none. The question's own length covers only the part
|
||||
// of the body it carries.
|
||||
// with body, which has been read of r, and with the length the visitor
|
||||
// announced, as -1 when it announced none. The question's own length covers
|
||||
// only the part of the body it carries.
|
||||
func asTheVisitorSentIt(r *http.Request, body []byte) *http.Request {
|
||||
sent := r.Clone(r.Context())
|
||||
announced := sent.Header.Get(ContentLengthHeader)
|
||||
|
||||
@@ -391,9 +391,9 @@ func startRProxy(
|
||||
}
|
||||
|
||||
// newDiscriminator makes discriminator's server, without starting it, and
|
||||
// returns it, what
|
||||
// it writes, and the queue of the alerts it raises, as the settings in env make
|
||||
// it. No alert is sent from the queue: they wait in it, for the test to look at.
|
||||
// returns it, what it writes, and the queue of the alerts it raises, as the
|
||||
// settings in env make it. No alert is sent from the queue: they wait in it,
|
||||
// for the test to look at.
|
||||
// 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
|
||||
// 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
|
||||
// discriminator.
|
||||
// It speaks HTTP/1.1 only. Go's HTTP/2 client refuses to send a visitor's Upgrade
|
||||
// header, or a Connection header other than close or keep-alive, and that would
|
||||
// read as discriminator giving no answer.
|
||||
// discriminator. It speaks HTTP/1.1 only. Go's HTTP/2 client refuses to send
|
||||
// a visitor's Upgrade header, or a Connection header other than close or
|
||||
// keep-alive, and that would read as discriminator giving no answer.
|
||||
func newDiscriminatorTransport() *http.Transport {
|
||||
transport := newTransport()
|
||||
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
|
||||
// answers each
|
||||
// question as answer does and each report as report does, and returns its URL.
|
||||
// answers each question as answer does and each report as report does, and
|
||||
// returns its URL.
|
||||
func startDiscriminatorAnsweringReports(
|
||||
t *testing.T, answer, report http.HandlerFunc,
|
||||
) string {
|
||||
@@ -1033,9 +1033,8 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
|
||||
}
|
||||
|
||||
// startDiscriminatorWriting starts a stand-in for discriminator that reads
|
||||
// each question and
|
||||
// answers it with text, as it is, and returns its URL. It can send what Go's
|
||||
// server would not.
|
||||
// each question and answers it with text, as it is, and returns its URL. It
|
||||
// can send what Go's server would not.
|
||||
func startDiscriminatorWriting(t *testing.T, text string) string {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in new issue
Block a user