|
| 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