mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
da7eb6f67d
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.
123 lines
4.5 KiB
YAML
123 lines
4.5 KiB
YAML
# A complete ProjectSend install using the official image.
|
|
#
|
|
# 1. Edit the passwords and APP_URL below.
|
|
# 2. docker compose -f compose.example.yaml up -d
|
|
# 3. Open APP_URL — the setup screen creates your administrator account.
|
|
#
|
|
# This is the file the Docker Hub description points at, so it is written
|
|
# for someone who has never seen the project before.
|
|
|
|
name: projectsend
|
|
|
|
services:
|
|
app:
|
|
image: projectsend/projectsend:2
|
|
restart: unless-stopped
|
|
ports:
|
|
# 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.
|
|
#
|
|
# 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
|
|
APP_DEBUG: "false"
|
|
|
|
# Generated on first boot and kept on the storage volume. Set it
|
|
# explicitly if you manage secrets elsewhere — but never change it on
|
|
# a running install: it decrypts existing data.
|
|
# APP_KEY: base64:...
|
|
|
|
DB_CONNECTION: mysql
|
|
DB_HOST: db
|
|
DB_PORT: "3306"
|
|
DB_DATABASE: projectsend
|
|
DB_USERNAME: projectsend
|
|
DB_PASSWORD: change-me-database
|
|
|
|
REDIS_HOST: redis
|
|
CACHE_STORE: redis
|
|
SESSION_DRIVER: redis
|
|
QUEUE_CONNECTION: redis
|
|
|
|
# Mail is easier to configure from System → Settings → Email once you
|
|
# are logged in — it has a "send test" button. These are the fallback
|
|
# until then.
|
|
MAIL_MAILER: smtp
|
|
MAIL_HOST: smtp.example.com
|
|
MAIL_PORT: "587"
|
|
MAIL_USERNAME: ""
|
|
MAIL_PASSWORD: ""
|
|
MAIL_FROM_ADDRESS: files@example.com
|
|
|
|
# Required whenever anything sits between your visitors and this
|
|
# container — which includes the reverse proxy you should be running.
|
|
# 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
|
|
# the first administrator unattended and skip the setup screen. Left
|
|
# commented, the setup screen creates it instead. Ignored once any
|
|
# user exists.
|
|
# ADMIN_NAME: Administrator
|
|
# ADMIN_EMAIL: admin@example.com
|
|
# ADMIN_PASSWORD: change-me-admin
|
|
volumes:
|
|
# Every uploaded file lives here, along with the generated APP_KEY.
|
|
# This is the volume to back up; losing it loses the data.
|
|
- storage:/var/www/html/storage
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
|
|
db:
|
|
image: mysql:8.4
|
|
restart: unless-stopped
|
|
environment:
|
|
MYSQL_DATABASE: projectsend
|
|
MYSQL_USER: projectsend
|
|
MYSQL_PASSWORD: change-me-database
|
|
MYSQL_ROOT_PASSWORD: change-me-root
|
|
volumes:
|
|
- db-data:/var/lib/mysql
|
|
healthcheck:
|
|
# The app waits for this before migrating, so a slow first start is
|
|
# normal rather than a failure.
|
|
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 20
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
restart: unless-stopped
|
|
volumes:
|
|
- redis-data:/data
|
|
|
|
volumes:
|
|
storage:
|
|
db-data:
|
|
redis-data:
|