Build docker image and push to registry.bitdeals.org / main-build-job (push) Successful in 2m21s
The chown at startup was the only thing that ever needed root here, and it served a case this project does not have: keys.dat arriving from a bind mount owned by somebody else. Every deployment uses a named volume, which takes its ownership from the image. So the chown goes, and everything that existed to survive it goes with it. USER bitmessage in the Dockerfile, from PID 1 onwards. drop_privs.py is deleted, the supervisor no longer re-executes itself with a trimmed bounding set, run.sh has no privileged prologue and no wrapper around the eight commands that used to run through one. What is left of run.sh differs from the version before any of this by eleven lines: two chowns gone, seven `gosu bitmessage` prefixes gone, one comment reworded. keys.dat gets its mode 600 at build time instead of on every start, because on every start there is now no root to set it. Its mode and ownership reach a fresh volume from the image, and every volume in service already carries them -- checked on all four live nodes: nothing under /home/bitmessage is owned by anyone but 2000. The setuid strip stays. It is two lines and it closes the one way a taken-over daemon could still have climbed. What the caller sets changes too, and in the right direction: `cap_drop: ALL` with nothing added back, where the previous commit needed seven capabilities handed in. Confinement that used to be split between the image and the caller now sits in one place. The image gives up defending itself when run with no options at all, which is the trade named in the README along with the bind mount it costs.
121 lines
5.2 KiB
Docker
121 lines
5.2 KiB
Docker
# A container for PyBitmessage daemon
|
|
FROM ubuntu:bionic
|
|
|
|
SHELL ["/bin/bash", "-exo", "pipefail", "-c"]
|
|
|
|
# Install dependencies. update and install share a layer on purpose: split
|
|
# across two, a cached update feeds install package lists that may be months
|
|
# stale, and the install then fails or pulls something unintended.
|
|
RUN apt-get update \
|
|
&& apt-get install -yq --no-install-suggests --no-install-recommends \
|
|
build-essential libcap-dev libssl-dev \
|
|
python-all-dev python-msgpack python-pip python-setuptools \
|
|
git
|
|
|
|
## Do not use cache when building next layers of the image.
|
|
ARG NOCACHE=0
|
|
|
|
WORKDIR /root/PyBitmessage
|
|
RUN git clone https://github.com/Bitmessage/PyBitmessage .
|
|
|
|
# Install
|
|
RUN pip2 install jsonrpclib .
|
|
|
|
# Raise the SQL-thread startup timeout from the stock 60 s.
|
|
#
|
|
# PyBitmessage kills the daemon outright if the SQL thread is not ready within
|
|
# sql_timeout seconds (class_objectProcessor.py -> os._exit(1)). The startup
|
|
# VACUUM of a messages.dat that has grown to a few hundred MB does not fit in
|
|
# 60 s, and since the process dies mid-VACUUM lastvacuumtime is never updated,
|
|
# so every later start retries the same doomed VACUUM and the node never comes
|
|
# back. Measured: 26 s for a 264 MB database on an idle host, and the last
|
|
# start that did survive used 36 s of the 60.
|
|
#
|
|
# The greps are load-bearing: the clone above is unpinned, so if upstream ever
|
|
# moves or renames the constant, a silent no-op sed would ship an image that
|
|
# looks fixed and is not. Fail the build instead. The .pyc is refreshed because
|
|
# at runtime /usr/local is root-owned while the daemon runs as bitmessage, so a
|
|
# stale one can only be recompiled to memory on every start.
|
|
RUN f=/usr/local/lib/python2.7/dist-packages/pybitmessage/helper_sql.py \
|
|
&& grep -q '^sql_timeout = 60$' "$f" \
|
|
&& sed -i 's/^sql_timeout = 60$/sql_timeout = 600/' "$f" \
|
|
&& grep -q '^sql_timeout = 600$' "$f" \
|
|
&& rm -f "${f}c" \
|
|
&& python -c "import py_compile; py_compile.compile('$f')"
|
|
|
|
FROM ubuntu:bionic
|
|
|
|
# 8442 is the XML-RPC API (keep it on loopback), 8444 the Bitmessage P2P port.
|
|
# The daemon listens on both regardless; publishing 8444 is what makes the node
|
|
# reachable for inbound peers.
|
|
EXPOSE 8442/tcp
|
|
EXPOSE 8444/tcp
|
|
|
|
ENV USER_UID=2000
|
|
ENV USER_GID=2000
|
|
ENV HOME=/home/bitmessage
|
|
ENV BITMESSAGE_HOME=${HOME}
|
|
|
|
COPY --from=0 /usr/local/ /usr/local/
|
|
COPY ./docker/healthy_check.py /usr/local/bin/
|
|
COPY ./docker/seed_addr_gen.py /usr/local/bin/
|
|
COPY ./docker/watchdog.py /usr/local/bin/
|
|
COPY ./docker/run.sh /usr/local/bin/
|
|
|
|
# Install dependencies. gosu is gone with the last thing that needed it: the
|
|
# container starts as the daemon's user and never changes user, so there is no
|
|
# privilege to drop at run time. moreutils went with it -- nothing in this
|
|
# repository ever called any of its tools.
|
|
RUN apt-get update \
|
|
&& apt-get install -yq --no-install-suggests --no-install-recommends python-setuptools \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Create a user
|
|
RUN addgroup --gid $USER_GID bitmessage ;\
|
|
useradd --uid $USER_UID --gid $USER_GID --skel /dev/null --create-home --home-dir $HOME bitmessage
|
|
|
|
WORKDIR ${HOME}
|
|
|
|
# Generate default config
|
|
RUN su bitmessage -c "pybitmessage -t"
|
|
|
|
# keys.dat holds the API password and the node's private keys, and the daemon
|
|
# writes it back at that mode anyway. Set here rather than on every start,
|
|
# because on every start there would be no root to do it: a named volume takes
|
|
# its first contents, and their ownership and modes, from the image.
|
|
RUN chmod 600 keys.dat
|
|
|
|
# Nothing here needs a setuid or setgid bit at run time, and every one of them
|
|
# is a way back to root for a daemon that has been taken over -- the more so
|
|
# under a caller that adds no-new-privileges, which leaves them as the one thing
|
|
# that could still have raised it. Strip them all, then insist none is left, so
|
|
# a package added here later cannot bring one back unnoticed. Last root step in
|
|
# the file, and it has to be: after USER below there is no chmod to be had.
|
|
RUN find / -xdev -type f -perm /6000 -exec chmod -s {} + \
|
|
&& [ -z "$(find / -xdev -type f -perm /6000)" ]
|
|
|
|
# The daemon's own user, from PID 1 onwards. Nothing in this image needs root
|
|
# once it is built: the files it works on are its own, and the only two it
|
|
# creates -- knownnodes.dat and messages.dat -- it creates in its home. That
|
|
# also settles what a `docker exec` and the healthcheck below run as.
|
|
#
|
|
# The caller no longer has to hand any capability back after cap_drop: ALL. In
|
|
# exchange, a bind mount over /home/bitmessage has to be owned by this uid; a
|
|
# named volume, which is what this image is meant for, inherits it from the
|
|
# image and needs nothing.
|
|
USER bitmessage
|
|
|
|
# One line, because a wrong USER is a whole class of surprise and this is where
|
|
# it is cheapest to find out.
|
|
RUN [ "$(id -u)" = "$USER_UID" ] && [ "$(id -g)" = "$USER_GID" ]
|
|
|
|
CMD ["sh", "/usr/local/bin/run.sh"]
|
|
|
|
## Check PyBitmessage active network connections.
|
|
## The start period covers the startup VACUUM of messages.dat, which takes tens
|
|
## of seconds once the database reaches a few hundred MB; without it the
|
|
## container reports unhealthy for that whole window on every restart.
|
|
HEALTHCHECK --retries=0 --interval=15s --start-period=180s \
|
|
CMD ["python", "/usr/local/bin/healthy_check.py"]
|
|
|