From bd4e8aa64e5e7dedb60ca6670d7ddb32d5af0db9 Mon Sep 17 00:00:00 2001 From: John Stephens Date: Thu, 1 Dec 2016 08:11:15 -0800 Subject: [PATCH] Add Windows secrets support Signed-off-by: John Stephens --- container/container_windows.go | 42 ++++++++++++++++-- daemon/container_operations_windows.go | 60 +++++++++++++++++++++++++- daemon/oci_windows.go | 48 +++++++++++++++++---- daemon/secrets_unsupported.go | 2 +- daemon/secrets_windows.go | 7 +++ 5 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 daemon/secrets_windows.go diff --git a/container/container_windows.go b/container/container_windows.go index 50e202f8c4..a328c0c4b5 100644 --- a/container/container_windows.go +++ b/container/container_windows.go @@ -8,10 +8,12 @@ import ( "path/filepath" containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/pkg/system" ) const ( - containerSecretMountPath = `C:\ProgramData\Docker\secrets` + containerSecretMountPath = `C:\ProgramData\Docker\secrets` + containerInternalSecretMountPath = `C:\ProgramData\Docker\internal\secrets` ) // Container holds fields specific to the Windows implementation. See @@ -47,14 +49,46 @@ func (container *Container) IpcMounts() []Mount { return nil } -// SecretMounts returns the mounts for the secret path -func (container *Container) SecretMounts() []Mount { +// CreateSecretSymlinks creates symlinks to files in the secret mount. +func (container *Container) CreateSecretSymlinks() error { + for _, r := range container.SecretReferences { + if r.File == nil { + continue + } + resolvedPath, _, err := container.ResolvePath(getSecretTargetPath(r)) + if err != nil { + return err + } + if err := system.MkdirAll(filepath.Dir(resolvedPath), 0); err != nil { + return err + } + if err := os.Symlink(filepath.Join(containerInternalSecretMountPath, r.SecretID), resolvedPath); err != nil { + return err + } + } + return nil } +// SecretMounts returns the mount for the secret path. +// All secrets are stored in a single mount on Windows. Target symlinks are +// created for each secret, pointing to the files in this mount. +func (container *Container) SecretMounts() []Mount { + var mounts []Mount + if len(container.SecretReferences) > 0 { + mounts = append(mounts, Mount{ + Source: container.SecretMountPath(), + Destination: containerInternalSecretMountPath, + Writable: false, + }) + } + + return mounts +} + // UnmountSecrets unmounts the fs for secrets func (container *Container) UnmountSecrets() error { - return nil + return os.RemoveAll(container.SecretMountPath()) } // DetachAndUnmount unmounts all volumes. diff --git a/daemon/container_operations_windows.go b/daemon/container_operations_windows.go index df29ee7f45..cd000bdccd 100644 --- a/daemon/container_operations_windows.go +++ b/daemon/container_operations_windows.go @@ -1,10 +1,15 @@ -// +build windows - package daemon import ( + "fmt" + "io/ioutil" + "os" + + "github.com/Sirupsen/logrus" "github.com/docker/docker/container" + "github.com/docker/docker/pkg/system" "github.com/docker/libnetwork" + "github.com/pkg/errors" ) func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) { @@ -35,6 +40,57 @@ func detachMounted(path string) error { return nil } +func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { + if len(c.SecretReferences) == 0 { + return nil + } + + localMountPath := c.SecretMountPath() + logrus.Debugf("secrets: setting up secret dir: %s", localMountPath) + + // create local secret root + if err := system.MkdirAllWithACL(localMountPath, 0); err != nil { + return errors.Wrap(err, "error creating secret local directory") + } + + defer func() { + if setupErr != nil { + if err := os.RemoveAll(localMountPath); err != nil { + logrus.Errorf("error cleaning up secret mount: %s", err) + } + } + }() + + if c.DependencyStore == nil { + return fmt.Errorf("secret store is not initialized") + } + + for _, s := range c.SecretReferences { + // TODO (ehazlett): use type switch when more are supported + if s.File == nil { + logrus.Error("secret target type is not a file target") + continue + } + + // secrets are created in the SecretMountPath on the host, at a + // single level + fPath := c.SecretFilePath(*s) + logrus.WithFields(logrus.Fields{ + "name": s.File.Name, + "path": fPath, + }).Debug("injecting secret") + secret := c.DependencyStore.Secrets().Get(s.SecretID) + 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") + } + } + + return nil +} + func killProcessDirectly(container *container.Container) error { return nil } diff --git a/daemon/oci_windows.go b/daemon/oci_windows.go index b00c714bd1..c8a8f83fbe 100644 --- a/daemon/oci_windows.go +++ b/daemon/oci_windows.go @@ -25,11 +25,51 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { // In base spec s.Hostname = c.FullHostname() + if err := daemon.setupSecretDir(c); err != nil { + return nil, err + } + // In s.Mounts mounts, err := daemon.setupMounts(c) if err != nil { return nil, err } + + var isHyperV bool + if c.HostConfig.Isolation.IsDefault() { + // Container using default isolation, so take the default from the daemon configuration + isHyperV = daemon.defaultIsolation.IsHyperV() + } else { + // Container may be requesting an explicit isolation mode. + isHyperV = c.HostConfig.Isolation.IsHyperV() + } + + // If the container has not been started, and has secrets, create symlinks + // to each secret. If it has been started before, the symlinks should have + // already been created. Also, it is important to not mount a Hyper-V + // container that has been started before, to protect the host from the + // container; for example, from malicious mutation of NTFS data structures. + if !c.HasBeenStartedBefore && len(c.SecretReferences) > 0 { + // The container file system is mounted before this function is called, + // except for Hyper-V containers, so mount it here in that case. + if isHyperV { + if err := daemon.Mount(c); err != nil { + return nil, err + } + } + err := c.CreateSecretSymlinks() + if isHyperV { + daemon.Unmount(c) + } + if err != nil { + return nil, err + } + } + + if m := c.SecretMounts(); m != nil { + mounts = append(mounts, m...) + } + for _, mount := range mounts { m := specs.Mount{ Source: mount.Source, @@ -64,14 +104,6 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { s.Process.User.Username = c.Config.User // In spec.Root. This is not set for Hyper-V containers - var isHyperV bool - if c.HostConfig.Isolation.IsDefault() { - // Container using default isolation, so take the default from the daemon configuration - isHyperV = daemon.defaultIsolation.IsHyperV() - } else { - // Container may be requesting an explicit isolation mode. - isHyperV = c.HostConfig.Isolation.IsHyperV() - } if !isHyperV { s.Root.Path = c.BaseFS } diff --git a/daemon/secrets_unsupported.go b/daemon/secrets_unsupported.go index d6f36fda1e..d55e8624d7 100644 --- a/daemon/secrets_unsupported.go +++ b/daemon/secrets_unsupported.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!windows package daemon diff --git a/daemon/secrets_windows.go b/daemon/secrets_windows.go new file mode 100644 index 0000000000..9054354c8d --- /dev/null +++ b/daemon/secrets_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package daemon + +func secretsSupported() bool { + return true +}