feat: refuse to start as root
Build docker image and push to registry.bitdeals.org / main-build-job (push) Successful in 2m5s

The image runs as its own unprivileged user, and everything in run.sh now
assumes it: keys.dat is worked on by its owner, no privilege is dropped
anywhere, and what confines the container is whatever the caller passed.
Started as root by a `user:` override, none of that holds and the container
looks identical from outside -- a silent loss of every property this image
was changed to have.

Four lines at the top of run.sh, and the reason is then the first line of
`docker logs`. It is the same bargain as the build-time checks: a wrong
posture should fail loudly rather than pass for a right one.
This commit is contained in:
2026-09-09 13:45:09 +00:00
parent 68eb019e59
commit 29a0957cb7
3 changed files with 18 additions and 2 deletions
+2 -1
View File
@@ -167,7 +167,8 @@ Container images are configured using parameters passed at runtime.
it creates in its own home. Nothing is dropped at run time because nothing it creates in its own home. Nothing is dropped at run time because nothing
privileged is held in the first place. No file in the image carries a setuid privileged is held in the first place. No file in the image carries a setuid
or setgid bit either, so a daemon that has been taken over has nothing left to or setgid bit either, so a daemon that has been taken over has nothing left to
climb. climb. A caller that puts the user back to root gets a refusal on the first
line of the log, not a container that looks the same and confines nothing.
The price is a bind mount. `/home/bitmessage` mounted from the host has to be The price is a bind mount. `/home/bitmessage` mounted from the host has to be
owned by uid 2000, because the container can no longer chown it on the way in; owned by uid 2000, because the container can no longer chown it on the way in;
+3 -1
View File
@@ -165,7 +165,9 @@ docker run -d \
которые заводит сам демон — `knownnodes.dat` и `messages.dat`, — он заводит в которые заводит сам демон — `knownnodes.dat` и `messages.dat`, — он заводит в
своём домашнем каталоге. Ничего не понижается на старте, потому что повышенных своём домашнем каталоге. Ничего не понижается на старте, потому что повышенных
прав изначально нет. Ни на одном файле образа нет бита setuid или setgid, так прав изначально нет. Ни на одном файле образа нет бита setuid или setgid, так
что захваченному демону не по чему подниматься. что захваченному демону не по чему подниматься. Вызывающая сторона, вернувшая
пользователя в root, получит отказ первой строкой журнала, а не контейнер,
который выглядит так же и не ограничивает ничего.
Плата за это — bind-монтирование. Каталог `/home/bitmessage`, приехавший с Плата за это — bind-монтирование. Каталог `/home/bitmessage`, приехавший с
хоста, должен принадлежать uid 2000: сменить владельца на входе контейнер хоста, должен принадлежать uid 2000: сменить владельца на входе контейнер
+13
View File
@@ -2,6 +2,19 @@
set -eu set -eu
# The image runs as its own unprivileged user and everything here assumes it:
# keys.dat is worked on by its owner, nothing drops a privilege anywhere, and
# what confines the container is whatever the caller passed -- cap_drop,
# no-new-privileges, read_only. Started as root instead, by a `user:` override
# or a `docker run -u 0`, none of that is true any more and the container looks
# exactly the same from outside. Refuse, where the reason is the first line of
# `docker logs`, rather than run on quietly with the wrong properties.
if [ "$(id -u)" = 0 ]
then
echo "run.sh: this image must not be run as root; drop the user override" >&2
exit 1
fi
export BITMESSAGE_API_USER="${BITMESSAGE_API_USER:-bitmessage_api_user}" export BITMESSAGE_API_USER="${BITMESSAGE_API_USER:-bitmessage_api_user}"
export BITMESSAGE_API_PASSWORD="${BITMESSAGE_API_PASSWORD:-bitmessage_api_password}" export BITMESSAGE_API_PASSWORD="${BITMESSAGE_API_PASSWORD:-bitmessage_api_password}"
export BITMESSAGE_SEED_ADDRESSES="${BITMESSAGE_SEED_ADDRESSES:-0}" export BITMESSAGE_SEED_ADDRESSES="${BITMESSAGE_SEED_ADDRESSES:-0}"