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

Reply via email to