sec: AppArmor profile skeletons + audit shipping draft + veilor-firstboot SELinux module (#3)

Co-authored-by: veilor-org <admin@veilor.org>
This commit is contained in:
s8n 2026-05-02 04:39:39 +01:00 committed by GitHub
parent b1976ccb4e
commit 1c99ae081f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 609 additions and 5 deletions

View file

@ -41,6 +41,21 @@ kickstart `%post` or the overlay tree shipped in `/etc`.
`sys_admin` and `perfmon` capabilities required by the modules-lock
service. Source: `scripts/selinux/veilor-systemd.te`.
### veilor-firstboot SELinux confinement
The first-boot password service is privileged (it has to write
`/etc/shadow`) but small. Module `veilor-firstboot` carves a tight domain:
- Allowed: read `/etc/passwd`, exec `passwd(1)`, write
`/var/lib/veilor-firstboot.done`, write `/etc/sddm.conf.d/`,
start `sddm.service`.
- `neverallow` rules block: network sockets (no phone-home),
`home_root_t` / `user_home_t` access, `sys_module`, `sys_ptrace`,
`sys_rawio`.
Source: `scripts/selinux/veilor-firstboot.te`. Build & load with
`scripts/selinux/build-policy.sh` (loads all modules in one pass).
## Network surface
- **firewalld** default zone = `drop`.
@ -119,6 +134,57 @@ sudo usbguard allow-device <id>
`bluetooth`, `ModemManager`, `gssproxy`, `atd`, `pcscd.socket`,
`pcscd.service`, `kdeconnectd` (removed at package level).
## AppArmor (v0.5)
Fedora 43 ships AppArmor alongside SELinux. veilor-os keeps SELinux as the
primary MAC layer (enforcing, targeted) but ships AppArmor profile
skeletons for high-risk userland binaries that benefit from a second,
binary-scoped policy on top of SELinux's role-based one.
Profiles live in `scripts/apparmor/`:
| Profile | Target | Default mode |
|---------|--------|--------------|
| `usr.bin.thorium` | Thorium browser | `complain` |
| `usr.local.bin.lm-studio` | LM Studio LLM runner | `complain` |
| `usr.bin.veilor-power` | Power profile switcher | `enforce` |
Profiles are **not** loaded automatically — they are opt-in until v0.5.
Enable a profile post-install with:
```bash
sudo dnf install apparmor-utils apparmor-parser
sudo install -m 0644 scripts/apparmor/usr.bin.thorium /etc/apparmor.d/
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.thorium
sudo aa-complain /etc/apparmor.d/usr.bin.thorium # log only
sudo aa-enforce /etc/apparmor.d/usr.bin.thorium # block
```
Refine `complain`-mode profiles with `aa-logprof` after exercising the
app through normal use; it converts logged denials into rule additions
interactively.
## Audit log shipping (optional)
Local journald is the default audit sink. For off-device shipping to a
trusted log collector (Loki / Wazuh / Splunk), veilor-os ships a
disabled-by-default plugin template:
- `/etc/audit/plugins.d/veilor-remote.conf` — auditd plugin shim
(set `active = yes` to enable).
- `/etc/audisp/audisp-remote.conf.disabled` — audisp-remote target
config template (rename to `audisp-remote.conf` and edit
`remote_server` to enable).
**Warning:** enabling remote audit shipping leaks every privileged syscall,
file-watch hit, and auth event off-device. Treat the collector as a host
with the same trust level as root. Only enable if the collector itself is
hardened and the transport is TLS or kerberized.
Reference integration paths in the template: Loki via promtail/vector
syslog source, Wazuh via local wazuh-agent (no network shipping needed),
Splunk via HEC bridge.
## What's *not* enabled by default
- **Disk swap** — replaced by zram (RAM-only, no key leak risk).

View file

@ -0,0 +1,58 @@
# veilor-os audisp-remote configuration template (DISABLED by default)
#
# IMPORTANT: enabling remote audit shipping leaks security events off-device.
# Only enable if you have a trusted log collector — the remote endpoint
# will receive every privileged syscall, file-watch hit, auth event, and
# sudoers/SSH config change recorded by auditd.
#
# To activate:
# 1. Set veilor-remote.conf `active = yes` (in /etc/audit/plugins.d/).
# 2. Copy this file to /etc/audisp/audisp-remote.conf (drop `.disabled`).
# 3. Edit `remote_server` + TLS settings below.
# 4. systemctl restart auditd
#
# Loki / Wazuh / Splunk integration paths:
#
# Loki - point remote_server at a syslog-to-Loki shim (promtail or
# vector with `syslog` source, format = "rfc5424"). Use TCP+TLS.
# Wazuh - run wazuh-agent locally; it pulls /var/log/audit/audit.log
# directly. In that case leave remote_server empty and rely on
# wazuh-agent's filebeat-style tailer instead of audisp-remote.
# Splunk - use a Splunk HEC bridge (rsyslog-omhttp or vector http sink).
# audisp-remote speaks plain syslog/TLS; it does not speak HEC
# natively.
# ---- transport ----
remote_server = logs.example.org
port = 60
transport = tcp # plain | tcp | krb5
queue_file = /var/spool/audit/remote.log
mode = immediate # immediate | forwarding
queue_depth = 10240
format = managed # managed | ascii
# ---- TLS (transport = tcp + use_libwrap=no recommended) ----
enable_krb5 = no
krb5_principal =
krb5_client_name = auditd
krb5_key_file = /etc/audit/audit.key
# ---- failure handling ----
network_failure_action = stop # ignore | syslog | exec | suspend | single | halt | stop
disk_low_action = syslog
disk_full_action = syslog
disk_error_action = syslog
remote_ending_action = reconnect
generic_error_action = syslog
generic_warning_action = syslog
overflow_action = syslog
# ---- heartbeat ----
heartbeat_timeout = 60
network_retry_time = 1
max_tries_per_record = 3
max_time_per_record = 5
# ---- formatting ----
# `managed` wraps each event in a syslog-RFC5424 header with veilor-os
# hostname + audit facility (LOG_AUTHPRIV). Loki/Splunk prefer this.

View file

@ -0,0 +1,23 @@
# veilor-os audit remote shipping (DISABLED by default)
#
# IMPORTANT: enabling remote audit shipping leaks security events off-device.
# Only enable if you have a trusted log collector (Loki / Wazuh / Splunk).
# The remote endpoint will see every privileged syscall, file watch hit,
# auth event, and sudoers change. Treat the collector with the same trust
# level as the host root account.
#
# Enable:
# 1. Edit `active = yes` below.
# 2. Configure /etc/audisp/audisp-remote.conf (see audisp-remote.conf.disabled).
# 3. systemctl restart auditd.
# 4. Verify with: auditctl -s | grep enabled
#
# Plugin pipes audit events out of auditd via a UNIX socket; audisp-remote
# reads from that socket and forwards to the configured remote_server.
active = no
direction = out
path = builtin_af_unix
type = builtin
args = /var/run/audit_events
format = string

View file

@ -0,0 +1,114 @@
# veilor-os AppArmor profile — Thorium browser (Chromium fork)
#
# Scope:
# Confine the Thorium browser binary at /usr/bin/thorium. Thorium is a
# Chromium derivative; it sandboxes its own renderer/GPU/utility processes,
# but the *browser* process itself runs with the full user's permissions
# unless an MAC layer scopes it down. This profile is that scope.
#
# Mode:
# complain — log violations to audit.log but do NOT block. This is the
# first-fit profile; the user is expected to refine it from observed
# denials before flipping to enforce. See `aa-logprof` to convert audit
# denials into rule additions.
#
# Manual enable:
# sudo install -m 0644 scripts/apparmor/usr.bin.thorium /etc/apparmor.d/
# sudo apparmor_parser -r /etc/apparmor.d/usr.bin.thorium
# sudo aa-complain /etc/apparmor.d/usr.bin.thorium # log only
# sudo aa-enforce /etc/apparmor.d/usr.bin.thorium # block
#
# NOT enabled in kickstart by default. v0.5 work.
#include <tunables/global>
profile thorium /usr/bin/thorium flags=(complain) {
#include <abstractions/base>
#include <abstractions/audio>
#include <abstractions/dbus-session>
#include <abstractions/fonts>
#include <abstractions/freedesktop.org>
#include <abstractions/gnome>
#include <abstractions/nameservice>
#include <abstractions/openssl>
#include <abstractions/X>
# ---- network: outbound HTTP/HTTPS only ----
network inet stream,
network inet6 stream,
network inet dgram, # DNS resolution
network inet6 dgram,
network netlink raw, # NetworkManager state queries
deny network raw,
deny network packet,
deny network bluetooth,
deny network can,
deny network rds,
deny network sctp,
# ---- binary + libs ----
/usr/bin/thorium mr,
/usr/lib/thorium/** mr,
/usr/share/thorium/** r,
/opt/thorium/** mr,
/etc/thorium/** r,
# ---- per-user state ----
owner @{HOME}/.config/thorium/** rwk,
owner @{HOME}/.cache/thorium/** rwk,
owner @{HOME}/.local/share/thorium/** rwk,
# ---- file pickers: only Downloads is writable ----
owner @{HOME}/Downloads/ rw,
owner @{HOME}/Downloads/** rwk,
owner @{HOME}/Documents/ r,
owner @{HOME}/Documents/** r,
owner @{HOME}/Pictures/ r,
owner @{HOME}/Pictures/** r,
# ---- /proc: own process only, deny memory peeking ----
owner /proc/@{pid}/** r,
deny /proc/*/mem rwk,
deny /proc/*/maps r,
deny /proc/sys/kernel/** w,
# ---- ptrace: forbidden ----
deny ptrace,
deny capability sys_ptrace,
# ---- kernel: no module load, no /dev/kmem, no /dev/mem ----
deny capability sys_module,
deny /dev/kmem rwk,
deny /dev/mem rwk,
deny /dev/port rwk,
deny /sys/kernel/** w,
# ---- temp ----
/tmp/ r,
owner /tmp/** rwk,
/var/tmp/ r,
owner /var/tmp/** rwk,
# ---- system info read-only ----
/etc/machine-id r,
/etc/os-release r,
/etc/localtime r,
/sys/devices/system/cpu/** r,
/sys/class/net/** r,
# ---- chrome sandbox helper (setuid/SUID-like child needs unconfined) ----
/usr/lib/thorium/chrome-sandbox Cx -> sandbox,
/usr/bin/xdg-open Pix,
profile sandbox {
#include <abstractions/base>
capability sys_admin,
capability sys_chroot,
capability sys_ptrace,
/usr/lib/thorium/chrome-sandbox mr,
/usr/lib/thorium/** mrix,
/proc/*/setgroups w,
/proc/*/uid_map w,
/proc/*/gid_map w,
}
}

View file

@ -0,0 +1,78 @@
# veilor-os AppArmor profile — veilor-power
#
# Scope:
# Confine /usr/local/bin/veilor-power, the power profile switcher. The
# script is small but invokes sudo to talk to tuned-adm; we want a tight
# surface so a compromised user shell cannot abuse the sudoers entry to
# pivot beyond profile switching.
#
# Mode:
# enforce — this binary is ours, the surface is small, no need for a
# complain runway. Verified rules at write time.
#
# Manual enable:
# sudo install -m 0644 scripts/apparmor/usr.bin.veilor-power /etc/apparmor.d/
# sudo apparmor_parser -r /etc/apparmor.d/usr.bin.veilor-power
# sudo aa-enforce /etc/apparmor.d/usr.bin.veilor-power
# # to debug:
# sudo aa-complain /etc/apparmor.d/usr.bin.veilor-power
#
# NOT enabled in kickstart by default. v0.5 work.
#include <tunables/global>
profile veilor-power /usr/local/bin/veilor-power flags=(enforce) {
#include <abstractions/base>
#include <abstractions/bash>
#include <abstractions/consoles>
# ---- the script itself + bash ----
/usr/local/bin/veilor-power r,
/usr/bin/bash ix,
/usr/bin/awk ix,
/usr/bin/cat ix,
# ---- read CPU + ASUS sysfs for status ----
/sys/devices/system/cpu/cpufreq/ r,
/sys/devices/system/cpu/cpufreq/** r,
/sys/devices/system/cpu/cpu*/cpufreq/ r,
/sys/devices/system/cpu/cpu*/cpufreq/** r,
/sys/devices/platform/asus-nb-wmi/ r,
/sys/devices/platform/asus-nb-wmi/** r,
# ---- sudo handoff to tuned-adm ----
/usr/bin/sudo Cx -> sudo_tuned,
/usr/bin/tuned-adm Pix,
# ---- forbidden ----
deny network,
deny ptrace,
deny capability sys_ptrace,
deny capability sys_module,
deny capability sys_rawio,
deny /dev/kmem rwk,
deny /dev/mem rwk,
deny /etc/shadow r,
deny /etc/sudoers w,
deny /etc/sudoers.d/** w,
deny @{HOME}/.ssh/** rwk,
deny @{HOME}/.gnupg/** rwk,
# ---- child profile for the sudo subprocess ----
profile sudo_tuned {
#include <abstractions/base>
#include <abstractions/authentication>
#include <abstractions/nameservice>
/usr/bin/sudo mr,
/etc/sudoers r,
/etc/sudoers.d/ r,
/etc/sudoers.d/veilor-power r,
/usr/bin/tuned-adm Pix,
/var/log/sudo* w,
/var/db/sudo/** rwk,
capability setuid,
capability setgid,
capability audit_write,
deny network,
}
}

View file

@ -0,0 +1,96 @@
# veilor-os AppArmor profile — LM Studio (local LLM runner)
#
# Scope:
# Confine LM Studio's binary. LM Studio loads arbitrary GGUF/safetensors
# weights and exposes an OpenAI-compatible HTTP server on :1234. The
# binary itself is closed-source — we don't trust it with the full home
# directory.
#
# Mode:
# complain initially. Flip to enforce once observed denials are reviewed.
#
# Manual enable:
# sudo install -m 0644 scripts/apparmor/usr.local.bin.lm-studio /etc/apparmor.d/
# sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.lm-studio
# sudo aa-complain /etc/apparmor.d/usr.local.bin.lm-studio
# sudo aa-enforce /etc/apparmor.d/usr.local.bin.lm-studio
#
# NOT enabled in kickstart by default. v0.5 work.
#include <tunables/global>
profile lm-studio /usr/local/bin/lm-studio flags=(complain) {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>
#include <abstractions/dbus-session>
#include <abstractions/freedesktop.org>
#include <abstractions/X>
#include <abstractions/fonts>
# ---- network: HTTP server :1234 + outbound model downloads ----
network inet stream,
network inet6 stream,
network inet dgram,
network inet6 dgram,
deny network raw,
deny network packet,
deny network bluetooth,
# ---- binary + electron runtime (LM Studio is Electron-based) ----
/usr/local/bin/lm-studio mr,
/opt/lm-studio/** mr,
/usr/lib/lm-studio/** mr,
# ---- model weights + metadata ----
owner @{HOME}/.lmstudio/ rw,
owner @{HOME}/.lmstudio/** rwk,
owner @{HOME}/.cache/lm-studio/** rwk,
owner @{HOME}/.config/LMStudio/** rwk,
# ---- temp ----
/tmp/ r,
owner /tmp/** rwk,
/var/tmp/ r,
owner /var/tmp/** rwk,
# ---- GPU device nodes (CUDA / ROCm / Vulkan) ----
/dev/dri/ r,
/dev/dri/** rw,
/dev/nvidia* rw,
/dev/nvidiactl rw,
/dev/nvidia-uvm rw,
/dev/nvidia-uvm-tools rw,
/dev/kfd rw,
/dev/shm/** rwk,
# ---- system info ----
/etc/machine-id r,
/etc/os-release r,
/etc/localtime r,
/sys/devices/system/cpu/** r,
/sys/class/drm/** r,
/proc/cpuinfo r,
/proc/meminfo r,
/proc/stat r,
# ---- /proc: own process only ----
owner /proc/@{pid}/** r,
deny /proc/*/mem rwk,
# ---- forbidden ----
deny ptrace,
deny capability sys_ptrace,
deny capability sys_module,
deny capability sys_rawio,
deny /dev/kmem rwk,
deny /dev/mem rwk,
deny /dev/port rwk,
deny /sys/kernel/** w,
deny /etc/shadow r,
deny @{HOME}/.ssh/** rwk,
deny @{HOME}/.gnupg/** rwk,
# ---- xdg / browser handoff for "Open in browser" UI button ----
/usr/bin/xdg-open Pix,
}

View file

@ -1,10 +1,43 @@
#!/usr/bin/env bash
# Build + load veilor-systemd SELinux policy module.
# Build + load veilor-os SELinux policy modules.
#
# Modules:
# veilor-systemd — capabilities for systemd-modules-load (post-boot lock)
# veilor-firstboot — confine /usr/local/sbin/veilor-firstboot one-shot
#
# Usage:
# sudo ./build-policy.sh # build + install all
# sudo ./build-policy.sh <name> # build + install one module
set -euo pipefail
cd "$(dirname "$0")"
checkmodule -M -m -o veilor-systemd.mod veilor-systemd.te
semodule_package -o veilor-systemd.pp -m veilor-systemd.mod
semodule -i veilor-systemd.pp
echo "[OK] veilor-systemd SELinux module loaded"
MODULES=(veilor-systemd veilor-firstboot)
if [[ $# -gt 0 ]]; then
MODULES=("$@")
fi
for m in "${MODULES[@]}"; do
if [[ ! -f "$m.te" ]]; then
echo "[ERR] $m.te not found" >&2
exit 1
fi
echo "[*] Building $m ..."
checkmodule -M -m -o "$m.mod" "$m.te"
semodule_package -o "$m.pp" -m "$m.mod"
semodule -i "$m.pp"
echo "[OK] $m loaded"
done
# Apply file context for veilor-firstboot if module just loaded.
if printf '%s\n' "${MODULES[@]}" | grep -qx veilor-firstboot; then
if command -v restorecon >/dev/null 2>&1; then
# Mark the binary + state file with the right types.
semanage fcontext -a -t veilor_firstboot_exec_t '/usr/local/sbin/veilor-firstboot' 2>/dev/null || true
semanage fcontext -a -t veilor_firstboot_state_t '/var/lib/veilor-firstboot\.done' 2>/dev/null || true
restorecon -v /usr/local/sbin/veilor-firstboot 2>/dev/null || true
[[ -e /var/lib/veilor-firstboot.done ]] && restorecon -v /var/lib/veilor-firstboot.done 2>/dev/null || true
fi
fi
echo "[done] all modules loaded"

View file

@ -0,0 +1,136 @@
policy_module(veilor-firstboot, 1.0)
#
# veilor-os SELinux module — confine veilor-firstboot.service.
#
# The firstboot service runs once on TTY1 before SDDM, prompts for the
# admin password, then enables SDDM and self-disables. It is privileged
# (it must be — `passwd` writes /etc/shadow) but the surface is small and
# bounded. This module narrows what the service is allowed to do so that a
# bug or hostile env in firstboot.sh can't, e.g., dial out, scrape /home,
# or load a kernel module.
#
# Build + load:
# cd scripts/selinux
# ./build-policy.sh # builds & loads all .te modules
#
# Verify:
# semodule -l | grep veilor-firstboot
# ls -Z /usr/local/sbin/veilor-firstboot
# -> system_u:object_r:veilor_firstboot_exec_t:s0
#
# Audit any denials with:
# ausearch -m AVC -ts recent -c veilor-firstboot
require {
type init_t;
type passwd_exec_t;
type passwd_file_t;
type shadow_t;
type systemd_unit_file_t;
type systemd_passwd_var_run_t;
type sddm_unit_file_t;
type sddm_var_lib_t;
type tmp_t;
type tty_device_t;
type devtty_t;
type self_runtime_t;
type chkpwd_exec_t;
type pam_var_run_t;
type security_t;
type fs_t;
type usr_t;
type bin_t;
type lib_t;
type etc_t;
type proc_t;
type unconfined_service_t;
class file { read write create unlink getattr setattr open execute execute_no_trans map };
class dir { read write add_name remove_name search getattr open };
class chr_file { read write open getattr ioctl };
class capability { setuid setgid chown dac_override dac_read_search fowner fsetid };
class process { transition signal sigchld sigkill noatsecure rlimitinh siginh };
class service { start stop status enable disable };
class systemd { start };
class lnk_file { read getattr };
class filesystem { getattr };
}
# ---------------------------------------------------------------------
# 1. Define the firstboot domain + executable type
# ---------------------------------------------------------------------
type veilor_firstboot_t;
type veilor_firstboot_exec_t;
type veilor_firstboot_state_t; # /var/lib/veilor-firstboot.done
init_daemon_domain(veilor_firstboot_t, veilor_firstboot_exec_t)
files_type(veilor_firstboot_state_t)
# Auto-transition: when init_t executes /usr/local/sbin/veilor-firstboot,
# enter veilor_firstboot_t.
domain_auto_trans(init_t, veilor_firstboot_exec_t, veilor_firstboot_t)
# ---------------------------------------------------------------------
# 2. Allow rules — what the service IS allowed to do
# ---------------------------------------------------------------------
# read /etc/passwd, /etc/group, /etc/shadow (passwd needs shadow write)
allow veilor_firstboot_t passwd_file_t:file { read getattr open };
allow veilor_firstboot_t shadow_t:file { read write open getattr setattr };
# exec passwd(1)
allow veilor_firstboot_t passwd_exec_t:file { read getattr open execute execute_no_trans map };
allow veilor_firstboot_t chkpwd_exec_t:file { read getattr open execute execute_no_trans map };
# capabilities passwd needs
allow veilor_firstboot_t self:capability { setuid setgid chown dac_override dac_read_search fowner fsetid };
# write the state marker /var/lib/veilor-firstboot.done
allow veilor_firstboot_t veilor_firstboot_state_t:file { create write open getattr setattr unlink };
allow veilor_firstboot_t veilor_firstboot_state_t:dir { search write add_name remove_name };
# write /etc/sddm.conf.d/ entries (autologin disable, theme, etc.)
allow veilor_firstboot_t sddm_var_lib_t:dir { read write search add_name remove_name open };
allow veilor_firstboot_t sddm_var_lib_t:file { read write create open getattr setattr };
# start sddm.service via systemctl
allow veilor_firstboot_t sddm_unit_file_t:file { read getattr open };
allow veilor_firstboot_t sddm_unit_file_t:service { start status enable disable };
allow veilor_firstboot_t init_t:system { start };
# tty1 I/O
allow veilor_firstboot_t tty_device_t:chr_file { read write open getattr ioctl };
allow veilor_firstboot_t devtty_t:chr_file { read write open getattr ioctl };
# usual base reads
allow veilor_firstboot_t bin_t:file { read getattr open execute execute_no_trans map };
allow veilor_firstboot_t lib_t:file { read getattr open execute execute_no_trans map };
allow veilor_firstboot_t usr_t:file { read getattr open };
allow veilor_firstboot_t etc_t:file { read getattr open };
allow veilor_firstboot_t etc_t:dir { read search getattr open };
allow veilor_firstboot_t fs_t:filesystem getattr;
allow veilor_firstboot_t self:fifo_file { read write };
allow veilor_firstboot_t self:unix_stream_socket { create connect read write };
# ---------------------------------------------------------------------
# 3. Deny rules — what the service is NOT allowed to do
# ---------------------------------------------------------------------
# no network — firstboot must never phone home
neverallow veilor_firstboot_t self:tcp_socket *;
neverallow veilor_firstboot_t self:udp_socket *;
neverallow veilor_firstboot_t self:rawip_socket *;
neverallow veilor_firstboot_t self:packet_socket *;
neverallow veilor_firstboot_t self:netlink_route_socket *;
# no kernel module load
neverallow veilor_firstboot_t self:capability sys_module;
# no /home access except the bits ferror-firstboot.sh writes (admin's
# .config dir staging, if any). /home/admin general read = forbidden.
neverallow veilor_firstboot_t home_root_t:dir { read write };
neverallow veilor_firstboot_t user_home_t:dir { read write search };
neverallow veilor_firstboot_t user_home_t:file { read write open };
# no ptrace, no /dev/mem, no /dev/kmem
neverallow veilor_firstboot_t self:capability sys_ptrace;
neverallow veilor_firstboot_t self:capability sys_rawio;