Honour forwarded headers only from trusted proxies (closes #259)
check / check (push) Failing after 7s
check / check (push) Failing after 7s
A new ClientIP middleware sets request.RemoteAddr to the client's address before the request is logged. X-Forwarded-For and X-Real-IP count only when the peer is in TRUSTED_PROXIES, which defaults to the RFC 1918 ranges. The login rate limit, the session's IP address and hostname, and the request log all use that address. Model: opus-5-5
This commit is contained in:
1 parent
bc8aa6bb96
commit
f5dd70047e
10 files changed
+599
-44
No files matched your search
@@ -1339,8 +1339,9 @@ Every session has a hostmask in IRC's form, `nick!username@hostname`:
|
||||
from, or that IP address when the lookup gives no name. The lookup waits up to
|
||||
three seconds.
|
||||
|
||||
Over the HTTP API, the server takes the client's IP address from the first entry
|
||||
of the `X-Forwarded-For` header, then `X-Real-IP`, then the TCP connection (see
|
||||
Over the HTTP API, the client's IP address is the address of the TCP connection.
|
||||
When that connection comes from a trusted proxy, the server reads the address
|
||||
from `X-Forwarded-For` or `X-Real-IP` instead (see
|
||||
[Reverse proxy](#reverse-proxy)).
|
||||
|
||||
Every user can see a hostmask: in `RPL_WHOISUSER` (`311`), `RPL_WHOREPLY`
|
||||
@@ -1450,10 +1451,9 @@ over the limit gets `429` `too many login attempts, try again later` with
|
||||
attempt is more than 15 minutes old. Session creation has no rate limit; the
|
||||
hashcash stamp is its cost.
|
||||
|
||||
The IP address comes from the first entry of `X-Forwarded-For`, else from
|
||||
`X-Real-IP`, else from the TCP connection. Without a reverse proxy that
|
||||
overwrites those headers, a client can set them itself and so pick any address
|
||||
(see [Reverse proxy](#reverse-proxy)).
|
||||
The IP address is the client's address as described in
|
||||
[Reverse proxy](#reverse-proxy). A client outside the trusted proxies cannot
|
||||
change it with `X-Forwarded-For` or `X-Real-IP`.
|
||||
|
||||
### Writing a client
|
||||
|
||||
@@ -1714,10 +1714,14 @@ Every setting is an environment variable. The server also reads a `.env` file in
|
||||
its working directory, and a YAML file `neoirc.yaml` in `/etc/neoirc/` or
|
||||
`$HOME/.config/neoirc/` with the same names as keys. A variable from the
|
||||
environment wins over one in `.env`, and both win over `neoirc.yaml`. An
|
||||
environment variable set to the empty string counts as unset, except
|
||||
`IRC_LISTEN_ADDR`, where the empty string turns the IRC listener off. A
|
||||
`neoirc.yaml` that is not valid YAML stops the server at startup. A setting that
|
||||
does not parse or is out of range also stops the server at startup, and the
|
||||
environment variable set to the empty string counts as unset, with two
|
||||
exceptions:
|
||||
|
||||
- For `IRC_LISTEN_ADDR`, the empty string turns the IRC listener off.
|
||||
- For `TRUSTED_PROXIES`, the empty string trusts no proxy.
|
||||
|
||||
A `neoirc.yaml` that is not valid YAML stops the server at startup. A setting
|
||||
that does not parse or is out of range also stops the server at startup, and the
|
||||
error names the setting.
|
||||
|
||||
- `PORT` (default `8080`): the HTTP port. `0` lets the system pick a free port.
|
||||
@@ -1747,6 +1751,11 @@ error names the setting.
|
||||
address. A fraction such as `0.5` is allowed. It must be above `0`.
|
||||
- `LOGIN_RATE_BURST` (default `5`): how many attempts an address may make at
|
||||
once before the rate applies. It must be at least `1`.
|
||||
- `TRUSTED_PROXIES` (default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`): the
|
||||
reverse proxies whose `X-Forwarded-For` and `X-Real-IP` headers the server
|
||||
believes. It is a comma-separated list of networks, such as `10.0.0.0/8`, and
|
||||
single addresses, such as `192.0.2.1`. A list replaces the default entirely.
|
||||
The empty string trusts nothing. See [Reverse proxy](#reverse-proxy).
|
||||
- `SESSION_IDLE_TIMEOUT` (default `720h`, 30 days): a duration with a unit, `h`
|
||||
for hours, `m` for minutes or `s` for seconds, such as `720h` or `36h30m`. A
|
||||
client with no request for this long is removed, and a session left with no
|
||||
@@ -1804,14 +1813,34 @@ Run the server behind a reverse proxy that terminates TLS. The server itself
|
||||
speaks plain HTTP, and the auth cookie is `Secure`, so browsers send it only
|
||||
over HTTPS. The proxy must:
|
||||
|
||||
- Set `X-Forwarded-For` to the client's address, replacing any value the client
|
||||
sent. The server reads that header first and trusts it from anyone; it reads
|
||||
`X-Real-IP` only when `X-Forwarded-For` is absent. Without a proxy that
|
||||
overwrites the header, a client can choose its own address, which sets its
|
||||
hostname and lets it dodge the login rate limit.
|
||||
- Connect from an address in `TRUSTED_PROXIES`. The default trusts the private
|
||||
ranges `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/16`. The setting must
|
||||
hold the address the server sees the proxy connect from. For a proxy on the
|
||||
same host:
|
||||
- With the server outside Docker, that address is `127.0.0.1` or `::1`. The
|
||||
default does not trust them, so set `TRUSTED_PROXIES=127.0.0.1,::1`.
|
||||
- Through a published Docker port, that address is the Docker bridge
|
||||
gateway, such as `172.17.0.1`. The default trusts it.
|
||||
- Add the client's address to `X-Forwarded-For`, or set `X-Real-IP`.
|
||||
- Wait at least 60 seconds for a response. A long poll can take 30 seconds, and
|
||||
the server's own write timeout is 60 seconds.
|
||||
|
||||
The server finds the client's address in these steps:
|
||||
|
||||
- A connection from outside `TRUSTED_PROXIES` is the client. The server ignores
|
||||
its `X-Forwarded-For` and `X-Real-IP` headers.
|
||||
- For a connection from a trusted proxy, the server reads `X-Forwarded-For` from
|
||||
right to left. It skips trusted addresses and takes the first untrusted one.
|
||||
When every entry is trusted, it takes the leftmost.
|
||||
- Without `X-Forwarded-For`, the server takes `X-Real-IP`.
|
||||
- When an entry it reads is not an IP address, the server uses the proxy's
|
||||
address.
|
||||
|
||||
The client's address sets the session's hostname and counts toward the login
|
||||
rate limit. Any connection from a trusted address can choose its client address
|
||||
through `X-Forwarded-For`. If clients connect directly from private addresses,
|
||||
set `TRUSTED_PROXIES` to the proxy's address alone.
|
||||
|
||||
Caddy:
|
||||
|
||||
```
|
||||
@@ -1861,9 +1890,10 @@ The IRC listener has no TLS.
|
||||
|
||||
The server logs with Go's `slog`, one line per event, to standard output. The
|
||||
lines are JSON, or plain text when standard output is a terminal. Every HTTP
|
||||
request is logged with its method, URL, protocol, status, latency, remote
|
||||
address, user agent, referrer and request ID. `DEBUG=true` adds debug-level
|
||||
lines.
|
||||
request is logged with its method, URL, protocol, status, latency, client
|
||||
address, user agent, referrer and request ID. The client address is the one
|
||||
found as described in [Reverse proxy](#reverse-proxy). `DEBUG=true` adds
|
||||
debug-level lines.
|
||||
|
||||
### The database
|
||||
|
||||
@@ -1946,8 +1976,8 @@ and their sessions to end. Then it closes the database.
|
||||
gets a message.
|
||||
- `internal/hashcash/`: stamp checking and minting.
|
||||
- `internal/ratelimit/`: the per-address login rate limiter.
|
||||
- `internal/middleware/`: request logging, cross-origin requests, the
|
||||
`Content-Security-Policy` header and metrics.
|
||||
- `internal/middleware/`: finding the client's address, request logging,
|
||||
cross-origin requests, the `Content-Security-Policy` header and metrics.
|
||||
- `internal/healthcheck/` and `internal/stats/`: the health check and its
|
||||
counters.
|
||||
- `internal/logger/` and `internal/globals/`: logging setup and build
|
||||
|
||||
Reference in new issue
Block a user