Skip to content

Commit bd4e8aa

Browse files
committed
Add Windows secrets support
Signed-off-by: John Stephens <[email protected]>
1 parent eb8abc9 commit bd4e8aa

File tree

5 files changed

+144
-15
lines changed

5 files changed

+144
-15
lines changed

container/container_windows.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import (
88
"path/filepath"
99

1010
containertypes "github.com/docker/docker/api/types/container"
11+
"github.com/docker/docker/pkg/system"
1112
)
1213

1314
const (
14-
containerSecretMountPath = `C:\ProgramData\Docker\secrets`
15+
containerSecretMountPath = `C:\ProgramData\Docker\secrets`
16+
containerInternalSecretMountPath = `C:\ProgramData\Docker\internal\secrets`
1517
)
1618

1719
// Container holds fields specific to the Windows implementation. See
@@ -47,14 +49,46 @@ func (container *Container) IpcMounts() []Mount {
4749
return nil
4850
}
4951

50-
// SecretMounts returns the mounts for the secret path
51-
func (container *Container) SecretMounts() []Mount {
52+
// CreateSecretSymlinks creates symlinks to files in the secret mount.
53+
func (container *Container) CreateSecretSymlinks() error {
54+
for _, r := range container.SecretReferences {
55+
if r.File == nil {
56+
continue
57+
}
58+
resolvedPath, _, err := container.ResolvePath(getSecretTargetPath(r))
59+
if err != nil {
60+
return err
61+
}
62+
if err := system.MkdirAll(filepath.Dir(resolvedPath), 0); err != nil {
63+
return err
64+
}
65+
if err := os.Symlink(filepath.Join(containerInternalSecretMountPath, r.SecretID), resolvedPath); err != nil {
66+
return err
67+
}
68+
}
69+
5270
return nil
5371
}
5472

73+
// SecretMounts returns the mount for the secret path.
74+
// All secrets are stored in a single mount on Windows. Target symlinks are
75+
// created for each secret, pointing to the files in this mount.
76+
func (container *Container) SecretMounts() []Mount {
77+
var mounts []Mount
78+
if len(container.SecretReferences) > 0 {
79+
mounts = append(mounts, Mount{
80+
Source: container.SecretMountPath(),
81+
Destination: containerInternalSecretMountPath,
82+
Writable: false,
83+
})
84+
}
85+
86+
return mounts
87+
}
88+
5589
// UnmountSecrets unmounts the fs for secrets
5690
func (container *Container) UnmountSecrets() error {
57-
return nil
91+
return os.RemoveAll(container.SecretMountPath())
5892
}
5993

6094
// DetachAndUnmount unmounts all volumes.

daemon/container_operations_windows.go

+58-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
// +build windows
2-
31
package daemon
42

53
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
8+
"github.com/Sirupsen/logrus"
69
"github.com/docker/docker/container"
10+
"github.com/docker/docker/pkg/system"
711
"github.com/docker/libnetwork"
12+
"github.com/pkg/errors"
813
)
914

1015
func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
@@ -35,6 +40,57 @@ func detachMounted(path string) error {
3540
return nil
3641
}
3742

43+
func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
44+
if len(c.SecretReferences) == 0 {
45+
return nil
46+
}
47+
48+
localMountPath := c.SecretMountPath()
49+
logrus.Debugf("secrets: setting up secret dir: %s", localMountPath)
50+
51+
// create local secret root
52+
if err := system.MkdirAllWithACL(localMountPath, 0); err != nil {
53+
return errors.Wrap(err, "error creating secret local directory")
54+
}
55+
56+
defer func() {
57+
if setupErr != nil {
58+
if err := os.RemoveAll(localMountPath); err != nil {
59+
logrus.Errorf("error cleaning up secret mount: %s", err)
60+
}
61+
}
62+
}()
63+
64+
if c.DependencyStore == nil {
65+
return fmt.Errorf("secret store is not initialized")
66+
}
67+
68+
for _, s := range c.SecretReferences {
69+
// TODO (ehazlett): use type switch when more are supported
70+
if s.File == nil {
71+
logrus.Error("secret target type is not a file target")
72+
continue
73+
}
74+
75+
// secrets are created in the SecretMountPath on the host, at a
76+
// single level
77+
fPath := c.SecretFilePath(*s)
78+
logrus.WithFields(logrus.Fields{
79+
"name": s.File.Name,
80+
"path": fPath,
81+
}).Debug("injecting secret")
82+
secret := c.DependencyStore.Secrets().Get(s.SecretID)
83+
if secret == nil {
84+
return fmt.Errorf("unable to get secret from secret store")
85+
}
86+
if err := ioutil.WriteFile(fPath, secret.Spec.Data, s.File.Mode); err != nil {
87+
return errors.Wrap(err, "error injecting secret")
88+
}
89+
}
90+
91+
return nil
92+
}
93+
3894
func killProcessDirectly(container *container.Container) error {
3995
return nil
4096
}

daemon/oci_windows.go

+40-8
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,51 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
2525
// In base spec
2626
s.Hostname = c.FullHostname()
2727

28+
if err := daemon.setupSecretDir(c); err != nil {
29+
return nil, err
30+
}
31+
2832
// In s.Mounts
2933
mounts, err := daemon.setupMounts(c)
3034
if err != nil {
3135
return nil, err
3236
}
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+
3373
for _, mount := range mounts {
3474
m := specs.Mount{
3575
Source: mount.Source,
@@ -64,14 +104,6 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
64104
s.Process.User.Username = c.Config.User
65105

66106
// 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-
}
75107
if !isHyperV {
76108
s.Root.Path = c.BaseFS
77109
}

daemon/secrets_unsupported.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// +build !linux
1+
// +build !linux,!windows
22

33
package daemon
44

daemon/secrets_windows.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// +build windows
2+
3+
package daemon
4+
5+
func secretsSupported() bool {
6+
return true
7+
}

0 commit comments

Comments
 (0)