Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions pkg/compose/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,20 @@ func (s *composeService) preChecks(project *types.Project, options api.PublishOp
if ok, err := s.checkOnlyBuildSection(project); !ok || err != nil {
return false, err
}
if ok, err := s.checkForBindMount(project); !ok || err != nil {
return false, err
bindMounts := s.checkForBindMount(project)
if len(bindMounts) > 0 {
fmt.Println("you are about to publish bind mounts declaration within your OCI artifact.\n" +
"only the bind mount declarations will be added to the OCI artifact (not content)\n" +
"please double check that you are not mounting potential user's sensitive directories or data")
for key, val := range bindMounts {
_, _ = fmt.Fprintln(s.dockerCli.Out(), key)
for _, v := range val {
_, _ = fmt.Fprintf(s.dockerCli.Out(), "%s\n", v.String())
}
}
if ok, err := acceptPublishBindMountDeclarations(s.dockerCli); err != nil || !ok {
return false, err
}
}
if options.AssumeYes {
return true, nil
Expand Down Expand Up @@ -325,6 +337,12 @@ func acceptPublishSensitiveData(cli command.Cli) (bool, error) {
return confirm, err
}

func acceptPublishBindMountDeclarations(cli command.Cli) (bool, error) {
msg := "Are you ok to publish these bind mount declarations? [y/N]: "
confirm, err := prompt.NewPrompt(cli.In(), cli.Out()).Confirm(msg, false)
return confirm, err
}

func envFileLayers(project *types.Project) []ocipush.Pushable {
var layers []ocipush.Pushable
for _, service := range project.Services {
Expand Down Expand Up @@ -361,15 +379,20 @@ func (s *composeService) checkOnlyBuildSection(project *types.Project) (bool, er
return true, nil
}

func (s *composeService) checkForBindMount(project *types.Project) (bool, error) {
for name, config := range project.Services {
func (s *composeService) checkForBindMount(project *types.Project) map[string][]types.ServiceVolumeConfig {
allFindings := map[string][]types.ServiceVolumeConfig{}
for serviceName, config := range project.Services {
bindMounts := []types.ServiceVolumeConfig{}
for _, volume := range config.Volumes {
if volume.Type == types.VolumeTypeBind {
return false, fmt.Errorf("cannot publish compose file: service %q relies on bind-mount. You should use volumes", name)
bindMounts = append(bindMounts, volume)
}
}
if len(bindMounts) > 0 {
allFindings[serviceName] = bindMounts
}
}
return true, nil
return allFindings
}

func (s *composeService) checkForSensitiveData(project *types.Project) ([]secrets.DetectedSecret, error) {
Expand Down
48 changes: 33 additions & 15 deletions pkg/e2e/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@ func TestPublishChecks(t *testing.T) {

t.Run("publish error environment", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-environment.yml",
"-p", projectName, "alpha", "publish", "test/test")
"-p", projectName, "publish", "test/test")
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `service "serviceA" has environment variable(s) declared.
To avoid leaking sensitive data,`})
})

t.Run("publish error env_file", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-env-file.yml",
"-p", projectName, "alpha", "publish", "test/test")
"-p", projectName, "publish", "test/test")
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `service "serviceA" has env_file declared.
service "serviceA" has environment variable(s) declared.
To avoid leaking sensitive data,`})
})

t.Run("publish multiple errors env_file and environment", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-multi-env-config.yml",
"-p", projectName, "alpha", "publish", "test/test")
"-p", projectName, "publish", "test/test")
// we don't in which order the services will be loaded, so we can't predict the order of the error messages
assert.Assert(t, strings.Contains(res.Combined(), `service "serviceB" has env_file declared.`), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), `service "serviceB" has environment variable(s) declared.`), res.Combined())
Expand All @@ -57,21 +57,21 @@ or remove sensitive data from your Compose configuration

t.Run("publish success environment", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/compose-environment.yml",
"-p", projectName, "alpha", "publish", "test/test", "--with-env", "-y", "--dry-run")
"-p", projectName, "publish", "test/test", "--with-env", "-y", "--dry-run")
assert.Assert(t, strings.Contains(res.Combined(), "test/test publishing"), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined())
})

t.Run("publish success env_file", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/compose-env-file.yml",
"-p", projectName, "alpha", "publish", "test/test", "--with-env", "-y", "--dry-run")
"-p", projectName, "publish", "test/test", "--with-env", "-y", "--dry-run")
assert.Assert(t, strings.Contains(res.Combined(), "test/test publishing"), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined())
})

t.Run("publish approve validation message", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-env-file.yml",
"-p", projectName, "alpha", "publish", "test/test", "--with-env", "--dry-run")
"-p", projectName, "publish", "test/test", "--with-env", "--dry-run")
cmd.Stdin = strings.NewReader("y\n")
res := icmd.RunCmd(cmd)
res.Assert(t, icmd.Expected{ExitCode: 0})
Expand All @@ -82,7 +82,7 @@ or remove sensitive data from your Compose configuration

t.Run("publish refuse validation message", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-env-file.yml",
"-p", projectName, "alpha", "publish", "test/test", "--with-env", "--dry-run")
"-p", projectName, "publish", "test/test", "--with-env", "--dry-run")
cmd.Stdin = strings.NewReader("n\n")
res := icmd.RunCmd(cmd)
res.Assert(t, icmd.Expected{ExitCode: 0})
Expand All @@ -93,13 +93,13 @@ or remove sensitive data from your Compose configuration

t.Run("publish with extends", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/compose-with-extends.yml",
"-p", projectName, "alpha", "publish", "test/test", "--dry-run")
"-p", projectName, "publish", "test/test", "--dry-run")
assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined())
})

t.Run("publish list env variables", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-multi-env-config.yml",
"-p", projectName, "alpha", "publish", "test/test", "--with-env", "--dry-run")
"-p", projectName, "publish", "test/test", "--with-env", "--dry-run")
cmd.Stdin = strings.NewReader("n\n")
res := icmd.RunCmd(cmd)
res.Assert(t, icmd.Expected{ExitCode: 0})
Expand All @@ -115,14 +115,32 @@ FOO=bar`), res.Combined())
})

t.Run("refuse to publish with bind mount", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-bind-mount.yml",
"-p", projectName, "alpha", "publish", "test/test", "--dry-run")
res.Assert(t, icmd.Expected{ExitCode: 1, Err: `cannot publish compose file: service "serviceA" relies on bind-mount. You should use volumes`})
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-bind-mount.yml",
"-p", projectName, "publish", "test/test", "--dry-run")
cmd.Stdin = strings.NewReader("n\n")
res := icmd.RunCmd(cmd)
res.Assert(t, icmd.Expected{ExitCode: 0})
assert.Assert(t, strings.Contains(res.Combined(), "you are about to publish bind mounts declaration within your OCI artifact."), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), "e2e/fixtures/publish:/user-data"), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these bind mount declarations? [y/N]:"), res.Combined())
assert.Assert(t, !strings.Contains(res.Combined(), "serviceA published"), res.Combined())
})

t.Run("publish with bind mount", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-bind-mount.yml",
"-p", projectName, "publish", "test/test", "--dry-run")
cmd.Stdin = strings.NewReader("y\n")
res := icmd.RunCmd(cmd)
res.Assert(t, icmd.Expected{ExitCode: 0})
assert.Assert(t, strings.Contains(res.Combined(), "you are about to publish bind mounts declaration within your OCI artifact."), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these bind mount declarations? [y/N]:"), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), "e2e/fixtures/publish:/user-data"), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined())
})

t.Run("refuse to publish with build section only", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-build-only.yml",
"-p", projectName, "alpha", "publish", "test/test", "--with-env", "-y", "--dry-run")
"-p", projectName, "publish", "test/test", "--with-env", "-y", "--dry-run")
res.Assert(t, icmd.Expected{ExitCode: 1})
assert.Assert(t, strings.Contains(res.Combined(), "your Compose stack cannot be published as it only contains a build section for service(s):"), res.Combined())
assert.Assert(t, strings.Contains(res.Combined(), "serviceA"), res.Combined())
Expand All @@ -131,13 +149,13 @@ FOO=bar`), res.Combined())

t.Run("refuse to publish with local include", func(t *testing.T) {
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-local-include.yml",
"-p", projectName, "alpha", "publish", "test/test", "--dry-run")
"-p", projectName, "publish", "test/test", "--dry-run")
res.Assert(t, icmd.Expected{ExitCode: 1, Err: "cannot publish compose file with local includes"})
})

t.Run("detect sensitive data", func(t *testing.T) {
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-sensitive.yml",
"-p", projectName, "alpha", "publish", "test/test", "--with-env", "--dry-run")
"-p", projectName, "publish", "test/test", "--with-env", "--dry-run")
cmd.Stdin = strings.NewReader("n\n")
res := icmd.RunCmd(cmd)
res.Assert(t, icmd.Expected{ExitCode: 0})
Expand Down