LibrePortal/scripts/docker/install/rootless/rootless_apparmor.sh
librelad b1ffe9d052 chore(rootless): trim AppArmor banner text
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-29 14:50:11 +01:00

91 lines
4.2 KiB
Bash

#!/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/<uid>/dockerd-rootless/netns
# - read /proc/<pid>/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/<pid>/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 <local/...>
#
# 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 <local/usr.bin.passt>` 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 <local/usr.bin.passt>" /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 <local/usr.bin.passt>"; 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
}