github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/artifact/vm/vm.go (about) 1 package vm 2 3 import ( 4 "context" 5 "io" 6 "os" 7 "strings" 8 "sync" 9 10 "golang.org/x/xerrors" 11 12 "github.com/devseccon/trivy/pkg/fanal/analyzer" 13 "github.com/devseccon/trivy/pkg/fanal/artifact" 14 "github.com/devseccon/trivy/pkg/fanal/cache" 15 "github.com/devseccon/trivy/pkg/fanal/handler" 16 "github.com/devseccon/trivy/pkg/fanal/types" 17 "github.com/devseccon/trivy/pkg/fanal/walker" 18 "github.com/devseccon/trivy/pkg/semaphore" 19 ) 20 21 type Type string 22 23 func (t Type) Prefix() string { 24 return string(t) + ":" 25 } 26 27 const ( 28 TypeAMI Type = "ami" 29 TypeEBS Type = "ebs" 30 TypeFile Type = "file" 31 ) 32 33 type Walker interface { 34 Walk(*io.SectionReader, string, walker.WalkFunc) error 35 } 36 37 func NewArtifact(target string, c cache.ArtifactCache, w Walker, opt artifact.Option) (artifact.Artifact, error) { 38 handlerManager, err := handler.NewManager(opt) 39 if err != nil { 40 return nil, xerrors.Errorf("handler init error: %w", err) 41 } 42 a, err := analyzer.NewAnalyzerGroup(opt.AnalyzerOptions()) 43 if err != nil { 44 return nil, xerrors.Errorf("analyzer group error: %w", err) 45 } 46 47 storage := Storage{ 48 cache: c, 49 analyzer: a, 50 handlerManager: handlerManager, 51 walker: w, 52 artifactOption: opt, 53 } 54 55 targetType := detectType(target) 56 switch targetType { 57 case TypeAMI: 58 target = strings.TrimPrefix(target, TypeAMI.Prefix()) 59 return newAMI(target, storage, opt.AWSRegion, opt.AWSEndpoint) 60 case TypeEBS: 61 target = strings.TrimPrefix(target, TypeEBS.Prefix()) 62 e, err := newEBS(target, storage, opt.AWSRegion, opt.AWSEndpoint) 63 if err != nil { 64 return nil, xerrors.Errorf("new EBS error: %w", err) 65 } 66 return e, nil 67 case TypeFile: 68 target = strings.TrimPrefix(target, TypeFile.Prefix()) 69 return newFile(target, storage) 70 } 71 return nil, xerrors.Errorf("unsupported format") 72 } 73 74 type Storage struct { 75 cache cache.ArtifactCache 76 analyzer analyzer.AnalyzerGroup 77 handlerManager handler.Manager 78 walker Walker 79 80 artifactOption artifact.Option 81 } 82 83 func (a *Storage) Analyze(ctx context.Context, r *io.SectionReader) (types.BlobInfo, error) { 84 var wg sync.WaitGroup 85 limit := semaphore.New(a.artifactOption.Parallel) 86 result := analyzer.NewAnalysisResult() 87 88 opts := analyzer.AnalysisOptions{ 89 Offline: a.artifactOption.Offline, 90 FileChecksum: a.artifactOption.FileChecksum, 91 } 92 93 // Prepare filesystem for post analysis 94 composite, err := a.analyzer.PostAnalyzerFS() 95 if err != nil { 96 return types.BlobInfo{}, xerrors.Errorf("unable to get post analysis filesystem: %w", err) 97 } 98 defer composite.Cleanup() 99 100 // TODO: Always walk from the root directory. Consider whether there is a need to be able to set optional 101 err = a.walker.Walk(r, "/", func(filePath string, info os.FileInfo, opener analyzer.Opener) error { 102 path := strings.TrimPrefix(filePath, "/") 103 if err := a.analyzer.AnalyzeFile(ctx, &wg, limit, result, "/", path, info, opener, nil, opts); err != nil { 104 return xerrors.Errorf("analyze file (%s): %w", path, err) 105 } 106 107 // Skip post analysis if the file is not required 108 analyzerTypes := a.analyzer.RequiredPostAnalyzers(path, info) 109 if len(analyzerTypes) == 0 { 110 return nil 111 } 112 113 // Build filesystem for post analysis 114 tmpFilePath, err := composite.CopyFileToTemp(opener, info) 115 if err != nil { 116 return xerrors.Errorf("failed to copy file to temp: %w", err) 117 } 118 119 if err = composite.CreateLink(analyzerTypes, "", path, tmpFilePath); err != nil { 120 return xerrors.Errorf("failed to write a file: %w", err) 121 } 122 123 return nil 124 }) 125 126 // Wait for all the goroutine to finish. 127 wg.Wait() 128 129 if err != nil { 130 return types.BlobInfo{}, xerrors.Errorf("walk vm error: %w", err) 131 } 132 133 // Post-analysis 134 if err = a.analyzer.PostAnalyze(ctx, composite, result, opts); err != nil { 135 return types.BlobInfo{}, xerrors.Errorf("post analysis error: %w", err) 136 } 137 138 result.Sort() 139 140 blobInfo := types.BlobInfo{ 141 SchemaVersion: types.BlobJSONSchemaVersion, 142 OS: result.OS, 143 Repository: result.Repository, 144 PackageInfos: result.PackageInfos, 145 Applications: result.Applications, 146 Secrets: result.Secrets, 147 Licenses: result.Licenses, 148 CustomResources: result.CustomResources, 149 } 150 151 if err = a.handlerManager.PostHandle(ctx, result, &blobInfo); err != nil { 152 return types.BlobInfo{}, xerrors.Errorf("failed to call hooks: %w", err) 153 } 154 155 return blobInfo, nil 156 } 157 158 func detectType(target string) Type { 159 switch { 160 case strings.HasPrefix(target, TypeAMI.Prefix()): 161 return TypeAMI 162 case strings.HasPrefix(target, TypeEBS.Prefix()): 163 return TypeEBS 164 case strings.HasPrefix(target, TypeFile.Prefix()): 165 return TypeFile 166 default: 167 return TypeFile 168 } 169 }