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

Relaunch "Update last modified time of combine-to-osv entries" #2165

Merged
merged 4 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 46 additions & 9 deletions vulnfeeds/cmd/combine-to-osv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path"
"strings"
"time"

"github.com/google/osv/vulnfeeds/cves"
"github.com/google/osv/vulnfeeds/utility"
Expand Down Expand Up @@ -42,13 +43,31 @@ func main() {
}

allCves := loadAllCVEs(*cvePath)
allParts := loadParts(*partsInputPath)
combinedData := combineIntoOSV(allCves, allParts, *cveListPath)
allParts, cveModifiedMap := loadParts(*partsInputPath)
combinedData := combineIntoOSV(allCves, allParts, *cveListPath, cveModifiedMap)
writeOSVFile(combinedData, *osvOutputPath)
}

// getModifiedTime gets the modification time of a given file
// This function assumes that the modified time on disk matches with it in GCS
func getModifiedTime(filePath string) (time.Time, error) {
var emptyTime time.Time
fileInfo, err := os.Stat(filePath)
if err != nil {
return emptyTime, err
}
parsedTime := fileInfo.ModTime()

return parsedTime, err
}

// loadInnerParts loads second level folder for the loadParts function
func loadInnerParts(innerPartInputPath string, output map[cves.CVEID][]vulns.PackageInfo) {
//
// Parameters:
// - innerPartInputPath: The inner part path, such as "parts/alpine"
// - output: A map to store all PackageInfos for each CVE ID
// - cvePartsModifiedTime: A map tracking the latest modification time of each CVE part files
func loadInnerParts(innerPartInputPath string, output map[cves.CVEID][]vulns.PackageInfo, cvePartsModifiedTime map[cves.CVEID]time.Time) {
dirInner, err := os.ReadDir(innerPartInputPath)
if err != nil {
Logger.Fatalf("Failed to read dir %q: %s", innerPartInputPath, err)
Expand All @@ -57,7 +76,8 @@ func loadInnerParts(innerPartInputPath string, output map[cves.CVEID][]vulns.Pac
if !strings.HasSuffix(entryInner.Name(), ".json") {
continue
}
file, err := os.Open(path.Join(innerPartInputPath, entryInner.Name()))
filePath := path.Join(innerPartInputPath, entryInner.Name())
file, err := os.Open(filePath)
if err != nil {
Logger.Fatalf("Failed to open PackageInfo JSON %q: %s", path.Join(innerPartInputPath, entryInner.Name()), err)
}
Expand All @@ -74,6 +94,17 @@ func loadInnerParts(innerPartInputPath string, output map[cves.CVEID][]vulns.Pac

Logger.Infof(
"Loaded Item: %s", entryInner.Name())

// Updates the latest OSV parts modified time of each CVE
modifiedTime, err := getModifiedTime(filePath)
if err != nil {
Logger.Warnf("Failed to get modified time of %s: %s", filePath, err)
continue
}
existingDate, exists := cvePartsModifiedTime[cveId]
if !exists || modifiedTime.After(existingDate) {
cvePartsModifiedTime[cveId] = modifiedTime
}
}
}

Expand All @@ -90,25 +121,27 @@ func loadInnerParts(innerPartInputPath string, output map[cves.CVEID][]vulns.Pac
//
// ## Returns
// A mapping of "CVE-ID": []<Affected Package Information>
func loadParts(partsInputPath string) map[cves.CVEID][]vulns.PackageInfo {
// A mapping of "CVE-ID": time.Time (the latest modified time of its part files)
func loadParts(partsInputPath string) (map[cves.CVEID][]vulns.PackageInfo, map[cves.CVEID]time.Time) {
dir, err := os.ReadDir(partsInputPath)
if err != nil {
Logger.Fatalf("Failed to read dir %q: %s", partsInputPath, err)
}
output := map[cves.CVEID][]vulns.PackageInfo{}
cvePartsModifiedTime := make(map[cves.CVEID]time.Time)
for _, entry := range dir {
if !entry.IsDir() {
Logger.Warnf("Unexpected file entry %q in %s", entry.Name(), partsInputPath)
continue
}
// map is already a reference type, so no need to pass in a pointer
loadInnerParts(path.Join(partsInputPath, entry.Name()), output)
loadInnerParts(path.Join(partsInputPath, entry.Name()), output, cvePartsModifiedTime)
}
return output
return output, cvePartsModifiedTime
}

// combineIntoOSV creates OSV entry by combining loaded CVEs from NVD and PackageInfo information from security advisories.
func combineIntoOSV(loadedCves map[cves.CVEID]cves.Vulnerability, allParts map[cves.CVEID][]vulns.PackageInfo, cveList string) map[cves.CVEID]*vulns.Vulnerability {
func combineIntoOSV(loadedCves map[cves.CVEID]cves.Vulnerability, allParts map[cves.CVEID][]vulns.PackageInfo, cveList string, cvePartsModifiedTime map[cves.CVEID]time.Time) map[cves.CVEID]*vulns.Vulnerability {
Logger.Infof("Begin writing OSV files")
convertedCves := map[cves.CVEID]*vulns.Vulnerability{}
for cveId, cve := range loadedCves {
Expand All @@ -129,6 +162,10 @@ func combineIntoOSV(loadedCves map[cves.CVEID]cves.Vulnerability, allParts map[c
for _, pkgInfo := range allParts[cveId] {
convertedCve.AddPkgInfo(pkgInfo)
}
cveModified, _ := time.Parse(time.RFC3339, convertedCve.Modified)
if cvePartsModifiedTime[cveId].After(cveModified) {
convertedCve.Modified = cvePartsModifiedTime[cveId].Format(time.RFC3339)
}
convertedCves[cveId] = convertedCve
}
return convertedCves
Expand All @@ -137,7 +174,7 @@ func combineIntoOSV(loadedCves map[cves.CVEID]cves.Vulnerability, allParts map[c
// writeOSVFile writes out the given osv objects into individual json files
func writeOSVFile(osvData map[cves.CVEID]*vulns.Vulnerability, osvOutputPath string) {
for vId, osv := range osvData {
file, err := os.OpenFile(path.Join(osvOutputPath, string(vId) + ".json"), os.O_CREATE|os.O_RDWR, 0644)
file, err := os.OpenFile(path.Join(osvOutputPath, string(vId)+".json"), os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
Logger.Fatalf("Failed to create/open file to write: %s", err)
}
Expand Down
54 changes: 51 additions & 3 deletions vulnfeeds/cmd/combine-to-osv/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log"
"os"
"testing"
"time"

"golang.org/x/exp/maps"

Expand Down Expand Up @@ -34,7 +35,7 @@ func loadTestData2(cveName string) cves.Vulnerability {
}

func TestLoadParts(t *testing.T) {
allParts := loadParts("../../test_data/parts")
allParts, _ := loadParts("../../test_data/parts")
expectedPartCount := 14
actualPartCount := len(allParts)

Expand Down Expand Up @@ -87,9 +88,9 @@ func TestCombineIntoOSV(t *testing.T) {
"CVE-2022-32746": loadTestData2("CVE-2022-32746"),
"CVE-2018-1000500": loadTestData2("CVE-2018-1000500"),
}
allParts := loadParts("../../test_data/parts")
allParts, cveModifiedTime := loadParts("../../test_data/parts")

combinedOSV := combineIntoOSV(cveStuff, allParts, "")
combinedOSV := combineIntoOSV(cveStuff, allParts, "", cveModifiedTime)

expectedCombined := 3
actualCombined := len(combinedOSV)
Expand All @@ -103,3 +104,50 @@ func TestCombineIntoOSV(t *testing.T) {
}
}
}

func TestGetModifiedTime(t *testing.T) {
_, err := getModifiedTime("../../test_data/parts/debian/CVE-2016-1585.debian.json")
if err != nil {
t.Errorf("Failed to get modified time.")
}

}

func TestUpdateModifiedDate(t *testing.T) {
var cveId1, cveId2 cves.CVEID
cveId1 = "CVE-2022-33745"
cveId2 = "CVE-2022-32746"

cveStuff := map[cves.CVEID]cves.Vulnerability{
cveId1: loadTestData2("CVE-2022-33745"),
cveId2: loadTestData2("CVE-2022-32746"),
}
allParts, _ := loadParts("../../test_data/parts")

cveModifiedTimeMock := make(map[cves.CVEID]time.Time)
time1 := "0001-00-00T00:00:00Z"
time2 := "2024-04-30T00:38:53Z"
modifiedTime1, _ := time.Parse(time.RFC3339, time1)
modifiedTime2, _ := time.Parse(time.RFC3339, time2)
cveModifiedTimeMock[cveId1] = modifiedTime1
cveModifiedTimeMock[cveId2] = modifiedTime2

combinedOSV := combineIntoOSV(cveStuff, allParts, "", cveModifiedTimeMock)

expectedCombined := 2
actualCombined := len(combinedOSV)

if actualCombined != expectedCombined {
t.Errorf("Expected %d in combination, got %d: %#v", expectedCombined, actualCombined, combinedOSV)
}

// Keeps CVE modified time if none of its parts have a later modification time
if combinedOSV[cveId1].Modified == time1 {
t.Errorf("Wrong modified time: %s", combinedOSV["CVE-2022-33745"].Modified)
}

// Updates the CVE's modified time if any of its parts have a later modification time
if combinedOSV[cveId2].Modified != time2 {
t.Errorf("Wrong modified time, expected: %s, got: %s", time2, combinedOSV["CVE-2022-32746"].Modified)
}
}
11 changes: 11 additions & 0 deletions vulnfeeds/cmd/debian/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"net/http"
"os"
"path"
Expand Down Expand Up @@ -59,6 +60,10 @@ func getDebianReleaseMap() (map[string]string, error) {
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return releaseMap, fmt.Errorf("HTTP request failed: %s", res.Status)
}

reader := csv.NewReader(res.Body)
reader.FieldsPerRecord = -1
data, err := reader.ReadAll()
Expand Down Expand Up @@ -198,6 +203,12 @@ func downloadDebianSecurityTracker() (DebianSecurityTrackerData, error) {
return nil, err
}

defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP request failed: %s", res.Status)
}

var decodedDebianData DebianSecurityTrackerData

if err := json.NewDecoder(res.Body).Decode(&decodedDebianData); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions vulnfeeds/cmd/debian/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ func Test_generateDebianSecurityTrackerOSV(t *testing.T) {
_ = json.NewDecoder(file).Decode(&decodedDebianData)

debianReleaseMap := make(map[string]string)
debianReleaseMap["trixie"] = "13"
debianReleaseMap["sarge"] = "3.1"
debianReleaseMap["stretch"] = "9"
debianReleaseMap["buster"] = "10"
debianReleaseMap["bullseye"] = "11"
debianReleaseMap["bookworm"] = "12"
debianReleaseMap["sarge"] = "3.1"
debianReleaseMap["stretch"] = "9"
debianReleaseMap["trixie"] = "13"

osvPkgInfos := generateDebianSecurityTrackerOSV(decodedDebianData, debianReleaseMap)
expectedCount := 2
Expand Down