1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
<h1>AppArmor security profiles for Docker</h1>
<p>AppArmor (Application Armor) is a Linux security module that protects an operating system and its applications from security threats. To use it, a system administrator associates an AppArmor security profile with each program. Docker expects to find an AppArmor policy loaded and enforced.</p> <p>Docker automatically generates and loads a default profile for containers named <code class="language-plaintext highlighter-rouge">docker-default</code>. The Docker binary generates this profile in <code class="language-plaintext highlighter-rouge">tmpfs</code> and then loads it into the kernel.</p> <blockquote> <p><strong>Note</strong>: This profile is used on containers, <em>not</em> on the Docker Daemon.</p> </blockquote> <p>A profile for the Docker Engine daemon exists but it is not currently installed with the <code class="language-plaintext highlighter-rouge">deb</code> packages. If you are interested in the source for the daemon profile, it is located in <a href="https://github.com/moby/moby/tree/master/contrib/apparmor">contrib/apparmor</a> in the Docker Engine source repository.</p> <h2 id="understand-the-policies">Understand the policies</h2> <p>The <code class="language-plaintext highlighter-rouge">docker-default</code> profile is the default for running containers. It is moderately protective while providing wide application compatibility. The profile is generated from the following <a href="https://github.com/moby/moby/blob/master/profiles/apparmor/template.go">template</a>.</p> <p>When you run a container, it uses the <code class="language-plaintext highlighter-rouge">docker-default</code> policy unless you override it with the <code class="language-plaintext highlighter-rouge">security-opt</code> option. For example, the following explicitly specifies the default policy:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker run --rm -it --security-opt apparmor=docker-default hello-world
</pre></div> <h2 id="load-and-unload-profiles">Load and unload profiles</h2> <p>To load a new profile into AppArmor for use with containers:</p> <div class="highlight"><pre class="highlight" data-language="">$ apparmor_parser -r -W /path/to/your_profile
</pre></div> <p>Then, run the custom profile with <code class="language-plaintext highlighter-rouge">--security-opt</code> like so:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker run --rm -it --security-opt apparmor=your_profile hello-world
</pre></div> <p>To unload a profile from AppArmor:</p> <div class="highlight"><pre class="highlight" data-language=""># unload the profile
$ apparmor_parser -R /path/to/profile
</pre></div> <h3 id="resources-for-writing-profiles">Resources for writing profiles</h3> <p>The syntax for file globbing in AppArmor is a bit different than some other globbing implementations. It is highly suggested you take a look at some of the below resources with regard to AppArmor profile syntax.</p> <ul> <li><a href="https://gitlab.com/apparmor/apparmor/wikis/QuickProfileLanguage">Quick Profile Language</a></li> <li><a href="https://gitlab.com/apparmor/apparmor/wikis/AppArmor_Core_Policy_Reference#AppArmor_globbing_syntax">Globbing Syntax</a></li> </ul> <h2 id="nginx-example-profile">Nginx example profile</h2> <p>In this example, you create a custom AppArmor profile for Nginx. Below is the custom profile.</p> <div class="highlight"><pre class="highlight" data-language="">#include <tunables/global>
profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network inet tcp,
network inet udp,
network inet icmp,
deny network raw,
deny network packet,
file,
umount,
deny /bin/** wl,
deny /boot/** wl,
deny /dev/** wl,
deny /etc/** wl,
deny /home/** wl,
deny /lib/** wl,
deny /lib64/** wl,
deny /media/** wl,
deny /mnt/** wl,
deny /opt/** wl,
deny /proc/** wl,
deny /root/** wl,
deny /sbin/** wl,
deny /srv/** wl,
deny /tmp/** wl,
deny /sys/** wl,
deny /usr/** wl,
audit /** w,
/var/run/nginx.pid w,
/usr/sbin/nginx ix,
deny /bin/dash mrwklx,
deny /bin/sh mrwklx,
deny /usr/bin/top mrwklx,
capability chown,
capability dac_override,
capability setuid,
capability setgid,
capability net_bind_service,
deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
# deny write to files not in /proc/<number>/** or /proc/sys/**
deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/mem rwklx,
deny @{PROC}/kmem rwklx,
deny @{PROC}/kcore rwklx,
deny mount,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
deny /sys/firmware/** rwklx,
deny /sys/kernel/security/** rwklx,
}
</pre></div> <ol> <li> <p>Save the custom profile to disk in the <code class="language-plaintext highlighter-rouge">/etc/apparmor.d/containers/docker-nginx</code> file.</p> <p>The file path in this example is not a requirement. In production, you could use another.</p> </li> <li> <p>Load the profile.</p> <div class="highlight"><pre class="highlight" data-language="">$ sudo apparmor_parser -r -W /etc/apparmor.d/containers/docker-nginx
</pre></div> </li> <li> <p>Run a container with the profile.</p> <p>To run nginx in detached mode:</p> <div class="highlight"><pre class="highlight" data-language="">$ docker run --security-opt "apparmor=docker-nginx" \
-p 80:80 -d --name apparmor-nginx nginx
</pre></div> </li> <li> <p>Exec into the running container.</p> <div class="highlight"><pre class="highlight" data-language="">$ docker container exec -it apparmor-nginx bash
</pre></div> </li> <li> <p>Try some operations to test the profile.</p> <div class="highlight"><pre class="highlight" data-language="">root@6da5a2a930b9:~# ping 8.8.8.8
ping: Lacking privilege for raw socket.
root@6da5a2a930b9:/# top
bash: /usr/bin/top: Permission denied
root@6da5a2a930b9:~# touch ~/thing
touch: cannot touch 'thing': Permission denied
root@6da5a2a930b9:/# sh
bash: /bin/sh: Permission denied
root@6da5a2a930b9:/# dash
bash: /bin/dash: Permission denied
</pre></div> </li> </ol> <p>Congrats! You just deployed a container secured with a custom apparmor profile!</p> <h2 id="debug-apparmor">Debug AppArmor</h2> <p>You can use <code class="language-plaintext highlighter-rouge">dmesg</code> to debug problems and <code class="language-plaintext highlighter-rouge">aa-status</code> check the loaded profiles.</p> <h3 id="use-dmesg">Use dmesg</h3> <p>Here are some helpful tips for debugging any problems you might be facing with regard to AppArmor.</p> <p>AppArmor sends quite verbose messaging to <code class="language-plaintext highlighter-rouge">dmesg</code>. Usually an AppArmor line looks like the following:</p> <div class="highlight"><pre class="highlight" data-language="">[ 5442.864673] audit: type=1400 audit(1453830992.845:37): apparmor="ALLOWED" operation="open" profile="/usr/bin/docker" name="/home/jessie/docker/man/man1/docker-attach.1" pid=10923 comm="docker" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0
</pre></div> <p>In the above example, you can see <code class="language-plaintext highlighter-rouge">profile=/usr/bin/docker</code>. This means the user has the <code class="language-plaintext highlighter-rouge">docker-engine</code> (Docker Engine Daemon) profile loaded.</p> <p>Look at another log line:</p> <div class="highlight"><pre class="highlight" data-language="">[ 3256.689120] type=1400 audit(1405454041.341:73): apparmor="DENIED" operation="ptrace" profile="docker-default" pid=17651 comm="docker" requested_mask="receive" denied_mask="receive"
</pre></div> <p>This time the profile is <code class="language-plaintext highlighter-rouge">docker-default</code>, which is run on containers by default unless in <code class="language-plaintext highlighter-rouge">privileged</code> mode. This line shows that apparmor has denied <code class="language-plaintext highlighter-rouge">ptrace</code> in the container. This is exactly as expected.</p> <h3 id="use-aa-status">Use aa-status</h3> <p>If you need to check which profiles are loaded, you can use <code class="language-plaintext highlighter-rouge">aa-status</code>. The output looks like:</p> <div class="highlight"><pre class="highlight" data-language="">$ sudo aa-status
apparmor module is loaded.
14 profiles are loaded.
1 profiles are in enforce mode.
docker-default
13 profiles are in complain mode.
/usr/bin/docker
/usr/bin/docker///bin/cat
/usr/bin/docker///bin/ps
/usr/bin/docker///sbin/apparmor_parser
/usr/bin/docker///sbin/auplink
/usr/bin/docker///sbin/blkid
/usr/bin/docker///sbin/iptables
/usr/bin/docker///sbin/mke2fs
/usr/bin/docker///sbin/modprobe
/usr/bin/docker///sbin/tune2fs
/usr/bin/docker///sbin/xtables-multi
/usr/bin/docker///sbin/zfs
/usr/bin/docker///usr/bin/xz
38 processes have profiles defined.
37 processes are in enforce mode.
docker-default (6044)
...
docker-default (31899)
1 processes are in complain mode.
/usr/bin/docker (29756)
0 processes are unconfined but have a profile defined.
</pre></div> <p>The above output shows that the <code class="language-plaintext highlighter-rouge">docker-default</code> profile running on various container PIDs is in <code class="language-plaintext highlighter-rouge">enforce</code> mode. This means AppArmor is actively blocking and auditing in <code class="language-plaintext highlighter-rouge">dmesg</code> anything outside the bounds of the <code class="language-plaintext highlighter-rouge">docker-default</code> profile.</p> <p>The output above also shows the <code class="language-plaintext highlighter-rouge">/usr/bin/docker</code> (Docker Engine daemon) profile is running in <code class="language-plaintext highlighter-rouge">complain</code> mode. This means AppArmor <em>only</em> logs to <code class="language-plaintext highlighter-rouge">dmesg</code> activity outside the bounds of the profile. (Except in the case of Ubuntu Trusty, where some interesting behaviors are enforced.)</p> <h2 id="contribute-dockers-apparmor-code">Contribute Docker’s AppArmor code</h2> <p>Advanced users and package managers can find a profile for <code class="language-plaintext highlighter-rouge">/usr/bin/docker</code> (Docker Engine Daemon) underneath <a href="https://github.com/moby/moby/tree/master/contrib/apparmor">contrib/apparmor</a> in the Docker Engine source repository.</p> <p>The <code class="language-plaintext highlighter-rouge">docker-default</code> profile for containers lives in <a href="https://github.com/moby/moby/tree/master/profiles/apparmor">profiles/apparmor</a>.</p>
<p><a href="https://docs.docker.com/search/?q=AppArmor">AppArmor</a>, <a href="https://docs.docker.com/search/?q=security">security</a>, <a href="https://docs.docker.com/search/?q=docker">docker</a>, <a href="https://docs.docker.com/search/?q=documentation">documentation</a></p>
<div class="_attribution">
<p class="_attribution-p">
© 2019 Docker, Inc.<br>Licensed under the Apache License, Version 2.0.<br>Docker and the Docker logo are trademarks or registered trademarks of Docker, Inc. in the United States and/or other countries.<br>Docker, Inc. and other parties may also have trademark rights in other terms used herein.<br>
<a href="https://docs.docker.com/engine/security/apparmor/" class="_attribution-link">https://docs.docker.com/engine/security/apparmor/</a>
</p>
</div>
|