Serve Prometheus metrics at /metrics behind basic auth (closes #94)
check / check (push) Successful in 2m47s
check / check (push) Successful in 2m47s
With METRICS_USERNAME and METRICS_PASSWORD both set, the backend records request metrics through go-http-metrics in a registry of its own, with Go's runtime and process metrics, and serves them at GET /metrics behind basic auth with those credentials; nginx passes /metrics to it. With neither set there is no such route; one alone stops the start with an error naming both, and a METRICS_USERNAME containing ":" stops it with an error naming that. Only requests that reach the health check or POST /api/v1/reports are recorded, not every request as the conventions show: the labels are path and method, which clients can make up without end. So POST /api/v1/reports is registered by its full path, not inside a route group. Deviation: go get and go mod tidy ran directly; no entrypoint adds a Go dependency yet (#45). Model: opus-5-5
This commit is contained in:
+32
-10
@@ -88,6 +88,8 @@ project layout:
|
||||
| `TRUSTED_PROXIES` | loopback + RFC1918 | Comma-separated CIDRs whose `X-Forwarded-For` / `X-Real-IP` headers are trusted for client IP resolution |
|
||||
| `REPORTS_PER_MINUTE` | `60` | Reports each client address may send a minute; see [Report limits](#report-limits) |
|
||||
| `CORS_ALLOWED_ORIGINS` | empty | Comma-separated origins whose pages may call the API; see [CORS](#cors) |
|
||||
| `METRICS_USERNAME` | empty | Basic auth user name for `/metrics`; see [Metrics](#metrics) |
|
||||
| `METRICS_PASSWORD` | empty | Basic auth password for `/metrics`; see [Metrics](#metrics) |
|
||||
|
||||
`TRUSTED_PROXIES` defaults to
|
||||
`127.0.0.1/32,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`. The loopback
|
||||
@@ -103,16 +105,16 @@ starting, with an error naming the variable. An empty variable counts as unset.
|
||||
### Container image
|
||||
|
||||
The root `Dockerfile` builds one image in which nginx listens on the public port
|
||||
8080, serves the frontend, and proxies `/api/` and `/.well-known/healthcheck` to
|
||||
this server. The image's entrypoint, `bin/entrypoint.sh`, starts the server as
|
||||
user `netwatch` (uid 1000) with `BIND_ADDRESS=127.0.0.1` and `PORT=8081`, so
|
||||
only nginx reaches it, and with `TRUSTED_PROXIES=127.0.0.1/32`, so it takes the
|
||||
client address nginx passes on and no other. `DATA_DIR` is `/data/reports`, on
|
||||
the `/data` volume; before starting the server, the entrypoint creates it and
|
||||
gives it and `/data` to `netwatch` with `netwatch-server prepare-data-dir`,
|
||||
which acts on nothing outside `/data`. nginx replaces the security headers this
|
||||
server sets with those in the root `security-headers.conf`, so those are what
|
||||
clients of the image see.
|
||||
8080, serves the frontend, and proxies `/api/`, `/.well-known/healthcheck` and
|
||||
`/metrics` to this server. The image's entrypoint, `bin/entrypoint.sh`, starts
|
||||
the server as user `netwatch` (uid 1000) with `BIND_ADDRESS=127.0.0.1` and
|
||||
`PORT=8081`, so only nginx reaches it, and with `TRUSTED_PROXIES=127.0.0.1/32`,
|
||||
so it takes the client address nginx passes on and no other. `DATA_DIR` is
|
||||
`/data/reports`, on the `/data` volume; before starting the server, the
|
||||
entrypoint creates it and gives it and `/data` to `netwatch` with
|
||||
`netwatch-server prepare-data-dir`, which acts on nothing outside `/data`. nginx
|
||||
replaces the security headers this server sets with those in the root
|
||||
`security-headers.conf`, so those are what clients of the image see.
|
||||
|
||||
The container's own `TRUSTED_PROXIES` goes to nginx instead: IP addresses or
|
||||
CIDRs, separated by commas, of the reverse proxies in front of the container.
|
||||
@@ -176,6 +178,26 @@ origin, `scheme://host` with an optional `:port`, as browsers send it: no path,
|
||||
not even a trailing `/`, and no `*`. Any other entry stops the server from
|
||||
starting, with an error naming `CORS_ALLOWED_ORIGINS`.
|
||||
|
||||
### Metrics
|
||||
|
||||
With both `METRICS_USERNAME` and `METRICS_PASSWORD` set, the server serves
|
||||
Prometheus metrics at `GET /metrics` to requests with those as their basic auth
|
||||
credentials, and answers any other with 401. For each request that reaches the
|
||||
health check or `POST /api/v1/reports`, those the rate limit refuses included,
|
||||
the metrics record its duration and response size, labelled with its path,
|
||||
method and status; they also count those requests in progress, and include Go's
|
||||
runtime and process metrics. No other request is recorded: not those to
|
||||
`/metrics` itself, and not those answered before they reach either route, such
|
||||
as a CORS preflight, or a request refused with 404 for a path no route has, 405
|
||||
for a method its route does not take, or 413 for declaring a body length over
|
||||
the 1 MiB limit. A report whose body goes over the limit without declaring its
|
||||
length reaches the route, is answered 413 there, and is recorded with that
|
||||
status. Clients can make up any number of paths and methods, and each would add
|
||||
labels to the metrics for as long as the server runs. With neither set, nothing
|
||||
is recorded and `/metrics` answers 404. One without the other stops the server
|
||||
from starting, with an error naming both; so does a `METRICS_USERNAME`
|
||||
containing `:`, which basic auth cannot carry, with an error naming it.
|
||||
|
||||
## TODO
|
||||
|
||||
- Add integration test that POSTs a report and verifies the compressed output
|
||||
|
||||
Reference in New Issue
Block a user