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

INC: P16g No MAC

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

apparmor

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 sed append approach (s/$/…​/) is fragile with long option lines — terminal line wrapping causes newlines inside the sed expression, truncating the command. The sed variable approach (${VAR} in double-quoted sed) caused double expansion. Use the full-line replacement approach below instead.

Also fixed: acpi_mask_gpe=0x6E was missing from arch-fallback.conf and arch-lts.conf — restored during this change.

# 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
Verified options line (identical across all three entries)
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

cat /sys/kernel/security/lsm

Includes apparmor

Service active

systemctl is-active apparmor

active

Profiles loaded

sudo aa-status | head -5

>0 profiles in enforce/complain

Boot parameter

grep apparmor /proc/cmdline

apparmor=1

Credential deny

sudo aa-status | grep -c enforce

>0 enforce-mode profiles

Docker integration

docker info | grep -i apparmor

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-notify for 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:

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: pacman -S apparmor, boot params updated on all 3 entries (arch, fallback, LTS), acpi_mask_gpe=0x6E restored on fallback + LTS, apparmor.service enabled. Updated sed approach to full-line replacement after append method failed due to terminal line wrapping.

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 flags=(unconfined) to confined with allow-all baseline + credential store deny rules in local/. Added flags=(attach_disconnected) to fix bwrap sandbox denials. All 3 browsers in enforce mode.