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: 2f7596aaef ("apparmor: do not save profile to /etc/apparmor.d")
Signed-off-by: Aleksa Sarai <asarai@suse.de>
This commit is contained in:
Aleksa Sarai 2016-12-06 00:12:17 +11:00
parent e440a57a79
commit 567ef8e785
No known key found for this signature in database
GPG key ID: 9E18AA267DDB8DB4
4 changed files with 40 additions and 15 deletions

View file

@ -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() {
if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
apparmorProfiles := []string{defaultApparmorProfile}
loaded, err := aaprofile.IsLoaded(defaultApparmorProfile)
if err != nil {
return fmt.Errorf("Could not check if %s AppArmor profile was loaded: %s", defaultApparmorProfile, err)
}
// 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)
}
}
// Nothing to do.
if loaded {
return nil
}
// Load the profile.
if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
return fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", defaultApparmorProfile)
}
}
return nil
}

View file

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

View file

@ -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

View file

@ -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()