The docker build contexts and syslog_bridge.py lived at repo root, which meant setuptools (include = ["decnet*"]) never shipped them. Agents installed via `pip install $RELEASE_DIR` got site-packages/decnet/** but no templates/, so every deploy blew up in deployer._sync_logging_helper with FileNotFoundError on templates/syslog_bridge.py. Move templates/ -> decnet/templates/ and declare it as setuptools package-data. Path resolutions in services/*.py and engine/deployer.py drop one .parent since templates now lives beside the code. Test fixtures, bandit exclude path, and coverage omit glob updated to match.
117 lines
5.1 KiB
Docker
117 lines
5.1 KiB
Docker
ARG BASE_IMAGE=debian:bookworm-slim
|
|
FROM ${BASE_IMAGE}
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
openssh-server \
|
|
sudo \
|
|
rsyslog \
|
|
curl \
|
|
wget \
|
|
vim \
|
|
nano \
|
|
net-tools \
|
|
procps \
|
|
htop \
|
|
git \
|
|
inotify-tools \
|
|
psmisc \
|
|
iproute2 \
|
|
iputils-ping \
|
|
ca-certificates \
|
|
nmap \
|
|
jq \
|
|
python3 \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
RUN mkdir -p /var/run/sshd /root/.ssh /var/log/journal /var/lib/systemd/coredump \
|
|
&& chmod 700 /var/lib/systemd/coredump
|
|
|
|
# sshd_config: allow root + password auth; VERBOSE so session lines carry
|
|
# client IP + session PID (needed for file-capture attribution).
|
|
RUN sed -i \
|
|
-e 's|^#\?PermitRootLogin.*|PermitRootLogin yes|' \
|
|
-e 's|^#\?PasswordAuthentication.*|PasswordAuthentication yes|' \
|
|
-e 's|^#\?ChallengeResponseAuthentication.*|ChallengeResponseAuthentication no|' \
|
|
-e 's|^#\?LogLevel.*|LogLevel VERBOSE|' \
|
|
/etc/ssh/sshd_config
|
|
|
|
# rsyslog: forward auth.* and user.* to PID 1's stdout in RFC 5424 format.
|
|
# /proc/1/fd/1 is the container-stdout fd Docker attached — writing there
|
|
# surfaces lines in `docker logs` without needing a named pipe + relay cat
|
|
# (which would be readable AND writable by any root-in-container process).
|
|
RUN printf '%s\n' \
|
|
'# auth + user events → container stdout as RFC 5424' \
|
|
'$template RFC5424fmt,"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n"' \
|
|
'auth,authpriv.* /proc/1/fd/1;RFC5424fmt' \
|
|
'user.* /proc/1/fd/1;RFC5424fmt' \
|
|
> /etc/rsyslog.d/50-journal-forward.conf
|
|
|
|
# Silence default catch-all rules so we own auth/user routing exclusively.
|
|
# Also disable rsyslog's privilege drop: PID 1's stdout (/proc/1/fd/1) is
|
|
# owned by root, so a syslog-user rsyslogd gets EACCES and silently drops
|
|
# every auth/user line (bash CMD events + file_captured emissions).
|
|
RUN sed -i \
|
|
-e 's|^\(\*\.\*;auth,authpriv\.none\)|#\1|' \
|
|
-e 's|^auth,authpriv\.\*|#auth,authpriv.*|' \
|
|
-e 's|^\$PrivDropToUser|#$PrivDropToUser|' \
|
|
-e 's|^\$PrivDropToGroup|#$PrivDropToGroup|' \
|
|
/etc/rsyslog.conf
|
|
|
|
# Sudo: log to syslog (auth facility) AND a local file with full I/O capture
|
|
RUN echo 'Defaults logfile="/var/log/sudo.log"' >> /etc/sudoers && \
|
|
echo 'Defaults syslog=auth' >> /etc/sudoers && \
|
|
echo 'Defaults log_input,log_output' >> /etc/sudoers
|
|
|
|
# Lived-in environment: motd, shell aliases, fake project files
|
|
RUN echo "Ubuntu 22.04.3 LTS" > /etc/issue.net && \
|
|
echo "Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-88-generic x86_64)" > /etc/motd && \
|
|
echo "" >> /etc/motd && \
|
|
echo " * Documentation: https://help.ubuntu.com" >> /etc/motd && \
|
|
echo " * Management: https://landscape.canonical.com" >> /etc/motd && \
|
|
echo " * Support: https://ubuntu.com/advantage" >> /etc/motd
|
|
|
|
RUN echo 'alias ll="ls -alF"' >> /root/.bashrc && \
|
|
echo 'alias la="ls -A"' >> /root/.bashrc && \
|
|
echo 'alias l="ls -CF"' >> /root/.bashrc && \
|
|
echo 'export HISTSIZE=1000' >> /root/.bashrc && \
|
|
echo 'export HISTFILESIZE=2000' >> /root/.bashrc && \
|
|
echo 'PROMPT_COMMAND='"'"'logger -p user.info -t bash "CMD uid=$UID user=$USER src=${SSH_CLIENT%% *} pwd=$PWD cmd=$(history 1 | sed "s/^ *[0-9]* *//")";'"'" >> /root/.bashrc
|
|
|
|
# Fake project files to look lived-in
|
|
RUN mkdir -p /root/projects /root/backups /var/www/html && \
|
|
printf '# TODO: migrate DB to new server\n# check cron jobs\n# update SSL cert\n' > /root/notes.txt && \
|
|
printf 'DB_HOST=10.0.0.5\nDB_USER=admin\nDB_PASS=changeme123\nDB_NAME=prod_db\n' > /root/projects/.env && \
|
|
printf '[Unit]\nDescription=App Server\n[Service]\nExecStart=/usr/bin/python3 /opt/app/server.py\n' > /root/projects/app.service
|
|
|
|
# Stage all capture sources in a scratch dir. Nothing here survives the layer:
|
|
# _build_stealth.py packs syslog_bridge.py + emit_capture.py + capture.sh into
|
|
# XOR+gzip+base64 blobs embedded directly in /entrypoint.sh, and the whole
|
|
# /tmp/build tree is wiped at the end of the RUN — so the final image has no
|
|
# `.py` file under /opt and no `journal-relay` script under /usr/libexec/udev.
|
|
COPY entrypoint.sh capture.sh syslog_bridge.py emit_capture.py \
|
|
argv_zap.c _build_stealth.py /tmp/build/
|
|
|
|
# argv_zap is compiled into a shared object disguised as a multiarch
|
|
# udev-companion library (sits next to real libudev.so.1). gcc is installed
|
|
# only for this build step and purged in the same layer.
|
|
RUN set -eu \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends gcc libc6-dev \
|
|
&& mkdir -p /usr/lib/x86_64-linux-gnu /usr/libexec/udev \
|
|
&& gcc -O2 -fPIC -shared \
|
|
-o /usr/lib/x86_64-linux-gnu/libudev-shared.so.1 \
|
|
/tmp/build/argv_zap.c -ldl \
|
|
&& apt-get purge -y gcc libc6-dev \
|
|
&& apt-get autoremove -y \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& ln -sf /usr/bin/inotifywait /usr/libexec/udev/kmsg-watch \
|
|
&& python3 /tmp/build/_build_stealth.py \
|
|
&& rm -rf /tmp/build
|
|
|
|
EXPOSE 22
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD kill -0 1 || exit 1
|
|
|
|
ENTRYPOINT ["/entrypoint.sh"]
|