Browse code

Add Windows secrets support

Signed-off-by: John Stephens <johnstep@docker.com>

John Stephens authored on 2016/12/02 01:11:15
Showing 5 changed files
... ...
@@ -8,10 +8,12 @@ import (
8 8
 	"path/filepath"
9 9
 
10 10
 	containertypes "github.com/docker/docker/api/types/container"
11
+	"github.com/docker/docker/pkg/system"
11 12
 )
12 13
 
13 14
 const (
14
-	containerSecretMountPath = `C:\ProgramData\Docker\secrets`
15
+	containerSecretMountPath         = `C:\ProgramData\Docker\secrets`
16
+	containerInternalSecretMountPath = `C:\ProgramData\Docker\internal\secrets`
15 17
 )
16 18
 
17 19
 // Container holds fields specific to the Windows implementation. See
... ...
@@ -47,14 +49,46 @@ func (container *Container) IpcMounts() []Mount {
47 47
 	return nil
48 48
 }
49 49
 
50
-// SecretMounts returns the mounts for the secret path
51
-func (container *Container) SecretMounts() []Mount {
50
+// CreateSecretSymlinks creates symlinks to files in the secret mount.
51
+func (container *Container) CreateSecretSymlinks() error {
52
+	for _, r := range container.SecretReferences {
53
+		if r.File == nil {
54
+			continue
55
+		}
56
+		resolvedPath, _, err := container.ResolvePath(getSecretTargetPath(r))
57
+		if err != nil {
58
+			return err
59
+		}
60
+		if err := system.MkdirAll(filepath.Dir(resolvedPath), 0); err != nil {
61
+			return err
62
+		}
63
+		if err := os.Symlink(filepath.Join(containerInternalSecretMountPath, r.SecretID), resolvedPath); err != nil {
64
+			return err
65
+		}
66
+	}
67
+
52 68
 	return nil
53 69
 }
54 70
 
71
+// SecretMounts returns the mount for the secret path.
72
+// All secrets are stored in a single mount on Windows. Target symlinks are
73
+// created for each secret, pointing to the files in this mount.
74
+func (container *Container) SecretMounts() []Mount {
75
+	var mounts []Mount
76
+	if len(container.SecretReferences) > 0 {
77
+		mounts = append(mounts, Mount{
78
+			Source:      container.SecretMountPath(),
79
+			Destination: containerInternalSecretMountPath,
80
+			Writable:    false,
81
+		})
82
+	}
83
+
84
+	return mounts
85
+}
86
+
55 87
 // UnmountSecrets unmounts the fs for secrets
56 88
 func (container *Container) UnmountSecrets() error {
57
-	return nil
89
+	return os.RemoveAll(container.SecretMountPath())
58 90
 }
59 91
 
60 92
 // DetachAndUnmount unmounts all volumes.
... ...
@@ -1,10 +1,15 @@
1
-// +build windows
2
-
3 1
 package daemon
4 2
 
5 3
 import (
4
+	"fmt"
5
+	"io/ioutil"
6
+	"os"
7
+
8
+	"github.com/Sirupsen/logrus"
6 9
 	"github.com/docker/docker/container"
10
+	"github.com/docker/docker/pkg/system"
7 11
 	"github.com/docker/libnetwork"
12
+	"github.com/pkg/errors"
8 13
 )
9 14
 
10 15
 func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
... ...
@@ -35,6 +40,57 @@ func detachMounted(path string) error {
35 35
 	return nil
36 36
 }
37 37
 
38
+func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
39
+	if len(c.SecretReferences) == 0 {
40
+		return nil
41
+	}
42
+
43
+	localMountPath := c.SecretMountPath()
44
+	logrus.Debugf("secrets: setting up secret dir: %s", localMountPath)
45
+
46
+	// create local secret root
47
+	if err := system.MkdirAllWithACL(localMountPath, 0); err != nil {
48
+		return errors.Wrap(err, "error creating secret local directory")
49
+	}
50
+
51
+	defer func() {
52
+		if setupErr != nil {
53
+			if err := os.RemoveAll(localMountPath); err != nil {
54
+				logrus.Errorf("error cleaning up secret mount: %s", err)
55
+			}
56
+		}
57
+	}()
58
+
59
+	if c.DependencyStore == nil {
60
+		return fmt.Errorf("secret store is not initialized")
61
+	}
62
+
63
+	for _, s := range c.SecretReferences {
64
+		// TODO (ehazlett): use type switch when more are supported
65
+		if s.File == nil {
66
+			logrus.Error("secret target type is not a file target")
67
+			continue
68
+		}
69
+
70
+		// secrets are created in the SecretMountPath on the host, at a
71
+		// single level
72
+		fPath := c.SecretFilePath(*s)
73
+		logrus.WithFields(logrus.Fields{
74
+			"name": s.File.Name,
75
+			"path": fPath,
76
+		}).Debug("injecting secret")
77
+		secret := c.DependencyStore.Secrets().Get(s.SecretID)
78
+		if secret == nil {
79
+			return fmt.Errorf("unable to get secret from secret store")
80
+		}
81
+		if err := ioutil.WriteFile(fPath, secret.Spec.Data, s.File.Mode); err != nil {
82
+			return errors.Wrap(err, "error injecting secret")
83
+		}
84
+	}
85
+
86
+	return nil
87
+}
88
+
38 89
 func killProcessDirectly(container *container.Container) error {
39 90
 	return nil
40 91
 }
... ...
@@ -25,11 +25,51 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
25 25
 	// In base spec
26 26
 	s.Hostname = c.FullHostname()
27 27
 
28
+	if err := daemon.setupSecretDir(c); err != nil {
29
+		return nil, err
30
+	}
31
+
28 32
 	// In s.Mounts
29 33
 	mounts, err := daemon.setupMounts(c)
30 34
 	if err != nil {
31 35
 		return nil, err
32 36
 	}
37
+
38
+	var isHyperV bool
39
+	if c.HostConfig.Isolation.IsDefault() {
40
+		// Container using default isolation, so take the default from the daemon configuration
41
+		isHyperV = daemon.defaultIsolation.IsHyperV()
42
+	} else {
43
+		// Container may be requesting an explicit isolation mode.
44
+		isHyperV = c.HostConfig.Isolation.IsHyperV()
45
+	}
46
+
47
+	// If the container has not been started, and has secrets, create symlinks
48
+	// to each secret. If it has been started before, the symlinks should have
49
+	// already been created. Also, it is important to not mount a Hyper-V
50
+	// container that has been started before, to protect the host from the
51
+	// container; for example, from malicious mutation of NTFS data structures.
52
+	if !c.HasBeenStartedBefore && len(c.SecretReferences) > 0 {
53
+		// The container file system is mounted before this function is called,
54
+		// except for Hyper-V containers, so mount it here in that case.
55
+		if isHyperV {
56
+			if err := daemon.Mount(c); err != nil {
57
+				return nil, err
58
+			}
59
+		}
60
+		err := c.CreateSecretSymlinks()
61
+		if isHyperV {
62
+			daemon.Unmount(c)
63
+		}
64
+		if err != nil {
65
+			return nil, err
66
+		}
67
+	}
68
+
69
+	if m := c.SecretMounts(); m != nil {
70
+		mounts = append(mounts, m...)
71
+	}
72
+
33 73
 	for _, mount := range mounts {
34 74
 		m := specs.Mount{
35 75
 			Source:      mount.Source,
... ...
@@ -64,14 +104,6 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
64 64
 	s.Process.User.Username = c.Config.User
65 65
 
66 66
 	// In spec.Root. This is not set for Hyper-V containers
67
-	var isHyperV bool
68
-	if c.HostConfig.Isolation.IsDefault() {
69
-		// Container using default isolation, so take the default from the daemon configuration
70
-		isHyperV = daemon.defaultIsolation.IsHyperV()
71
-	} else {
72
-		// Container may be requesting an explicit isolation mode.
73
-		isHyperV = c.HostConfig.Isolation.IsHyperV()
74
-	}
75 67
 	if !isHyperV {
76 68
 		s.Root.Path = c.BaseFS
77 69
 	}
... ...
@@ -1,4 +1,4 @@
1
-// +build !linux
1
+// +build !linux,!windows
2 2
 
3 3
 package daemon
4 4
 
5 5
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+// +build windows
1
+
2
+package daemon
3
+
4
+func secretsSupported() bool {
5
+	return true
6
+}