diff --git a/templates/ssh/Dockerfile b/templates/ssh/Dockerfile index 9f67c9f..6dd46a3 100644 --- a/templates/ssh/Dockerfile +++ b/templates/ssh/Dockerfile @@ -34,13 +34,15 @@ RUN sed -i \ -e 's|^#\?LogLevel.*|LogLevel VERBOSE|' \ /etc/ssh/sshd_config -# rsyslog: forward auth.* and user.* to named pipe in RFC 5424 format. -# The entrypoint relays the pipe to stdout for Docker log capture. +# 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' \ - '# syslog-relay log bridge — auth + user events → named pipe as RFC 5424' \ + '# 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.* |/run/systemd/journal/syslog-relay;RFC5424fmt' \ - 'user.* |/run/systemd/journal/syslog-relay;RFC5424fmt' \ + '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 diff --git a/templates/ssh/entrypoint.sh b/templates/ssh/entrypoint.sh index 0ac78d5..8c59325 100644 --- a/templates/ssh/entrypoint.sh +++ b/templates/ssh/entrypoint.sh @@ -31,15 +31,10 @@ ls /var/www/html HIST fi -# Logging pipeline: named pipe → rsyslogd (RFC 5424) → stdout → Docker log capture. -# Pipe lives under /run/systemd/journal/ and the relay process is cloaked via -# exec -a so `ps aux` shows "systemd-journal-fwd" instead of a raw `cat`. -mkdir -p /run/systemd/journal -mkfifo /run/systemd/journal/syslog-relay - -bash -c 'exec -a "systemd-journal-fwd" cat /run/systemd/journal/syslog-relay' & - -# Start rsyslog (reads /etc/rsyslog.d/50-journal-forward.conf, writes to the pipe above) +# Logging pipeline: rsyslogd (RFC 5424) → /proc/1/fd/1 → Docker log capture. +# No intermediate pipe/relay — a named FIFO would be readable AND writable +# by any root-in-container process, letting an attacker either eavesdrop on +# the SIEM feed or inject forged log lines. rsyslogd # File-catcher: mirror attacker drops into host-mounted quarantine with attribution. diff --git a/tests/test_ssh.py b/tests/test_ssh.py index e6985b0..3a3dc6e 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -144,27 +144,25 @@ def test_dockerfile_prompt_command_logger(): assert "logger" in df -def test_entrypoint_creates_named_pipe(): - assert "mkfifo" in _entrypoint_text() - - -def test_entrypoint_relay_pipe_path_is_disguised(): +def test_entrypoint_has_no_named_pipe(): + # Named pipes in the container are a liability — readable and writable + # by any root process. The log bridge must not rely on one. ep = _entrypoint_text() - # Pipe lives under /run/systemd/journal/, not the obvious /var/run/decnet-logs. - assert "/run/systemd/journal/syslog-relay" in ep - assert "decnet-logs" not in ep + assert "mkfifo" not in ep + assert "syslog-relay" not in ep -def test_entrypoint_cat_relay_is_cloaked(): +def test_entrypoint_has_no_relay_cat(): + # No intermediate cat relay either (removed together with the pipe). ep = _entrypoint_text() - # `cat` is invoked via exec -a so ps shows systemd-journal-fwd. - assert "systemd-journal-fwd" in ep - assert "exec -a" in ep + assert "systemd-journal-fwd" not in ep -def test_dockerfile_rsyslog_uses_disguised_pipe(): +def test_dockerfile_rsyslog_targets_pid1_stdout(): df = _dockerfile_text() - assert "/run/systemd/journal/syslog-relay" in df + # rsyslog writes straight to /proc/1/fd/1 — no pipe file on disk. + assert "/proc/1/fd/1" in df + assert "syslog-relay" not in df assert "decnet-logs" not in df