Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handling of same fix version with different elsa ids and include elsa ids for aqua storage,remove them before saving into oss db #484

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 60 additions & 15 deletions pkg/vulnsrc/oracle-oval/oracle-oval.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,12 @@ func (vs *VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error {
Entries: []types.Advisory{
{
FixedVersion: affectedPkg.FixedVersion,
Arches: []string{affectedPkg.Arch},
Arches: []string{
affectedPkg.Arch,
},
VendorIDs: []string{
elsaID,
},
},
},
}
Expand Down Expand Up @@ -217,7 +222,12 @@ type archFlavor struct {
Flavor PkgFlavor
}

// mergeAdvisoriesEntries merges advisories by picking the latest version for each arch+flavor.
type versionVendorID struct {
Version string
VendorID string
}

// mergeAdvisoriesEntries merges advisories by picking the latest version (with vendorID) for each arch+flavor.
// There may be multiple advisories that fix the same vulnerability, possibly providing multiple fixed versions.
// In this case, we need to determine the "latest" version, which is now defined as the highest (greatest) version number.
//
Expand All @@ -227,18 +237,21 @@ func mergeAdvisoriesEntries(advisories types.Advisories) types.Advisories {
// Step 1: Select the latest version per (arch, flavor)
latestVersions := selectLatestVersions(advisories)

// Step 2: Aggregate arches by their chosen version
versionToArches := make(map[string][]string)
// Step 2: Aggregate arches by their chosen version + vendorID
versionToArches := make(map[versionVendorID][]string)
for k, v := range latestVersions {
versionToArches[v] = append(versionToArches[v], k.Arch)
}

// Step 3: Build final entries, sorted by version
entries := lo.MapToSlice(versionToArches, func(ver string, arches []string) types.Advisory {
entries := lo.MapToSlice(versionToArches, func(ver versionVendorID, arches []string) types.Advisory {
sort.Strings(arches)
return types.Advisory{
FixedVersion: ver,
FixedVersion: ver.Version,
Arches: arches,
VendorIDs: []string{
ver.VendorID,
},
}
})
sort.Slice(entries, func(i, j int) bool {
Expand All @@ -252,23 +265,27 @@ func mergeAdvisoriesEntries(advisories types.Advisories) types.Advisories {
}

// selectLatestVersions selects the latest (highest) version per (arch, flavor)
func selectLatestVersions(advisories types.Advisories) map[archFlavor]string {
latestVersions := make(map[archFlavor]string) // key: archFlavor -> highest fixedVersion
func selectLatestVersions(advisories types.Advisories) map[archFlavor]versionVendorID {
latestVersions := make(map[archFlavor]versionVendorID) // key: archFlavor -> highest fixedVersion
for _, entry := range advisories.Entries {
if len(entry.Arches) == 0 || entry.FixedVersion == "" {
continue
}
arch := entry.Arches[0] // Before merging `arches`, it always contains only 1 arch
arch := entry.Arches[0] // Before merging `arches`, it always contains only 1 arch
vendorID := entry.VendorIDs[0] // Before merging `VendorIDs`, it always contains only 1 elsaID
flavor := PackageFlavor(entry.FixedVersion)
key := archFlavor{
Arch: arch,
Flavor: flavor,
}

currentVer := version.NewVersion(entry.FixedVersion)
if existing, ok := latestVersions[key]; !ok || currentVer.GreaterThan(version.NewVersion(existing)) {
if existing, ok := latestVersions[key]; !ok || currentVer.GreaterThan(version.NewVersion(existing.Version)) {
// Keep the higher (latest) version
latestVersions[key] = entry.FixedVersion
latestVersions[key] = versionVendorID{
Version: entry.FixedVersion,
VendorID: vendorID,
}
}
}
return latestVersions
Expand All @@ -277,15 +294,18 @@ func selectLatestVersions(advisories types.Advisories) map[archFlavor]string {
// determinePrimaryFixedVersion determines primary fixed version for backward compatibility
// It is chosen as the highest normal flavor version on x86_64 if any exist.
// If no normal flavor version exists, the maximum version in lexical order is chosen.
func determinePrimaryFixedVersion(latestVersions map[archFlavor]string) string {
func determinePrimaryFixedVersion(latestVersions map[archFlavor]versionVendorID) string {
primaryFixedVersion := latestVersions[archFlavor{
Arch: "x86_64",
Flavor: NormalPackageFlavor,
}]
if primaryFixedVersion == "" {
primaryFixedVersion = lo.Max(lo.Values(latestVersions)) // Chose the maximum value in lexical order for idempotency
if primaryFixedVersion.Version == "" {
vers := lo.MapToSlice(latestVersions, func(_ archFlavor, v versionVendorID) string {
return v.Version
})
primaryFixedVersion.Version = lo.Max(vers) // Chose the maximum value in lexical order for idempotency
}
return primaryFixedVersion
return primaryFixedVersion.Version
}

type PkgFlavor string
Expand Down Expand Up @@ -327,6 +347,7 @@ func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error {
}

for pkg, advisory := range input.Advisories {
advisory = removeVendorIDs(advisory)
platformName := pkg.PlatformName()
if err := o.PutAdvisoryDetail(tx, input.VulnID, pkg.Name, []string{platformName}, advisory); err != nil {
return eb.With("package_name", pkg.Name).With("bucket_name", platformName).Wrapf(err, "failed to save advisory")
Expand All @@ -335,6 +356,30 @@ func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error {
return nil
}

// removeVendorIDs removes VendorID from advisories + merges arches for advisories (by fixedVersion).
// This is needed to save space in OSS trivy-db.
// But Aqua storage requires this information.
func removeVendorIDs(advs types.Advisories) types.Advisories {
versionToArches := make(map[string][]string) // fixed version -> arches
for _, entry := range advs.Entries {
versionToArches[entry.FixedVersion] = append(versionToArches[entry.FixedVersion], entry.Arches...)
}

entries := lo.MapToSlice(versionToArches, func(ver string, arches []string) types.Advisory {
sort.Strings(arches)
return types.Advisory{
FixedVersion: ver,
Arches: arches,
}
})
sort.Slice(entries, func(i, j int) bool {
return entries[i].FixedVersion < entries[j].FixedVersion // Sorting lexicographically
})

advs.Entries = entries
return advs
}

func (o *Oracle) Get(release, pkgName, arch string) ([]types.Advisory, error) {
eb := oops.In("oracle").Tags("oval").With("release", release).With("package_name", pkgName).With("arch", arch)
bucket := fmt.Sprintf(platformFormat, release)
Expand Down
44 changes: 44 additions & 0 deletions pkg/vulnsrc/oracle-oval/oracle-oval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,50 @@ func TestVulnSrc_Update(t *testing.T) {
dir: filepath.Join("testdata", "sad"),
wantErr: "json decode error",
},
{
name: "same fix version with different ELSA ids and architectures",
dir: filepath.Join("testdata", "multiple-elsa-ids-same-fix-version"),
wantValues: []vulnsrctest.WantValues{
{
Key: []string{"data-source", "Oracle Linux 8"},
Value: types.DataSource{
ID: vulnerability.OracleOVAL,
Name: "Oracle Linux OVAL definitions",
URL: "https://linux.oracle.com/security/oval/",
},
},
{
Key: []string{"advisory-detail", "CVE-2016-10228", "Oracle Linux 8", "glibc"},
Value: types.Advisories{
FixedVersion: "2:2.28-151.0.1.ksplice1.el8",
Entries: []types.Advisory{
{
FixedVersion: "2:2.28-151.0.1.ksplice1.el8",
Arches: []string{
"i386",
"x86_64",
},
},
},
},
},
{
Key: []string{"vulnerability-detail", "CVE-2016-10228", "oracle-oval"},
Value: types.VulnerabilityDetail{
Title: "ELSA-2021-9344: glibc security update (IMPORTANT)",
References: []string{
"https://linux.oracle.com/cve/CVE-2016-10228.html",
"https://linux.oracle.com/errata/ELSA-2021-9344.html",
},
Severity: types.SeverityHigh,
},
},
{
Key: []string{"vulnerability-id", "CVE-2016-10228"},
Value: map[string]interface{}{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"Title": "ELSA-2021-9280: glibc security update (IMPORTANT)",
"Description": "",
"Platform": [
"Oracle Linux 8"
],
"References": [
{
"Source": "elsa",
"URI": "https://linux.oracle.com/errata/ELSA-2021-9280.html",
"ID": "ELSA-2021-9280"
},
{
"Source": "CVE",
"URI": "https://linux.oracle.com/cve/CVE-2016-10228.html",
"ID": "CVE-2016-10228"
}
],
"Criteria": {
"Operator": "AND",
"Criterias": [
{
"Operator": "OR",
"Criterias": [
{
"Operator": "AND",
"Criterias": [
{
"Operator": "OR",
"Criterias": [
{
"Operator": "AND",
"Criterias": null,
"Criterions": [
{
"Comment": "glibc is earlier than 2:2.28-151.0.1.ksplice1.el8"
},
{
"Comment": "glibc is signed with the Oracle Linux 8 key"
},
{
"Comment": "glibc is ksplice-based"
}
]
}
],
"Criterions": null
}
],
"Criterions": [
{
"Comment": "Oracle Linux arch is x86_64"
}
]
}
],
"Criterions": null
}
],
"Criterions": [
{
"Comment": "Oracle Linux 8 is installed"
}
]
},
"Severity": "IMPORTANT",
"Cves": [
{
"Impact": "",
"Href": "https://linux.oracle.com/cve/CVE-2016-10228.html",
"ID": "CVE-2016-10228"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"Title": "ELSA-2021-9344: glibc security update (IMPORTANT)",
"Description": "",
"Platform": [
"Oracle Linux 8"
],
"References": [
{
"Source": "elsa",
"URI": "https://linux.oracle.com/errata/ELSA-2021-9344.html",
"ID": "ELSA-2021-9280"
},
{
"Source": "CVE",
"URI": "https://linux.oracle.com/cve/CVE-2016-10228.html",
"ID": "CVE-2016-10228"
}
],
"Criteria": {
"Operator": "AND",
"Criterias": [
{
"Operator": "OR",
"Criterias": [
{
"Operator": "AND",
"Criterias": [
{
"Operator": "OR",
"Criterias": [
{
"Operator": "AND",
"Criterias": null,
"Criterions": [
{
"Comment": "glibc is earlier than 2:2.28-151.0.1.ksplice1.el8"
},
{
"Comment": "glibc is signed with the Oracle Linux 8 key"
},
{
"Comment": "glibc is ksplice-based"
}
]
}
],
"Criterions": null
}
],
"Criterions": [
{
"Comment": "Oracle Linux arch is i386"
}
]
}
],
"Criterions": null
}
],
"Criterions": [
{
"Comment": "Oracle Linux 8 is installed"
}
]
},
"Severity": "IMPORTANT",
"Cves": [
{
"Impact": "",
"Href": "https://linux.oracle.com/cve/CVE-2016-10229.html",
"ID": "CVE-2016-10228"
}
]
}