From 67d282a5c95ca1d25cd4e9c688e89191f662d448 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Tue, 11 Apr 2017 13:34:19 -0400 Subject: [PATCH] support custom paths for secrets This adds support to specify custom container paths for secrets. Signed-off-by: Evan Hazlett --- container/container.go | 17 ++++++ container/container_notlinux.go | 4 +- container/container_unit_test.go | 16 ++++++ container/container_unix.go | 25 ++++----- container/container_windows.go | 8 ++- daemon/container_operations_unix.go | 10 +--- daemon/oci_linux.go | 4 +- .../docker_cli_service_create_test.go | 55 ++++++++++++------- opts/secret.go | 5 -- 9 files changed, 92 insertions(+), 52 deletions(-) diff --git a/container/container.go b/container/container.go index 92161e708d..6e07495468 100644 --- a/container/container.go +++ b/container/container.go @@ -948,3 +948,20 @@ func (container *Container) InitializeStdio(iop libcontainerd.IOPipe) error { return nil } + +// SecretMountPath returns the path of the secret mount for the container +func (container *Container) SecretMountPath() string { + return filepath.Join(container.Root, "secrets") +} + +func (container *Container) getLocalSecretPath(r *swarmtypes.SecretReference) string { + return filepath.Join(container.SecretMountPath(), filepath.Base(r.File.Name)) +} + +func getSecretTargetPath(r *swarmtypes.SecretReference) string { + if filepath.IsAbs(r.File.Name) { + return r.File.Name + } + + return filepath.Join(containerSecretMountPath, r.File.Name) +} diff --git a/container/container_notlinux.go b/container/container_notlinux.go index f65653e992..768c762d2f 100644 --- a/container/container_notlinux.go +++ b/container/container_notlinux.go @@ -12,8 +12,8 @@ func detachMounted(path string) error { return unix.Unmount(path, 0) } -// SecretMount returns the mount for the secret path -func (container *Container) SecretMount() *Mount { +// SecretMounts returns the mounts for the secret path +func (container *Container) SecretMounts() []Mount { return nil } diff --git a/container/container_unit_test.go b/container/container_unit_test.go index f301f25bbe..01d06e4eb8 100644 --- a/container/container_unit_test.go +++ b/container/container_unit_test.go @@ -1,9 +1,11 @@ package container import ( + "path/filepath" "testing" "github.com/docker/docker/api/types/container" + swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/signal" ) @@ -58,3 +60,17 @@ func TestContainerStopTimeout(t *testing.T) { t.Fatalf("Expected 15, got %v", s) } } + +func TestContainerSecretReferenceDestTarget(t *testing.T) { + ref := &swarmtypes.SecretReference{ + File: &swarmtypes.SecretReferenceFileTarget{ + Name: "app", + }, + } + + d := getSecretTargetPath(ref) + expected := filepath.Join(containerSecretMountPath, "app") + if d != expected { + t.Fatalf("expected secret dest %q; received %q", expected, d) + } +} diff --git a/container/container_unix.go b/container/container_unix.go index 898a1bdceb..44ad422556 100644 --- a/container/container_unix.go +++ b/container/container_unix.go @@ -163,11 +163,6 @@ func (container *Container) NetworkMounts() []Mount { return mounts } -// SecretMountPath returns the path of the secret mount for the container -func (container *Container) SecretMountPath() string { - return filepath.Join(container.Root, "secrets") -} - // CopyImagePathContent copies files in destination to the volume. func (container *Container) CopyImagePathContent(v volume.Volume, destination string) error { rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.BaseFS, destination), container.BaseFS) @@ -253,17 +248,21 @@ func (container *Container) IpcMounts() []Mount { return mounts } -// SecretMount returns the mount for the secret path -func (container *Container) SecretMount() *Mount { - if len(container.SecretReferences) > 0 { - return &Mount{ - Source: container.SecretMountPath(), - Destination: containerSecretMountPath, +// SecretMounts returns the mount for the secret path +func (container *Container) SecretMounts() []Mount { + var mounts []Mount + for _, r := range container.SecretReferences { + // secrets are created in the SecretMountPath at a single level + // i.e. /var/run/secrets/foo + srcPath := container.getLocalSecretPath(r) + mounts = append(mounts, Mount{ + Source: srcPath, + Destination: getSecretTargetPath(r), Writable: false, - } + }) } - return nil + return mounts } // UnmountSecrets unmounts the local tmpfs for secrets diff --git a/container/container_windows.go b/container/container_windows.go index ab56b61033..1d6c1debd5 100644 --- a/container/container_windows.go +++ b/container/container_windows.go @@ -10,6 +10,10 @@ import ( containertypes "github.com/docker/docker/api/types/container" ) +const ( + containerSecretMountPath = `C:\ProgramData\Docker\secrets` +) + // Container holds fields specific to the Windows implementation. See // CommonContainer for standard fields common to all containers. type Container struct { @@ -43,8 +47,8 @@ func (container *Container) IpcMounts() []Mount { return nil } -// SecretMount returns the mount for the secret path -func (container *Container) SecretMount() *Mount { +// SecretMounts returns the mount for the secret path +func (container *Container) SecretMounts() []Mount { return nil } diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index 0b602ef20c..03b3e5df8e 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -177,13 +177,9 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { return fmt.Errorf("secret target type is not a file target") } - targetPath := filepath.Clean(s.File.Name) - // ensure that the target is a filename only; no paths allowed - if targetPath != filepath.Base(targetPath) { - return fmt.Errorf("error creating secret: secret must not be a path") - } - - fPath := filepath.Join(localMountPath, targetPath) + // secrets are created in the SecretMountPath at a single level + // i.e. /var/run/secrets/foo + fPath := filepath.Join(localMountPath, filepath.Base(s.File.Name)) if err := idtools.MkdirAllAs(filepath.Dir(fPath), 0700, rootUID, rootGID); err != nil { return errors.Wrap(err, "error creating secret mount path") } diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index a469a63b53..f04188b171 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -750,8 +750,8 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { } ms = append(ms, tmpfsMounts...) - if m := c.SecretMount(); m != nil { - ms = append(ms, *m) + if m := c.SecretMounts(); m != nil { + ms = append(ms, m...) } sort.Sort(mounts(ms)) diff --git a/integration-cli/docker_cli_service_create_test.go b/integration-cli/docker_cli_service_create_test.go index 1ff9b482cf..dc37ec60cf 100644 --- a/integration-cli/docker_cli_service_create_test.go +++ b/integration-cli/docker_cli_service_create_test.go @@ -90,35 +90,48 @@ func (s *DockerSwarmSuite) TestServiceCreateWithSecretSimple(c *check.C) { c.Assert(refs[0].File.Name, checker.Equals, testName) c.Assert(refs[0].File.UID, checker.Equals, "0") c.Assert(refs[0].File.GID, checker.Equals, "0") + + out, err = d.Cmd("service", "rm", serviceName) + c.Assert(err, checker.IsNil, check.Commentf(out)) + d.DeleteSecret(c, testName) } -func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTarget(c *check.C) { +func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTargetPaths(c *check.C) { d := s.AddDaemon(c, true, true) - serviceName := "test-service-secret" - testName := "test_secret" - id := d.CreateSecret(c, swarm.SecretSpec{ - Annotations: swarm.Annotations{ - Name: testName, - }, - Data: []byte("TESTINGDATA"), - }) - c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id)) - testTarget := "testing" + testPaths := map[string]string{ + "app": "/etc/secret", + "test_secret": "test_secret", + } + for testName, testTarget := range testPaths { + serviceName := "svc-" + testName + id := d.CreateSecret(c, swarm.SecretSpec{ + Annotations: swarm.Annotations{ + Name: testName, + }, + Data: []byte("TESTINGDATA"), + }) + c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id)) - out, err := d.Cmd("service", "create", "--name", serviceName, "--secret", fmt.Sprintf("source=%s,target=%s", testName, testTarget), "busybox", "top") - c.Assert(err, checker.IsNil, check.Commentf(out)) + out, err := d.Cmd("service", "create", "--name", serviceName, "--secret", fmt.Sprintf("source=%s,target=%s", testName, testTarget), "busybox", "top") + c.Assert(err, checker.IsNil, check.Commentf(out)) - out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}", serviceName) - c.Assert(err, checker.IsNil) + out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}", serviceName) + c.Assert(err, checker.IsNil) - var refs []swarm.SecretReference - c.Assert(json.Unmarshal([]byte(out), &refs), checker.IsNil) - c.Assert(refs, checker.HasLen, 1) + var refs []swarm.SecretReference + c.Assert(json.Unmarshal([]byte(out), &refs), checker.IsNil) + c.Assert(refs, checker.HasLen, 1) - c.Assert(refs[0].SecretName, checker.Equals, testName) - c.Assert(refs[0].File, checker.Not(checker.IsNil)) - c.Assert(refs[0].File.Name, checker.Equals, testTarget) + c.Assert(refs[0].SecretName, checker.Equals, testName) + c.Assert(refs[0].File, checker.Not(checker.IsNil)) + c.Assert(refs[0].File.Name, checker.Equals, testTarget) + + out, err = d.Cmd("service", "rm", serviceName) + c.Assert(err, checker.IsNil, check.Commentf(out)) + + d.DeleteSecret(c, testName) + } } func (s *DockerSwarmSuite) TestServiceCreateMountTmpfs(c *check.C) { diff --git a/opts/secret.go b/opts/secret.go index 56ed29eb5b..a1fde54d91 100644 --- a/opts/secret.go +++ b/opts/secret.go @@ -4,7 +4,6 @@ import ( "encoding/csv" "fmt" "os" - "path/filepath" "strconv" "strings" @@ -53,10 +52,6 @@ func (o *SecretOpt) Set(value string) error { case "source", "src": options.SecretName = value case "target": - tDir, _ := filepath.Split(value) - if tDir != "" { - return fmt.Errorf("target must not be a path") - } options.File.Name = value case "uid": options.File.UID = value