Skip to content

Commit fa9a925

Browse files
simonwalz-worksimonwalz
authored andcommitted
doc: what AppArmor confines on CuOS, and what it does not
Three profiles are loaded and only one of them matters. The page says so, names the four places that stay unconfined and why - the privileged updater above all - and records the /data trap: apparmor mediates the resolved path, so a stock profile written against /var/lib/... denies the write that lands in /data. Signed-off-by: Simon Walz <contact@simonwalz.de>
1 parent 4050fbb commit fa9a925

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

‎docs/architecture.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ In-place patch (`cuos patch*`) modifies `/system.json` directly and keeps previo
140140
| Network | Minimal exposed ports, host network only if required |
141141
| Privileges | App container without `--privileged`; granular capabilities |
142142
| CA | Inject custom CA via `custom_ca_certs` & rotate regularly |
143+
| Confinement | AppArmor is on, and dockerd confines the app container with `docker-default` — `docs/common/apparmor.md` |
143144

144145
---
145146

@@ -150,3 +151,4 @@ In-place patch (`cuos patch*`) modifies `/system.json` directly and keeps previo
150151
* `image-factory/create_image.sh` – Raw image creation
151152
* `installer-factory/create_iso.sh` – Installer ISO
152153
* `docs/common/btrfs-usage.md` – Subvolume layout
154+
* `docs/common/apparmor.md` – What AppArmor confines, and what it does not

‎docs/common/apparmor.md‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# AppArmor
2+
3+
CuOS boots with AppArmor enabled (`apparmor=1` on the kernel command line) on
4+
every platform except LXC. `cuos selftest` reports the state under the
5+
`apparmor` check.
6+
7+
## What is confined
8+
9+
| | Profile | Where it comes from |
10+
|---|---|---|
11+
| The application container | `docker-default` | dockerd's built-in template, loaded when the daemon starts |
12+
| `dhclient` | `/usr/sbin/dhclient` | Debian's `isc-dhcp-client` package |
13+
| PAM's password check | `unix-chkpwd` | Debian's `apparmor` package |
14+
15+
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.
19+
20+
## What is not confined
21+
22+
- **dockerd, and the CuOS scripts.** A daemon that must be able to do anything
23+
cannot usefully be confined, and the CuOS scripts run as root and start
24+
privileged containers.
25+
- **The updater.** `do-update.sh` runs it with `--privileged`, and Docker runs
26+
privileged containers unconfined. It repartitions the root disk, so it needs
27+
to be. It is pulled by digest, and the digest is checked before it runs.
28+
- **sshd.** Debian ships no profile for it, and it is masked unless
29+
`os_ssh_server` is set.
30+
- **The LXC platform.** Profiles are loaded into the host's kernel and a
31+
container cannot load its own, so the LXC image carries no AppArmor at all.
32+
Confining a CuOS guest is the LXC host's business.
33+
- **The installer ISO.** A separate image without the AppArmor package.
34+
35+
## Giving an application more than `docker-default`
36+
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:
40+
41+
```
42+
LABEL dev.cuos.app_command="--security-opt apparmor=my-profile ..."
43+
```
44+
45+
Note that the label **replaces** the default flag set rather than adding to it.
46+
47+
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.
50+
51+
## Reading a denial
52+
53+
```
54+
journalctl -b | grep DENIED
55+
```
56+
57+
Each line names the profile, the operation and the path. To take one profile
58+
out of the way without touching the rest:
59+
60+
```
61+
aa-complain /etc/apparmor.d/usr.sbin.dhclient # log, do not block
62+
aa-enforce /etc/apparmor.d/usr.sbin.dhclient # back to blocking
63+
```
64+
65+
Both are lost on the next boot, which is what you want while diagnosing. A
66+
permanent addition goes into the profile's `local/` include —
67+
`/etc/apparmor.d/local/<profile>` — which survives package upgrades.
68+
69+
## The `/data` trap
70+
71+
CuOS keeps mutable state in `/data` and symlinks into it: `/var/log`,
72+
`/var/lib/dhcp`, `/var/lib/docker`, `/var/lib/containerd`, `/root`.
73+
74+
**AppArmor mediates the resolved path, not the symlink.** A stock profile that
75+
allows `/var/lib/dhcp/dhclient*` therefore denies the write that actually lands
76+
on `/data/dhcp/dhclient.leases`. That is why the image ships
77+
78+
```
79+
/data/dhcp/ r,
80+
/data/dhcp/dhclient* lrw,
81+
```
82+
83+
in `/etc/apparmor.d/local/usr.sbin.dhclient`. Any profile adopted from a
84+
distribution package has to be read against these symlinks first.

0 commit comments

Comments
 (0)