Skip to content

fix(log): su syslog root#4370

Closed
MichaelSp wants to merge 2 commits intodocker-mailserver:masterfrom
MichaelSp:patch-1
Closed

fix(log): su syslog root#4370
MichaelSp wants to merge 2 commits intodocker-mailserver:masterfrom
MichaelSp:patch-1

Conversation

@MichaelSp
Copy link
Copy Markdown
Contributor

@MichaelSp MichaelSp commented Feb 16, 2025

Description

This addresses an issue raised
docker-mailserver/docker-mailserver-helm#137

Line 10 enforces permissions of the /var/log/mail folder to be syslog:root. As described in the issue, in my helm-based setup this causes the following warning

/etc/cron.daily/logrotate:
error: skipping "/var/log/mail/mail.log" because parent directory has insecure permissions (It's world writable or writable by group which is not "root") Set "su" directive in config file to tell logrotate which user/group should be used for rotation.
error: skipping "/var/log/mail/rspamd.log" because parent directory has insecure permissions (It's world writable or writable by group which is not "root") Set "su" directive in config file to tell logrotate which user/group should be used for rotation.
run-parts: /etc/cron.daily/logrotate exited with return code 1

The proposed change will implement what the warning suggests.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Improvement (non-breaking change that does improve existing functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (README.md or the documentation under docs/)
  • If necessary, I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have added information about changes made in this PR to CHANGELOG.md

@georglauterbach
Copy link
Copy Markdown
Member

@casperklein @polarathene should we cram this into v15.0.0?

@casperklein
Copy link
Copy Markdown
Member

If we merge this, this can be in v15 👍


I wonder, what the initial motivation for syslog:root was:

chown syslog:root /var/log/mail


It's world writable or writable by group which is not "root")

root@mail:/# ls -ahld /var/log/mail
drwxr-xr-x 2 syslog root 4.0K Feb  5 04:49 /var/log/mail

It's neither world writeable nor writable by group which is not "root" 🤔

@georglauterbach georglauterbach requested review from casperklein and polarathene and removed request for polarathene February 16, 2025 22:15
@georglauterbach
Copy link
Copy Markdown
Member

Please verify that we do not accidentally include a potential regression with this change right before the v15.0.0 release. I do not know enough about the permissions at the moment, hence I am not sure whether the actual permissions need a change or whether this PR's script change is accurate.

@polarathene
Copy link
Copy Markdown
Member

polarathene commented Feb 16, 2025

TL;DR:

  • This verbose comment is for my benefit for technical details / historical context of how we ended up here 😅
  • Skip to the next comment for actionable feedback.

I wonder, what the initial motivation for syslog:root was

Unclear, it was committed to the Dockerfile in April 2016, the only context is a reference to the DMS v2 tracking issue but nothing in that issue thread or linked commit provides any context.


EDIT: Assumption is rsyslog runs as the syslog user (unexpected if we explicitly need to create a syslog user for it to function though), which if true answers the ownership requirement for /var/log/mail which is our own centralized log dir for DMS (prior to introducing supervisor, but both are required atm AFAIK). root group ownership is likely to infer that nothing else needs access via group permissions.

Additionally when deploying a container as rootless there is a common pattern to only change the runtime user while keeping a root group (docker run --user 1000:0), but that isn't too relevant here, definitely wasn't when this was implemented.

You could also use SGID bit IIRC to have any service with write permission to a directory to enforce files created are assigned the common parent directory group.


It's neither world writeable nor writable by group which is not "root" 🤔

That'd depend on the context. /var/log/mail is a location DMS has volume mounted, which like /var/mail means a bind mount replaces all ownership/permissions assigned at the image (hence why these runtime fixes came about, and why this type of thing belongs in mail-state.sh (setup-stack.sh, see below) as we'd skip these kind of fixes during container restarts otherwise).

For /var/mail this removes the SGID bit that Debian otherwise has on the folder, I don't think we restore that since I identified it (I have an open issue with all the details regarding this concern and related issues to resolve).

As the linked bug report shows, their bind mount volume has modified permissions to 777. That or it's a k8s / helm related config difference for how the volume mount is handled. Perhaps that's why the bug report was raised at the helm repo and not here?

Related history / context

We do have this other related change in the Dockerfile:

# no syslog user in Debian compared to Ubuntu
adduser --system syslog

That was introduced in Jan 2018 during the base image swap from Ubuntu 16.04 to Debian 9 Stretch (PR):

rsyslog does not add syslog user and has different conf-structure

While this runtime snippet:

https://github.com/docker-mailserver/docker-mailserver/blame/1a938dfb15e94f43f1af5ddaf7b8276aba92a825/target/scripts/startup/setup.d/log.sh#L3-L11

Originates from Oct 2019 (associated issue + PR). Basically belongs in mail-state.sh AFAIK (setup-stack.sh, see below).

The Oct 2019 issue was specifically about /var/log/mail/clamav.log failing, and expecting clamav:clamav permissions, although the PR/commit is clamav:adm for the clamav.log and freshclam.log files, along with syslog:root for /var/log/mail.

Not sure if we're actually leveraging the supervisor ClamAV logs for anything:

[program:clamav]
startsecs=0
stopwaitsecs=55
autostart=false
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/usr/sbin/clamd -c /etc/clamav/clamd.conf

The two separate ClamAV logs are configured only via the Dockerfile at build (?), probably should be extracted out to a script 🤷‍♂️ (even if that's only run during build-time)

touch /var/log/mail/clamav.log
chown -R clamav:root /var/log/mail/clamav.log
touch /var/log/mail/freshclam.log
chown -R clamav:root /var/log/mail/freshclam.log
sedfile -i -r 's|/var/log/mail|/var/log/mail/mail|g' /etc/rsyslog.conf
sedfile -i -r 's|;auth,authpriv.none|;mail.none;mail.error;auth,authpriv.none|g' /etc/rsyslog.conf
sedfile -i -r 's|LogFile /var/log/clamav/|LogFile /var/log/mail/|g' /etc/clamav/clamd.conf
sedfile -i -r 's|UpdateLogFile /var/log/clamav/|UpdateLogFile /var/log/mail/|g' /etc/clamav/freshclam.conf
sedfile -i -r 's|/var/log/clamav|/var/log/mail|g' /etc/logrotate.d/clamav-daemon
sedfile -i -r 's|invoke-rc.d.*|/usr/bin/supervisorctl signal hup clamav >/dev/null \|\| true|g' /etc/logrotate.d/clamav-daemon
sedfile -i -r 's|/var/log/clamav|/var/log/mail|g' /etc/logrotate.d/clamav-freshclam
sedfile -i -r '/postrotate/,/endscript/d' /etc/logrotate.d/clamav-freshclam
sedfile -i -r 's|/var/log/mail|/var/log/mail/mail|g' /etc/logrotate.d/rsyslog
sedfile -i -r '/\/var\/log\/mail\/mail.log/d' /etc/logrotate.d/rsyslog

Even with syslog, we have rsyslog run via supervisord. I haven't looked for config for this, presumably something has it run as the syslog user to enforce that requirement, root ownership is likely a side-effect of just not implying a need access by anything else via group:

[program:rsyslog]
startsecs=0
stopwaitsecs=55
autostart=false
autorestart=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
command=/usr/sbin/rsyslogd -n


Probably should address for DMS v15

DMS v12 release split the runtime fix for /var/log/mail + separate ClamAV log files, so now we have as of DMS v15 (unless changed before release),

local FILE
for FILE in /var/log/mail/{clamav,freshclam}.log; do
touch "${FILE}"
chown clamav:adm "${FILE}"
chmod 640 "${FILE}"
done

function _setup_logs_general() {
_log 'debug' 'Setting up general log files'
# File/folder permissions are fine when using docker volumes, but may be wrong
# when file system folders are mounted into the container.
# Set the expected values and create missing folders/files just in case.
mkdir -p /var/log/{mail,supervisor}
chown syslog:root /var/log/mail
}

Until we have a better location, the current location for such fixes is grouped into this method:

function _setup_directory_and_file_permissions() {
_log 'trace' 'Removing leftover PID files from a stop/start'
find /var/run/ -not -name 'supervisord.pid' -name '*.pid' -delete
touch /dev/shm/supervisor.sock
_log 'debug' 'Checking /var/mail permissions'
if ! _chown_var_mail_if_necessary; then
_dms_panic__general 'Failed to fix /var/mail permissions'
fi
_log 'debug' 'Removing files and directories from older versions'
rm -rf /var/mail-state/spool-postfix/{dev,etc,lib,pid,usr,private/auth}
_rspamd_get_envs
# /tmp/docker-mailserver/rspamd/dkim
if [[ -d ${RSPAMD_DMS_DKIM_D} ]]; then
_log 'debug' "Ensuring '${RSPAMD_DMS_DKIM_D}' is owned by '_rspamd:_rspamd'"
chown -R _rspamd:_rspamd "${RSPAMD_DMS_DKIM_D}"
fi
}

DMS v15 correctly runs that even for container restarts so we could move these changes into that 👍


Will eventually improve

I've covered extensive info on our current logging situation in the past (see the "What are you going to contribute?" section)

  • April 2016 (DMS v2) centralized services logs to live at /var/log/mail to pair with a volume mount for persistence.
  • August 2017 introduced Supervisord, and over time more services were properly configured to manage logs through stdout to collect service logs at a different location, while remaining syslog output is collected in our central mail log that the container outputs to stdout (for Docker's own log drivers to leverage), and our tests rely on this mail log file.

That'll get tackled once I've found time to return to the Vector syslog PR. With Vector we can better manage ingesting log files, syslog, etc and output to centralized logs for individual services or other formats for structured logs, etc.

@polarathene
Copy link
Copy Markdown
Member

polarathene commented Feb 16, 2025

UPDATE:

  • Advice is to apply the interim fix this comment suggests.
  • Pretty sure I grok the situation and history fully (well 99%), as detailed in my next comment.

should we cram this into v15.0.0?

I'm not sure if it makes sense to merge this PR, like @casperklein pointed out it doesn't look like this would be reproduced unless the user has modified permissions for their volume bind mount.

I don't know how likely that should be needed to support or expect. If this concern doesn't occur with our defaults, then we could just make the changes I mention in "Probably should address for DMS v15" section in my previous comment, which can add another line to reset permissions for /var/log/mail/ to the expected 755 (rwx rx rx)?


docker-mailserver/docker-mailserver-helm#137 (comment) mentions the helm / k8s config (sorry not too familiar with that) is a limitation for managing this, I'm not sure how compatible the fix I propose would be with the helm project, if someone can confirm it's compatible that'd be great 👍

@casperklein
Copy link
Copy Markdown
Member

EDIT: Assumption is rsyslog runs as the syslog user

rsyslogd runs as root:

root@mail:/# ps aux|grep rsyslogd
root      2332  0.0  0.0 152120  2936 ?        Sl   Feb16   0:01 /usr/sbin/rsyslogd -n

@polarathene
Copy link
Copy Markdown
Member

polarathene commented Feb 17, 2025

TL;DR: Ok I think I got the gist of it now 💪

  • syslog user and related chown appears redundant, should be safe to remove 👍
  • ClamAV related commands also should be fine to remove, given the fix at image build to ensure a fixed UID/GID (avoiding mapping mismatch when order of packages installed changes the incremental ID that'd otherwise have been assigned).

Am I confident in avoiding a breaking change? No. Technically we should also ensure ownership is changed away from syslog once removing that too, not that it should matter in practice. I'd need more time to go over everything I've documented here and verify, so perhaps best to delay that.

Earlier suggestion as an interim fix is still valid for DMS v15.


Summary:

  • syslog ownership appears to be inherited from the Ubuntu to Debian base image switch.
    • This seems redundant and not actually required anymore. It's inclusion at runtime was with resolving the ClamAV ownership errors and not questioned/explained during review.
    • Base image upgrade from Ubuntu to Debian adding the user syslog as a fix would only have been to support the later chown command from DMS v2, not questioned during review.
    • DMS v2 would have likely added the chown for syslog for similar reason as explained below, a compatibility fix for volume mounts where the friendly user/group names map to different UID/GID between distro. Ubuntu potentially had rsyslog configured to use the syslog user, unlike with Debian.
  • Ownership fixes were for ClamAV from what I can tell shouldn't have been needed
    • Likely occurred due to volume mount and an upgrade as this would have been before I introduced a fixed UID/GID for clamav, hence a mismatch during image upgrade.
    • ClamAV configs have the user clamav configured, and only a clamav group for the ClamAV socket. Thus correcting the owner of the log files at container startup was a fix that should no longer be necessary.

rsyslogd runs as root

It may still be configured somewhere to write as syslog?

Initial /var/log/mail/mail.log setup (Dockerfile adjusts rsyslog config to write syslog mail filtered logs to this file, our test suite checks this too):

# Container logs will receive updates from this log file:
MAIN_LOGFILE=/var/log/mail/mail.log
# NOTE: rsyslogd would usually create this later during `_start_daemons`, however it would already exist if the container was restarted.
touch "${MAIN_LOGFILE}"
# Ensure `tail` follows the correct position of the log file for this container start (new logs begin once `_start_daemons` is called)
TAIL_START=$(( $(wc -l < "${MAIN_LOGFILE}") + 1 ))
[[ ${LOG_LEVEL} =~ (debug|trace) ]] && print-environment
_start_daemons
# Container start-up scripts completed. `tail` will now pipe the log updates to stdout:
_log 'info' "${HOSTNAME} is up and running"
exec tail -Fn "+${TAIL_START}" "${MAIN_LOGFILE}"

Symlink created to /var/log/mail.log:

# -----------------------------------------------
# --- Fail2Ban, DKIM & DMARC --------------------
# -----------------------------------------------
COPY target/fail2ban/jail.local /etc/fail2ban/jail.local
COPY target/fail2ban/fail2ban.d/fixes.local /etc/fail2ban/fail2ban.d/fixes.local
RUN <<EOF
ln -s /var/log/mail/mail.log /var/log/mail.log
ln -sf /var/log/mail/fail2ban.log /var/log/fail2ban.log
EOF


rsyslog package

Installing the package in debian:12-slim image creates /etc/rsyslog.conf with these config snippets:

###########################
#### GLOBAL DIRECTIVES ####
###########################

#
# Set the default permissions for all log files.
#
$FileOwner root
$FileGroup adm
$FileCreateMode 0640
$DirCreateMode 0755
$Umask 0022

#
# Where to place spool and state files
#
$WorkDirectory /var/spool/rsyslog

#
# Include all config files in /etc/rsyslog.d/
#
$IncludeConfig /etc/rsyslog.d/*.conf
###############
#### RULES ####
###############

#
# Log anything besides private authentication messages to a single log file
#
*.*;auth,authpriv.none          -/var/log/syslog

#
# Log commonly used facilities to their own log file
#
auth,authpriv.*                 /var/log/auth.log
cron.*                          -/var/log/cron.log
kern.*                          -/var/log/kern.log
mail.*                          -/var/log/mail.log
user.*                          -/var/log/user.log

#
# Emergencies are sent to everybody logged in.
#
*.emerg                         :omusrmsg:*
#################
#### MODULES ####
#################

module(load="imuxsock") # provides support for local system logging
module(load="imklog")   # provides kernel logging support
#module(load="immark")  # provides --MARK-- message capability

# provides UDP syslog reception
#module(load="imudp")
#input(type="imudp" port="514")

# provides TCP syslog reception
#module(load="imtcp")
#input(type="imtcp" port="514")

DMS Dockerfile changes to that config:

sedfile -i -r 's|/var/log/mail|/var/log/mail/mail|g' /etc/rsyslog.conf
sedfile -i -r 's|;auth,authpriv.none|;mail.none;mail.error;auth,authpriv.none|g' /etc/rsyslog.conf

# prevent syslog warning about imklog permissions
sedfile -i -e 's/^module(load=\"imklog\")/#module(load=\"imklog\")/' /etc/rsyslog.conf

mkdir /var/log/mail
chown syslog:root /var/log/mail

Observations

  • mail-state.sh doesn't have any handling for persisting rsyslog state ($WorkDirectory /var/spool/rsyslog)
  • We have rsyslog run as root via supervisord service, and the original rsyslog config /etc/rsyslog.conf shown above runs the process as root:adm with permissions for files/dirs configured fine, while /etc/rsyslog.d/ is empty.
  • We modify rsyslog config to:
    • route syslog entries received marked with the mail facility from /var/log/mail.log (DMS makes this a symlink instead) to /var/log/mail/mail.log
    • Alter the filter for /var/log/syslog to prepend mail.none;mail.error (I forget this syntax but I've documented it somewhere in our issues 😅)
    • Disable the kernel logging since we're running in a container, not a host/system syslog process where that'd be relevant.

@polarathene
Copy link
Copy Markdown
Member

I have applied the interim fix I suggested: #4374

I do not know if that'll resolve the issue with k8s / helm but I don't think the proposed solution from this PR is the right fix? (especially since we'd be going back to root ownership in future)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants