From 5f4bc4f916f433a4ba258980a6c2fbdbd76d64f3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sun, 13 Apr 2014 14:20:04 +0000 Subject: [PATCH 1/5] Use apparmor_parser directly The current load script does alot of things. If it does not find the parser loaded on the system it will just exit 0 and not load the profile. We think it should fail loudly if it cannot load the profile and apparmor is enabled on the system. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/apparmor/apparmor.go | 8 +++-- pkg/libcontainer/apparmor/setup.go | 48 +++++++++------------------ 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/pkg/libcontainer/apparmor/apparmor.go b/pkg/libcontainer/apparmor/apparmor.go index 5de241dd97..0987398124 100644 --- a/pkg/libcontainer/apparmor/apparmor.go +++ b/pkg/libcontainer/apparmor/apparmor.go @@ -8,12 +8,16 @@ package apparmor import "C" import ( "io/ioutil" + "os" "unsafe" ) func IsEnabled() bool { - buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") - return err == nil && len(buf) > 1 && buf[0] == 'Y' + if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil { + buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") + return err == nil && len(buf) > 1 && buf[0] == 'Y' + } + return false } func ApplyProfile(pid int, name string) error { diff --git a/pkg/libcontainer/apparmor/setup.go b/pkg/libcontainer/apparmor/setup.go index 548e72f550..e32e8156ca 100644 --- a/pkg/libcontainer/apparmor/setup.go +++ b/pkg/libcontainer/apparmor/setup.go @@ -14,8 +14,6 @@ const ( ) const DefaultProfile = ` -# AppArmor profile from lxc for containers. - #include profile docker-default flags=(attach_disconnected,mediate_deleted) { #include @@ -24,43 +22,28 @@ profile docker-default flags=(attach_disconnected,mediate_deleted) { file, umount, - # ignore DENIED message on / remount - deny mount options=(ro, remount) -> /, - - # allow tmpfs mounts everywhere mount fstype=tmpfs, - - # allow mqueue mounts everywhere mount fstype=mqueue, - - # allow fuse mounts everywhere mount fstype=fuse.*, - - # allow bind mount of /lib/init/fstab for lxcguest - mount options=(rw, bind) /lib/init/fstab.lxc/ -> /lib/init/fstab/, - - # deny writes in /proc/sys/fs but allow binfmt_misc to be mounted mount fstype=binfmt_misc -> /proc/sys/fs/binfmt_misc/, - deny @{PROC}/sys/fs/** wklx, - - # allow efivars to be mounted, writing to it will be blocked though mount fstype=efivarfs -> /sys/firmware/efi/efivars/, + mount fstype=fusectl -> /sys/fs/fuse/connections/, + mount fstype=securityfs -> /sys/kernel/security/, + mount fstype=debugfs -> /sys/kernel/debug/, + mount fstype=proc -> /proc/, + mount fstype=sysfs -> /sys/, - # block some other dangerous paths + deny @{PROC}/sys/fs/** wklx, deny @{PROC}/sysrq-trigger rwklx, deny @{PROC}/mem rwklx, deny @{PROC}/kmem rwklx, deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx, deny @{PROC}/sys/kernel/*/** wklx, - # deny writes in /sys except for /sys/fs/cgroup, also allow - # fusectl, securityfs and debugfs to be mounted there (read-only) - mount fstype=fusectl -> /sys/fs/fuse/connections/, - mount fstype=securityfs -> /sys/kernel/security/, - mount fstype=debugfs -> /sys/kernel/debug/, + deny mount options=(ro, remount) -> /, deny mount fstype=debugfs -> /var/lib/ureadahead/debugfs/, - mount fstype=proc -> /proc/, - mount fstype=sysfs -> /sys/, + deny mount fstype=devpts, + deny /sys/[^f]*/** wklx, deny /sys/f[^s]*/** wklx, deny /sys/fs/[^c]*/** wklx, @@ -68,12 +51,6 @@ profile docker-default flags=(attach_disconnected,mediate_deleted) { deny /sys/fs/cg[^r]*/** wklx, deny /sys/firmware/efi/efivars/** rwklx, deny /sys/kernel/security/** rwklx, - mount options=(move) /sys/fs/cgroup/cgmanager/ -> /sys/fs/cgroup/cgmanager.lower/, - - # the container may never be allowed to mount devpts. If it does, it - # will remount the host's devpts. We could allow it to do it with - # the newinstance option (but, right now, we don't). - deny mount fstype=devpts, } ` @@ -101,11 +78,13 @@ func InstallDefaultProfile(backupPath string) error { return err } defer f.Close() + src, err := os.Open(DefaultProfilePath) if err != nil { return err } defer src.Close() + if _, err := io.Copy(f, src); err != nil { return err } @@ -120,7 +99,10 @@ func InstallDefaultProfile(backupPath string) error { return err } - output, err := exec.Command("/lib/init/apparmor-profile-load", "docker").CombinedOutput() + // the current functionality of the load script is the exit 0 if the parser does not exist. + // we think we should fail loudly if you have apparmor enabled but not the parser to load + // the profile for use. + output, err := exec.Command("/sbin/apparmor_parser", "-r", "-W", "docker").CombinedOutput() if err != nil { return fmt.Errorf("Error loading docker profile: %s (%s)", err, output) } From 052cc5a6378ee4bbe1ef79e5632e2439d68ddbde Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sun, 13 Apr 2014 23:33:25 +0000 Subject: [PATCH 2/5] Move apparmor to top level pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/{libcontainer => }/apparmor/apparmor.go | 0 pkg/{libcontainer => }/apparmor/apparmor_disabled.go | 0 pkg/{libcontainer => }/apparmor/setup.go | 0 pkg/libcontainer/nsinit/init.go | 2 +- runtime/execdriver/native/create.go | 2 +- runtime/execdriver/native/driver.go | 2 +- runtime/execdriver/native/template/default_template.go | 2 +- 7 files changed, 4 insertions(+), 4 deletions(-) rename pkg/{libcontainer => }/apparmor/apparmor.go (100%) rename pkg/{libcontainer => }/apparmor/apparmor_disabled.go (100%) rename pkg/{libcontainer => }/apparmor/setup.go (100%) diff --git a/pkg/libcontainer/apparmor/apparmor.go b/pkg/apparmor/apparmor.go similarity index 100% rename from pkg/libcontainer/apparmor/apparmor.go rename to pkg/apparmor/apparmor.go diff --git a/pkg/libcontainer/apparmor/apparmor_disabled.go b/pkg/apparmor/apparmor_disabled.go similarity index 100% rename from pkg/libcontainer/apparmor/apparmor_disabled.go rename to pkg/apparmor/apparmor_disabled.go diff --git a/pkg/libcontainer/apparmor/setup.go b/pkg/apparmor/setup.go similarity index 100% rename from pkg/libcontainer/apparmor/setup.go rename to pkg/apparmor/setup.go diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index b6c02eafd5..0e85c0e4be 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -8,9 +8,9 @@ import ( "runtime" "syscall" + "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/utils" diff --git a/runtime/execdriver/native/create.go b/runtime/execdriver/native/create.go index 12546145f9..ac67839e4b 100644 --- a/runtime/execdriver/native/create.go +++ b/runtime/execdriver/native/create.go @@ -4,9 +4,9 @@ import ( "fmt" "os" + "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/apparmor" "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/runtime/execdriver/native/configuration" "github.com/dotcloud/docker/runtime/execdriver/native/template" diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index d18865e508..c99cae31dd 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -3,9 +3,9 @@ package native import ( "encoding/json" "fmt" + "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/runtime/execdriver" diff --git a/runtime/execdriver/native/template/default_template.go b/runtime/execdriver/native/template/default_template.go index d3c433a317..c354637fcb 100644 --- a/runtime/execdriver/native/template/default_template.go +++ b/runtime/execdriver/native/template/default_template.go @@ -1,9 +1,9 @@ package template import ( + "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/apparmor" ) // New returns the docker default configuration for libcontainer From 6c26a87901d12188dfd9986d9211f6077a286f9d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sun, 13 Apr 2014 23:37:12 +0000 Subject: [PATCH 3/5] Ignore is not exist error Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/apparmor/setup.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/apparmor/setup.go b/pkg/apparmor/setup.go index e32e8156ca..349476219b 100644 --- a/pkg/apparmor/setup.go +++ b/pkg/apparmor/setup.go @@ -99,11 +99,15 @@ func InstallDefaultProfile(backupPath string) error { return err } - // the current functionality of the load script is the exit 0 if the parser does not exist. - // we think we should fail loudly if you have apparmor enabled but not the parser to load - // the profile for use. output, err := exec.Command("/sbin/apparmor_parser", "-r", "-W", "docker").CombinedOutput() - if err != nil { + if err != nil && !os.IsNotExist(err) { + if e, ok := err.(*exec.Error); ok { + // keeping with the current profile load code, if the parser does not exist then + // just return + if e.Err == exec.ErrNotFound || os.IsNotExist(e.Err) { + return nil + } + } return fmt.Errorf("Error loading docker profile: %s (%s)", err, output) } return nil From 3061a6a2ab0395c626f9acaa2e5d9c17152b0475 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 14 Apr 2014 05:22:45 +0000 Subject: [PATCH 4/5] Generate imports based on what is avaliable Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/apparmor/gen.go | 92 +++++++++++++++++++++++++++++++++++++++++++ pkg/apparmor/setup.go | 61 +++++++--------------------- 2 files changed, 107 insertions(+), 46 deletions(-) create mode 100644 pkg/apparmor/gen.go diff --git a/pkg/apparmor/gen.go b/pkg/apparmor/gen.go new file mode 100644 index 0000000000..bf9be2ae0b --- /dev/null +++ b/pkg/apparmor/gen.go @@ -0,0 +1,92 @@ +package apparmor + +import ( + "io" + "os" + "text/template" +) + +type data struct { + Name string + Imports []string + InnerImports []string +} + +const baseTemplate = ` +{{range $value := .Imports}} +{{$value}} +{{end}} + +profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { +{{range $value := .InnerImports}} + {{$value}} +{{end}} + + network, + capability, + file, + umount, + + mount fstype=tmpfs, + mount fstype=mqueue, + mount fstype=fuse.*, + mount fstype=binfmt_misc -> /proc/sys/fs/binfmt_misc/, + mount fstype=efivarfs -> /sys/firmware/efi/efivars/, + mount fstype=fusectl -> /sys/fs/fuse/connections/, + mount fstype=securityfs -> /sys/kernel/security/, + mount fstype=debugfs -> /sys/kernel/debug/, + mount fstype=proc -> /proc/, + mount fstype=sysfs -> /sys/, + + deny @{PROC}/sys/fs/** wklx, + deny @{PROC}/sysrq-trigger rwklx, + deny @{PROC}/mem rwklx, + deny @{PROC}/kmem rwklx, + deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx, + deny @{PROC}/sys/kernel/*/** wklx, + + deny mount options=(ro, remount) -> /, + deny mount fstype=debugfs -> /var/lib/ureadahead/debugfs/, + deny mount fstype=devpts, + + 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/efi/efivars/** rwklx, + deny /sys/kernel/security/** rwklx, +} +` + +func generateProfile(out io.Writer) error { + compiled, err := template.New("apparmor_profile").Parse(baseTemplate) + if err != nil { + return err + } + data := &data{ + Name: "docker-default", + } + if tuntablesExists() { + data.Imports = append(data.Imports, "#include ") + } + if abstrctionsEsists() { + data.InnerImports = append(data.InnerImports, "#include ") + } + if err := compiled.Execute(out, data); err != nil { + return err + } + return nil +} + +// check if the tunables/global exist +func tuntablesExists() bool { + _, err := os.Stat("/etc/apparmor.d/tunables/global") + return err == nil +} + +// check if abstractions/base exist +func abstrctionsEsists() bool { + _, err := os.Stat("/etc/apparmor.d/abstractions/base") + return err == nil +} diff --git a/pkg/apparmor/setup.go b/pkg/apparmor/setup.go index 349476219b..2401f63414 100644 --- a/pkg/apparmor/setup.go +++ b/pkg/apparmor/setup.go @@ -3,7 +3,6 @@ package apparmor import ( "fmt" "io" - "io/ioutil" "os" "os/exec" "path" @@ -13,47 +12,6 @@ const ( DefaultProfilePath = "/etc/apparmor.d/docker" ) -const DefaultProfile = ` -#include -profile docker-default flags=(attach_disconnected,mediate_deleted) { - #include - network, - capability, - file, - umount, - - mount fstype=tmpfs, - mount fstype=mqueue, - mount fstype=fuse.*, - mount fstype=binfmt_misc -> /proc/sys/fs/binfmt_misc/, - mount fstype=efivarfs -> /sys/firmware/efi/efivars/, - mount fstype=fusectl -> /sys/fs/fuse/connections/, - mount fstype=securityfs -> /sys/kernel/security/, - mount fstype=debugfs -> /sys/kernel/debug/, - mount fstype=proc -> /proc/, - mount fstype=sysfs -> /sys/, - - deny @{PROC}/sys/fs/** wklx, - deny @{PROC}/sysrq-trigger rwklx, - deny @{PROC}/mem rwklx, - deny @{PROC}/kmem rwklx, - deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx, - deny @{PROC}/sys/kernel/*/** wklx, - - deny mount options=(ro, remount) -> /, - deny mount fstype=debugfs -> /var/lib/ureadahead/debugfs/, - deny mount fstype=devpts, - - 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/efi/efivars/** rwklx, - deny /sys/kernel/security/** rwklx, -} -` - func InstallDefaultProfile(backupPath string) error { if !IsEnabled() { return nil @@ -95,15 +53,26 @@ func InstallDefaultProfile(backupPath string) error { return err } - if err := ioutil.WriteFile(DefaultProfilePath, []byte(DefaultProfile), 0644); err != nil { + f, err := os.OpenFile(DefaultProfilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { return err } + if err := generateProfile(f); err != nil { + f.Close() + return err + } + f.Close() - output, err := exec.Command("/sbin/apparmor_parser", "-r", "-W", "docker").CombinedOutput() + cmd := exec.Command("/sbin/apparmor_parser", "-r", "-W", "docker") + // to use the parser directly we have to make sure we are in the correct + // dir with the profile + cmd.Dir = "/etc/apparmor.d" + + output, err := cmd.CombinedOutput() if err != nil && !os.IsNotExist(err) { if e, ok := err.(*exec.Error); ok { - // keeping with the current profile load code, if the parser does not exist then - // just return + // keeping with the current profile load code, if the parser does not + // exist then just return if e.Err == exec.ErrNotFound || os.IsNotExist(e.Err) { return nil } From ac814ee3c76a3851d361e8dddfed7ac93ddf10e2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 21 Apr 2014 10:28:04 -0700 Subject: [PATCH 5/5] Make sure @proc is defined Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/apparmor/gen.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/apparmor/gen.go b/pkg/apparmor/gen.go index bf9be2ae0b..825e646d92 100644 --- a/pkg/apparmor/gen.go +++ b/pkg/apparmor/gen.go @@ -69,6 +69,8 @@ func generateProfile(out io.Writer) error { } if tuntablesExists() { data.Imports = append(data.Imports, "#include ") + } else { + data.Imports = append(data.Imports, "@{PROC}=/proc/") } if abstrctionsEsists() { data.InnerImports = append(data.InnerImports, "#include ")