Skip to content

Commit 42ece84

Browse files
committed
doc: what docker-default forbids, and how a compose service opts out
The page said what is confined but not what confinement costs. Three denials in moby's template reach ordinary containers: mount, writes below /sys, and sysctl -w from the inside. That hits deployments through cuos-iac hardest - sysfs GPIO on the boards, FUSE, systemd as PID 1 - and cap_add: [SYS_ADMIN] does not lift any of it, which is the part that surprises. security_opt in the compose file is the per-service way out, and unlike the app image's label it adds rather than replaces. Also corrected: every container dockerd starts gets docker-default, not just the application container; unix-chkpwd ships in complain mode; and the ~100 profiles of the apparmor package cannot deny anything, which is the answer to whether they should be deleted. Read off the packages in trixie - apparmor 4.1.0-1, isc-dhcp-client 4.4.3-P1-8 - and moby's template.go at v26.1.5. Not re-checked against a booted system beyond aa-status showing docker-default, dhclient and lsb_release loaded. Signed-off-by: Simon Walz <contact@simonwalz.de>
1 parent d163a5a commit 42ece84

1 file changed

Lines changed: 63 additions & 10 deletions

File tree

‎docs/common/apparmor.md‎

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@ every platform except LXC. `cuos selftest` reports the state under the
88

99
| | Profile | Where it comes from |
1010
|---|---|---|
11-
| The application container | `docker-default` | dockerd's built-in template, loaded when the daemon starts |
11+
| Every container dockerd starts | `docker-default` | dockerd's built-in template, loaded when the daemon starts |
1212
| `dhclient` | `/usr/sbin/dhclient` | Debian's `isc-dhcp-client` package |
13-
| PAM's password check | `unix-chkpwd` | Debian's `apparmor` package |
13+
| PAM's password check | `unix-chkpwd`, in complain mode | Debian's `apparmor` package |
1414

1515
That is the whole list, and `docker-default` is the point of the exercise: it is
16-
what stands between the application container and the host. Debian's
17-
`apparmor-profiles` and `apparmor-profiles-extra` packages are not installed —
18-
they cover samba, syslog, avahi and desktop software, none of which is here.
16+
what stands between a container and the host — the application container and
17+
everything deployed through cuos-iac alike. Debian's `apparmor-profiles` and
18+
`apparmor-profiles-extra` packages are not installed — they cover samba,
19+
syslog, avahi and desktop software, none of which is here.
20+
21+
The `apparmor` package's own ~100 profiles are inert here: most are the Debian
22+
13 userns stubs (`flags=(unconfined)`, body `userns,`), and every one of the
23+
rest is either in complain mode or carries no attachment path. None of them can
24+
deny anything, including inside a container that runs unconfined.
1925

2026
## What is not confined
2127

@@ -32,11 +38,57 @@ they cover samba, syslog, avahi and desktop software, none of which is here.
3238
Confining a CuOS guest is the LXC host's business.
3339
- **The installer ISO.** A separate image without the AppArmor package.
3440

41+
## What `docker-default` forbids a container
42+
43+
The profile is permissive by design: `network,`, `capability,`, `file,` and
44+
`umount,` are allowed outright, so every file and every capability is on the
45+
table. What it carries is a short list of denials, and three of them reach
46+
ordinary containers.
47+
48+
| Denied | What stops working |
49+
|---|---|
50+
| `deny mount,` | FUSE (sshfs, s3fs, rclone), `mount --bind` in an entrypoint, NFS/CIFS from inside, docker-in-docker, systemd as PID 1 |
51+
| writes below `/sys`, everything but `/sys/fs/cgroup/**` | sysfs GPIO (`/sys/class/gpio/export`), LEDs, PWM, backlight, driver `bind`/`unbind`, `/sys/class/net/*` |
52+
| `deny @{PROC}/sys/[^k]** w,` | `sysctl -w` run inside the container |
53+
54+
Reading is untouched — only writes to `/sys` and `/proc` are denied.
55+
56+
**`cap_add: [SYS_ADMIN]` does not lift any of this.** The capability is granted
57+
and AppArmor refuses anyway, and the error names neither. `privileged: true`
58+
does lift it, because Docker runs privileged containers unconfined.
59+
60+
Unaffected, and usually the cheaper way out of a denial:
61+
62+
- `volumes:`, `tmpfs:`, `read_only:` and `sysctls:` in a compose file — runc
63+
applies those before the container starts, and runc is unconfined.
64+
- Device nodes: `/dev/gpiochip*` (libgpiod), `/dev/i2c-*`, `/dev/spidev*` are
65+
covered by `file,`. Only the sysfs route to the same hardware is denied.
66+
67+
`docker-default` separates a container from the **host**, not from other
68+
containers. Every container carries the same profile name, so the
69+
`signal (send,receive) peer=docker-default` and
70+
`ptrace (trace,read) peer=docker-default` rules of the template apply between
71+
containers as soon as they share a PID namespace (`pid: host`,
72+
`pid: service:…`).
73+
74+
The rules above are moby's built-in template as of 26.1.5, the version in
75+
Debian trixie's `docker.io`. Read `aa-status` on the device rather than this
76+
page if a denial does not match.
77+
3578
## Giving an application more than `docker-default`
3679

37-
The flags of the application container come from the `dev.cuos.app_command`
38-
label on the app image (see `cuos-app-init.md`), so the profile is selected
39-
there:
80+
For a service deployed through cuos-iac, the profile is chosen per service in
81+
the compose file, and adds to what is already there:
82+
83+
```yml
84+
services:
85+
app:
86+
security_opt:
87+
- apparmor=unconfined
88+
```
89+
90+
For the application container, the flags come from the `dev.cuos.app_command`
91+
label on the app image (see `cuos-app-init.md`), so it is selected there:
4092

4193
```
4294
LABEL dev.cuos.app_command="--security-opt apparmor=my-profile ..."
@@ -45,8 +97,9 @@ LABEL dev.cuos.app_command="--security-opt apparmor=my-profile ..."
4597
Note that the label **replaces** the default flag set rather than adding to it.
4698

4799
The profile itself has to be loaded into the kernel before the container
48-
starts, and CuOS ships no mechanism for that yet. While an application is being
49-
brought up, `--security-opt apparmor=unconfined` is the escape hatch.
100+
starts, and CuOS ships no mechanism for that yet. So the choice today is
101+
`docker-default` or nothing: while an application is being brought up,
102+
`--security-opt apparmor=unconfined` is the escape hatch.
50103

51104
## Reading a denial
52105

0 commit comments

Comments
 (0)