Confining Resources inside Docker Containers with AppArmor
Can docker containers be protected via AppArmor? Well yes, they can and in fact, they are already being protected by apparmor in your serves. Learn more about how docker and apparmor works and make your existing docker setup more secure
Until my last post on AppArmor, you have seen me working on a localhost environment and confining the programs files. But what for programs running inside docker or the docker engine itself? Can they also be confined or it is a limitation of AppArmor?
Well, the answer to the above questions is YES. You can control certain privileges and access rights in the docker container using AppArmor. In fact, if you have AppArmor and docker-engine already installed, the docker is already using it under the hood. For demonstration, I will be using the lab provided by AttackDefense
Getting Docker Security Option Information
In this lab, you will have a root user shell with docker CLI installed and an AppArmor security option that exists. This means that the docker support AppArmor for the container.

A profile named docker-default
is automatically loaded in tmpfs
and enforced while installing docker. So that is why you can't see any file in /etc/apparmor.d/

docker-default
generated by docker and loaded into the kernelThe profile is generated from the following moby/moby#profiles/apparmor/template.go
template and is general for all the containers. This profile is used on containers, not on the Docker Daemon. If you are interested in the daemon profile, checkout out contrib/apparmor.
Unconfined vs Confined Containers
You can also explicitly specify the profile used --security-opt apparmor=[PROFILE_NAME|unconfined]
to set up the access controls for the container. By default, the profile name would be docker-default
for all the containers. When you set apparmor=unconfined
it will not load the docker-default profile for that container.

docker-default
AppArmor profileNow if you will set the apparmor status to unconfined, it will not show the process id running in enforced mode.

All the activities performed in the docker container which flags apparmor can be found in the file /var/log/audit/audit.log
of the host file system. This is because the docker container is running with containerd and which is running on the host system via docker.service systemd unit

Creating your own Profile for Docker
So far you have seen the docker injects its own default apparmor profile into tmpfs and that is invisible to you. But the interesting part is you can load your profile to a particular container.
So here's what docker does:
- If you omit
--security-opt
field, it will automatically load a global docker-profile into the container. - If you set appamor=unconfined, it will disable the apparmor for that container but will still work for the old running containers
- If you set apparmor=<PROFILE_NAME> it will load that apparmor profile in the container.
You can override the default profile by creating your own profile with the same name and loading it in the kernel
$ cat << EOF > default-docker-profile
#include <tunables/global>
profile docker-default flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
YOUR PROFILE DEFINITION HERE
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
ptrace (trace,read) peer=docker-app,
}
$ sudo apparmor_parser -r default-docker-profile
$ sudo docker run -it myimage:latest bash
In the above example, I have reloaded the profile instead of adding it, that is because the profile name docker-default
already exists. If you will try to add, it will throw an error which is following

Now let's create our own docker profile where we will disable the unmounting. To make things easy, let's copy the existing docker-default.profile
to docker-app.profile
and make changes in that file as shown below
- Change the profile name to avoid conflicts with the already loaded docker-default profile
- profile docker-default flags=(attach_disconnected,mediate_deleted) { + profile docker-app flags=(attach_disconnected,mediate_deleted) {
- Add
deny
in front of umount. Remember all the rules are by default allowed, you have to explicitly label them as to deny- umount, + deny umount,
- Configure ptrace with current docker profile to avoid conflicts with another profile
- ptrace (trace,read) peer=docker-default, + ptrace (trace,read) peer=docker-app,
After the profile is loaded, you need to first add it to the kernel using apparmor_parser -a docker-app.profile
. In this case, if you will use a -r
flag, it will throw an error because the docker-app profile is not found in the kernel.

docker-app
profile in the kernel Let's start the container with CAP_SYS_ADMIN
capability and this time apparmor profile set to docker-app
. Now if you will try to unmount /etc/hosts file from the container, it will throw the permission denied error

docker-app
profileNow check out audit logs and you will find umount operation got logged and apparmor denied that access

References
- https://attackdefense.com/challengedetails?cid=1837
- https://docs.docker.com/engine/security/apparmor/
- https://tbhaxor.com/understanding-container-architecture/
- https://tbhaxor.com/writing-apparmor-profile-from-scratch/
- https://cloud.google.com/container-optimized-os/docs/how-to/secure-apparmor
- https://dockerlabs.collabnix.com/advanced/security/apparmor/