The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/2151
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Note that this only allows privileged containers to load apparmor profiles, and only then with something like: diff --git a/config/apparmor/abstractions/container-base b/config/apparmor/abstractions/container-base index fe24ff3..7138249 100644 --- a/config/apparmor/abstractions/container-base +++ b/config/apparmor/abstractions/container-base @@ -93,7 +93,7 @@ mount fstype=sysfs -> /sys/, mount options=(rw, nosuid, nodev, noexec, remount) -> /sys/, deny /sys/firmware/efi/efivars/** rwklx, - deny /sys/kernel/security/** rwklx, + # deny /sys/kernel/security/** rwklx, mount options=(move) /sys/fs/cgroup/cgmanager/ -> /sys/fs/cgroup/cgmanager.lower/, mount options=(ro, nosuid, nodev, noexec, remount, strictatime) -> /sys/fs/cgroup/, We'll need to do something with the permissions on /sys/kernel/security/apparmor to allow unprivileged users to write to it. I'll be in touch with the security team about that, but for now I don't think this hurts anything. Signed-off-by: Tycho Andersen <tycho.ander...@canonical.com>
From 2f93569e660ded55d554a4a4e6a8065d94835b42 Mon Sep 17 00:00:00 2001 From: Tycho Andersen <tycho.ander...@canonical.com> Date: Fri, 17 Jun 2016 17:51:17 +0000 Subject: [PATCH] apparmor: create an apparmor namespace for each container Note that this only allows privileged containers to load apparmor profiles, and only then with something like: diff --git a/config/apparmor/abstractions/container-base b/config/apparmor/abstractions/container-base index fe24ff3..7138249 100644 --- a/config/apparmor/abstractions/container-base +++ b/config/apparmor/abstractions/container-base @@ -93,7 +93,7 @@ mount fstype=sysfs -> /sys/, mount options=(rw, nosuid, nodev, noexec, remount) -> /sys/, deny /sys/firmware/efi/efivars/** rwklx, - deny /sys/kernel/security/** rwklx, + # deny /sys/kernel/security/** rwklx, mount options=(move) /sys/fs/cgroup/cgmanager/ -> /sys/fs/cgroup/cgmanager.lower/, mount options=(ro, nosuid, nodev, noexec, remount, strictatime) -> /sys/fs/cgroup/, We'll need to do something with the permissions on /sys/kernel/security/apparmor to allow unprivileged users to write to it. I'll be in touch with the security team about that, but for now I don't think this hurts anything. Signed-off-by: Tycho Andersen <tycho.ander...@canonical.com> --- lxd/apparmor.go | 87 ++++++++++++++++++++++++++++++++++++++++++++-------- lxd/container_lxc.go | 25 +++++++++++++-- lxd/daemon.go | 11 +++++++ test/suites/basic.sh | 5 +-- 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/lxd/apparmor.go b/lxd/apparmor.go index ce25c50..c2c2327 100644 --- a/lxd/apparmor.go +++ b/lxd/apparmor.go @@ -47,6 +47,22 @@ const NESTING_AA_PROFILE = ` signal, ` +const DEFAULT_AA_NAMESPACE_PROFILE = ` +#include <tunables/global> +profile "lxd-default" flags=(attach_disconnected,mediate_deleted) { + #include <abstractions/lxc/container-base> + + # Special exception for cgroup namespaces + %s + + # user input raw.apparmor below here + %s + + # nesting support goes here if needed + %s + change_profile -> ":%s://*", +}` + const DEFAULT_AA_PROFILE = ` #include <tunables/global> profile "%s" flags=(attach_disconnected,mediate_deleted) { @@ -63,17 +79,35 @@ profile "%s" flags=(attach_disconnected,mediate_deleted) { change_profile -> "%s", }` -func AAProfileFull(c container) string { - lxddir := shared.VarPath("") - if len(c.Name())+len(lxddir)+7 >= 253 { +func mkApparmorName(name string) string { + if len(name)+7 >= 253 { hash := sha256.New() - io.WriteString(hash, lxddir) - lxddir = fmt.Sprintf("%x", hash.Sum(nil)) + io.WriteString(hash, name) + return fmt.Sprintf("%x", hash.Sum(nil)) } + return name +} + +func AANamespace(c container) string { + /* / is not allowed in apparmor namespace names; let's also trim the + * leading / so it doesn't look like "-var-lib-lxd" + */ + lxddir := strings.Replace(shared.VarPath("")[1:], "/", "-", -1) + lxddir = mkApparmorName(lxddir) return fmt.Sprintf("lxd-%s_<%s>", c.Name(), lxddir) } +func AAProfileFull(c container) string { + if aaStacking { + return fmt.Sprintf(":%s://lxd-default", AANamespace(c)) + } else { + lxddir := shared.VarPath("") + lxddir = mkApparmorName(lxddir) + return fmt.Sprintf("lxd-%s_<%s>", c.Name(), lxddir) + } +} + func AAProfileShort(c container) string { return fmt.Sprintf("lxd-%s", c.Name()) } @@ -99,7 +133,26 @@ func getAAProfileContent(c container) string { nesting = NESTING_AA_PROFILE } - return fmt.Sprintf(DEFAULT_AA_PROFILE, AAProfileFull(c), AAProfileCgns(), rawApparmor, nesting, AAProfileFull(c)) + if aaStacking { + return fmt.Sprintf( + DEFAULT_AA_NAMESPACE_PROFILE, + AAProfileCgns(), + rawApparmor, + nesting, + AANamespace(c), + ) + } else { + full := AAProfileFull(c) + + return fmt.Sprintf( + DEFAULT_AA_PROFILE, + full, + AAProfileCgns(), + rawApparmor, + nesting, + full, + ) + } } func runApparmor(command string, c container) error { @@ -107,12 +160,17 @@ func runApparmor(command string, c container) error { return nil } - cmd := exec.Command("apparmor_parser", []string{ + args := []string{ fmt.Sprintf("-%sWL", command), path.Join(aaPath, "cache"), path.Join(aaPath, "profiles", AAProfileShort(c)), - }...) + } + if aaStacking { + args = append([]string{"-n", AANamespace(c)}, args...) + } + + cmd := exec.Command("apparmor_parser", args...) output, err := cmd.CombinedOutput() if err != nil { shared.Log.Error("Running apparmor", @@ -165,14 +223,19 @@ func AALoadProfile(c container) error { return runApparmor(APPARMOR_CMD_LOAD, c) } -// Ensure that the container's policy is unloaded to free kernel memory. This -// does not delete the policy from disk or cache. -func AAUnloadProfile(c container) error { +// Ensure that the container's policy namespace is unloaded to free kernel +// memory. This does not delete the policy from disk or cache. +func AADestroy(c container) error { if !aaAdmin { return nil } - return runApparmor(APPARMOR_CMD_UNLOAD, c) + if aaStacking { + content := []byte(fmt.Sprintf(":%s:", AANamespace(c))) + return ioutil.WriteFile("/sys/kernel/security/apparmor/.remove", content, 0) + } else { + return runApparmor(APPARMOR_CMD_UNLOAD, c) + } } // Parse the profile without loading it into the kernel. diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go index 6fd0ee4..427e17c 100644 --- a/lxd/container_lxc.go +++ b/lxd/container_lxc.go @@ -293,7 +293,12 @@ func (c *containerLXC) initLXC() error { } // Base config - err = lxcSetConfigItem(cc, "lxc.cap.drop", "mac_admin mac_override sys_time sys_module sys_rawio") + toDrop := "sys_time sys_module sys_rawio" + if !aaStacking || c.IsPrivileged() { + toDrop = toDrop + " mac_admin mac_override" + } + + err = lxcSetConfigItem(cc, "lxc.cap.drop", toDrop) if err != nil { return err } @@ -490,7 +495,19 @@ func (c *containerLXC) initLXC() error { } } else { // If not currently confined, use the container's profile - err := lxcSetConfigItem(cc, "lxc.aa_profile", AAProfileFull(c)) + profile := AAProfileFull(c) + + /* In the unprivileged case, we're relying on the user + * namespace to do all the access control. We can thus + * just move the container into a namespace and leave + * it unconfined, so it can load its own profiles if it + * wants to. + */ + if aaStacking && !c.IsPrivileged() { + profile = fmt.Sprintf(":%s:", AANamespace(c)) + } + + err := lxcSetConfigItem(cc, "lxc.aa_profile", profile) if err != nil { return err } @@ -1450,7 +1467,9 @@ func (c *containerLXC) OnStop(target string) error { } // Unload the apparmor profile - AAUnloadProfile(c) + if err := AADestroy(c); err != nil { + shared.Log.Error("failed to destroy apparmor namespace", log.Ctx{"container": c.Name(), "err": err}) + } // FIXME: The go routine can go away once we can rely on LXC_TARGET go func(c *containerLXC, target string, wg *sync.WaitGroup) { diff --git a/lxd/daemon.go b/lxd/daemon.go index 37a2338..8657746 100644 --- a/lxd/daemon.go +++ b/lxd/daemon.go @@ -41,6 +41,7 @@ import ( var aaAdmin = true var aaAvailable = true var aaConfined = false +var aaStacking = false // CGroup var cgBlkioController = false @@ -626,6 +627,16 @@ func (d *Daemon) Init() error { } } + if aaAvailable { + content, err := ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/stack") + if err == nil && string(content) == "yes\n" { + aaStacking = true + shared.Log.Warn("Enabled apparmor stacking") + } else { + shared.Log.Warn("Kernel doesn't support apparmor stacking") + } + } + /* Detect CGroup support */ cgBlkioController = shared.PathExists("/sys/fs/cgroup/blkio/") if !cgBlkioController { diff --git a/test/suites/basic.sh b/test/suites/basic.sh index 7e4f915..0f5f265 100644 --- a/test/suites/basic.sh +++ b/test/suites/basic.sh @@ -299,9 +299,10 @@ test_basic_usage() { # check that an apparmor profile is created for this container, that it is # unloaded on stop, and that it is deleted when the container is deleted lxc launch testimage lxd-apparmor-test - aa-status | grep "lxd-lxd-apparmor-test_<${LXD_DIR}>" + aa_namespace="lxd-lxd-apparmor-test_<$(echo "${LXD_DIR}" | sed -e 's/\//-/g' -e 's/^.//')>" + aa-status | grep ":${aa_namespace}://lxd-default" lxc stop lxd-apparmor-test --force - ! aa-status | grep -q "lxd-lxd-apparmor-test_<${LXD_DIR}>" + ! aa-status | grep -q ":${aa_namespace}://lxd-default" lxc delete lxd-apparmor-test [ ! -f "${LXD_DIR}/security/apparmor/profiles/lxd-lxd-apparmor-test" ]
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel