Browse Source

Add Windows secrets support

Signed-off-by: John Stephens <johnstep@docker.com>
John Stephens 8 years ago
parent
commit
bd4e8aa64e

+ 38 - 4
container/container_windows.go

@@ -8,10 +8,12 @@ import (
 	"path/filepath"
 	"path/filepath"
 
 
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/pkg/system"
 )
 )
 
 
 const (
 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
 // Container holds fields specific to the Windows implementation. See
@@ -47,14 +49,46 @@ func (container *Container) IpcMounts() []Mount {
 	return nil
 	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
 	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
 // UnmountSecrets unmounts the fs for secrets
 func (container *Container) UnmountSecrets() error {
 func (container *Container) UnmountSecrets() error {
-	return nil
+	return os.RemoveAll(container.SecretMountPath())
 }
 }
 
 
 // DetachAndUnmount unmounts all volumes.
 // DetachAndUnmount unmounts all volumes.

+ 58 - 2
daemon/container_operations_windows.go

@@ -1,10 +1,15 @@
-// +build windows
-
 package daemon
 package daemon
 
 
 import (
 import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
+	"github.com/pkg/errors"
 )
 )
 
 
 func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
 func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
@@ -35,6 +40,57 @@ func detachMounted(path string) error {
 	return nil
 	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 {
 func killProcessDirectly(container *container.Container) error {
 	return nil
 	return nil
 }
 }

+ 40 - 8
daemon/oci_windows.go

@@ -25,11 +25,51 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 	// In base spec
 	// In base spec
 	s.Hostname = c.FullHostname()
 	s.Hostname = c.FullHostname()
 
 
+	if err := daemon.setupSecretDir(c); err != nil {
+		return nil, err
+	}
+
 	// In s.Mounts
 	// In s.Mounts
 	mounts, err := daemon.setupMounts(c)
 	mounts, err := daemon.setupMounts(c)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		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 {
 	for _, mount := range mounts {
 		m := specs.Mount{
 		m := specs.Mount{
 			Source:      mount.Source,
 			Source:      mount.Source,
@@ -64,14 +104,6 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 	s.Process.User.Username = c.Config.User
 	s.Process.User.Username = c.Config.User
 
 
 	// In spec.Root. This is not set for Hyper-V containers
 	// 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 {
 	if !isHyperV {
 		s.Root.Path = c.BaseFS
 		s.Root.Path = c.BaseFS
 	}
 	}

+ 1 - 1
daemon/secrets_unsupported.go

@@ -1,4 +1,4 @@
-// +build !linux
+// +build !linux,!windows
 
 
 package daemon
 package daemon
 
 

+ 7 - 0
daemon/secrets_windows.go

@@ -0,0 +1,7 @@
+// +build windows
+
+package daemon
+
+func secretsSupported() bool {
+	return true
+}