File docker-mount-secrets.patch of Package docker

From 17cd15ba4160f0e0830453529b9b01edc308d847 Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <asarai@suse.de>
Date: Mon, 11 Apr 2016 22:54:35 +1000
Subject: [PATCH] SUSE: implement SUSE container secrets

This allows for us to pass in host credentials to a container, allowing
for SUSEConnect to work with containers.

THIS PATCH IS NOT TO BE UPSTREAMED, DUE TO THE FACT THAT IT IS
SUSE-SPECIFIC, AND UPSTREAM DOES NOT APPROVE OF THIS CONCEPT BECAUSE IT
MAKES BUILDS NOT ENTIRELY REPRODUCIBLE.

Signed-off-by: Aleksa Sarai <asarai@suse.de>
---
 container/container_unix.go         |  63 ++++++++++++
 daemon/container_operations_unix.go |  50 ++++++++++
 daemon/daemon_unix.go               |   6 +-
 daemon/oci_linux.go                 |   6 ++
 daemon/start.go                     |   6 ++
 daemon/suse_secrets.go              | 184 ++++++++++++++++++++++++++++++++++++
 6 files changed, 313 insertions(+), 2 deletions(-)
 create mode 100644 daemon/suse_secrets.go

diff --git a/container/container_unix.go b/container/container_unix.go
index 2727b81..07a0710 100644
--- a/container/container_unix.go
+++ b/container/container_unix.go
@@ -35,6 +35,8 @@ type Container struct {
 	HostsPath       string
 	ShmPath         string
 	ResolvConfPath  string
+	// SUSE:secrets :: We need to add the container-specific secrets path here.
+	SuseSecretsPath string
 	SeccompProfile  string
 	NoNewPrivileges bool
 }
@@ -256,6 +258,67 @@ func (container *Container) IpcMounts() []Mount {
 	return mounts
 }
 
+// SUSE:secrets :: SuseSecretsResourcePath returns the path to the container's
+// personal /run/secrets tmpfs.
+func (container *Container) SuseSecretsResourcePath() (string, error) {
+	return container.GetRootResourcePath("suse.secrets")
+}
+
+// SUSE:secrets :: SuseSecretMounts returns the list of mounts required for the
+// SUSE-specific /run/secrets patch. The container's personal /run/secrets tmpfs
+// has already been set up at this point.
+func (container *Container) SuseSecretMounts() []Mount {
+	var mounts []Mount
+
+	logrus.WithFields(logrus.Fields{
+		"container": container.ID,
+		"path":      container.SuseSecretsPath,
+		"hasmount":  container.HasMountFor("/run/secrets"),
+	}).Debug("SUSE:secrets :: adding container secrets to mountpoint")
+
+	// TODO(SUSE): How do we register for HasMountFor().
+	if !container.HasMountFor("/run/secrets") {
+		label.SetFileLabel(container.SuseSecretsPath, container.MountLabel)
+		mounts = append(mounts, Mount{
+			Source:      container.SuseSecretsPath,
+			Destination: "/run/secrets",
+			Writable:    true,
+			Propagation: volume.DefaultPropagationMode,
+		})
+	}
+
+	return mounts
+}
+
+// SUSE:secrets :: Unmounts the container's personal /run/secrets tmpfs using the
+// provided function. This is done to clean up the mountpoints properly.
+func (container *Container) UnmountSuseSecretMounts(unmount func(string) error) {
+	logrus.WithFields(logrus.Fields{
+		"container": container.ID,
+		"hasmount":  container.HasMountFor("/run/secrets"),
+	}).Debug("SUSE:secrets :: requested to clean up container secrets")
+
+	if !container.HasMountFor("/run/secrets") {
+		logrus.Debugf("SUSE:secrets :: cleaning up secrets mount for container")
+
+		suseSecretsPath, err := container.SuseSecretsResourcePath()
+		if err != nil {
+			logrus.Error("SUSE:secrets :: failed to clean up secrets mounts: no secrets resource path found for container %v: %v", container.ID, err)
+		}
+
+		if suseSecretsPath != "" {
+			logrus.WithFields(logrus.Fields{
+				"path": suseSecretsPath,
+			}).Debugf("SUSE:secrets :: actually unmounting conatiner secrets")
+
+			if err := unmount(suseSecretsPath); err != nil && !os.IsNotExist(err) {
+				// We can't error out here.
+				logrus.Warnf("SUSE:secrets :: failed to clean up secrets mounts: failed to umount %s: %v", suseSecretsPath, err)
+			}
+		}
+	}
+}
+
 // UpdateContainer updates configuration of a container.
 func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
 	container.Lock()
diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go
index 55bd3fc..a3ab7fb 100644
--- a/daemon/container_operations_unix.go
+++ b/daemon/container_operations_unix.go
@@ -184,6 +184,56 @@ func (daemon *Daemon) getPidContainer(container *container.Container) (*containe
 	return c, nil
 }
 
+// SUSE:secrets :: Create a container's personal /run/secrets tmpfs and fill it
+// with the host's credentials.
+func (daemon *Daemon) setupSuseSecrets(c *container.Container) (err error) {
+	c.SuseSecretsPath, err = c.SuseSecretsResourcePath()
+	if err != nil {
+		return err
+	}
+
+	if !c.HasMountFor("/run/secrets") {
+		rootUID, rootGID := daemon.GetRemappedUIDGID()
+		if err = idtools.MkdirAllAs(c.SuseSecretsPath, 0700, rootUID, rootGID); err != nil {
+			return fmt.Errorf("SUSE:secrets :: failed to create container secret: %v", err)
+		}
+		if err = syscall.Mount("tmpfs", c.SuseSecretsPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel("", c.GetMountLabel())); err != nil {
+			return fmt.Errorf("SUSE:secrets :: mounting secrets tmpfs: %v", err)
+		}
+		// We need to defer a cleanup, to make sure errors that occur before the container
+		// starts don't cause wasted memory due to tmpfs-es that aren't being used.
+		defer func() {
+			if err != nil {
+				logrus.Infof("SUSE::secrets :: cleaning up secrets mount due to failed setup")
+				c.UnmountSuseSecretMounts(detachMounted)
+			}
+		}()
+		if err = os.Chown(c.SuseSecretsPath, rootUID, rootGID); err != nil {
+			return fmt.Errorf("SUSE:secrets :: failed to chown container secret to (uid=%d,gid=%d): %v", rootUID, rootGID, err)
+		}
+
+		// Now we need to inject the credentials. But in order to play properly with
+		// user namespaces, they must be owned by rootUID:rootGID.
+
+		data, err := getHostSuseSecretData()
+		if err != nil {
+			return fmt.Errorf("SUSE:secrets :: failed to get host secret data: %v", err)
+		}
+
+		uidMap, gidMap := daemon.GetUIDGIDMaps()
+		for _, s := range data {
+			if err := s.SaveTo(c.SuseSecretsPath, uidMap, gidMap); err != nil {
+				logrus.WithFields(logrus.Fields{
+					"s.path": s.Path,
+					"path":   c.SuseSecretsPath,
+				}).Errorf("SUSE:secrets :: failed to save secret data: %v", err)
+			}
+		}
+	}
+
+	return
+}
+
 func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
 	var err error
 
diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go
index f266912..2ced1b8 100644
--- a/daemon/daemon_unix.go
+++ b/daemon/daemon_unix.go
@@ -809,8 +809,10 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *Config) e
 // the container from unwanted side-effects on the rw layer.
 func setupInitLayer(initLayer string, rootUID, rootGID int) error {
 	for pth, typ := range map[string]string{
-		"/dev/pts":         "dir",
-		"/dev/shm":         "dir",
+		"/dev/pts": "dir",
+		"/dev/shm": "dir",
+		// SUSE:secrets :: We need to add the mountpoint in the init layer.
+		"/run/secrets":     "dir",
 		"/proc":            "dir",
 		"/sys":             "dir",
 		"/.dockerenv":      "file",
diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go
index 4459d02..6af7d35 100644
--- a/daemon/oci_linux.go
+++ b/daemon/oci_linux.go
@@ -656,6 +656,10 @@ func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, e
 	if err := daemon.setupIpcDirs(c); err != nil {
 		return nil, err
 	}
+	// SUSE:secrets :: We need to set up the container-specific secrets tmpfs here.
+	if err := daemon.setupSuseSecrets(c); err != nil {
+		return nil, err
+	}
 
 	ms, err := daemon.setupMounts(c)
 	if err != nil {
@@ -663,6 +667,8 @@ func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, e
 	}
 	ms = append(ms, c.IpcMounts()...)
 	ms = append(ms, c.TmpfsMounts()...)
+	// SUSE:secrets :: We add the mounts to the OCI config which containerd then uses.
+	ms = append(ms, c.SuseSecretMounts()...)
 	sort.Sort(mounts(ms))
 	if err := setMounts(daemon, &s, c, ms); err != nil {
 		return nil, fmt.Errorf("linux mounts: %v", err)
diff --git a/daemon/start.go b/daemon/start.go
index fcf24c5..57a8c33 100644
--- a/daemon/start.go
+++ b/daemon/start.go
@@ -173,6 +173,12 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
 
 	container.UnmountIpcMounts(detachMounted)
 
+	// TODO(SUSE): Make sure this gets called by containerCleanup. Do we need to
+	//             port this part of the patch there as well?
+
+	// SUSE:secrets :: We need to unmount stuff here so that we clean up properly.
+	container.UnmountSuseSecretMounts(detachMounted)
+
 	if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
 		// FIXME: remove once reference counting for graphdrivers has been refactored
 		// Ensure that all the mounts are gone
diff --git a/daemon/suse_secrets.go b/daemon/suse_secrets.go
new file mode 100644
index 0000000..417a1a9
--- /dev/null
+++ b/daemon/suse_secrets.go
@@ -0,0 +1,184 @@
+package daemon
+
+// SUSE:secrets :: This is a set of functions to copy host credentials into a
+// container's /run/secrets.
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"syscall"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/idtools"
+)
+
+// TODO(SUSE): We need to reimplement this to use tar. Immediately.
+
+// Creating a fake file.
+type SuseFakeFile struct {
+	Path string
+	Uid  int
+	Gid  int
+	Mode os.FileMode
+	Data []byte
+}
+
+func (s *SuseFakeFile) SaveTo(dir string, uidMap, gidMap []idtools.IDMap) error {
+	// Create non-existant path components with an owner of root (other FakeFiles
+	// will clean this up if the owner is critical).
+	rootUid, rootGid, err := idtools.GetRootUIDGID(uidMap, gidMap)
+
+	path := filepath.Join(dir, s.Path)
+	if err := idtools.MkdirAllNewAs(filepath.Dir(path), 0755, rootUid, rootGid); err != nil && !os.IsExist(err) {
+		return err
+	}
+
+	uid, err := idtools.ToHost(s.Uid, uidMap)
+	if err != nil {
+		return err
+	}
+
+	gid, err := idtools.ToHost(s.Gid, gidMap)
+	if err != nil {
+		return err
+	}
+
+	if s.Mode.IsDir() {
+		if err := idtools.MkdirAs(path, s.Mode, uid, gid); err != nil {
+			return err
+		}
+	} else {
+		if err := ioutil.WriteFile(path, s.Data, s.Mode); err != nil {
+			return err
+		}
+	}
+
+	return os.Chown(path, uid, gid)
+}
+
+// readDir will recurse into a directory prefix/dir, and return the set of secrets
+// in that directory. The Path attribute of each has the prefix stripped. Symlinks
+// are evaluated.
+func readDir(prefix, dir string) ([]*SuseFakeFile, error) {
+	var suseFiles []*SuseFakeFile
+
+	path := filepath.Join(prefix, dir)
+
+	fi, err := os.Stat(path)
+	if err != nil {
+		// Ignore dangling symlinks.
+		if os.IsNotExist(err) {
+			logrus.Warnf("SUSE:secrets :: dangling symlink: %s", path)
+			return suseFiles, nil
+		}
+		return nil, err
+	}
+
+	stat, ok := fi.Sys().(*syscall.Stat_t)
+	if !ok {
+		logrus.Warnf("SUSE:secrets :: failed to cast directory stat_t: defaulting to owned by root:root: %s", path)
+	}
+
+	suseFiles = append(suseFiles, &SuseFakeFile{
+		Path: dir,
+		Uid:  int(stat.Uid),
+		Gid:  int(stat.Gid),
+		Mode: fi.Mode(),
+	})
+
+	files, err := ioutil.ReadDir(path)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, f := range files {
+		subpath := filepath.Join(dir, f.Name())
+
+		if f.IsDir() {
+			secrets, err := readDir(prefix, subpath)
+			if err != nil {
+				return nil, err
+			}
+			suseFiles = append(suseFiles, secrets...)
+		} else {
+			secrets, err := readFile(prefix, subpath)
+			if err != nil {
+				return nil, err
+			}
+			suseFiles = append(suseFiles, secrets...)
+		}
+	}
+
+	return suseFiles, nil
+}
+
+func readFile(prefix, file string) ([]*SuseFakeFile, error) {
+	var suseFiles []*SuseFakeFile
+
+	path := filepath.Join(prefix, file)
+	fi, err := os.Stat(path)
+	if err != nil {
+		// Ignore dangling symlinks.
+		if os.IsNotExist(err) {
+			logrus.Warnf("SUSE:secrets :: dangling symlink: %s", path)
+			return suseFiles, nil
+		}
+		return nil, err
+	}
+
+	stat, ok := fi.Sys().(*syscall.Stat_t)
+	if !ok {
+		logrus.Warnf("SUSE:secrets :: failed to cast file stat_t: defaulting to owned by root:root: %s", path)
+	}
+
+	if fi.IsDir() {
+		secrets, err := readDir(prefix, file)
+		if err != nil {
+			return nil, err
+		}
+		suseFiles = append(suseFiles, secrets...)
+	} else {
+		bytes, err := ioutil.ReadFile(path)
+		if err != nil {
+			return nil, err
+		}
+		suseFiles = append(suseFiles, &SuseFakeFile{
+			Path: file,
+			Uid:  int(stat.Uid),
+			Gid:  int(stat.Gid),
+			Mode: fi.Mode(),
+			Data: bytes,
+		})
+	}
+
+	return suseFiles, nil
+}
+
+func getHostSuseSecretData() ([]*SuseFakeFile, error) {
+	secrets := []*SuseFakeFile{}
+
+	credentials, err := readDir("/etc/zypp", "credentials.d")
+	if err != nil {
+		if os.IsNotExist(err) {
+			credentials = []*SuseFakeFile{}
+		} else {
+			logrus.Errorf("SUSE:secrets :: error while reading zypp credentials: %s", err)
+			return nil, err
+		}
+	}
+	secrets = append(secrets, credentials...)
+
+	suseConnect, err := readFile("/etc", "SUSEConnect")
+	if err != nil {
+		if os.IsNotExist(err) {
+			suseConnect = []*SuseFakeFile{}
+		} else {
+			logrus.Errorf("SUSE:secrets :: error while reading /etc/SUSEConnect: %s", err)
+			return nil, err
+		}
+	}
+	secrets = append(secrets, suseConnect...)
+
+	return secrets, nil
+}