CR: P16g AppArmor Deployment
1. Change Summary
| Field | Value |
|---|---|
CR ID |
CR-2026-04-04-p16g-apparmor-deployment |
Date |
2026-04-04 |
Priority |
P2 - High (security gap on secrets-handling workstation) |
Type |
Security Hardening |
Status |
Phase 2 complete — browsers confined |
Requestor |
Evan Rosado |
Implementor |
Evan Rosado |
Risk Level |
Medium (boot parameter change requires reboot; misconfigured profiles can break applications) |
Systems Affected |
modestus-p16g |
Predecessor |
N/A |
Related INC |
2. Objective
Deploy AppArmor as the Mandatory Access Control framework on the P16g. Establish complain-mode baselines, then enforce custom profiles that deny high-risk applications (browsers, node/npm, Docker) access to ~/.secrets/, ~/.gnupg/, ~/.age/, and gopass stores.
3. Background
The P16g was deployed on 2026-04-02 without any MAC system. Arch Linux ships with AppArmor compiled into the default kernel (CONFIG_SECURITY_APPARMOR=y) but does not enable it at boot. The workstation handles age-encrypted secrets, GPG private keys, Vault SSH certificates, and gopass credential stores — all accessible by any user-space process without confinement.
See INC-2026-04-04-002 for the incident report.
4. Current State
-
LSM stack:
lockdown,capability,yama— no MAC -
All user processes have unrestricted access to all user-owned files
-
No AppArmor package installed
-
No profiles loaded
-
Kernel has
CONFIG_SECURITY_APPARMOR=y(compiled in, not enabled)
5. Target State
-
LSM stack:
lockdown,capability,yama,integrity,apparmor,bpf -
AppArmor service enabled and running
-
Complain-mode profiles for all applications (Phase 1)
-
Enforce-mode profiles for high-risk apps with explicit denies on credential stores (Phase 2)
-
Custom profiles for node/npm, browsers, Docker (Phase 3)
6. Implementation Plan
6.1. Phase 1: Install & Enable (Day 1)
6.1.1. 1a. Install AppArmor userspace
sudo pacman -S apparmor
| Package | Purpose |
|---|---|
|
Meta-package: parser, utilities, profiles, systemd service |
6.1.2. 1b. Enable AppArmor in boot parameters
AppArmor is compiled into the Arch kernel but must be activated via kernel command line.
# BEFORE — check current boot parameters
cat /proc/cmdline
# Find the loader entry
ls /boot/loader/entries/
# Read current entry
cat /boot/loader/entries/arch.conf
Three boot entries exist: arch.conf, arch-fallback.conf, arch-lts.conf. All three must be updated.
|
Lesson learned during execution: The Also fixed: |
# BEFORE — verify all three entries
grep '^options' /boot/loader/entries/arch.conf
grep '^options' /boot/loader/entries/arch-fallback.conf
grep '^options' /boot/loader/entries/arch-lts.conf
# APPLY — full-line replacement (one line each, no wrapping)
# arch.conf
sudo sed -i 's|^options.*|options cryptdevice=UUID=a33cc5e6-0e54-4aa4-bc26-d08a212aa32a:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle acpi_mask_gpe=0x6E lsm=landlock,lockdown,yama,integrity,apparmor,bpf apparmor=1 security=apparmor|' /boot/loader/entries/arch.conf
# arch-fallback.conf
sudo sed -i 's|^options.*|options cryptdevice=UUID=a33cc5e6-0e54-4aa4-bc26-d08a212aa32a:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle acpi_mask_gpe=0x6E lsm=landlock,lockdown,yama,integrity,apparmor,bpf apparmor=1 security=apparmor|' /boot/loader/entries/arch-fallback.conf
# arch-lts.conf
sudo sed -i 's|^options.*|options cryptdevice=UUID=a33cc5e6-0e54-4aa4-bc26-d08a212aa32a:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle acpi_mask_gpe=0x6E lsm=landlock,lockdown,yama,integrity,apparmor,bpf apparmor=1 security=apparmor|' /boot/loader/entries/arch-lts.conf
# VERIFY — all three entries, full file review
cat /boot/loader/entries/arch.conf
cat /boot/loader/entries/arch-fallback.conf
cat /boot/loader/entries/arch-lts.conf
options cryptdevice=UUID=a33cc5e6-0e54-4aa4-bc26-d08a212aa32a:cryptroot root=/dev/mapper/cryptroot rootflags=subvol=@ rw nvidia_drm.modeset=1 mem_sleep_default=s2idle acpi_mask_gpe=0x6E lsm=landlock,lockdown,yama,integrity,apparmor,bpf apparmor=1 security=apparmor
6.1.3. 1c. Enable AppArmor service
sudo systemctl enable apparmor.service
6.1.4. 1d. Reboot and verify
sudo reboot
After reboot:
# Verify LSM stack
cat /sys/kernel/security/lsm
# Verify AppArmor is active
aa-enabled
# Check loaded profiles
sudo aa-status
6.2. Phase 2: Complain-Mode Baseline (Day 1-3)
6.2.1. 2a. Load default profiles in complain mode
# Set all profiles to complain mode (logs violations but doesn't block)
sudo aa-complain /etc/apparmor.d/*
# Verify profile count
sudo aa-status | head -10
6.2.2. 2b. Use the system normally for 2-3 days
Normal usage generates audit logs showing what each application accesses. This data informs custom profiles.
# Monitor AppArmor audit events
sudo journalctl -k | grep -i apparmor | tail -20
# Generate profile suggestions from logs
sudo aa-logprof
6.3. Phase 3: Enforce High-Risk Profiles (Day 3-7)
6.3.1. 3a. Identify high-risk applications
| Application | Risk | Priority |
|---|---|---|
Firefox / Chromium |
Browser exploits → credential exfiltration |
High |
node / npm |
Supply chain attacks via npm packages |
High |
Docker daemon |
Container escape → host filesystem |
High |
python / pip |
Malicious packages |
Medium |
Claude Code (node-based) |
Broad filesystem access by design |
Medium — profile carefully |
6.3.2. 3b. Create custom deny rules for credential stores
The critical protection: deny high-risk apps access to sensitive directories.
# Example: deny Firefox access to credential stores
# /etc/apparmor.d/local/usr.bin.firefox
deny owner @{HOME}/.secrets/ rw,
deny owner @{HOME}/.secrets/** rw,
deny owner @{HOME}/.gnupg/ rw,
deny owner @{HOME}/.gnupg/** rw,
deny owner @{HOME}/.age/ rw,
deny owner @{HOME}/.age/** rw,
deny owner @{HOME}/.local/share/gopass/ rw,
deny owner @{HOME}/.local/share/gopass/** rw,
deny owner @{HOME}/.ssh/id_* rw,
6.3.3. 3c. Enforce profiles for verified applications
# Switch from complain to enforce (one at a time, test each)
sudo aa-enforce /etc/apparmor.d/usr.bin.firefox
# Verify enforcement
sudo aa-status | grep -A2 enforce
6.4. Phase 4: Docker Confinement (Day 7+)
# Verify Docker uses AppArmor by default
docker info | grep -i apparmor
# Run a test container and verify AppArmor profile is applied
docker run --rm alpine cat /proc/self/attr/current
7. Verification
| Check | Command | Expected |
|---|---|---|
LSM stack |
|
Includes |
Service active |
|
|
Profiles loaded |
|
>0 profiles in enforce/complain |
Boot parameter |
|
|
Credential deny |
|
>0 enforce-mode profiles |
Docker integration |
|
AppArmor listed as security option |
8. Rollback
# If AppArmor causes application breakage:
# 1. Set problem profile to complain mode
sudo aa-complain /etc/apparmor.d/<profile-name>
# 2. If all profiles are problematic, disable at boot
# Remove apparmor=1 and security=apparmor from boot entry
sudo sed -i 's/ lsm=landlock,lockdown,yama,integrity,apparmor,bpf apparmor=1 security=apparmor//' /boot/loader/entries/arch.conf
sudo reboot
| Removing AppArmor from boot parameters re-exposes the full attack surface. Prefer switching individual profiles to complain mode over disabling the entire framework. |
9. Future Considerations
9.1. Razer Parity
modestus-razer has the same MAC gap. After validating AppArmor on P16g, replicate the deployment:
-
Copy custom profiles via dots-quantum or rsync
-
Add AppArmor stow package to dots-quantum for consistent deployment
9.2. Profile Management
-
Store custom profiles in dots-quantum as a stow package (
apparmor/.etc/apparmor.d/local/) -
Version control profile changes alongside dotfiles
-
Consider
aa-notifyfor desktop notifications on deny events
9.3. P16g Deploy Runbook Update
AppArmor is now tracked as Phase 12 (Security Hardening) in the P16g deployment runbook:
-
Covers AppArmor (4 sub-phases), UFW, SSH hardening, port audit
10. Related
-
INC: P16g No MAC — Incident that triggered this CR
-
INC: Vault Backup SELinux — Related MAC incident on server infrastructure
11. Changelog
| Date | Author | Change |
|---|---|---|
2026-04-04 |
Evan Rosado |
Initial CR — AppArmor deployment plan for P16g with phased rollout |
2026-04-05 |
Evan Rosado |
Phase 1 executed: |
2026-04-05 |
Evan Rosado |
Phase 1 verified post-reboot: 162 profiles loaded, 79 enforce, AppArmor in LSM stack. Phase 2 executed: browser profiles (Firefox, Chrome, Chromium) converted from |