Browse code

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>

Aleksa Sarai authored on 2016/12/05 22:12:17
Showing 4 changed files
... ...
@@ -3,7 +3,8 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
-	"github.com/Sirupsen/logrus"
6
+	"fmt"
7
+
7 8
 	aaprofile "github.com/docker/docker/profiles/apparmor"
8 9
 	"github.com/opencontainers/runc/libcontainer/apparmor"
9 10
 )
... ...
@@ -13,18 +14,23 @@ const (
13 13
 	defaultApparmorProfile = "docker-default"
14 14
 )
15 15
 
16
-func installDefaultAppArmorProfile() {
16
+func ensureDefaultAppArmorProfile() error {
17 17
 	if apparmor.IsEnabled() {
18
+		loaded, err := aaprofile.IsLoaded(defaultApparmorProfile)
19
+		if err != nil {
20
+			return fmt.Errorf("Could not check if %s AppArmor profile was loaded: %s", defaultApparmorProfile, err)
21
+		}
22
+
23
+		// Nothing to do.
24
+		if loaded {
25
+			return nil
26
+		}
27
+
28
+		// Load the profile.
18 29
 		if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
19
-			apparmorProfiles := []string{defaultApparmorProfile}
20
-
21
-			// Allow daemon to run if loading failed, but are active
22
-			// (possibly through another run, manually, or via system startup)
23
-			for _, policy := range apparmorProfiles {
24
-				if loaded, err := aaprofile.IsLoaded(policy); err != nil || !loaded {
25
-					logrus.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
26
-				}
27
-			}
30
+			return fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", defaultApparmorProfile)
28 31
 		}
29 32
 	}
33
+
34
+	return nil
30 35
 }
... ...
@@ -2,5 +2,6 @@
2 2
 
3 3
 package daemon
4 4
 
5
-func installDefaultAppArmorProfile() {
5
+func ensureDefaultAppArmorProfile() error {
6
+	return nil
6 7
 }
... ...
@@ -524,7 +524,10 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
524 524
 		logrus.Warnf("Failed to configure golang's threads limit: %v", err)
525 525
 	}
526 526
 
527
-	installDefaultAppArmorProfile()
527
+	if err := ensureDefaultAppArmorProfile(); err != nil {
528
+		logrus.Errorf(err.Error())
529
+	}
530
+
528 531
 	daemonRepo := filepath.Join(config.Root, "containers")
529 532
 	if err := idtools.MkdirAllAs(daemonRepo, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
530 533
 		return nil, err
... ...
@@ -733,12 +733,27 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
733 733
 	}
734 734
 
735 735
 	if apparmor.IsEnabled() {
736
-		appArmorProfile := "docker-default"
737
-		if len(c.AppArmorProfile) > 0 {
736
+		var appArmorProfile string
737
+		if c.AppArmorProfile != "" {
738 738
 			appArmorProfile = c.AppArmorProfile
739 739
 		} else if c.HostConfig.Privileged {
740 740
 			appArmorProfile = "unconfined"
741
+		} else {
742
+			appArmorProfile = "docker-default"
743
+		}
744
+
745
+		if appArmorProfile == "docker-default" {
746
+			// Unattended upgrades and other fun services can unload AppArmor
747
+			// profiles inadvertently. Since we cannot store our profile in
748
+			// /etc/apparmor.d, nor can we practically add other ways of
749
+			// telling the system to keep our profile loaded, in order to make
750
+			// sure that we keep the default profile enabled we dynamically
751
+			// reload it if necessary.
752
+			if err := ensureDefaultAppArmorProfile(); err != nil {
753
+				return nil, err
754
+			}
741 755
 		}
756
+
742 757
 		s.Process.ApparmorProfile = appArmorProfile
743 758
 	}
744 759
 	s.Process.SelinuxLabel = c.GetProcessLabel()