Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
113 changes: 113 additions & 0 deletions pkg/compose/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,116 @@ 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 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))
})
}
}
Loading