AppArmor profile per binary path with install hint; verified network examples

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 22:08:37 +02:00
co-authored by Claude Sonnet 5.5
parent 530582e826
commit c3ebf21e9c
11 changed files with 133 additions and 7 deletions
+45 -3
View File
@@ -8,9 +8,10 @@ doos get_or_create [OPTIONS] NAME
doos attach [OPTIONS] NAME [-- CMD...]
doos delete [--yes] NAME
doos image list | add [--sha256 HEX] SOURCE [NAME] | delete [--yes] NAME
doos apparmor # profile text for this binary (Ubuntu 24.04+)
```
Version: see `VERSION` (currently 1.1.0). Release history: `CHANGELOG.md`.
Version: see `VERSION` (currently 1.1.1). Release history: `CHANGELOG.md`.
User-level containers in C++20: overlayfs over the host's `/usr`, `/etc`, `/opt`, user/mount/pid namespaces, networking through slirp4netns. No root at runtime.
@@ -23,12 +24,17 @@ doos delete [--yes] NAME
Build: `cmake -S . -B build && cmake --build build`. Test: `tests/run.sh`.
Requirements: Linux >= 5.11, `slirp4netns` (default network mode), `pasta` (only for `--net pasta`), `gnupg` (signature checks; optional), `uidmap` (`newuidmap`/`newgidmap`, for more than one id inside; falls back to one id), `curl`, `tar` and `sha256sum` (images). On Ubuntu 24.04+ install the AppArmor profile once:
Requirements: Linux >= 5.11, `slirp4netns` (default network mode), `pasta` (only for `--net pasta`), `gnupg` (signature checks; optional), `uidmap` (`newuidmap`/`newgidmap`, for more than one id inside; falls back to one id), `curl`, `tar` and `sha256sum` (images). On Ubuntu 24.04+ (`kernel.apparmor_restrict_unprivileged_userns=1`) AppArmor must allow user namespaces for the
binary. Generate a profile for the exact path doos is installed at, once:
```
sudo cp apparmor/doos /etc/apparmor.d/doos && sudo apparmor_parser -r /etc/apparmor.d/doos
doos apparmor | sudo tee /etc/apparmor.d/doos-local >/dev/null && sudo apparmor_parser -r /etc/apparmor.d/doos-local
```
If you skip this, doos tells you the exact command (with a file name unique to the binary's path, so installing doos
in a second place does not replace the first profile). `apparmor/doos` is a static profile for packaging that covers
`/usr/local/bin`, `/usr/bin`, `/opt/doos/bin`, `~/.local/bin`, `~/bin` and `~/projects/doos/build`.
See `SPEC.md` (behavior, limits) and `docs/RESEARCH.md` (what works, measurements, known gaps).
## Usage examples
@@ -67,6 +73,41 @@ doos attach --image mybase c1
Writes always stay in the container (copy-on-write); `/root` and `/var` persist, `/tmp` is memory-backed and lost on exit. A container is `running` while its first `attach` command is alive; more `attach` calls join it. Data persists until `delete --yes`.
## Networking
Every example below was run on the development machine. Pick the mode when you create the container; it cannot change later.
| Mode | Outbound | Host `127.0.0.1` from inside | Reach a port served inside, from the host | Host abstract unix sockets |
|---|---|---|---|---|
| `slirp` (default) | yes | blocked | no | not shared |
| `pasta` | yes | reachable | yes (forwarded automatically) | not shared |
| `host` | yes | the same network | yes | **shared** |
| `none` | no | no | no | not shared |
```
# slirp (default): the internet works, the host's loopback does not
doos attach web -- curl -sS -o /dev/null -w "%{http_code}\n" https://example.com # 200
doos attach web -- curl -sS -m 3 http://127.0.0.1:8099 # fails: host loopback is blocked
doos attach web -- getent hosts example.com # DNS works (resolver 10.0.2.3)
# pasta: behaves like the host network, in a private namespace
doos attach --net pasta dev -- curl -sS http://127.0.0.1:8099 # reaches a service on the host's loopback
doos attach dev -- python3 -m http.server 8088 --bind 0.0.0.0 # then, on the host: curl http://127.0.0.1:8088/
doos attach --image alpine --net pasta al -- wget -qO- https://example.com # works with images too
# none: no network at all, only loopback
doos attach --net none iso -- curl -m 3 https://example.com # curl exits 6, /proc/net/dev lists only lo
# host: the host's own network namespace, explicit opt-in (prints a note recommending pasta)
doos attach --net host h -- curl -sS -o /dev/null -w "%{http_code}\n" https://example.com # 200
```
Notes, all measured:
- `slirp` has no inbound path: a server started in a `slirp` container is not reachable from the host (connection fails). Use `pasta` for that.
- `pasta` notices a newly bound port after about 2 seconds, so start the server and give it a moment before connecting from the host.
- Two `pasta` containers can talk to each other through the host's loopback: start a server in one (`python3 -m http.server 8066 --bind 0.0.0.0`), wait about 5 seconds, then `doos attach cli -- curl http://127.0.0.1:8066/` from the other returns 200. The host's own public address is not forwarded; use `127.0.0.1` or the default gateway address.
- `pasta` starts in about 65 ms per attach, `slirp` about 40 ms, `none` about 10 ms.
## Options
| Option | Meaning | Default | Later |
@@ -108,6 +149,7 @@ Changeable options apply the next time the container starts. Examples: `doos att
| `limits are not enforced and the size cap only warns` | No delegated cgroup v2 (no systemd user manager). Limits and cap are advisory. |
| `was killed by its memory limit` | Raise `--memory`. `/tmp` is memory-backed and counts. |
| `hit its process limit` | Raise `--pids`; threads count as processes. |
| `AppArmor restricts user namespaces for this binary` | Run the command doos prints (`doos apparmor \| sudo tee ... && sudo apparmor_parser -r ...`), once per install path. |
| `Operation not permitted` from `mount`, `unshare`, `keyctl`, io_uring | Hardening blocked it. Use `--hardening relaxed` on that container. |
| `signature NOT checked (gpg is not installed)` | Install `gnupg` for signature checks; the checksum still applies. |
| `doos: seeded ...: unreadable skipped: N` | Host entries the user cannot read were left out of the container. Informational. |