Browse code

Move AppArmor policy to contrib & deb packaging

The automatic installation of AppArmor policies prevents the
management of custom, site-specific apparmor policies for the
default container profile. Furthermore, this change will allow
a future policy for the engine itself to be written without demanding
the engine be able to arbitrarily create and manage AppArmor policies.

- Add deb package suggests for apparmor.
- Ubuntu postinst use aa-status & fix policy path
- Add the policies to the debian packages.
- Add apparmor tests for writing proc files
Additional restrictions against modifying files in proc
are enforced by AppArmor. Ensure that AppArmor is preventing
access to these files, not simply Docker's configuration of proc.
- Remove /proc/k?mem from AA policy
The path to mem and kmem are in /dev, not /proc
and cannot be restricted successfully through AppArmor.
The device cgroup will need to be sufficient here.
- Load contrib/apparmor during integration tests
Note that this is somewhat dirty because we
cannot restore the host to its original configuration.
However, it should be noted that prior to this patch
series, the Docker daemon itself was loading apparmor
policy from within the tests, so this is no dirtier or
uglier than the status-quo.

Signed-off-by: Eric Windisch <eric@windisch.us>

Eric Windisch authored on 2015/05/12 12:00:05
Showing 9 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+#include <tunables/global>
1
+
2
+profile docker-default flags=(attach_disconnected,mediate_deleted) {
3
+  #include <abstractions/base>
4
+
5
+  network,
6
+  capability,
7
+  file,
8
+  umount,
9
+
10
+  deny @{PROC}/sys/fs/** wklx,
11
+  deny @{PROC}/sysrq-trigger rwklx,
12
+  deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
13
+  deny @{PROC}/sys/kernel/*/** wklx,
14
+
15
+  deny mount,
16
+
17
+  deny /sys/[^f]*/** wklx,
18
+  deny /sys/f[^s]*/** wklx,
19
+  deny /sys/fs/[^c]*/** wklx,
20
+  deny /sys/fs/c[^g]*/** wklx,
21
+  deny /sys/fs/cg[^r]*/** wklx,
22
+  deny /sys/firmware/efi/efivars/** rwklx,
23
+  deny /sys/kernel/security/** rwklx,
24
+}
... ...
@@ -50,6 +50,7 @@ for version in "${versions[@]}"; do
50 50
 		build-essential # "essential for building Debian packages"
51 51
 		curl ca-certificates # for downloading Go
52 52
 		debhelper # for easy ".deb" building
53
+		dh-apparmor # for apparmor debhelper
53 54
 		dh-systemd # for systemd debhelper integration
54 55
 		git # for "git commit" info in "docker -v"
55 56
 		libapparmor-dev # for "sys/apparmor.h"
56 57
deleted file mode 100644
... ...
@@ -1,124 +0,0 @@
1
-// +build linux
2
-
3
-package native
4
-
5
-import (
6
-	"fmt"
7
-	"io"
8
-	"os"
9
-	"os/exec"
10
-	"path"
11
-	"text/template"
12
-
13
-	"github.com/opencontainers/runc/libcontainer/apparmor"
14
-)
15
-
16
-const (
17
-	apparmorProfilePath = "/etc/apparmor.d/docker"
18
-)
19
-
20
-type data struct {
21
-	Name         string
22
-	Imports      []string
23
-	InnerImports []string
24
-}
25
-
26
-const baseTemplate = `
27
-{{range $value := .Imports}}
28
-{{$value}}
29
-{{end}}
30
-
31
-profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
32
-{{range $value := .InnerImports}}
33
-  {{$value}}
34
-{{end}}
35
-
36
-  network,
37
-  capability,
38
-  file,
39
-  umount,
40
-
41
-  deny @{PROC}/sys/fs/** wklx,
42
-  deny @{PROC}/sysrq-trigger rwklx,
43
-  deny @{PROC}/mem rwklx,
44
-  deny @{PROC}/kmem rwklx,
45
-  deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
46
-  deny @{PROC}/sys/kernel/*/** wklx,
47
-
48
-  deny mount,
49
-
50
-  deny /sys/[^f]*/** wklx,
51
-  deny /sys/f[^s]*/** wklx,
52
-  deny /sys/fs/[^c]*/** wklx,
53
-  deny /sys/fs/c[^g]*/** wklx,
54
-  deny /sys/fs/cg[^r]*/** wklx,
55
-  deny /sys/firmware/efi/efivars/** rwklx,
56
-  deny /sys/kernel/security/** rwklx,
57
-}
58
-`
59
-
60
-func generateProfile(out io.Writer) error {
61
-	compiled, err := template.New("apparmor_profile").Parse(baseTemplate)
62
-	if err != nil {
63
-		return err
64
-	}
65
-	data := &data{
66
-		Name: "docker-default",
67
-	}
68
-	if tunablesExists() {
69
-		data.Imports = append(data.Imports, "#include <tunables/global>")
70
-	} else {
71
-		data.Imports = append(data.Imports, "@{PROC}=/proc/")
72
-	}
73
-	if abstractionsExists() {
74
-		data.InnerImports = append(data.InnerImports, "#include <abstractions/base>")
75
-	}
76
-	if err := compiled.Execute(out, data); err != nil {
77
-		return err
78
-	}
79
-	return nil
80
-}
81
-
82
-// check if the tunables/global exist
83
-func tunablesExists() bool {
84
-	_, err := os.Stat("/etc/apparmor.d/tunables/global")
85
-	return err == nil
86
-}
87
-
88
-// check if abstractions/base exist
89
-func abstractionsExists() bool {
90
-	_, err := os.Stat("/etc/apparmor.d/abstractions/base")
91
-	return err == nil
92
-}
93
-
94
-func installApparmorProfile() error {
95
-	if !apparmor.IsEnabled() {
96
-		return nil
97
-	}
98
-
99
-	// Make sure /etc/apparmor.d exists
100
-	if err := os.MkdirAll(path.Dir(apparmorProfilePath), 0755); err != nil {
101
-		return err
102
-	}
103
-
104
-	f, err := os.OpenFile(apparmorProfilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
105
-	if err != nil {
106
-		return err
107
-	}
108
-	if err := generateProfile(f); err != nil {
109
-		f.Close()
110
-		return err
111
-	}
112
-	f.Close()
113
-
114
-	cmd := exec.Command("/sbin/apparmor_parser", "-r", "-W", "docker")
115
-	// to use the parser directly we have to make sure we are in the correct
116
-	// dir with the profile
117
-	cmd.Dir = "/etc/apparmor.d"
118
-
119
-	output, err := cmd.CombinedOutput()
120
-	if err != nil {
121
-		return fmt.Errorf("Error loading docker apparmor profile: %s (%s)", err, output)
122
-	}
123
-	return nil
124
-}
... ...
@@ -50,10 +50,6 @@ func NewDriver(root, initPath string, options []string) (*driver, error) {
50 50
 	if err := sysinfo.MkdirAll(root, 0700); err != nil {
51 51
 		return nil, err
52 52
 	}
53
-	// native driver root is at docker_root/execdriver/native. Put apparmor at docker_root
54
-	if err := installApparmorProfile(); err != nil {
55
-		return nil, err
56
-	}
57 53
 
58 54
 	// choose cgroup manager
59 55
 	// this makes sure there are no breaking changes to people
... ...
@@ -9,3 +9,4 @@ contrib/init/systemd/docker.socket lib/systemd/system/
9 9
 contrib/mk* usr/share/docker-engine/contrib/
10 10
 contrib/nuke-graph-directory.sh usr/share/docker-engine/contrib/
11 11
 contrib/syntax/nano/Dockerfile.nanorc usr/share/nano/
12
+contrib/apparmor/* etc/apparmor.d/
... ...
@@ -32,5 +32,8 @@ override_dh_installudev:
32 32
 	# match our existing priority
33 33
 	dh_installudev --priority=z80
34 34
 
35
+override_dh_install:
36
+	dh_apparmor --profile-name=docker -pdocker-engine
37
+
35 38
 %:
36 39
 	dh $@ --with=bash-completion $(shell command -v dh_systemd_enable > /dev/null 2>&1 && echo --with=systemd)
... ...
@@ -35,6 +35,8 @@ if [ -z "$DOCKER_TEST_HOST" ]; then
35 35
 		(
36 36
 			set -x
37 37
 			/etc/init.d/apparmor start
38
+
39
+			/sbin/apparmor_parser -r -W -T contrib/apparmor/
38 40
 		)
39 41
 	fi
40 42
 
... ...
@@ -72,6 +72,10 @@ bundle_ubuntu() {
72 72
 		done
73 73
 	done
74 74
 
75
+	# Include contributed apparmor policy
76
+	mkdir -p "$DIR/etc/apparmor.d/"
77
+	cp contrib/apparmor/docker "$DIR/etc/apparmor.d/"
78
+
75 79
 	# Copy the binary
76 80
 	# This will fail if the binary bundle hasn't been built
77 81
 	mkdir -p "$DIR/usr/bin"
... ...
@@ -89,6 +93,10 @@ if [ "$1" = 'configure' ] && [ -z "$2" ]; then
89 89
 	fi
90 90
 fi
91 91
 
92
+if ( aa-status --enabled ); then
93
+	/sbin/apparmor_parser -r -W -T /etc/apparmor.d/docker
94
+fi
95
+
92 96
 if ! { [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; }; then
93 97
 	# we only need to do this if upstart isn't in charge
94 98
 	update-rc.d docker defaults > /dev/null || true
... ...
@@ -149,6 +157,7 @@ EOF
149 149
 			--deb-recommends git \
150 150
 			--deb-recommends xz-utils \
151 151
 			--deb-recommends 'cgroupfs-mount | cgroup-lite' \
152
+			--deb-suggests apparmor \
152 153
 			--description "$PACKAGE_DESCRIPTION" \
153 154
 			--maintainer "$PACKAGE_MAINTAINER" \
154 155
 			--conflicts docker \
... ...
@@ -2518,3 +2518,25 @@ func (s *DockerSuite) TestVolumeFromMixedRWOptions(c *check.C) {
2518 2518
 		c.Fatalf("Expected RW volume was RO")
2519 2519
 	}
2520 2520
 }
2521
+
2522
+func (s *DockerSuite) TestRunWriteFilteredProc(c *check.C) {
2523
+	testRequires(c, Apparmor)
2524
+
2525
+	testWritePaths := []string{
2526
+		/* modprobe and core_pattern should both be denied by generic
2527
+		 * policy of denials for /proc/sys/kernel. These files have been
2528
+		 * picked to be checked as they are particularly sensitive to writes */
2529
+		"/proc/sys/kernel/modprobe",
2530
+		"/proc/sys/kernel/core_pattern",
2531
+		"/proc/sysrq-trigger",
2532
+	}
2533
+	for i, filePath := range testWritePaths {
2534
+		name := fmt.Sprintf("writeprocsieve-%d", i)
2535
+
2536
+		shellCmd := fmt.Sprintf("exec 3>%s", filePath)
2537
+		runCmd := exec.Command(dockerBinary, "run", "--privileged", "--security-opt", "apparmor:docker-default", "--name", name, "busybox", "sh", "-c", shellCmd)
2538
+		if out, exitCode, err := runCommandWithOutput(runCmd); err == nil || exitCode == 0 {
2539
+			c.Fatalf("Open FD for write should have failed with permission denied, got: %s, %v", out, err)
2540
+		}
2541
+	}
2542
+}