Skip to content
Open
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
39 changes: 38 additions & 1 deletion pkg/compose/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,10 +407,26 @@ func (s *composeService) checkOnlyBuildSection(project *types.Project) (bool, er
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{}
var bindMounts []types.ServiceVolumeConfig
for _, volume := range config.Volumes {
if volume.Type == types.VolumeTypeBind {
bindMounts = append(bindMounts, volume)
continue
}
if volume.Type == types.VolumeTypeVolume && volume.Source != "" {
if topLevel, ok := project.Volumes[volume.Source]; ok {
if isDriverOptsBind(topLevel) {
device := strings.TrimSpace(topLevel.DriverOpts["device"])
bindMounts = append(bindMounts, types.ServiceVolumeConfig{
Type: types.VolumeTypeBind,
Source: device,
Target: volume.Target,
ReadOnly: volume.ReadOnly,
Consistency: volume.Consistency,
Bind: volume.Bind,
})
}
}
}
}
if len(bindMounts) > 0 {
Expand All @@ -420,6 +436,27 @@ func (s *composeService) checkForBindMount(project *types.Project) map[string][]
return allFindings
}

func isDriverOptsBind(v types.VolumeConfig) bool {
if v.Driver != "" && v.Driver != "local" {
return false
}
opts := v.DriverOpts
if len(opts) == 0 {
return false
}
device := strings.TrimSpace(opts["device"])
if device == "" {
return false
}
for _, opt := range strings.Split(opts["o"], ",") {
switch strings.TrimSpace(opt) {
case "bind", "rbind":
return true
}
}
return false
}

func (s *composeService) checkForSensitiveData(ctx context.Context, project *types.Project) ([]secrets.DetectedSecret, error) {
var allFindings []secrets.DetectedSecret
scan := scanner.NewDefaultScanner()
Expand Down
121 changes: 121 additions & 0 deletions pkg/compose/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,124 @@ func Test_publish_decline_returns_ErrCanceled(t *testing.T) {
assert.Assert(t, errors.Is(err, api.ErrCanceled),
"expected api.ErrCanceled when user declines, got: %v", err)
}

func Test_checkForBindMount_namedVolume_driverOptsBind(t *testing.T) {
project := &types.Project{
Services: types.Services{
"web": {
Name: "web",
Image: "nginx",
Volumes: []types.ServiceVolumeConfig{
{
Type: types.VolumeTypeVolume,
Source: "secret_host_data",
Target: "/mnt/data",
},
{
Type: types.VolumeTypeVolume,
Source: "normal_vol",
Target: "/data",
},
},
},
},
Volumes: types.Volumes{
"secret_host_data": {
Driver: "local",
DriverOpts: map[string]string{
"type": "none",
"o": "bind",
"device": "/Users/admin/.ssh",
},
},
"normal_vol": {
Driver: "local",
},
},
}

svc := &composeService{}
findings := svc.checkForBindMount(project)

assert.Equal(t, 1, len(findings["web"]), "expected exactly one bind mount finding for web")
assert.Equal(t, "/Users/admin/.ssh", findings["web"][0].Source)
assert.Equal(t, "/mnt/data", findings["web"][0].Target)
}

func Test_isDriverOptsBind(t *testing.T) {
tests := []struct {
name string
volume types.VolumeConfig
expected bool
}{
{
name: "plain bind",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "bind", "device": "/host/path"},
},
expected: true,
},
{
name: "rbind",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "rbind", "device": "/host/path"},
},
expected: true,
},
{
name: "comma-separated ro,bind — validates split logic",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "ro,bind", "device": "/host/path"},
},
expected: true,
},
{
name: "nobind must not match — guards against substring false positive",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "nobind", "device": "/host/path"},
},
expected: false,
},
{
name: "no device key",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "bind"},
},
expected: false,
},
{
name: "empty device value",
volume: types.VolumeConfig{
Driver: "local",
DriverOpts: map[string]string{"o": "bind", "device": " "},
},
expected: false,
},
{
name: "empty driver_opts",
volume: types.VolumeConfig{
Driver: "local",
},
expected: false,
},
{
name: "non-local driver",
volume: types.VolumeConfig{
Driver: "nfs",
DriverOpts: map[string]string{"o": "bind", "device": "/host/path"},
},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, isDriverOptsBind(tt.volume))
})
}
}