Accepting request 477670 from home:cyphar:containers
This massively cleans up the docker mount secrets patch we have to use the new Docker Swarm framework for /run/secrets (which doesn't require swarm mode with these patches). OBS-URL: https://build.opensuse.org/request/show/477670 OBS-URL: https://build.opensuse.org/package/show/Virtualization:containers/docker?expand=0&rev=172
This commit is contained in:
parent
f0d9c665f7
commit
9c1f006520
@ -1,391 +0,0 @@
|
||||
diff --git a/container/container_unix.go b/container/container_unix.go
|
||||
index f92d586..70cc74f 100644
|
||||
--- a/container/container_unix.go
|
||||
+++ b/container/container_unix.go
|
||||
@@ -39,6 +39,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
|
||||
}
|
||||
@@ -281,6 +283,67 @@ func (container *Container) UnmountSecrets() error {
|
||||
return detachMounted(container.SecretMountPath())
|
||||
}
|
||||
|
||||
+// 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: string(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 2296045..d6f7889 100644
|
||||
--- a/daemon/container_operations_unix.go
|
||||
+++ b/daemon/container_operations_unix.go
|
||||
@@ -87,6 +87,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/initlayer/setup_unix.go b/daemon/initlayer/setup_unix.go
|
||||
index e83c275..8c56310 100644
|
||||
--- a/daemon/initlayer/setup_unix.go
|
||||
+++ b/daemon/initlayer/setup_unix.go
|
||||
@@ -18,8 +18,10 @@ import (
|
||||
// the container from unwanted side-effects on the rw layer.
|
||||
func Setup(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 1daefc5..8e27f1d 100644
|
||||
--- a/daemon/oci_linux.go
|
||||
+++ b/daemon/oci_linux.go
|
||||
@@ -693,6 +693,10 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
||||
if err := daemon.setupSecretDir(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 {
|
||||
@@ -711,6 +715,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
||||
ms = append(ms, *m)
|
||||
}
|
||||
|
||||
+ // 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 6c94fd5..9bbdc21 100644
|
||||
--- a/daemon/start.go
|
||||
+++ b/daemon/start.go
|
||||
@@ -205,6 +205,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/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
|
||||
+}
|
@ -1,7 +1,12 @@
|
||||
-------------------------------------------------------------------
|
||||
Mon Mar 6 18:11:47 UTC 2017 - jmassaguerpla@suse.com
|
||||
Wed Mar 8 00:48:46 UTC 2017 - asarai@suse.com
|
||||
|
||||
- fix docker-mount-secrets.patch to apply to docker-1.13.0
|
||||
- Clean up docker-mount-secrets.patch to use the new swarm secrets internals of
|
||||
Docker 1.13.0, which removes the need to implement any secret handling
|
||||
ourselves. This resulted in a split up of the patch.
|
||||
- docker-mount-secrets.patch
|
||||
+ secrets-0001-daemon-allow-directory-creation-in-run-secrets.patch
|
||||
+ secrets-0002-SUSE-implement-SUSE-container-secrets.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Mar 6 15:31:02 UTC 2017 - jmassaguerpla@suse.com
|
||||
|
@ -68,7 +68,8 @@ Patch103: boltdb_bolt_add_brokenUnaligned.patch
|
||||
# which is not snapshotted when images are committed. Note that if you modify
|
||||
# this patch, please also modify the patch in the suse-secrets-v<version>
|
||||
# branch in http://github.com/suse/docker.mirror.
|
||||
Patch200: docker-mount-secrets.patch
|
||||
Patch200: secrets-0001-daemon-allow-directory-creation-in-run-secrets.patch
|
||||
Patch201: secrets-0002-SUSE-implement-SUSE-container-secrets.patch
|
||||
Patch300: integration-cli-fix-TestInfoEnsureSucceeds.patch
|
||||
Patch301: pr31549-cmd-docker-fix-TestDaemonCommand.patch
|
||||
BuildRequires: audit
|
||||
@ -175,6 +176,7 @@ Test package for docker. It contains the source code and the tests.
|
||||
# nothing
|
||||
%else
|
||||
%patch200 -p1
|
||||
%patch201 -p1
|
||||
%endif
|
||||
%ifnarch %{go_arches}
|
||||
%patch100 -p1
|
||||
|
@ -0,0 +1,39 @@
|
||||
From 0c4cf4fac76f2a5272a808665985a2e0df6af0db Mon Sep 17 00:00:00 2001
|
||||
From: Aleksa Sarai <asarai@suse.de>
|
||||
Date: Wed, 8 Mar 2017 12:41:54 +1100
|
||||
Subject: [PATCH 1/2] daemon: allow directory creation in /run/secrets
|
||||
|
||||
Since FileMode can have the directory bit set, allow a SecretStore
|
||||
implementation to return secrets that are actually directories. This is
|
||||
useful for creating directories and subdirectories of secrets.
|
||||
|
||||
Backport: https://github.com/docker/docker/pull/31632
|
||||
Signed-off-by: Aleksa Sarai <asarai@suse.de>
|
||||
---
|
||||
daemon/container_operations_unix.go | 10 ++++++++--
|
||||
1 file changed, 8 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go
|
||||
index 2296045765d4..8527a7907373 100644
|
||||
--- a/daemon/container_operations_unix.go
|
||||
+++ b/daemon/container_operations_unix.go
|
||||
@@ -195,8 +195,14 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
|
||||
if secret == nil {
|
||||
return fmt.Errorf("unable to get secret from secret store")
|
||||
}
|
||||
- if err := ioutil.WriteFile(fPath, secret.Spec.Data, s.File.Mode); err != nil {
|
||||
- return errors.Wrap(err, "error injecting secret")
|
||||
+ if s.File.Mode.IsDir() {
|
||||
+ if err := os.Mkdir(fPath, s.File.Mode); err != nil {
|
||||
+ return errors.Wrap(err, "error injecting secret dir")
|
||||
+ }
|
||||
+ } else {
|
||||
+ if err := ioutil.WriteFile(fPath, secret.Spec.Data, s.File.Mode); err != nil {
|
||||
+ return errors.Wrap(err, "error injecting secret")
|
||||
+ }
|
||||
}
|
||||
|
||||
uid, err := strconv.Atoi(s.File.UID)
|
||||
--
|
||||
2.12.0
|
||||
|
290
secrets-0002-SUSE-implement-SUSE-container-secrets.patch
Normal file
290
secrets-0002-SUSE-implement-SUSE-container-secrets.patch
Normal file
@ -0,0 +1,290 @@
|
||||
From a3106907d4786ed7d624201debdd43dc41fb4b97 Mon Sep 17 00:00:00 2001
|
||||
From: Aleksa Sarai <asarai@suse.de>
|
||||
Date: Wed, 8 Mar 2017 11:43:29 +1100
|
||||
Subject: [PATCH 2/2] 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>
|
||||
---
|
||||
daemon/start.go | 5 +
|
||||
daemon/suse_secrets.go | 246 +++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
2 files changed, 251 insertions(+)
|
||||
create mode 100644 daemon/suse_secrets.go
|
||||
|
||||
diff --git a/daemon/start.go b/daemon/start.go
|
||||
index 6c94fd5482d0..3c06eed778d7 100644
|
||||
--- a/daemon/start.go
|
||||
+++ b/daemon/start.go
|
||||
@@ -146,6 +146,11 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
|
||||
return err
|
||||
}
|
||||
|
||||
+ // SUSE:secrets -- inject the SUSE secret store
|
||||
+ if err := daemon.injectSuseSecretStore(container); err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+
|
||||
spec, err := daemon.createSpec(container)
|
||||
if err != nil {
|
||||
return err
|
||||
diff --git a/daemon/suse_secrets.go b/daemon/suse_secrets.go
|
||||
new file mode 100644
|
||||
index 000000000000..591abc998e67
|
||||
--- /dev/null
|
||||
+++ b/daemon/suse_secrets.go
|
||||
@@ -0,0 +1,246 @@
|
||||
+/*
|
||||
+ * suse-secrets: patch for Docker to implement SUSE secrets
|
||||
+ * Copyright (C) 2017 SUSE LLC.
|
||||
+ *
|
||||
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
||||
+ * you may not use this file except in compliance with the License.
|
||||
+ * You may obtain a copy of the License at
|
||||
+ *
|
||||
+ * http://www.apache.org/licenses/LICENSE-2.0
|
||||
+ *
|
||||
+ * Unless required by applicable law or agreed to in writing, software
|
||||
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
||||
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
+ * See the License for the specific language governing permissions and
|
||||
+ * limitations under the License.
|
||||
+ */
|
||||
+
|
||||
+package daemon
|
||||
+
|
||||
+import (
|
||||
+ "fmt"
|
||||
+ "io/ioutil"
|
||||
+ "os"
|
||||
+ "path/filepath"
|
||||
+ "syscall"
|
||||
+
|
||||
+ "github.com/Sirupsen/logrus"
|
||||
+ "github.com/docker/distribution/digest"
|
||||
+ "github.com/docker/docker/container"
|
||||
+
|
||||
+ swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||
+ swarmexec "github.com/docker/swarmkit/agent/exec"
|
||||
+ swarmapi "github.com/docker/swarmkit/api"
|
||||
+)
|
||||
+
|
||||
+func init() {
|
||||
+ // Output to tell us in logs that SUSE:secrets is enabled.
|
||||
+ logrus.Infof("SUSE:secrets :: enabled")
|
||||
+}
|
||||
+
|
||||
+// Creating a fake file.
|
||||
+type SuseFakeFile struct {
|
||||
+ Path string
|
||||
+ Uid int
|
||||
+ Gid int
|
||||
+ Mode os.FileMode
|
||||
+ Data []byte
|
||||
+}
|
||||
+
|
||||
+func (s SuseFakeFile) id() string {
|
||||
+ return fmt.Sprintf("suse::%s:%s", digest.FromBytes(s.Data), s.Path)
|
||||
+}
|
||||
+
|
||||
+func (s SuseFakeFile) toSecret() *swarmapi.Secret {
|
||||
+ return &swarmapi.Secret{
|
||||
+ ID: s.id(),
|
||||
+ Internal: true,
|
||||
+ Spec: swarmapi.SecretSpec{
|
||||
+ Data: s.Data,
|
||||
+ },
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func (s SuseFakeFile) toSecretReference() *swarmtypes.SecretReference {
|
||||
+ return &swarmtypes.SecretReference{
|
||||
+ SecretID: s.id(),
|
||||
+ SecretName: s.id(),
|
||||
+ File: &swarmtypes.SecretReferenceFileTarget{
|
||||
+ Name: s.Path,
|
||||
+ UID: fmt.Sprintf("%d", s.Uid),
|
||||
+ GID: fmt.Sprintf("%d", s.Gid),
|
||||
+ Mode: s.Mode,
|
||||
+ },
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// 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
|
||||
+}
|
||||
+
|
||||
+// readFile returns a secret given a file under a given prefix.
|
||||
+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
|
||||
+}
|
||||
+
|
||||
+// getHostSuseSecretData returns the list of SuseFakeFiles the need to be added
|
||||
+// as SUSE secrets.
|
||||
+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
|
||||
+}
|
||||
+
|
||||
+// In order to reduce the amount of code touched outside of this file, we
|
||||
+// implement the swarm API for SecretGetter. This asserts that this requirement
|
||||
+// will always be matched.
|
||||
+var _ swarmexec.SecretGetter = &suseSecretGetter{}
|
||||
+
|
||||
+type suseSecretGetter struct {
|
||||
+ dfl swarmexec.SecretGetter
|
||||
+ secrets map[string]*swarmapi.Secret
|
||||
+}
|
||||
+
|
||||
+func (s *suseSecretGetter) Get(id string) *swarmapi.Secret {
|
||||
+ logrus.Debugf("SUSE:secrets :: id=%s requested from suseSecretGetter", id)
|
||||
+
|
||||
+ secret, ok := s.secrets[id]
|
||||
+ if !ok {
|
||||
+ // fallthrough
|
||||
+ return s.dfl.Get(id)
|
||||
+ }
|
||||
+
|
||||
+ return secret
|
||||
+}
|
||||
+
|
||||
+func (daemon *Daemon) injectSuseSecretStore(c *container.Container) error {
|
||||
+ newSecretStore := &suseSecretGetter{
|
||||
+ dfl: c.SecretStore,
|
||||
+ secrets: make(map[string]*swarmapi.Secret),
|
||||
+ }
|
||||
+
|
||||
+ secrets, err := getHostSuseSecretData()
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+
|
||||
+ for _, secret := range secrets {
|
||||
+ newSecretStore.secrets[secret.id()] = secret.toSecret()
|
||||
+ c.SecretReferences = append(c.SecretReferences, secret.toSecretReference())
|
||||
+ }
|
||||
+
|
||||
+ c.SecretStore = newSecretStore
|
||||
+ return nil
|
||||
+}
|
||||
--
|
||||
2.12.0
|
||||
|
Loading…
Reference in New Issue
Block a user