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>
| 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 |
| ... | ... |
@@ -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) |
| ... | ... |
@@ -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 |
+} |