mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-11 22:38:54 +00:00
Publish the quickstart on loopback, since it trusts any proxy
compose.example.yaml does two things that are each fine alone and unsafe
together:
ports:
- "8080:80" # Docker binds 0.0.0.0 unless told otherwise
environment:
TRUSTED_PROXIES: "*" # believe the X-Forwarded-For of whoever connects
Behind a proxy that appends the header, "*" is correct and harmless --
Symfony strips the peer and takes the real client the proxy appended. The
example never gets there. It publishes the container on every interface,
so a visitor can reach port 8080 themselves, and then *they* are the peer
the application has been told to trust. `X-Forwarded-For: 203.0.113.9`
makes request()->ip() return exactly that.
What that costs, all of it on the signed-out surface:
- the login lockout, keyed on `email|ip` in LoginRequest::throttleKey()
- throttle:6,1 on register, password-email, password-reset, two-factor
- throttle:30,1 on share-link, public-browse, public-comment
- the download log, the activity log, and the `ip_address` recorded on
guest comments -- which FileComments::post calls "the one handle that
makes spam actionable"
Rotate the header and every one of them counts a different attacker.
The project's own test states the primitive: TrustedProxiesTest sets
trustedproxy.proxies = '*', sends X-Forwarded-For from a *direct* client,
and asserts the address is taken.
The documentation has always qualified "*" correctly -- .env.example says
it is "only safe when nothing but the proxy can reach the app", and
dockerhub-overview.md repeats it. The example file is what did not meet
its own precondition, and it is the file the Docker Hub description tells
a first-time reader to copy.
Publishing on 127.0.0.1 restores the precondition: a proxy on the host, or
in this compose file, still reaches it; nothing off the machine does. This
repository's own compose.yaml already publishes Adminer that way, for the
same reason.
The two settings are now documented as a pair in all three places that
carry them, including what to do when the proxy is on another host: bind
to the interface it arrives from and name that address in TRUSTED_PROXIES
instead of "*".
DOCKER.md's health-check command changes with it -- it told the reader to
curl <host-ip>:8080 from the same machine, which the new binding does not
answer. It now says 127.0.0.1:8080.
No test: this is packaging and prose. `docker compose config` parses the
edited file.
This commit is contained in:
@@ -121,6 +121,13 @@ Without it every visitor appears to come from the proxy. The login rate limiter
|
||||
your users as one attacker, and the download log records the proxy's address instead of the
|
||||
person's. `compose.example.yaml` already sets this.
|
||||
|
||||
`"*"` means "trust whoever connected to me", so it belongs with a published port only the proxy can
|
||||
reach — which is why `compose.example.yaml` publishes on `127.0.0.1`. If anybody can open the
|
||||
container's port directly, they are the proxy as far as this setting is concerned, and the
|
||||
`X-Forwarded-For` they send is the address the rate limiters and the download log will use. Where
|
||||
the proxy runs on another host, publish on the interface it arrives from and name that address or
|
||||
subnet here instead of `"*"`.
|
||||
|
||||
Leaving it unset does not cause a `502` — that means your proxy could not get a usable response out
|
||||
of the container at all, which is a different problem with a different fix. It does cause a **419
|
||||
"page expired"**. Without it the application never learns the proxy terminated TLS, so it builds
|
||||
@@ -177,7 +184,7 @@ is the quickest way to separate "the app is down" from "the proxy cannot reach t
|
||||
during an outage, from the same machine:
|
||||
|
||||
```sh
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://<host-ip>:8080/up # straight at the container
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/up # straight at the container
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://files.example.com/up
|
||||
```
|
||||
|
||||
|
||||
@@ -17,7 +17,23 @@ services:
|
||||
# Put a TLS-terminating proxy in front of this in any real install.
|
||||
# ProjectSend issues download links and password-reset emails using
|
||||
# APP_URL, so that value — not this port — is what users must reach.
|
||||
- "8080:80"
|
||||
#
|
||||
# Bound to the loopback address, not to every interface, because
|
||||
# TRUSTED_PROXIES below is "*". That setting tells the application to
|
||||
# believe the X-Forwarded-For header of whoever connects to it, which
|
||||
# is correct behind a proxy and catastrophic when anybody can connect
|
||||
# directly: a visitor who reaches this port themselves is then the
|
||||
# "proxy", and can hand the application any client IP they like —
|
||||
# which is enough to walk straight through the login lockout, every
|
||||
# named rate limit, and the address recorded in the download log.
|
||||
#
|
||||
# Publishing on the loopback address keeps the proxy (on this host,
|
||||
# or in this compose file) able to reach it while nothing off the
|
||||
# machine can. If you move the proxy to another host, publish on the
|
||||
# interface it comes from and narrow TRUSTED_PROXIES to that address
|
||||
# or subnet at the same time — the two settings only make sense
|
||||
# together.
|
||||
- "127.0.0.1:8080:80"
|
||||
environment:
|
||||
APP_URL: https://files.example.com
|
||||
APP_ENV: production
|
||||
@@ -55,6 +71,10 @@ services:
|
||||
# Without it every visitor appears to come from the proxy: the login
|
||||
# rate limiter treats all of your users as one attacker, and the
|
||||
# download log records the proxy's address.
|
||||
#
|
||||
# "*" means "trust whoever connects to me", which is only safe when
|
||||
# nothing but the proxy can — which is what the loopback binding
|
||||
# above is for. Change one and you have to change the other.
|
||||
TRUSTED_PROXIES: "*"
|
||||
|
||||
# Optional: uncomment these — with a password of your own — to create
|
||||
|
||||
@@ -40,7 +40,10 @@ services:
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Put a TLS-terminating proxy in front of this in any real install.
|
||||
- "8080:80"
|
||||
# Bound to loopback because TRUSTED_PROXIES below is "*": the
|
||||
# application then believes the X-Forwarded-For of whoever connects,
|
||||
# so nobody but the proxy may be able to.
|
||||
- "127.0.0.1:8080:80"
|
||||
environment:
|
||||
APP_URL: https://files.example.com
|
||||
APP_ENV: production
|
||||
@@ -58,6 +61,8 @@ services:
|
||||
|
||||
# Required whenever anything sits between your visitors and this
|
||||
# container — including the reverse proxy you should be running.
|
||||
# "*" trusts whoever connects, so it goes together with the loopback
|
||||
# binding above: change one and you have to change the other.
|
||||
TRUSTED_PROXIES: "*"
|
||||
|
||||
# Optional: uncomment these — with a password of your own — to create
|
||||
|
||||
Reference in New Issue
Block a user