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"
@@ -226,15 +231,37 @@ func (s *composeService) generateImageDigestsOverride(ctx context.Context, proje
226231 return override .MarshalYAML ()
227232}
228233
234+ //nolint:gocyclo
229235func (s * composeService ) preChecks (project * types.Project , options api.PublishOptions ) (bool , error ) {
230- if ok , err := s .checkOnlyBuildSection (project ); ! ok {
236+ if ok , err := s .checkOnlyBuildSection (project ); ! ok || err != nil {
237+ return false , err
238+ }
239+ if ok , err := s .checkForBindMount (project ); ! ok || err != nil {
231240 return false , err
232241 }
242+ if options .AssumeYes {
243+ return true , nil
244+ }
245+ detectedSecrets , err := s .checkForSensitiveData (project )
246+ if err != nil {
247+ return false , err
248+ }
249+ if len (detectedSecrets ) > 0 {
250+ fmt .Println ("you are about to publish sensitive data within your OCI artifact.\n " +
251+ "please double check that you are not leaking sensitive data" )
252+ for _ , val := range detectedSecrets {
253+ _ , _ = fmt .Fprintln (s .dockerCli .Out (), val .Type )
254+ _ , _ = fmt .Fprintf (s .dockerCli .Out (), "%q: %s\n " , val .Key , val .Value )
255+ }
256+ if ok , err := acceptPublishSensitiveData (s .dockerCli ); err != nil || ! ok {
257+ return false , err
258+ }
259+ }
233260 envVariables , err := s .checkEnvironmentVariables (project , options )
234261 if err != nil {
235262 return false , err
236263 }
237- if ! options . AssumeYes && len (envVariables ) > 0 {
264+ if len (envVariables ) > 0 {
238265 fmt .Println ("you are about to publish environment variables within your OCI artifact.\n " +
239266 "please double check that you are not leaking sensitive data" )
240267 for key , val := range envVariables {
@@ -243,17 +270,10 @@ func (s *composeService) preChecks(project *types.Project, options api.PublishOp
243270 _ , _ = fmt .Fprintf (s .dockerCli .Out (), "%s=%v\n " , k , * v )
244271 }
245272 }
246- return acceptPublishEnvVariables (s .dockerCli )
247- }
248-
249- for name , config := range project .Services {
250- for _ , volume := range config .Volumes {
251- if volume .Type == types .VolumeTypeBind {
252- return false , fmt .Errorf ("cannot publish compose file: service %q relies on bind-mount. You should use volumes" , name )
253- }
273+ if ok , err := acceptPublishEnvVariables (s .dockerCli ); err != nil || ! ok {
274+ return false , err
254275 }
255276 }
256-
257277 return true , nil
258278}
259279
@@ -299,6 +319,12 @@ func acceptPublishEnvVariables(cli command.Cli) (bool, error) {
299319 return confirm , err
300320}
301321
322+ func acceptPublishSensitiveData (cli command.Cli ) (bool , error ) {
323+ msg := "Are you ok to publish these sensitive data? [y/N]: "
324+ confirm , err := prompt .NewPrompt (cli .In (), cli .Out ()).Confirm (msg , false )
325+ return confirm , err
326+ }
327+
302328func envFileLayers (project * types.Project ) []ocipush.Pushable {
303329 var layers []ocipush.Pushable
304330 for _ , service := range project .Services {
@@ -334,3 +360,99 @@ func (s *composeService) checkOnlyBuildSection(project *types.Project) (bool, er
334360 }
335361 return true , nil
336362}
363+
364+ func (s * composeService ) checkForBindMount (project * types.Project ) (bool , error ) {
365+ for name , config := range project .Services {
366+ for _ , volume := range config .Volumes {
367+ if volume .Type == types .VolumeTypeBind {
368+ return false , fmt .Errorf ("cannot publish compose file: service %q relies on bind-mount. You should use volumes" , name )
369+ }
370+ }
371+ }
372+ return true , nil
373+ }
374+
375+ func (s * composeService ) checkForSensitiveData (project * types.Project ) ([]secrets.DetectedSecret , error ) {
376+ var allFindings []secrets.DetectedSecret
377+ scan := scanner .NewDefaultScanner ()
378+ // Check all compose files
379+ for _ , file := range project .ComposeFiles {
380+ in , err := composeFileAsByteReader (file , project )
381+ if err != nil {
382+ return nil , err
383+ }
384+
385+ findings , err := scan .ScanReader (in )
386+ if err != nil {
387+ return nil , fmt .Errorf ("failed to scan compose file %s: %w" , file , err )
388+ }
389+ allFindings = append (allFindings , findings ... )
390+ }
391+ for _ , service := range project .Services {
392+ // Check env files
393+ for _ , envFile := range service .EnvFiles {
394+ findings , err := scan .ScanFile (envFile .Path )
395+ if err != nil {
396+ return nil , fmt .Errorf ("failed to scan env file %s: %w" , envFile .Path , err )
397+ }
398+ allFindings = append (allFindings , findings ... )
399+ }
400+ }
401+
402+ // Check configs defined by files
403+ for _ , config := range project .Configs {
404+ if config .File != "" {
405+ findings , err := scan .ScanFile (config .File )
406+ if err != nil {
407+ return nil , fmt .Errorf ("failed to scan config file %s: %w" , config .File , err )
408+ }
409+ allFindings = append (allFindings , findings ... )
410+ }
411+ }
412+
413+ // Check secrets defined by files
414+ for _ , secret := range project .Secrets {
415+ if secret .File != "" {
416+ findings , err := scan .ScanFile (secret .File )
417+ if err != nil {
418+ return nil , fmt .Errorf ("failed to scan secret file %s: %w" , secret .File , err )
419+ }
420+ allFindings = append (allFindings , findings ... )
421+ }
422+ }
423+
424+ return allFindings , nil
425+ }
426+
427+ func composeFileAsByteReader (filePath string , project * types.Project ) (io.Reader , error ) {
428+ composeFile , err := os .ReadFile (filePath )
429+ if err != nil {
430+ return nil , fmt .Errorf ("failed to open compose file %s: %w" , filePath , err )
431+ }
432+ base , err := loader .LoadWithContext (context .TODO (), types.ConfigDetails {
433+ WorkingDir : project .WorkingDir ,
434+ Environment : project .Environment ,
435+ ConfigFiles : []types.ConfigFile {
436+ {
437+ Filename : filePath ,
438+ Content : composeFile ,
439+ },
440+ },
441+ }, func (options * loader.Options ) {
442+ options .SkipValidation = true
443+ options .SkipExtends = true
444+ options .SkipConsistencyCheck = true
445+ options .ResolvePaths = true
446+ options .SkipInterpolation = true
447+ options .SkipResolveEnvironment = true
448+ })
449+ if err != nil {
450+ return nil , err
451+ }
452+
453+ in , err := base .MarshalYAML ()
454+ if err != nil {
455+ return nil , err
456+ }
457+ return bytes .NewBuffer (in ), nil
458+ }
0 commit comments