diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index 39b531624..1db3bc3db 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -523,6 +523,8 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types return created, err } } + + err = s.injectSecrets(ctx, project, service, created.ID) return created, err } diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 4b0969d54..0c6bf8067 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -889,6 +889,10 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name) } + if definedSecret.Environment != "" { + continue + } + mount, err := buildMount(p, types.ServiceVolumeConfig{ Type: types.VolumeTypeBind, Source: definedSecret.File, diff --git a/pkg/compose/secrets.go b/pkg/compose/secrets.go new file mode 100644 index 000000000..a7114cbb3 --- /dev/null +++ b/pkg/compose/secrets.go @@ -0,0 +1,88 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "archive/tar" + "bytes" + "context" + "fmt" + "time" + + "github.com/compose-spec/compose-go/types" + moby "github.com/docker/docker/api/types" +) + +func (s *composeService) injectSecrets(ctx context.Context, project *types.Project, service types.ServiceConfig, id string) error { + for _, config := range service.Secrets { + secret := project.Secrets[config.Source] + if secret.Environment == "" { + continue + } + + env, ok := project.Environment[secret.Environment] + if !ok { + return fmt.Errorf("environment variable %q required by secret %q is not set", secret.Environment, secret.Name) + } + b, err := createTar(env, config) + if err != nil { + return err + } + + err = s.apiClient().CopyToContainer(ctx, id, "/", &b, moby.CopyToContainerOptions{ + CopyUIDGID: true, + }) + if err != nil { + return err + } + } + return nil +} + +func createTar(env string, config types.ServiceSecretConfig) (bytes.Buffer, error) { + value := []byte(env) + b := bytes.Buffer{} + tarWriter := tar.NewWriter(&b) + mode := uint32(0400) + if config.Mode != nil { + mode = *config.Mode + } + + target := config.Target + if config.Target == "" { + target = "/run/secrets/" + config.Source + } else if !isUnixAbs(config.Target) { + target = "/run/secrets/" + config.Target + } + + header := &tar.Header{ + Name: target, + Size: int64(len(value)), + Mode: int64(mode), + ModTime: time.Now(), + } + err := tarWriter.WriteHeader(header) + if err != nil { + return bytes.Buffer{}, err + } + _, err = tarWriter.Write(value) + if err != nil { + return bytes.Buffer{}, err + } + err = tarWriter.Close() + return b, err +} diff --git a/pkg/e2e/fixtures/env-secret/compose.yaml b/pkg/e2e/fixtures/env-secret/compose.yaml new file mode 100644 index 000000000..159dcac70 --- /dev/null +++ b/pkg/e2e/fixtures/env-secret/compose.yaml @@ -0,0 +1,11 @@ +services: + foo: + image: alpine + secrets: + - bar + command: cat /run/secrets/bar + +secrets: + bar: + environment: SECRET + diff --git a/pkg/e2e/secrets_test.go b/pkg/e2e/secrets_test.go new file mode 100644 index 000000000..9e92e124b --- /dev/null +++ b/pkg/e2e/secrets_test.go @@ -0,0 +1,35 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package e2e + +import ( + "testing" + + "gotest.tools/v3/icmd" +) + +func TestSecretFromEnv(t *testing.T) { + c := NewParallelE2eCLI(t, binDir) + + t.Run("compose run", func(t *testing.T) { + res := icmd.RunCmd(c.NewDockerCmd("compose", "-f", "./fixtures/env-secret/compose.yaml", "run", "foo"), + func(cmd *icmd.Cmd) { + cmd.Env = append(cmd.Env, "SECRET=BAR") + }) + res.Assert(t, icmd.Expected{Out: "BAR"}) + }) +}