Skip to content

Latest commit

 

History

History
186 lines (151 loc) · 6.37 KB

File metadata and controls

186 lines (151 loc) · 6.37 KB

Spam reporter service

Systemd service for reporting received email as spam

Features

  • Runs as daemon
  • Natively Systemd-compliant
    • Runs as notify-type
  • Deploys in own Python venv to be immune of system changes
  • Asynchronous / event-driven
  • Secure:
    • SElinux-policy
    • Daemon uses Linux capabilities to restrict privileges
  • Configurable with separate file in /etc

D-Bus

Bus-types

For docs, see: https://dbus.freedesktop.org/doc/dbus-python/tutorial.html#connecting-to-the-bus

  • Per user SessionBus, dbus-send --session
  • Global SystemBus, dbus-send --system

Note: On a request, default is --session.

Install into --system

Policy install (as root):

  1. Copy file spamreporter-dbus.conf into directory /etc/dbus-1/system.d/
  2. Make policy change effective: systemctl reload dbus
  3. List available services:
    dbus-send \
      --system \
      --print-reply \
      --type=method_call \
      --dest=org.freedesktop.DBus \
      /org/freedesktop/DBus org.freedesktop.DBus.ListNames
    Response will contain published interface:
    method return time=123.456 sender=org.freedesktop.DBus -> destination=:1.1234 serial=3 reply_serial=2
    array [
      string "org.freedesktop.DBus"
      string "fi.hqcodeshop.SpamReporter"
      ...
    ]
    
  4. Verify published service details:
    busctl introspect fi.hqcodeshop.SpamReporter /fi/hqcodeshop/SpamReporter fi.hqcodeshop.SpamReporter
    Response will contain published interface:
    NAME                 TYPE      SIGNATURE RESULT/VALUE FLAGS
    .Ping                method    -         s            -
    .ReportFile          method    s         s            -
    
  5. Test service with a ping:
    dbus-send --print-reply \
      --system \
      --type=method_call \
      --dest=fi.hqcodeshop.SpamReporter \
      /fi/hqcodeshop/SpamReporter fi.hqcodeshop.SpamReporter.Ping
    Response will contain a greeting to the caller:
    method return time=1647618215.603226 sender=:1.250 -> destination=:1.252 serial=6 reply_serial=2
       string "Hi Joe User in system-bus! pong"
    
  6. Done!

Test ping --session

When service is running, see if D-Bus works.

Run:

dbus-send \
  --print-reply \
  --type=method_call \
  --dest=fi.hqcodeshop.SpamReporter \
  /fi/hqcodeshop/SpamReporter fi.hqcodeshop.SpamReporter.Ping

Response (based on user who sent the ping):

   string "Hi Joe Nobody! pong"

Test send email --system

When service is running, following command will report file named -FILENAME-HERE- as spam.

dbus-send \
  --system \
  --print-reply \
  --type=method_call \
  --dest=--dest=fi.hqcodeshop.SpamReporter \
  /fi/hqcodeshop/SpamReporter fi.hqcodeshop.SpamReporter.ReportFile "string:-FILENAME-HERE-"

Systemd

Docs: https://www.freedesktop.org/software/systemd/man/systemd.service.html

Notify-type daemon

For best results, this type of service runs as type notify. Practically this means, the Python code needs to ping Linux every 30 seconds as keep-alive pulse. Miss one ping and Linux will restart the service.

Pinging / notifying is done using asynchronous calls to reduce idle CPU-load to absolute minimum.

Capabilities

.service definition has following:

CapabilityBoundingSet=CAP_AUDIT_WRITE CAP_DAC_READ_SEARCH CAP_IPC_LOCK CAP_SYS_NICE

man 7 capabilities @ https://man7.org/linux/man-pages/man7/capabilities.7.html

  • CAP_AUDIT_WRITE (since Linux 2.6.11)
    • Write records to kernel auditing log.
  • CAP_DAC_READ_SEARCH
    • Bypass file read permission checks and directory read and execute permission checks;
    • invoke open_by_handle_at(2);
    • use the linkat(2) AT_EMPTY_PATH flag to create a link to a file referred to by a file descriptor.
  • CAP_IPC_LOCK
    • Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2));
    • Allocate memory using huge pages (memfd_create(2), mmap(2), shmctl(2)).
  • CAP_SYS_NICE
    • Lower the process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes;
    • set real-time scheduling policies for calling process, and set scheduling policies and priorities for arbitrary processes (sched_setscheduler(2), sched_setparam(2), sched_setattr(2));
    • set CPU affinity for arbitrary processes (sched_setaffinity(2));
    • set I/O scheduling class and priority for arbitrary processes (ioprio_set(2));
    • apply migrate_pages(2) to arbitrary processes and allow processes to be migrated to arbitrary nodes;
    • apply move_pages(2) to arbitrary processes;
    • use the MPOL_MF_MOVE_ALL flag with mbind(2) and move_pages(2).

UID / GID of the service

Service is run as root.

When running as non-root, capability CAP_DAC_READ_SEARCH does allow reading of other users' files, but doesn't allow traversing directories or querying for file existence. Thus, basic operation of this daemon is unavailable.

For security reasons, it would be sensible for daemon to run as non-root, however, there doesn't seem to be suitable capability to allow iterating directory contents or traversing filesystem while ignoring permissions. Such functionality is critical, feasible options for non-root operation are lacking, so daemon runs as root.

D-Bus

When running system D-Bus, it is possible only as root. For security reasons it would be advisable to run as non-root, but in D-Bus that is not possible.

Checking effective capabilities of the service

  1. Find PID, systemctl status spammer-reporter
  2. Run command: gawk '/^CapEff/ {print $2}' /proc/<PID-OF-PROCESS>/status | xargs -n1 -I {} capsh --decode={}
  3. Expected output: 0x0000000020804004=cap_dac_read_search,cap_ipc_lock,cap_sys_nice,cap_audit_write

Note: See man-page for definitions of: Permitted, Inheritable, Effective, Bounding and Ambient capability sets.

SElinux

See subdirectory SElinux/ for details.

Test run service

  • Use SElinux-tool runcon. Set system_u:system_r:spammerblock_t:s0 as security context.
runcon system_u:system_r:spammerblock_t:s0 \
  /usr/libexec/spammer-block/bin/python \
  spammer_block_commands/reporter_service.py \
  system \
  --config /etc/sysconfig/spammer-reporter.toml

Temporarily disable all don't audit -rules

semodule -DB

Now all possible deny-rules are logged and can be traced with audit2allow.