Bläddra i källkod

daemon: switch to 'ensure' workflow for AppArmor profiles

In certain cases (unattended upgrades), system services can disable
loaded AppArmor profiles. However, since /etc being read-only is a
supported setup we cannot just write a copy of the profile to
/etc/apparmor.d.

Instead, dynamically load the docker-default AppArmor profile if a
container is started with that profile set. This code will short-cut if
the profile is already loaded.

Fixes: 2f7596aaef3a ("apparmor: do not save profile to /etc/apparmor.d")
Signed-off-by: Aleksa Sarai <asarai@suse.de>
(cherry picked from commit 567ef8e7858ca4f282f598ba1f5a951cbad39e83)
Signed-off-by: Victor Vieux <vieux@docker.com>
Aleksa Sarai 8 år sedan
förälder
incheckning
80c3ed1c0c

+ 17 - 11
daemon/apparmor_default.go

@@ -3,7 +3,8 @@
 package daemon
 
 import (
-	"github.com/Sirupsen/logrus"
+	"fmt"
+
 	aaprofile "github.com/docker/docker/profiles/apparmor"
 	"github.com/opencontainers/runc/libcontainer/apparmor"
 )
@@ -13,18 +14,23 @@ const (
 	defaultApparmorProfile = "docker-default"
 )
 
-func installDefaultAppArmorProfile() {
+func ensureDefaultAppArmorProfile() error {
 	if apparmor.IsEnabled() {
+		loaded, err := aaprofile.IsLoaded(defaultApparmorProfile)
+		if err != nil {
+			return fmt.Errorf("Could not check if %s AppArmor profile was loaded: %s", defaultApparmorProfile, err)
+		}
+
+		// Nothing to do.
+		if loaded {
+			return nil
+		}
+
+		// Load the profile.
 		if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
-			apparmorProfiles := []string{defaultApparmorProfile}
-
-			// Allow daemon to run if loading failed, but are active
-			// (possibly through another run, manually, or via system startup)
-			for _, policy := range apparmorProfiles {
-				if loaded, err := aaprofile.IsLoaded(policy); err != nil || !loaded {
-					logrus.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
-				}
-			}
+			return fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", defaultApparmorProfile)
 		}
 	}
+
+	return nil
 }

+ 2 - 1
daemon/apparmor_default_unsupported.go

@@ -2,5 +2,6 @@
 
 package daemon
 
-func installDefaultAppArmorProfile() {
+func ensureDefaultAppArmorProfile() error {
+	return nil
 }

+ 4 - 1
daemon/daemon.go

@@ -524,7 +524,10 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
 		logrus.Warnf("Failed to configure golang's threads limit: %v", err)
 	}
 
-	installDefaultAppArmorProfile()
+	if err := ensureDefaultAppArmorProfile(); err != nil {
+		logrus.Errorf(err.Error())
+	}
+
 	daemonRepo := filepath.Join(config.Root, "containers")
 	if err := idtools.MkdirAllAs(daemonRepo, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
 		return nil, err

+ 17 - 2
daemon/oci_linux.go

@@ -733,12 +733,27 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 	}
 
 	if apparmor.IsEnabled() {
-		appArmorProfile := "docker-default"
-		if len(c.AppArmorProfile) > 0 {
+		var appArmorProfile string
+		if c.AppArmorProfile != "" {
 			appArmorProfile = c.AppArmorProfile
 		} else if c.HostConfig.Privileged {
 			appArmorProfile = "unconfined"
+		} else {
+			appArmorProfile = "docker-default"
+		}
+
+		if appArmorProfile == "docker-default" {
+			// Unattended upgrades and other fun services can unload AppArmor
+			// profiles inadvertently. Since we cannot store our profile in
+			// /etc/apparmor.d, nor can we practically add other ways of
+			// telling the system to keep our profile loaded, in order to make
+			// sure that we keep the default profile enabled we dynamically
+			// reload it if necessary.
+			if err := ensureDefaultAppArmorProfile(); err != nil {
+				return nil, err
+			}
 		}
+
 		s.Process.ApparmorProfile = appArmorProfile
 	}
 	s.Process.SelinuxLabel = c.GetProcessLabel()

+ 1 - 1
integration-cli/docker_cli_swarm_test.go

@@ -1057,7 +1057,7 @@ func (s *DockerSwarmSuite) TestSwarmNetworkIPAMOptions(c *check.C) {
 	c.Assert(err, checker.IsNil, check.Commentf(out))
 
 	// make sure task has been deployed.
-	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
+	waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 1)
 
 	out, err = d.Cmd("network", "inspect", "--format", "{{.IPAM.Options}}", "foo")
 	c.Assert(err, checker.IsNil, check.Commentf(out))