Browse Source

Merge pull request #32571 from ehazlett/secret-targets

Support Custom Secret Targets
Brian Goff 8 years ago
parent
commit
08fdce7738

+ 18 - 0
container/container.go

@@ -948,3 +948,21 @@ 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")
+}
+
+// SecretFilePath returns the path to the location of a secret on the host.
+func (container *Container) SecretFilePath(secretRef swarmtypes.SecretReference) string {
+	return filepath.Join(container.SecretMountPath(), secretRef.SecretID)
+}
+
+func getSecretTargetPath(r *swarmtypes.SecretReference) string {
+	if filepath.IsAbs(r.File.Name) {
+		return r.File.Name
+	}
+
+	return filepath.Join(containerSecretMountPath, r.File.Name)
+}

+ 2 - 2
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
 }
 

+ 16 - 0
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)
+	}
+}

+ 12 - 13
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,
-			Writable:    false,
+// SecretMounts returns the mounts for the secret path.
+func (container *Container) SecretMounts() []Mount {
+	var mounts []Mount
+	for _, r := range container.SecretReferences {
+		if r.File == nil {
+			continue
 		}
+		mounts = append(mounts, Mount{
+			Source:      container.SecretFilePath(*r),
+			Destination: getSecretTargetPath(r),
+			Writable:    false,
+		})
 	}
 
-	return nil
+	return mounts
 }
 
 // UnmountSecrets unmounts the local tmpfs for secrets

+ 6 - 2
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 mounts for the secret path
+func (container *Container) SecretMounts() []Mount {
 	return nil
 }
 

+ 3 - 7
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 on the host, at a
+		// single level
+		fPath := c.SecretFilePath(*s)
 		if err := idtools.MkdirAllAs(filepath.Dir(fPath), 0700, rootUID, rootGID); err != nil {
 			return errors.Wrap(err, "error creating secret mount path")
 		}

+ 2 - 2
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))

+ 2 - 2
hack/dockerfile/binaries-commits

@@ -11,5 +11,5 @@ VNDR_COMMIT=c56e082291115e369f77601f9c071dd0b87c7120
 BINDATA_COMMIT=a0ff2567cfb70903282db057e799fd826784d41d
 
 # CLI
-DOCKERCLI_REPO=https://github.com/docker/cli
-DOCKERCLI_COMMIT=c3648a9c9400d45524cc71b8fca4085b192c626f
+DOCKERCLI_REPO=https://github.com/aaronlehmann/cli
+DOCKERCLI_COMMIT=fd5a5910409fe5ed862b29de45a66f2bf8056894

+ 100 - 10
integration-cli/docker_cli_service_create_test.go

@@ -5,6 +5,7 @@ package main
 import (
 	"encoding/json"
 	"fmt"
+	"path/filepath"
 	"strings"
 
 	"github.com/docker/docker/api/types"
@@ -90,23 +91,91 @@ 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)
+
+	testPaths := map[string]string{
+		"app":                  "/etc/secret",
+		"test_secret":          "test_secret",
+		"relative_secret":      "relative/secret",
+		"escapes_in_container": "../secret",
+	}
+
+	var secretFlags []string
+
+	for testName, testTarget := range testPaths {
+		id := d.CreateSecret(c, swarm.SecretSpec{
+			Annotations: swarm.Annotations{
+				Name: testName,
+			},
+			Data: []byte("TESTINGDATA " + testName + " " + testTarget),
+		})
+		c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id))
+
+		secretFlags = append(secretFlags, "--secret", fmt.Sprintf("source=%s,target=%s", testName, testTarget))
+	}
+
+	serviceName := "svc"
+	serviceCmd := []string{"service", "create", "--name", serviceName}
+	serviceCmd = append(serviceCmd, secretFlags...)
+	serviceCmd = append(serviceCmd, "busybox", "top")
+	out, err := d.Cmd(serviceCmd...)
+	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)
+
+	var refs []swarm.SecretReference
+	c.Assert(json.Unmarshal([]byte(out), &refs), checker.IsNil)
+	c.Assert(refs, checker.HasLen, len(testPaths))
+
+	var tasks []swarm.Task
+	waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) {
+		tasks = d.GetServiceTasks(c, serviceName)
+		return len(tasks) > 0, nil
+	}, checker.Equals, true)
+
+	task := tasks[0]
+	waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) {
+		if task.NodeID == "" || task.Status.ContainerStatus.ContainerID == "" {
+			task = d.GetTask(c, task.ID)
+		}
+		return task.NodeID != "" && task.Status.ContainerStatus.ContainerID != "", nil
+	}, checker.Equals, true)
+
+	for testName, testTarget := range testPaths {
+		path := testTarget
+		if !filepath.IsAbs(path) {
+			path = filepath.Join("/run/secrets", path)
+		}
+		out, err := d.Cmd("exec", task.Status.ContainerStatus.ContainerID, "cat", path)
+		c.Assert(err, checker.IsNil)
+		c.Assert(out, checker.Equals, "TESTINGDATA "+testName+" "+testTarget)
+	}
+
+	out, err = d.Cmd("service", "rm", serviceName)
+	c.Assert(err, checker.IsNil, check.Commentf(out))
+}
+
+func (s *DockerSwarmSuite) TestServiceCreateWithSecretReferencedTwice(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,
+			Name: "mysecret",
 		},
 		Data: []byte("TESTINGDATA"),
 	})
 	c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id))
-	testTarget := "testing"
 
-	out, err := d.Cmd("service", "create", "--name", serviceName, "--secret", fmt.Sprintf("source=%s,target=%s", testName, testTarget), "busybox", "top")
+	serviceName := "svc"
+	out, err := d.Cmd("service", "create", "--name", serviceName, "--secret", "source=mysecret,target=target1", "--secret", "source=mysecret,target=target2", "busybox", "top")
 	c.Assert(err, checker.IsNil, check.Commentf(out))
 
 	out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}", serviceName)
@@ -114,11 +183,32 @@ func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTarget(c *check.C) {
 
 	var refs []swarm.SecretReference
 	c.Assert(json.Unmarshal([]byte(out), &refs), checker.IsNil)
-	c.Assert(refs, checker.HasLen, 1)
+	c.Assert(refs, checker.HasLen, 2)
 
-	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)
+	var tasks []swarm.Task
+	waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) {
+		tasks = d.GetServiceTasks(c, serviceName)
+		return len(tasks) > 0, nil
+	}, checker.Equals, true)
+
+	task := tasks[0]
+	waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) {
+		if task.NodeID == "" || task.Status.ContainerStatus.ContainerID == "" {
+			task = d.GetTask(c, task.ID)
+		}
+		return task.NodeID != "" && task.Status.ContainerStatus.ContainerID != "", nil
+	}, checker.Equals, true)
+
+	for _, target := range []string{"target1", "target2"} {
+		c.Assert(err, checker.IsNil, check.Commentf(out))
+		path := filepath.Join("/run/secrets", target)
+		out, err := d.Cmd("exec", task.Status.ContainerStatus.ContainerID, "cat", path)
+		c.Assert(err, checker.IsNil)
+		c.Assert(out, checker.Equals, "TESTINGDATA")
+	}
+
+	out, err = d.Cmd("service", "rm", serviceName)
+	c.Assert(err, checker.IsNil, check.Commentf(out))
 }
 
 func (s *DockerSwarmSuite) TestServiceCreateMountTmpfs(c *check.C) {

+ 0 - 5
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