github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/image/image.go (about) 1 package image 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/google/go-containerregistry/pkg/name" 9 v1 "github.com/google/go-containerregistry/pkg/v1" 10 multierror "github.com/hashicorp/go-multierror" 11 "golang.org/x/xerrors" 12 13 "github.com/devseccon/trivy/pkg/fanal/types" 14 "github.com/devseccon/trivy/pkg/log" 15 ) 16 17 type imageSourceFunc func(ctx context.Context, imageName string, ref name.Reference, option types.ImageOptions) (types.Image, func(), error) 18 19 var imageSourceFuncs = map[types.ImageSource]imageSourceFunc{ 20 types.ContainerdImageSource: tryContainerdDaemon, 21 types.PodmanImageSource: tryPodmanDaemon, 22 types.DockerImageSource: tryDockerDaemon, 23 types.RemoteImageSource: tryRemote, 24 } 25 26 func NewContainerImage(ctx context.Context, imageName string, opt types.ImageOptions) (types.Image, func(), error) { 27 if len(opt.ImageSources) == 0 { 28 return nil, func() {}, xerrors.New("no image sources supplied") 29 } 30 31 var errs error 32 var nameOpts []name.Option 33 if opt.RegistryOptions.Insecure { 34 nameOpts = append(nameOpts, name.Insecure) 35 } 36 37 ref, err := name.ParseReference(imageName, nameOpts...) 38 if err != nil { 39 return nil, func() {}, xerrors.Errorf("failed to parse the image name: %w", err) 40 } 41 42 for _, src := range opt.ImageSources { 43 trySrc, ok := imageSourceFuncs[src] 44 if !ok { 45 log.Logger.Warnf("Unknown image source: '%s'", src) 46 continue 47 } 48 49 img, cleanup, err := trySrc(ctx, imageName, ref, opt) 50 if err == nil { 51 // Return v1.Image if the image is found 52 return img, cleanup, nil 53 } 54 err = multierror.Prefix(err, fmt.Sprintf("%s error:", src)) 55 errs = multierror.Append(errs, err) 56 } 57 58 return nil, func() {}, errs 59 } 60 61 func ID(img v1.Image) (string, error) { 62 h, err := img.ConfigName() 63 if err != nil { 64 return "", xerrors.Errorf("unable to get the image ID: %w", err) 65 } 66 return h.String(), nil 67 } 68 69 func LayerIDs(img v1.Image) ([]string, error) { 70 conf, err := img.ConfigFile() 71 if err != nil { 72 return nil, xerrors.Errorf("unable to get the config file: %w", err) 73 } 74 75 var layerIDs []string 76 for _, d := range conf.RootFS.DiffIDs { 77 layerIDs = append(layerIDs, d.String()) 78 } 79 return layerIDs, nil 80 } 81 82 // GuessBaseImageIndex tries to guess index of base layer 83 // 84 // e.g. In the following example, we should detect layers in debian:8. 85 // 86 // FROM debian:8 87 // RUN apt-get update 88 // COPY mysecret / 89 // ENTRYPOINT ["entrypoint.sh"] 90 // CMD ["somecmd"] 91 // 92 // debian:8 may be like 93 // 94 // ADD file:5d673d25da3a14ce1f6cf66e4c7fd4f4b85a3759a9d93efb3fd9ff852b5b56e4 in / 95 // CMD ["/bin/sh"] 96 // 97 // In total, it would be like: 98 // 99 // ADD file:5d673d25da3a14ce1f6cf66e4c7fd4f4b85a3759a9d93efb3fd9ff852b5b56e4 in / 100 // CMD ["/bin/sh"] # empty layer (detected) 101 // RUN apt-get update 102 // COPY mysecret / 103 // ENTRYPOINT ["entrypoint.sh"] # empty layer (skipped) 104 // CMD ["somecmd"] # empty layer (skipped) 105 // 106 // This method tries to detect CMD in the second line and assume the first line is a base layer. 107 // 1. Iterate histories from the bottom. 108 // 2. Skip all the empty layers at the bottom. In the above example, "entrypoint.sh" and "somecmd" will be skipped 109 // 3. If it finds CMD, it assumes that it is the end of base layers. 110 // 4. It gets all the layers as base layers above the CMD found in #3. 111 func GuessBaseImageIndex(histories []v1.History) int { 112 baseImageIndex := -1 113 var foundNonEmpty bool 114 for i := len(histories) - 1; i >= 0; i-- { 115 h := histories[i] 116 117 // Skip the last CMD, ENTRYPOINT, etc. 118 if !foundNonEmpty { 119 if h.EmptyLayer { 120 continue 121 } 122 foundNonEmpty = true 123 } 124 125 if !h.EmptyLayer { 126 continue 127 } 128 129 // Detect CMD instruction in base image 130 if strings.HasPrefix(h.CreatedBy, "/bin/sh -c #(nop) CMD") || 131 strings.HasPrefix(h.CreatedBy, "CMD") { // BuildKit 132 baseImageIndex = i 133 break 134 } 135 } 136 return baseImageIndex 137 }