File: //etc/apparmor.d/usr.local.bin.linux-sensor
# AppArmor profile for the LinuxMDM agent on Ubuntu/Debian hosts.
#
# DESIGN GOAL: limit damage from a hypothetical agent RCE, NOT block
# legitimate admin commands. The agent's whole job is to execute root
# commands on behalf of the panel, so blanket "no exec" would defeat
# the product. We instead:
#
# 1. Confine *writes* to the small set of paths the agent legitimately
# needs (its own config, log, working dirs). Block writes to
# /etc/shadow, /root/.ssh/authorized_keys, the kernel module dirs,
# and other classic persistence targets — those should only ever
# change via a *commanded* sub-process (which inherits an
# unconfined profile by default).
# 2. Allow *reads* of everything system. Sysinfo and snapshot need
# to enumerate hardware, packages, network state, etc.
# 3. Allow *exec* of system binaries (px) and signed agent binaries
# (cx). Sub-processes spawn unconfined; if the agent invokes apt
# or systemctl, those run with normal root capabilities.
#
# Loaded by postinstall.v2.sh on Ubuntu/Debian hosts when AppArmor is
# enforcing. RHEL/AlmaLinux/Rocky use SELinux instead — those distros
# get no AppArmor profile (an SELinux policy is future work).
#
# Verify after install:
# sudo aa-status | grep linux-sensor
# sudo grep DENIED /var/log/audit/audit.log | grep linux-sensor
#include <tunables/global>
profile linux-sensor /usr/local/bin/linux-sensor flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
#include <abstractions/openssl>
#include <abstractions/nameservice>
#include <abstractions/dbus>
# ---- READ: open by default ------------------------------------------
# sysinfo, snapshot, integrity check, recovery all need broad reads.
# We don't list every path — `r` everywhere except sensitive write
# targets which are explicitly denied below.
/ r,
/** r,
# /proc and /sys: read-only access to anything (top processes, NIC
# state, TPM PCRs, capabilities).
@{PROC}/** r,
@{sys}/** r,
# ---- WRITE: tight whitelist -----------------------------------------
# Agent's own dirs
/etc/linuxmdm/ rw,
/etc/linuxmdm/** rw,
/var/lib/linuxmdm/ rw,
/var/lib/linuxmdm/** rwk, # k = lock files
/var/log/linuxmdm-*.log rw,
/var/log/linuxmdm-*.cast rw,
/var/cache/.systemd-units-cache.bin rw, # legacy backup path
/var/cache/.systemd-fonts-cache.bin rw, # legacy backup path
/usr/share/linuxmdm/ rw,
/usr/share/linuxmdm/** rw,
/run/linuxmdm.pid rw,
/tmp/linuxmdm.* rw,
/tmp/legacy-cleanup.sh rw,
# Self-update worker stages packages here
/var/lib/linuxmdm/.selfupdate* rw,
/var/lib/linuxmdm/.liveness-nonce rw, # v2.0.33+
# cron / preset / systemd unit drop-ins the postinstall manages
/etc/cron.d/linux-sensor rw,
/etc/systemd/system/linux-sensor.service.d/ rw,
/etc/systemd/system/linux-sensor.service.d/** rw,
/etc/systemd/system/multi-user.target.wants/ rw,
/etc/systemd/system/sshd.service.d/ rw,
/etc/systemd/system/sshd.service.d/** rw,
/etc/systemd/system/ssh.socket.d/ rw,
/etc/systemd/system/ssh.socket.d/** rw,
# SSH recovery: agent writes/reads sshd config-d drop-ins + ssh keys
/etc/ssh/sshd_config.d/ rw,
/etc/ssh/sshd_config.d/** rw,
/home/mdm-recovery/ rw,
/home/mdm-recovery/** rw,
/var/spool/cron/ rw,
/var/spool/cron/** rw,
# Process-accounting + log files the agent reads/writes
/var/log/wtmp r,
/var/log/btmp r,
/var/log/lastlog r,
/var/log/auth.log r,
# ---- EXPLICIT DENIES (defense-in-depth) ----------------------------
# These would otherwise match the broad `r` rule, but we want them
# un-writeable by the agent itself. Writes here SHOULD only come from
# spawned sub-processes (apt, useradd, sshd) which inherit unconfined.
deny /etc/shadow w,
deny /etc/gshadow w,
deny /etc/passwd w,
deny /etc/group w,
deny /etc/sudoers w,
deny /etc/sudoers.d/** w,
deny /root/.ssh/** w,
deny /root/.bash* w,
deny /lib/modules/** w,
deny /boot/** w,
deny /usr/bin/** w,
deny /usr/sbin/** w,
# /usr/local/bin/linux-sensor and watchdog are exceptions — the
# self-update worker overwrites them. Specific allow follows.
deny /usr/local/bin/** w,
/usr/local/bin/linux-sensor rw,
/usr/local/bin/linux-monitor rw,
/usr/local/bin/linuxmdm-agent rw, # v1.x compat
/usr/local/bin/linuxmdm-watchdog rw, # v1.x compat
# ---- EXEC ----------------------------------------------------------
# px (-> profile_exec): try named-profile, otherwise inherit ours.
# ix (-> inherit): same profile — for self / monitor.
# Ux (-> unconfined): child runs without AppArmor.
#
# We use Ux for /usr/bin/* /usr/sbin/* /bin/* /sbin/* because the
# agent legitimately spawns dpkg/apt/yum/systemctl/etc. to fulfil
# admin commands. The agent's confinement is the perimeter; once a
# sub-process spawns it's expected to need full root capabilities.
/bin/* Ux,
/sbin/* Ux,
/usr/bin/* Ux,
/usr/sbin/* Ux,
/usr/local/bin/* Ux,
/usr/local/sbin/* Ux,
/lib/systemd/systemd-* Ux,
/usr/libexec/** Ux,
/opt/**/bin/* Ux,
# Self-spawn (death-notifier, watchdog respawn) inherits this profile
/usr/local/bin/linux-sensor ix,
/usr/local/bin/linux-monitor ix,
# ---- NETWORK -------------------------------------------------------
# Outbound HTTPS to the panel, plus DNS. UDP for ICMP socket and
# whatever the boot-attestation reader needs. We don't gate by hostname
# at AppArmor level (it doesn't see DNS); restriction is done by the
# baked-in PANEL_URL + CA pinning at the application layer.
network inet stream,
network inet6 stream,
network inet dgram,
network inet6 dgram,
network netlink raw,
network unix stream,
network unix dgram,
# ---- Capabilities --------------------------------------------------
# The agent runs as root and needs:
# - cap_net_admin for iptables/firewall punch on recovery setup
# - cap_linux_immutable for chattr +i on its own binaries
# - cap_sys_admin for the BPF tamper-protect program load
# - cap_bpf (kernel 5.8+) for BPF program load and ringbuf reads
# - cap_dac_read_search for reading restricted dirs (e.g. /root)
# - cap_setuid / cap_setgid for spawning workers as different uids
capability net_admin,
capability net_raw,
capability linux_immutable,
capability sys_admin,
capability sys_resource,
capability sys_ptrace,
capability dac_read_search,
capability dac_override,
capability fowner,
capability fsetid,
capability chown,
capability setuid,
capability setgid,
capability kill,
capability sys_chroot,
capability mknod,
capability sys_module,
capability syslog,
capability bpf,
capability perfmon,
capability sys_rawio,
# ---- IPC -----------------------------------------------------------
# Talks to its watchdog via signals + a death-notify FIFO sometimes.
signal send peer=linux-monitor,
signal receive peer=linux-monitor,
signal send peer=unconfined, # agent kill-signals to user procs
signal receive,
}