diff --git a/api/types/swarm/secret.go b/api/types/swarm/secret.go index 3323ba212d..0700177588 100644 --- a/api/types/swarm/secret.go +++ b/api/types/swarm/secret.go @@ -11,7 +11,7 @@ type Secret struct { type SecretSpec struct { Annotations - Data []byte `json",omitempty"` + Data []byte `json:",omitempty"` } type SecretReferenceMode int diff --git a/cli/command/service/parse.go b/cli/command/service/parse.go index 41883fb445..596d8e50d8 100644 --- a/cli/command/service/parse.go +++ b/cli/command/service/parse.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "path/filepath" "strings" "github.com/docker/docker/api/types" @@ -31,6 +32,13 @@ func parseSecretString(secretString string) (string, string, error) { } else { targetName = secretName } + + // ensure target is a filename only; no paths allowed + tDir, _ := filepath.Split(targetName) + if tDir != "" { + return "", "", fmt.Errorf("target must not have a path") + } + return secretName, targetName, nil } diff --git a/container/container_unix.go b/container/container_unix.go index 099073b83e..6fc6c53c27 100644 --- a/container/container_unix.go +++ b/container/container_unix.go @@ -22,8 +22,8 @@ import ( "golang.org/x/sys/unix" ) -// DefaultSHMSize is the default size (64MB) of the SHM which will be mounted in the container const ( + // DefaultSHMSize is the default size (64MB) of the SHM which will be mounted in the container DefaultSHMSize int64 = 67108864 containerSecretMountPath = "/run/secrets" ) @@ -178,6 +178,7 @@ 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") } @@ -267,19 +268,19 @@ func (container *Container) IpcMounts() []Mount { return mounts } -// SecretMounts returns the list of Secret mounts -func (container *Container) SecretMounts() []Mount { - var mounts []Mount +// SecretMount returns the list of Secret mounts +func (container *Container) SecretMount() Mount { + var mount Mount if len(container.Secrets) > 0 { - mounts = append(mounts, Mount{ + mount = Mount{ Source: container.SecretMountPath(), Destination: containerSecretMountPath, Writable: false, - }) + } } - return mounts + return mount } // UnmountSecrets unmounts the local tmpfs for secrets diff --git a/daemon/cluster/convert/container.go b/daemon/cluster/convert/container.go index 38143c6861..6436b01c60 100644 --- a/daemon/cluster/convert/container.go +++ b/daemon/cluster/convert/container.go @@ -79,12 +79,9 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec { func secretReferencesToGRPC(sr []*types.SecretReference) []*swarmapi.SecretReference { refs := []*swarmapi.SecretReference{} for _, s := range sr { - var mode swarmapi.SecretReference_Mode - switch s.Mode { - case types.SecretReferenceSystem: + mode := swarmapi.SecretReference_FILE + if s.Mode == types.SecretReferenceSystem { mode = swarmapi.SecretReference_SYSTEM - default: - mode = swarmapi.SecretReference_FILE } refs = append(refs, &swarmapi.SecretReference{ SecretID: s.SecretID, diff --git a/daemon/cluster/convert/secret.go b/daemon/cluster/convert/secret.go index e26fe84ce4..fc5b43594f 100644 --- a/daemon/cluster/convert/secret.go +++ b/daemon/cluster/convert/secret.go @@ -1,7 +1,6 @@ package convert import ( - "github.com/Sirupsen/logrus" swarmtypes "github.com/docker/docker/api/types/swarm" swarmapi "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/protobuf/ptypes" @@ -9,7 +8,6 @@ import ( // SecretFromGRPC converts a grpc Secret to a Secret. func SecretFromGRPC(s *swarmapi.Secret) swarmtypes.Secret { - logrus.Debugf("%+v", s) secret := swarmtypes.Secret{ ID: s.ID, Digest: s.Digest, @@ -33,14 +31,12 @@ func SecretFromGRPC(s *swarmapi.Secret) swarmtypes.Secret { } // SecretSpecToGRPC converts Secret to a grpc Secret. -func SecretSpecToGRPC(s swarmtypes.SecretSpec) (swarmapi.SecretSpec, error) { - spec := swarmapi.SecretSpec{ +func SecretSpecToGRPC(s swarmtypes.SecretSpec) swarmapi.SecretSpec { + return swarmapi.SecretSpec{ Annotations: swarmapi.Annotations{ Name: s.Name, Labels: s.Labels, }, Data: s.Data, } - - return spec, nil } diff --git a/daemon/cluster/executor/container/attachment.go b/daemon/cluster/executor/container/attachment.go index a897268b26..4dce78cd46 100644 --- a/daemon/cluster/executor/container/attachment.go +++ b/daemon/cluster/executor/container/attachment.go @@ -2,9 +2,9 @@ package container import ( executorpkg "github.com/docker/docker/daemon/cluster/executor" + "github.com/docker/swarmkit/agent/exec" "github.com/docker/swarmkit/api" "golang.org/x/net/context" - "src/github.com/docker/swarmkit/agent/exec" ) // networkAttacherController implements agent.Controller against docker's API. diff --git a/daemon/cluster/secrets.go b/daemon/cluster/secrets.go index 305f2afab5..ca795192be 100644 --- a/daemon/cluster/secrets.go +++ b/daemon/cluster/secrets.go @@ -63,10 +63,7 @@ func (c *Cluster) CreateSecret(s types.SecretSpec) (string, error) { ctx, cancel := c.getRequestContext() defer cancel() - secretSpec, err := convert.SecretSpecToGRPC(s) - if err != nil { - return "", err - } + secretSpec := convert.SecretSpecToGRPC(s) r, err := c.node.client.CreateSecret(ctx, &swarmapi.CreateSecretRequest{Spec: &secretSpec}) @@ -111,10 +108,7 @@ func (c *Cluster) UpdateSecret(id string, version uint64, spec types.SecretSpec) ctx, cancel := c.getRequestContext() defer cancel() - secretSpec, err := convert.SecretSpecToGRPC(spec) - if err != nil { - return err - } + secretSpec := convert.SecretSpecToGRPC(spec) if _, err := c.client.UpdateSecret(ctx, &swarmapi.UpdateSecretRequest{ diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index a3031ba3e6..e208962030 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -13,6 +13,7 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/cloudflare/cfssl/log" containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/daemon/links" @@ -25,6 +26,7 @@ import ( "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/label" "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } @@ -146,36 +148,58 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) error { localMountPath := c.SecretMountPath() logrus.Debugf("secrets: setting up secret dir: %s", localMountPath) + var setupErr error + + defer func(err error) { + if err != nil { + // cleanup + _ = detachMounted(localMountPath) + + if err := os.RemoveAll(localMountPath); err != nil { + log.Errorf("error cleaning up secret mount: %s", err) + } + } + }(setupErr) + // create tmpfs if err := os.MkdirAll(localMountPath, 0700); err != nil { - return fmt.Errorf("error creating secret local mount path: %s", err) + setupErr = errors.Wrap(err, "error creating secret local mount path") } if err := mount.Mount("tmpfs", localMountPath, "tmpfs", "nodev"); err != nil { - return fmt.Errorf("unable to setup secret mount: %s", err) + setupErr = errors.Wrap(err, "unable to setup secret mount") } for _, s := range c.Secrets { - fPath := filepath.Join(localMountPath, s.Target) - if err := os.MkdirAll(filepath.Dir(fPath), 0700); err != nil { - return fmt.Errorf("error creating secret mount path: %s", err) + // ensure that the target is a filename only; no paths allowed + tDir, tPath := filepath.Split(s.Target) + if tDir != "" { + setupErr = fmt.Errorf("error creating secret: secret must not have a path") } - logrus.Debugf("injecting secret: name=%s path=%s", s.Name, fPath) + fPath := filepath.Join(localMountPath, tPath) + if err := os.MkdirAll(filepath.Dir(fPath), 0700); err != nil { + setupErr = errors.Wrap(err, "error creating secret mount path") + } + + logrus.WithFields(logrus.Fields{ + "name": s.Name, + "path": fPath, + }).Debug("injecting secret") if err := ioutil.WriteFile(fPath, s.Data, s.Mode); err != nil { - return fmt.Errorf("error injecting secret: %s", err) + setupErr = errors.Wrap(err, "error injecting secret") } if err := os.Chown(fPath, s.Uid, s.Gid); err != nil { - return fmt.Errorf("error setting ownership for secret: %s", err) + setupErr = errors.Wrap(err, "error setting ownership for secret") } } // remount secrets ro if err := mount.Mount("tmpfs", localMountPath, "tmpfs", "remount,ro"); err != nil { - return fmt.Errorf("unable to remount secret dir as readonly: %s", err) + setupErr = errors.Wrap(err, "unable to remount secret dir as readonly") } - return nil + return setupErr } func killProcessDirectly(container *container.Container) error { diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index d74ddd053b..6889a737c5 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -718,7 +718,8 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { } ms = append(ms, tmpfsMounts...) - ms = append(ms, c.SecretMounts()...) + ms = append(ms, c.SecretMount()) + sort.Sort(mounts(ms)) if err := setMounts(daemon, &s, c, ms); err != nil { return nil, fmt.Errorf("linux mounts: %v", err) diff --git a/daemon/secrets.go b/daemon/secrets.go index 342b33c2a3..1d13adaaa4 100644 --- a/daemon/secrets.go +++ b/daemon/secrets.go @@ -5,8 +5,9 @@ import ( containertypes "github.com/docker/docker/api/types/container" ) +// SetContainerSecrets sets the container secrets needed func (daemon *Daemon) SetContainerSecrets(name string, secrets []*containertypes.ContainerSecret) error { - if !secretsSupported() { + if !secretsSupported() && len(secrets) > 0 { logrus.Warn("secrets are not supported on this platform") return nil } diff --git a/integration-cli/daemon_swarm.go b/integration-cli/daemon_swarm.go index 50e464cdd2..222fa018e9 100644 --- a/integration-cli/daemon_swarm.go +++ b/integration-cli/daemon_swarm.go @@ -284,6 +284,16 @@ func (d *SwarmDaemon) listServices(c *check.C) []swarm.Service { return services } +func (d *SwarmDaemon) listSecrets(c *check.C) []swarm.Service { + status, out, err := d.SockRequest("GET", "/secrets", nil) + c.Assert(err, checker.IsNil, check.Commentf(string(out))) + c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out))) + + secrets := []swarm.Secret{} + c.Assert(json.Unmarshal(out, &secrets), checker.IsNil) + return services +} + func (d *SwarmDaemon) getSwarm(c *check.C) swarm.Swarm { var sw swarm.Swarm status, out, err := d.SockRequest("GET", "/swarm", nil) diff --git a/integration-cli/docker_api_swarm_test.go b/integration-cli/docker_api_swarm_test.go index 229c7a8e03..be6efeed4c 100644 --- a/integration-cli/docker_api_swarm_test.go +++ b/integration-cli/docker_api_swarm_test.go @@ -1263,3 +1263,27 @@ func (s *DockerSwarmSuite) TestAPISwarmServicesUpdateWithName(c *check.C) { c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out))) waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, instances) } + +func (s *DockerSwarmSuite) TestAPISwarmSecretsEmptyList(c *check.C) { + d := s.AddDaemon(c, true, true) + + secrets := d.listSecrets(c) + c.Assert(secrets, checker.NotNil) + c.Assert(len(secrets), checker.Equals, 0, check.Commentf("secrets: %#v", secrets)) +} + +//func (s *DockerSwarmSuite) TestAPISwarmServicesCreate(c *check.C) { +// d := s.AddDaemon(c, true, true) +// +// instances := 2 +// id := d.createService(c, simpleTestService, setInstances(instances)) +// waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, instances) +// +// service := d.getService(c, id) +// instances = 5 +// d.updateService(c, service, setInstances(instances)) +// waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, instances) +// +// d.removeService(c, service.ID) +// waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 0) +//}