Skip to content

SELinux & AppArmor

Most Linux distributions ship a mandatory access control (MAC) layer that runs on top of normal file permissions. On Red Hat–family systems that layer is SELinux; on Debian and Ubuntu it is AppArmor. In their default modes they can block a freshly installed service from binding a port, connecting to a database, or writing to a directory — even when the file ownership looks correct.

This page covers the rare cases where the MAC layer interferes with a native package install (the .deb/.rpm deployment managed by systemd). Docker deployments are covered briefly at the end.

When you need this page

A working install needs nothing here. Reach for this page only if, after install, the API will not bind its port, the monitor cannot reach the database, or a service cannot write to its working directories — and the logs point at the security layer (AVC / apparmor= denials) rather than at configuration.

LiMon's native package runs as the dedicated limon user and keeps everything under /opt/limon:

Path Purpose
/opt/limon/src Service working directory
/opt/limon/venv/bin Python interpreter and Gunicorn
/opt/limon/config config.yml and generated DB credentials
/opt/limon/commands API command drop directory (api.command_dir)
/opt/limon/reports Generated reports and Prometheus files
/opt/limon/tools Vendor binaries (lmutil, rlmutil, lmxendutil, DSLicSrv)

The API binds 8585 by default and connects to MariaDB/MySQL on 3306.

SELinux (RHEL, CentOS, Rocky, Alma)

When SELinux is in enforcing mode, the default policy can block the LiMon services. The most reliable fix is to run the services once in permissive mode, collect the real denials from the audit log, and compile a custom policy module from exactly those denials.

Step 1 — Install the troubleshooting tools

sudo dnf install setroubleshoot-server audit policycoreutils-python-utils

audit2allow and semanage ship in policycoreutils-python-utils on current releases (older systems used policycoreutils-devel).

Step 2 — Switch to permissive mode temporarily

In permissive mode, violations are logged but not blocked, so the services can run while you gather every denial. This resets on reboot.

sudo setenforce 0

Step 3 — Start the services and exercise every action

sudo systemctl start limon-monitor.service
sudo systemctl start limon-api.service

Let it run through a full polling cycle (10–15 minutes) so every code path executes. Triggering a manual poll through the API exercises the command-directory and report paths too:

curl -X POST http://localhost:8585/api/v1/admin/command/force_poll \
  -H "X-API-KEY: <your-admin-api-key>" -d '{}'

Step 4 — Generate a custom policy module

Build the module from the limon denials in the audit log:

# Review the rules that would be generated (human-readable .te output)
sudo grep limon /var/log/audit/audit.log | audit2allow -m limonlocal

# If the rules look correct, compile and install the module
sudo grep limon /var/log/audit/audit.log | audit2allow -M limonlocal

The second command writes limonlocal.te (readable) and limonlocal.pp (compiled) and loads the compiled module into the kernel. A typical rule grants the service the ability to connect to the database port:

module limonlocal 1.0;
require {
    type unconfined_service_t;
    type mysqld_port_t;
    class tcp_socket name_connect;
}
allow unconfined_service_t mysqld_port_t:tcp_socket name_connect;

Step 5 — Return to enforcing mode

sudo setenforce 1

To make enforcing mode permanent across reboots, ensure SELINUX=enforcing in /etc/selinux/config. The services should now run with the custom module loaded.

Common manual fixes

If you prefer to apply the most common policies by hand instead of generating a module, the following cover the usual denials. Generating a module with audit2allow is still the more robust route because it captures denials specific to your environment.

# Allow the API to bind a non-standard port (default 8585).
# Port labeling is the fix that actually matters for most installs.
sudo semanage port -a -t http_port_t -p tcp 8585

LiMon's systemd units run in the unconfined unconfined_service_t domain, so outbound database connections and writes to /opt/limon are not restricted — port labeling above is normally the only manual fix needed.

If you have confined LiMon under a custom domain, allow the DB connection in that domain's policy and label persistent paths (such as the command directory) with an appropriate persistent type for that domain:

# Only when running LiMon under a custom confined domain:
# sudo semanage fcontext -a -t <limon_command_t> "/opt/limon/commands(/.*)?"
# sudo restorecon -Rv /opt/limon/commands

Read denials in plain language

sudo sealert -a /var/log/audit/audit.log summarizes recent denials and often suggests the exact semanage/setsebool command to run.

AppArmor (Debian and Ubuntu)

AppArmor confines a process only when a profile is loaded and attached to that process's executable path. LiMon ships no AppArmor profile, and its interpreter (/opt/limon/venv/bin/python) and Gunicorn do not match any profile shipped by Debian or Ubuntu. As a result the LiMon services normally run unconfined under AppArmor, and a standard install needs no AppArmor changes.

The one place AppArmor can still bite is a local MariaDB/MySQL install: the database ships its own mysqld profile. If you relocate the data directory or sockets, that profile — not LiMon — is what blocks the database, and the fix belongs to the database profile.

Diagnose

# Show loaded profiles and which mode each is in
sudo aa-status

# Look for AppArmor denials (DENIED, apparmor="DENIED")
sudo dmesg | grep -i apparmor
sudo journalctl -k | grep -i apparmor
# On systems with auditd:
sudo grep apparmor /var/log/audit/audit.log

A denial line names the offending profile= and the name= it tried to access — that tells you which profile to adjust.

Relax or fix a profile

If a profile (for example mysqld) is blocking a path LiMon depends on, put it in complain mode while you confirm the cause, then add a permanent local override:

# Install the helper tools if needed
sudo apt install apparmor-utils

# Put a profile into complain mode (logs instead of blocks)
sudo aa-complain /usr/sbin/mariadbd     # or /usr/sbin/mysqld

# After confirming the access is legitimate, add a local override and reload
echo '  /opt/limon/** rwk,' | sudo tee -a /etc/apparmor.d/local/usr.sbin.mariadbd
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mariadbd

# Return the profile to enforcing once the override is in place
sudo aa-enforce /usr/sbin/mariadbd

Edit the local file, not the shipped profile

Add rules under /etc/apparmor.d/local/ rather than editing the distribution's profile directly, so package updates don't overwrite your changes. The exact profile filename (usr.sbin.mariadbd vs usr.sbin.mysqld) depends on your database package.

Docker deployments

The Docker stack carries its own runtime confinement, so host-level policy rarely needs tuning:

  • SELinux: Docker labels container processes with container_t and isolates them. If you bind-mount a host directory into a LiMon container and hit denials, add the :z (shared) or :Z (private) suffix to the volume mount so Docker relabels it, e.g. -v /host/path:/opt/limon/reports:z.
  • AppArmor: containers run under Docker's default docker-default profile. You normally leave it in place; override it per container only if a denial points at it.

See also