feat(minimald): ship an AppArmor profile granting minimald userns - #740
Conversation
Ubuntu 24.04+ defaults kernel.apparmor_restrict_unprivileged_userns=1,
which bars unconfined programs from creating unprivileged user
namespaces. Every minimald session is one (hakoniwa forks and the child
writes /proc/self/uid_map), so on a stock 24.04 host no session can
start: the child dies with EPERM before it runs, surfacing only as an
opaque uid_map DIAG in the daemon log.
Ship the accommodation Ubuntu intends, the same shape the distro ships
for rootlesskit, runc and podman: an unconfined-mode profile whose sole
purpose is to give minimald a named label carrying the `userns`
permission. It confines nothing, and unlike setting the sysctl to 0 it
does not hand user namespaces back to every other program on the host.
minimald has no single install path (the installer puts it in
${MINIMAL_BIN:-~/.local/bin}, a package would use /usr/bin), and AppArmor
attaches profiles by executable path, so the attachment set lives in a
tunable; --path extends it for dev builds via tunables/minimald.d.
Verified on Ubuntu with the restriction on: without the profile an
interactive attach yields no sandbox shell; with it loaded the daemon
runs as `minimald (unconfined)` and the shell comes up in its own user
namespace. Unconfined processes remain unable to unshare.
Refs: #610
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 48 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds an AppArmor profile and installer for minimald user namespaces, detects host restrictions, packages and tests the artifacts, documents Ubuntu 24.04+ setup, integrates uninstall handling, and extends CI validation. ChangesAppArmor support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Administrator
participant Installer
participant AppArmor
participant minimald
participant sandbox2
Administrator->>Installer: Install bundled AppArmor profile
Installer->>AppArmor: Copy tunables and profile
Installer->>AppArmor: Load profile with apparmor_parser
minimald->>sandbox2: Check user namespace restriction
sandbox2->>AppArmor: Inspect host restriction and label
AppArmor-->>sandbox2: Return restriction state
sandbox2-->>minimald: Provide remediation result
Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
shellcheck SC2155: `readonly VAR=$(cmd)` masks the command's exit status, so a missing packaging/apparmor dir would go unnoticed instead of failing the script.
native-daemon-e2e lifted Ubuntu's unprivileged-userns restriction (`sysctl ...=0`) on the theory that the session sandbox needs it. It does not: session-e2e.sh drives activate/list/destroy/stop only and never forks a session sandbox, so the job needs no user namespace — verified by running the e2e green with the restriction left on. Remove the step and leave a comment so it is not re-added; correct the job header, which wrongly claimed the e2e execs inside the sandbox (it does not — that path is proven in Rust, per session-e2e.sh). Sandbox-forking coverage that genuinely needs userns stays put: the workspace tests and the minimald-root-integration job (whose own sysctl step is unaffected — its userns comes from the cargo test binary, not minimald). Refs: #610
The curl|sh installer had no path to the AppArmor profile that lets
minimald create the unprivileged user namespace every session sandbox
needs: the profile and its loader lived only in the source tree, so
`sudo scripts/install-apparmor-profile.sh` was a dead reference for
anyone who installed via curl|sh. On stock Ubuntu 24.04+ that left every
session dying at uid_map with an opaque EPERM and no in-product hint.
Plumb it in without ever elevating from the installer:
- stage-release.sh ships packaging/apparmor/{minimald,tunables/minimald}
and install-apparmor-profile.sh as data-prefix components, installed to
~/.local/share/minimal/apparmor/.
- install.sh prints a gated, Linux-only advisory when it detects
apparmor_restrict_unprivileged_userns=1, pointing at the shipped loader
(sudo bash ...). Advice only; it never runs sudo.
- On --uninstall, offer to remove the system profile too: prompt on a
terminal, advise the root command otherwise, run before the record walk
deletes the loader. Never touches /etc/apparmor.d unattended.
- install-apparmor-profile.sh resolves its source dir from either the
checkout layout or the shipped-flat layout.
- The install loop now creates each component's parent dir, so a
component whose subpath nests directories installs correctly.
install_test.sh covers placement, the advisory on/off, darwin-skip, and
the uninstall advisory (no elevation, loader removed).
Refs: #610
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/ci-linux-native.yml (1)
113-135: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winRequire CODEOWNER approval for this workflow change.
.github/workflows/ci-linux-native.ymlis frozen and CODEOWNER-gated, so this userns setup removal should go through the workflow owners or be reverted.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci-linux-native.yml around lines 113 - 135, Restore the user namespace setup in the native CI workflow, or obtain and preserve the required CODEOWNER approval for its removal. Update the workflow section associated with the native E2E job and remove the comment asserting that userns setup is intentionally unnecessary unless the approved change is retained.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/reference/linux-host-setup.md`:
- Around line 52-54: Remove the `$` shell prompt from the command-only console
block in the Linux host setup documentation, leaving the command itself
unchanged and without adding output.
In `@scripts/install.sh`:
- Around line 805-816: Update the Linux user-namespace advisory block around
userns_sysctl and apparmor_loader to query the bundled AppArmor loader’s
check/status mode before printing the warning. Only emit the existing messages
when the profile is not already active, while preserving the current sysctl,
loader existence, and message conditions.
---
Outside diff comments:
In @.github/workflows/ci-linux-native.yml:
- Around line 113-135: Restore the user namespace setup in the native CI
workflow, or obtain and preserve the required CODEOWNER approval for its
removal. Update the workflow section associated with the native E2E job and
remove the comment asserting that userns setup is intentionally unnecessary
unless the approved change is retained.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a259da7c-9983-45ce-b1a1-18bf9c2c6b3c
📒 Files selected for processing (7)
.github/workflows/ci-linux-native.ymlREADME.mddocs/reference/linux-host-setup.mdscripts/install-apparmor-profile.shscripts/install.shscripts/install_test.shscripts/stage-release.sh
🚧 Files skipped from review as they are similar to previous changes (2)
- README.md
- scripts/install-apparmor-profile.sh
The install-time advisory checked only the sysctl and the shipped loader, so a reinstall on an already-remediated host still claimed the sandbox "cannot start" — the profile is loaded and sessions work, yet the sysctl stays 1. Gate the advisory on the absence of the installed profile at /etc/apparmor.d/minimald (via the same MINIMAL_OVERRIDE_- APPARMOR_DIR seam the uninstall path uses), so it fires only on a host that is restricted and not yet remediated. Also show the loader's output in the curl-installed docs example, so the command block carries output like its sibling (satisfies markdownlint MD014). Both raised by CodeRabbit on #740.
…tartup Every session sandbox starts by unsharing an unprivileged user namespace, forked from the daemon with no exec in between. On a host that refuses it (stock Ubuntu 24.04+ with an unconfined daemon, or user namespaces disabled outright) the denial previously surfaced only when the first attach died writing /proc/self/uid_map, with nothing useful in the daemon log. sandbox2 gains user_namespaces_restriction() — a read-only probe of the userns quota, the apparmor_restrict_unprivileged_userns sysctl, euid, and the process's own AppArmor label, mirroring the crate's existing net-namespace probe — and minimald warns at startup naming the restriction and the exact fix. Root (the in-guest microVM pid-1) and confined daemons stay silent, so the common case logs nothing. Refs: #610 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reference docs ship to https://docs.minimal.dev/ with this release, so the advisory's details link should point there rather than at a GitHub blob URL. The build-specs link to minimal.ncl stays on GitHub: it targets source, not a hosted doc page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… running binary The startup warning hardcoded ~/.local/share for the shipped loader, so the advised command was wrong whenever XDG_DATA_HOME was set, and it omitted --path, so a daemon outside the tunable's stock attachment set (a custom MINIMAL_BIN, a dev build) was told to load a profile that would never attach to it. paths gains minimal_data_dir(), resolving exactly like the installer's data prefix, and the fix string now carries --path <current_exe>. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The parse loop consumes $@ before the root check, so the hinted `sudo $0 $*` always printed bare — a copy-pasted retry of --uninstall would install, and one of --path would skip the attachment. Capture the original argv up front and print that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… only for bin/lib swaps Two review findings against the advisory and the R5.5 stop: - The stock tunable attaches only /usr/bin, /usr/local/bin, and ~/.local/bin, so for a custom MINIMAL_BIN the advised command now carries --path "$bindir/minimald", and a loaded profile counts as remediation only when the tunables actually name that binary — previously the advisory suppressed itself while sessions stayed broken. - stop_running_daemon now fires only for bin/lib component swaps: replacing a data file (the shipped apparmor text) must not kill live sessions to swap a file the running daemon does not serve from. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nothing stage_repo_file wrote the three apparmor files into ARTIFACTS_DIR before the dry-run branch, mutating a directory the caller owns (and dying outright when it is read-only). Stage into the ephemeral workdir and let the manifest loop prefer staged files over same-named artifacts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ox proof The native-e2e lane dropped its userns sysctl step on the rationale that session-e2e.sh forks no sandbox — true when written, falsified by the in-sandbox `min add` proof (#760) merged since: on a restricted runner the proof's sandbox dies at uid_map and surfaces as the misleading 'jq was already present' error (the absent-marker never prints). Load the shipped profile from the script itself, attached to the minimald the run will spawn — dogfooding the documented remediation on CI and on any restricted host running the e2e locally. Native Linux only: VM lanes create their userns in-guest as root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
060875b to
2eb3cea
Compare
Closes #610 via option 1 — ship an AppArmor profile for
minimaldgrantinguserns,— and plumb it into thecurl … | shinstaller so a stock Ubuntu 24.04+ user is told exactly what to run, at install time, without a checkout.Why
Ubuntu 24.04+ defaults
kernel.apparmor_restrict_unprivileged_userns=1, which bars unconfined programs from creating unprivileged user namespaces. Every minimald sandbox is one, so on a stock 24.04 host no session can start — the child dies writing/proc/self/uid_mapwith EPERM, surfacing only as an opaque DIAG in the daemon log.The profile is the accommodation Ubuntu intends, and is the same shape the distro ships for
rootlesskit,runcandpodman:flags=(unconfined)— it confines nothing — existing only to give minimald a named label that can carry theusernspermission. Unlike setting the sysctl to 0, it does not hand user namespaces back to every other program on the host.What the user experiences during install
The profile is delivered and surfaced by the installer itself — no checkout required, and the installer never asks for
sudo.On any host —
curl … | shdownloads the binaries and, on Linux, three extra files (the profile, its tunable, and the loader) into~/.local/share/minimal/apparmor/. The install finishes normally.On a restricted host (stock Ubuntu 24.04+) — after placing the files, the installer reads
kernel.apparmor_restrict_unprivileged_userns. If it is1— the one case where sessions would otherwise die atuid_map— it ends with a plain-language note, instead of leaving the user to discover the failure later at firstattach:The install still exits
0; the user runs that one root command when they choose to. That command is the only place a password is asked for.Summary by CodeRabbit
Summary by CodeRabbit
minimaldto support unprivileged user namespaces on Ubuntu 24.04+.XDG_DATA_HOME.--pathusage and a sysctl workaround.