feat(rootless): proper AppArmor profile for pasta network driver
The Debian-shipped passt AppArmor profile (/etc/apparmor.d/usr.bin.passt)
denies the accesses pasta needs to plumb rootlesskit's netns:
- ptrace_read on the rootlesskit child to enter its user namespace
- read /run/user/<uid>/dockerd-rootless/netns (the netns file)
- read /proc/<pid>/net/{tcp,tcp6,udp,udp6} for implicit port forwarding
Without these the rootless docker daemon fails with:
pasta failed with exit code 1:
Couldn't open user namespace /proc/<pid>/ns/user: Permission denied
scripts/docker/install/rootless/rootless_apparmor.sh:
New installRootlessApparmorForPasta() — idempotent fixup.
1. Adds `include if exists <local/usr.bin.passt>` to the main profile
(one line; re-adding is a no-op via grep).
2. Writes /etc/apparmor.d/local/usr.bin.passt with the four rules
pasta needs. The /local/ pattern is the standard Debian AppArmor
hook for site-managed overrides — survives `apt upgrade passt`
because it's outside the package's managed paths.
3. Reloads via apparmor_parser -r.
Called from installDockerRootless after the override.conf write, gated
on $rootless_net == pasta. slirp4netns installs skip it.
This box was already manually patched while debugging the pasta swap —
the installer-side change makes it idempotent across reinstalls and
applies the same fix on any other host that installs rootless docker
with pasta as the net driver.
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
e842d25b31
commit
4063283db1
90
scripts/docker/install/rootless/rootless_apparmor.sh
Normal file
90
scripts/docker/install/rootless/rootless_apparmor.sh
Normal file
@ -0,0 +1,90 @@
|
||||
#!/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 access 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
|
||||
}
|
||||
@ -157,6 +157,14 @@ EOL"
|
||||
local result=$(sudo chown $CFG_DOCKER_INSTALL_USER:$CFG_DOCKER_INSTALL_USER $override_conf_file)
|
||||
checkSuccess "Updating ownership for override.conf"
|
||||
|
||||
# Pasta needs explicit AppArmor permissions that the Debian-shipped
|
||||
# passt profile doesn't include by default (ptrace_read on the
|
||||
# rootlesskit child + a couple of /proc + /run paths). Skip silently
|
||||
# when slirp4netns is selected — that path doesn't go through passt.
|
||||
if [[ "$rootless_net" == "pasta" ]]; then
|
||||
installRootlessApparmorForPasta
|
||||
fi
|
||||
|
||||
# NOTE: we deliberately do NOT set "userland-proxy": false here. Disabling
|
||||
# it makes rootless dockerd require br_netfilter
|
||||
# (/proc/sys/net/bridge/bridge-nf-call-iptables), which isn't present in
|
||||
|
||||
@ -39,6 +39,7 @@ docker_scripts=(
|
||||
"docker/install/rooted/rooted_docker_check.sh"
|
||||
"docker/install/rooted/rooted_docker_compose.sh"
|
||||
"docker/install/rooted/rooted_docker.sh"
|
||||
"docker/install/rootless/rootless_apparmor.sh"
|
||||
"docker/install/rootless/rootless_docker.sh"
|
||||
"docker/install/rootless/rootless_start_setup.sh"
|
||||
"docker/install/rootless/rootless_uninstall.sh"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user