1717package compose
1818
1919import (
20+ "bytes"
2021 "context"
2122 "crypto/sha256"
2223 "errors"
2324 "fmt"
25+ "io"
2426 "os"
2527
28+ "github.com/DefangLabs/secret-detector/pkg/scanner"
29+ "github.com/DefangLabs/secret-detector/pkg/secrets"
30+
2631 "github.com/compose-spec/compose-go/v2/loader"
2732 "github.com/compose-spec/compose-go/v2/types"
2833 "github.com/distribution/reference"
@@ -259,15 +264,37 @@ func (s *composeService) generateImageDigestsOverride(ctx context.Context, proje
259264 return override .MarshalYAML ()
260265}
261266
267+ //nolint:gocyclo
262268func (s * composeService ) preChecks (project * types.Project , options api.PublishOptions ) (bool , error ) {
263- if ok , err := s .checkOnlyBuildSection (project ); ! ok {
269+ if ok , err := s .checkOnlyBuildSection (project ); ! ok || err != nil {
270+ return false , err
271+ }
272+ if ok , err := s .checkForBindMount (project ); ! ok || err != nil {
264273 return false , err
265274 }
275+ if options .AssumeYes {
276+ return true , nil
277+ }
278+ detectedSecrets , err := s .checkForSensitiveData (project )
279+ if err != nil {
280+ return false , err
281+ }
282+ if len (detectedSecrets ) > 0 {
283+ fmt .Println ("you are about to publish sensitive data within your OCI artifact.\n " +
284+ "please double check that you are not leaking sensitive data" )
285+ for _ , val := range detectedSecrets {
286+ _ , _ = fmt .Fprintln (s .dockerCli .Out (), val .Type )
287+ _ , _ = fmt .Fprintf (s .dockerCli .Out (), "%q: %s\n " , val .Key , val .Value )
288+ }
289+ if ok , err := acceptPublishSensitiveData (s .dockerCli ); err != nil || ! ok {
290+ return false , err
291+ }
292+ }
266293 envVariables , err := s .checkEnvironmentVariables (project , options )
267294 if err != nil {
268295 return false , err
269296 }
270- if ! options . AssumeYes && len (envVariables ) > 0 {
297+ if len (envVariables ) > 0 {
271298 fmt .Println ("you are about to publish environment variables within your OCI artifact.\n " +
272299 "please double check that you are not leaking sensitive data" )
273300 for key , val := range envVariables {
@@ -276,17 +303,10 @@ func (s *composeService) preChecks(project *types.Project, options api.PublishOp
276303 _ , _ = fmt .Fprintf (s .dockerCli .Out (), "%s=%v\n " , k , * v )
277304 }
278305 }
279- return acceptPublishEnvVariables (s .dockerCli )
280- }
281-
282- for name , config := range project .Services {
283- for _ , volume := range config .Volumes {
284- if volume .Type == types .VolumeTypeBind {
285- return false , fmt .Errorf ("cannot publish compose file: service %q relies on bind-mount. You should use volumes" , name )
286- }
306+ if ok , err := acceptPublishEnvVariables (s .dockerCli ); err != nil || ! ok {
307+ return false , err
287308 }
288309 }
289-
290310 return true , nil
291311}
292312
@@ -332,6 +352,12 @@ func acceptPublishEnvVariables(cli command.Cli) (bool, error) {
332352 return confirm , err
333353}
334354
355+ func acceptPublishSensitiveData (cli command.Cli ) (bool , error ) {
356+ msg := "Are you ok to publish these sensitive data? [y/N]: "
357+ confirm , err := prompt .NewPrompt (cli .In (), cli .Out ()).Confirm (msg , false )
358+ return confirm , err
359+ }
360+
335361func envFileLayers (project * types.Project ) []ocipush.Pushable {
336362 var layers []ocipush.Pushable
337363 for _ , service := range project .Services {
@@ -367,3 +393,103 @@ func (s *composeService) checkOnlyBuildSection(project *types.Project) (bool, er
367393 }
368394 return true , nil
369395}
396+
397+ func (s * composeService ) checkForBindMount (project * types.Project ) (bool , error ) {
398+ for name , config := range project .Services {
399+ for _ , volume := range config .Volumes {
400+ if volume .Type == types .VolumeTypeBind {
401+ return false , fmt .Errorf ("cannot publish compose file: service %q relies on bind-mount. You should use volumes" , name )
402+ }
403+ }
404+ }
405+ return true , nil
406+ }
407+
408+ func (s * composeService ) checkForSensitiveData (project * types.Project ) ([]secrets.DetectedSecret , error ) {
409+ var allFindings []secrets.DetectedSecret
410+ scan := scanner .NewDefaultScanner ()
411+
412+ projecYaml , _ := project .MarshalYAML ()
413+ _ , _ = scan .ScanReader (bytes .NewReader (projecYaml ))
414+
415+ // Check all compose files
416+ for _ , file := range project .ComposeFiles {
417+ in , err := composeFileAsByteReader (file , project )
418+ if err != nil {
419+ return nil , err
420+ }
421+
422+ findings , err := scan .ScanReader (in )
423+ if err != nil {
424+ return nil , fmt .Errorf ("failed to scan compose file %s: %w" , file , err )
425+ }
426+ allFindings = append (allFindings , findings ... )
427+ }
428+ for _ , service := range project .Services {
429+ // Check env files
430+ for _ , envFile := range service .EnvFiles {
431+ findings , err := scan .ScanFile (envFile .Path )
432+ if err != nil {
433+ return nil , fmt .Errorf ("failed to scan env file %s: %w" , envFile .Path , err )
434+ }
435+ allFindings = append (allFindings , findings ... )
436+ }
437+ }
438+
439+ // Check configs defined by files
440+ for _ , config := range project .Configs {
441+ if config .File != "" {
442+ findings , err := scan .ScanFile (config .File )
443+ if err != nil {
444+ return nil , fmt .Errorf ("failed to scan config file %s: %w" , config .File , err )
445+ }
446+ allFindings = append (allFindings , findings ... )
447+ }
448+ }
449+
450+ // Check secrets defined by files
451+ for _ , secret := range project .Secrets {
452+ if secret .File != "" {
453+ findings , err := scan .ScanFile (secret .File )
454+ if err != nil {
455+ return nil , fmt .Errorf ("failed to scan secret file %s: %w" , secret .File , err )
456+ }
457+ allFindings = append (allFindings , findings ... )
458+ }
459+ }
460+
461+ return allFindings , nil
462+ }
463+
464+ func composeFileAsByteReader (filePath string , project * types.Project ) (io.Reader , error ) {
465+ composeFile , err := os .ReadFile (filePath )
466+ if err != nil {
467+ return nil , fmt .Errorf ("failed to open compose file %s: %w" , filePath , err )
468+ }
469+ base , err := loader .LoadWithContext (context .TODO (), types.ConfigDetails {
470+ WorkingDir : project .WorkingDir ,
471+ Environment : project .Environment ,
472+ ConfigFiles : []types.ConfigFile {
473+ {
474+ Filename : filePath ,
475+ Content : composeFile ,
476+ },
477+ },
478+ }, func (options * loader.Options ) {
479+ options .SkipValidation = true
480+ options .SkipExtends = true
481+ options .SkipConsistencyCheck = true
482+ options .ResolvePaths = true
483+ options .SkipInterpolation = true
484+ options .SkipResolveEnvironment = true
485+ })
486+ if err != nil {
487+ return nil , err
488+ }
489+
490+ in , err := base .MarshalYAML ()
491+ if err != nil {
492+ return nil , err
493+ }
494+ return bytes .NewBuffer (in ), nil
495+ }
0 commit comments