daemon: respect explicit AppArmor profile on privileged containers#52215
Conversation
This adds two patches with fixes/improvements for the Docker engine - `0001-daemon-respect-explicit-AppArmor-profile-on-privileg.patch`: Makes sure that AppArmor rules are always loaded, also on reboot. This is a long standing bug in Docker and affects Supervisor which is a privileged container with an AppArmor profile. Upstream PR: moby/moby#52215 - `0002-bridge-protect-bridge-subnet-from-direct-external-ac.patch`: Makes sure that the whole network (including gateway IP) of any Docker bridge network in NAT mode is firewalled from access from the outside. This essentially implements on Docker level what Supervisor applies on startup with home-assistant/supervisor#6650. Upstream PR: moby/moby#52224.
This adds two patches with fixes/improvements for the Docker engine - `0001-daemon-respect-explicit-AppArmor-profile-on-privileg.patch`: Makes sure that AppArmor rules are always loaded, also on reboot. This is a long standing bug in Docker and affects Supervisor which is a privileged container with an AppArmor profile. Upstream PR: moby/moby#52215 - `0002-bridge-protect-bridge-subnet-from-direct-external-ac.patch`: Makes sure that the whole network (including gateway IP) of any Docker bridge network in NAT mode is firewalled from access from the outside. This essentially implements on Docker level what Supervisor applies on startup with home-assistant/supervisor#6650. Upstream PR: moby/moby#52224.
|
Thanks! I think this makes sense to do, but also recall I looked at this code (but maybe it was on That may have been on (change LGTM, but just want to be sure there wasn't something ... somewhere ...) |
There was a problem hiding this comment.
Hm... actually; looks like we DO care now? Can you double check this part?
There was a problem hiding this comment.
Hm, good point, I tested this in real world, so now was wondering how this worked 😅
So turns out that parseSecurityOpt reparses the security options (container.SecurityOptions, which contains the promoted field container.AppArmorProfile). I've adjusted this comment.
There was a problem hiding this comment.
Ah! I checked out your branch, and ... YES, now I understand it as well. I'll probably have a look at cleaning up that logic;
- Naming it
parseSecurityOptdoesn't call out "has side-effects" parseSecurityOptlooks to patch the container's config in-place, but can return early on errors, so now we may end up in some halfway state.
It'd probably be clearer to make that function return a fresh container.SecurityOptions, then if there's no errors, we update it on the container.
Either way; unrelated to this PR, so keeping that for a follow-up.
There was a problem hiding this comment.
Heh, and now that I think of it; I THINK this was one of the reasons I started to split these into separate structs .. three years ago (yikes! time flies);
This adds two patches with fixes/improvements for the Docker engine - `0001-daemon-respect-explicit-AppArmor-profile-on-privileg.patch`: Makes sure that AppArmor rules are always loaded, also on reboot. This is a long standing bug in Docker and affects Supervisor which is a privileged container with an AppArmor profile. Upstream PR: moby/moby#52215 - `0002-bridge-protect-bridge-subnet-from-direct-external-ac.patch`: Makes sure that the whole network (including gateway IP) of any Docker bridge network in NAT mode is firewalled from access from the outside. This essentially implements on Docker level what Supervisor applies on startup with home-assistant/supervisor#6650. Upstream PR: moby/moby#52224.
This adds two patches with fixes/improvements for the Docker engine - `0001-daemon-respect-explicit-AppArmor-profile-on-privileg.patch`: Makes sure that AppArmor rules are always loaded, also on reboot. This is a long standing bug in Docker and affects Supervisor which is a privileged container with an AppArmor profile. Upstream PR: moby/moby#52215 - `0002-bridge-protect-bridge-subnet-from-direct-external-ac.patch`: Makes sure that the whole network (including gateway IP) of any Docker bridge network in NAT mode is firewalled from access from the outside. This essentially implements on Docker level what Supervisor applies on startup with home-assistant/supervisor#6650. Upstream PR: moby/moby#52224. (cherry picked from commit 50c1efd)
In saveAppArmorConfig, the privileged check unconditionally overwrites the AppArmor profile to "unconfined", even when the user explicitly set a custom profile via --security-opt apparmor=<profile>. This causes the persisted container.AppArmorProfile to be "unconfined" regardless of user intent. On first container start this is masked because createSpec/WithApparmor runs before saveAppArmorConfig and builds the OCI spec from the in-memory value (which still has the correct profile from creation). But saveAppArmorConfig then overwrites it to "unconfined" and CheckpointTo persists that to disk. On subsequent starts (restart, stop+start, host reboot), the container loads with AppArmorProfile "unconfined", and WithApparmor picks up that stale value — resulting in the container running without its intended AppArmor policy. Fix the condition nesting so that "unconfined" and "docker-default" are only applied as defaults when no explicit profile was set via SecurityOpt. This makes saveAppArmorConfig consistent with the existing behavior in both WithApparmor (oci_linux.go) and execSetPlatformOpt (exec_linux.go), which already give explicit profiles precedence over the privileged default. The bug was introduced in d97a00d (Docker 17.04, moby#27083) and survived a refactor in 483aa62 (moby#43130). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> Signed-off-by: Stefan Agner <[email protected]>
ec31d01 to
80c22f0
Compare
|
Did a quick rebase, and squashed the commits; it's clearer to have the comment change in the same commit. |
|
Thanks for the quick turnaround on this PR 🙏 , I see it will be part of Docker v29.4.0 🎉 Also a huge thank you to @donaldguy, he initially reported the issue (part of home-assistant/supervisor#4839) on our end, and also traced it back to the code lines in Docker in private conversations 🙏 . |
|
Thank you (both!) for contributing, and for linking some of the changes related! The history definitely was helpful here, because there's definitely some "illogical" code paths / behavior in our code that was intentional, but for which the exact reason may not be well-documented, so it sometimes requires a good amount of archeology to verify if there was a specific reason. In this case, that didn't appear to be, so changing looks like the right decision. That said; tens of millions of installs it's possible someone, somewhere depends on the behavior, but we'll see if someone did 😂
Yup! and v29.4.0 should be available for install now on download.docker.com 👍 |
What I did
Fixed
saveAppArmorConfigunconditionally overwriting a user-specified AppArmor profile with"unconfined"on privileged containers. This caused containers with an explicit--security-opt apparmor=<profile>to lose their intended AppArmor policy after a restart (stop+start, host reboot, or daemon restart).How I did it
Inverted the condition nesting in
saveAppArmorConfig(daemon/container_linux.go) so that"unconfined"(privileged default) and"docker-default"are only applied when no explicit profile was set viaSecurityOpt. Previously thePrivilegedcheck came first and unconditionally overwrote any profile parsed fromSecurityOpt.The bug manifests as follows:
docker start,createSpec→WithApparmorbuilds the OCI spec using the correct in-memoryAppArmorProfilevalue (set duringdocker create).saveAppArmorConfigthen runs after spec creation and overwritesAppArmorProfileto"unconfined"for privileged containers.CheckpointTopersists this incorrect"unconfined"value to disk.AppArmorProfile = "unconfined", andWithApparmorpicks up that stale value — the container runs without its intended AppArmor policy.The fix makes
saveAppArmorConfigconsistent with the existing precedence in bothWithApparmor(oci_linux.go) andexecSetPlatformOpt(exec_linux.go), which already give explicit profiles priority over the privileged default.The bug was introduced in d97a00d (Docker 17.04, #27083) and survived a refactor in 483aa62 (#43130).
How to verify it
apparmor_parser -r /path/to/my-profile)Human readable description for the release notes
A picture of a cute animal (not mandatory but encouraged)
From: https://en.wikipedia.org/wiki/File:9-banded-armadillo.jpg
An armadillo — because it knows a thing or two about wearing proper armor at all times 🛡️