#!/bin/bash # AppArmor profile shim for the rootless Docker + pasta combo. # # Debian's stock `passt` profile (shipped by the `passt` package, used for the # pasta network driver) is enforce-mode and denies the accesses pasta needs to # wire up rootlesskit's net namespace: # # - ptrace(read) into the rootlesskit child to enter its user namespace # - read /run/user//dockerd-rootless/netns # - read /proc//net/{tcp,tcp6,udp,udp6} for implicit port-forwarding # # Without these the daemon fails to start with # error: failed to setup network: pasta failed with exit code 1: # Couldn't open user namespace /proc//ns/user: Permission denied # # We extend the profile via the standard local-override pattern: # /etc/apparmor.d/local/usr.bin.passt — our rules # /etc/apparmor.d/usr.bin.passt — include if exists # # Idempotent — safe to call on every rootless install. The local file survives # `apt upgrade passt` (it's outside the package's managed paths). The one-line # include statement could theoretically get reverted by an apt upgrade; we # re-add it on every run as a belt-and-braces guard. installRootlessApparmorForPasta() { # Bail silently when apparmor isn't on this system at all — Fedora/RHEL, # ungrouped Debian minimals, etc. Nothing to patch. if [[ ! -d /etc/apparmor.d ]]; then return 0 fi if [[ ! -f /etc/apparmor.d/usr.bin.passt ]]; then isNotice "AppArmor present but no /etc/apparmor.d/usr.bin.passt — passt may be too old or not installed under apparmor." return 0 fi isHeader "AppArmor: enabling pasta for rootless Docker" # 1. Add `include if exists ` to the main profile if # it isn't already there. The line lives just before the closing `}` of # the profile block. if ! sudo grep -q "include if exists " /etc/apparmor.d/usr.bin.passt; then # Insert right before the final `}` of the file. We don't try to be # surgical about which `}` — passt's profile has exactly one top-level # close, and any nested `{` `}` (e.g. inside abstractions) are # behind `include` lines, not literal in this file. sudo awk ' /^}/ && !done { print " include if exists "; done=1 } { print } ' /etc/apparmor.d/usr.bin.passt | sudo tee /etc/apparmor.d/usr.bin.passt.new > /dev/null sudo mv /etc/apparmor.d/usr.bin.passt.new /etc/apparmor.d/usr.bin.passt checkSuccess "Added include-local line to /etc/apparmor.d/usr.bin.passt" else isSuccessful "Main passt profile already sources /etc/apparmor.d/local/usr.bin.passt" fi # 2. Write (or overwrite — same content every time) the local rules file. sudo mkdir -p /etc/apparmor.d/local sudo tee /etc/apparmor.d/local/usr.bin.passt > /dev/null <<'EOF' # Managed by LibrePortal (installRootlessApparmorForPasta). Edits here are # fine but will be overwritten on the next rootless reinstall — put your # customisations in a sibling file (e.g. local/usr.bin.passt.local) and # include them yourself if you need to extend this further. # # These rules permit rootless Docker's pasta network driver to: # - ptrace_read the rootlesskit child to enter its user namespace # - read the netns file rootlesskit writes # - read per-process socket tables for implicit port-forwarding ptrace (read) peer=unconfined, /run/user/[0-9]*/dockerd-rootless/netns r, /proc/*/net/tcp r, /proc/*/net/tcp6 r, /proc/*/net/udp r, /proc/*/net/udp6 r, EOF checkSuccess "Wrote /etc/apparmor.d/local/usr.bin.passt" # 3. Reload the profile. apparmor_parser -r updates an in-kernel profile # in place, so already-running pasta processes see the new rules on the # next syscall — no daemon restart strictly needed, but the caller # restarts docker anyway when override.conf changes. if sudo apparmor_parser -r /etc/apparmor.d/usr.bin.passt 2>/dev/null; then isSuccessful "Reloaded passt AppArmor profile" else isError "apparmor_parser -r failed — passt may still deny pasta. Check /var/log/syslog or journalctl for the error." return 1 fi }