github.com/wolfi-dev/wolfictl@v0.16.11/pkg/advisory/secdb.go (about) 1 package advisory 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "sort" 8 9 "github.com/chainguard-dev/clog" 10 "github.com/wolfi-dev/wolfictl/pkg/advisory/secdb" 11 "github.com/wolfi-dev/wolfictl/pkg/configs" 12 v2 "github.com/wolfi-dev/wolfictl/pkg/configs/advisory/v2" 13 ) 14 15 const apkURL = "{{urlprefix}}/{{reponame}}/{{arch}}/{{pkg.name}}-{{pkg.ver}}.apk" 16 17 // BuildSecurityDatabaseOptions contains the options for building a database. 18 type BuildSecurityDatabaseOptions struct { 19 AdvisoryDocIndices []*configs.Index[v2.Document] 20 21 URLPrefix string 22 Archs []string 23 Repo string 24 } 25 26 var ( 27 ErrNoPackageSecurityData = errors.New("no package security data found") 28 ErrorPackageCollision = errors.New("found multiple advisory documents for the same package") 29 ) 30 31 // BuildSecurityDatabase builds an Alpine-style security database from the given options. 32 func BuildSecurityDatabase(ctx context.Context, opts BuildSecurityDatabaseOptions) ([]byte, error) { 33 log := clog.FromContext(ctx) 34 var packageEntries []secdb.PackageEntry 35 36 seenPackages := make(map[string]struct{}) 37 38 for _, index := range opts.AdvisoryDocIndices { 39 var indexPackageEntries []secdb.PackageEntry 40 41 for _, doc := range index.Select().Configurations() { 42 if _, exists := seenPackages[doc.Package.Name]; exists { 43 // TODO Merge the events between multiple advisories 44 log.InfoContextf(ctx, "cannot process additional advisory data for package %q: %v", doc.Package.Name, ErrorPackageCollision) 45 } 46 seenPackages[doc.Package.Name] = struct{}{} 47 48 if len(doc.Advisories) == 0 { 49 continue 50 } 51 52 secfixes := make(secdb.Secfixes) 53 54 sort.Slice(doc.Advisories, func(i, j int) bool { 55 return doc.Advisories[i].ID < doc.Advisories[j].ID 56 }) 57 58 for _, advisory := range doc.Advisories { 59 if len(advisory.Events) == 0 { 60 continue 61 } 62 63 sortedEvents := advisory.SortedEvents() 64 latest := sortedEvents[len(advisory.Events)-1] 65 66 addVulnToPkgVersion := func(vulnID string) { 67 switch latest.Type { 68 case v2.EventTypeFixed: 69 version := latest.Data.(v2.Fixed).FixedVersion 70 secfixes[version] = append(secfixes[version], vulnID) 71 case v2.EventTypeFalsePositiveDetermination: 72 secfixes[secdb.NAK] = append(secfixes[secdb.NAK], vulnID) 73 } 74 } 75 76 // Get vulnerability from advisory ID 77 78 vulnID := advisory.ID 79 addVulnToPkgVersion(vulnID) 80 81 // Get vulnerabilities from advisory aliases 82 83 for _, alias := range advisory.Aliases { 84 vulnID := alias 85 addVulnToPkgVersion(vulnID) 86 } 87 } 88 89 if len(secfixes) == 0 { 90 continue 91 } 92 93 pe := secdb.PackageEntry{ 94 Pkg: secdb.Package{ 95 Name: doc.Package.Name, 96 Secfixes: secfixes, 97 }, 98 } 99 100 indexPackageEntries = append(indexPackageEntries, pe) 101 } 102 103 if len(indexPackageEntries) == 0 { 104 // Catch the unexpected case where an advisories directory contains no security data. 105 return nil, ErrNoPackageSecurityData 106 } 107 108 packageEntries = append(packageEntries, indexPackageEntries...) 109 } 110 111 db := secdb.Database{ 112 APKURL: apkURL, 113 Archs: opts.Archs, 114 Repo: opts.Repo, 115 URLPrefix: opts.URLPrefix, 116 Packages: packageEntries, 117 } 118 119 return json.MarshalIndent(db, "", " ") 120 }