github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/artifact/sbom/sbom.go (about) 1 package sbom 2 3 import ( 4 "context" 5 "crypto/sha256" 6 "encoding/json" 7 "os" 8 "path/filepath" 9 10 "github.com/opencontainers/go-digest" 11 "golang.org/x/xerrors" 12 13 "github.com/devseccon/trivy/pkg/fanal/analyzer" 14 "github.com/devseccon/trivy/pkg/fanal/artifact" 15 "github.com/devseccon/trivy/pkg/fanal/cache" 16 "github.com/devseccon/trivy/pkg/fanal/handler" 17 "github.com/devseccon/trivy/pkg/fanal/types" 18 "github.com/devseccon/trivy/pkg/log" 19 "github.com/devseccon/trivy/pkg/sbom" 20 ) 21 22 type Artifact struct { 23 filePath string 24 cache cache.ArtifactCache 25 analyzer analyzer.AnalyzerGroup 26 handlerManager handler.Manager 27 28 artifactOption artifact.Option 29 } 30 31 func NewArtifact(filePath string, c cache.ArtifactCache, opt artifact.Option) (artifact.Artifact, error) { 32 return Artifact{ 33 filePath: filepath.Clean(filePath), 34 cache: c, 35 artifactOption: opt, 36 }, nil 37 } 38 39 func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) { 40 f, err := os.Open(a.filePath) 41 if err != nil { 42 return types.ArtifactReference{}, xerrors.Errorf("failed to open sbom file error: %w", err) 43 } 44 defer f.Close() 45 46 // Format auto-detection 47 format, err := sbom.DetectFormat(f) 48 if err != nil { 49 return types.ArtifactReference{}, xerrors.Errorf("failed to detect SBOM format: %w", err) 50 } 51 log.Logger.Infof("Detected SBOM format: %s", format) 52 53 bom, err := sbom.Decode(f, format) 54 if err != nil { 55 return types.ArtifactReference{}, xerrors.Errorf("SBOM decode error: %w", err) 56 } 57 58 blobInfo := types.BlobInfo{ 59 SchemaVersion: types.BlobJSONSchemaVersion, 60 OS: bom.OS, 61 PackageInfos: bom.Packages, 62 Applications: bom.Applications, 63 } 64 65 cacheKey, err := a.calcCacheKey(blobInfo) 66 if err != nil { 67 return types.ArtifactReference{}, xerrors.Errorf("failed to calculate a cache key: %w", err) 68 } 69 70 if err = a.cache.PutBlob(cacheKey, blobInfo); err != nil { 71 return types.ArtifactReference{}, xerrors.Errorf("failed to store blob (%s) in cache: %w", cacheKey, err) 72 } 73 74 var artifactType types.ArtifactType 75 switch format { 76 case sbom.FormatCycloneDXJSON, sbom.FormatCycloneDXXML, sbom.FormatAttestCycloneDXJSON, sbom.FormatLegacyCosignAttestCycloneDXJSON: 77 artifactType = types.ArtifactCycloneDX 78 case sbom.FormatSPDXTV, sbom.FormatSPDXJSON: 79 artifactType = types.ArtifactSPDX 80 81 } 82 83 return types.ArtifactReference{ 84 Name: a.filePath, 85 Type: artifactType, 86 ID: cacheKey, // use a cache key as pseudo artifact ID 87 BlobIDs: []string{cacheKey}, 88 89 // Keep an original report 90 CycloneDX: bom.CycloneDX, 91 }, nil 92 } 93 94 func (a Artifact) Clean(reference types.ArtifactReference) error { 95 return a.cache.DeleteBlobs(reference.BlobIDs) 96 } 97 98 func (a Artifact) calcCacheKey(blobInfo types.BlobInfo) (string, error) { 99 // calculate hash of JSON and use it as pseudo artifactID and blobID 100 h := sha256.New() 101 if err := json.NewEncoder(h).Encode(blobInfo); err != nil { 102 return "", xerrors.Errorf("json error: %w", err) 103 } 104 105 d := digest.NewDigest(digest.SHA256, h) 106 cacheKey, err := cache.CalcKey(d.String(), a.analyzer.AnalyzerVersions(), a.handlerManager.Versions(), a.artifactOption) 107 if err != nil { 108 return "", xerrors.Errorf("cache key: %w", err) 109 } 110 111 return cacheKey, nil 112 }