diff --git a/README.md b/README.md index ca20a65..0669246 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Generate NeuVector vulnerability database | **OS based** | | Alpine | https://secdb.alpinelinux.org/ | | Amazon | https://alas.aws.amazon.com/ | +| Chainguard | https://packages.cgr.dev/chainguard/security.json | | Debian | https://security-tracker.debian.org/tracker/data/json | | Microsoft mariner | https://github.com/microsoft/CBL-MarinerVulnerabilityData | | Oracle OS | https://linux.oracle.com/oval/ | @@ -18,6 +19,7 @@ Generate NeuVector vulnerability database | Red Hat/CentOS | https://www.redhat.com/security/data/oval/v2/ | | SUSE Linux | https://ftp.suse.com/pub/projects/security/oval/ | | Ubuntu | https://launchpad.net/ubuntu-cve-tracker | +| Wolfi | https://packages.wolfi.dev/os/security.json | | **Application based** | | .NET | https://github.com/advisories, https://www.cvedetails.com/vulnerability-list/vendor_id-26/ | | apache | https://www.cvedetails.com/vendor/45/Apache.html | diff --git a/dbgen.go b/dbgen.go index dc47cfc..d3a97f9 100644 --- a/dbgen.go +++ b/dbgen.go @@ -17,6 +17,7 @@ import ( _ "github.com/vul-dbgen/updater/fetchers/alpine" _ "github.com/vul-dbgen/updater/fetchers/amazon" _ "github.com/vul-dbgen/updater/fetchers/apps" + _ "github.com/vul-dbgen/updater/fetchers/chainguard" _ "github.com/vul-dbgen/updater/fetchers/debian" _ "github.com/vul-dbgen/updater/fetchers/mariner" _ "github.com/vul-dbgen/updater/fetchers/oracle" @@ -25,6 +26,7 @@ import ( _ "github.com/vul-dbgen/updater/fetchers/rocky" _ "github.com/vul-dbgen/updater/fetchers/suse" _ "github.com/vul-dbgen/updater/fetchers/ubuntu" + _ "github.com/vul-dbgen/updater/fetchers/wolfi" ) func usage() { diff --git a/memdb.go b/memdb.go index de21fc4..6f0fd81 100644 --- a/memdb.go +++ b/memdb.go @@ -144,6 +144,8 @@ const ( dbSuse dbPhoton dbRocky + dbWolfi + dbChainguard dbMax ) @@ -181,6 +183,8 @@ func (db *memDB) UpdateDb(version string) bool { dbs.buffers[dbSuse] = dbBuffer{namespace: "sles", indexFile: "suse_index.tb", fullFile: "suse_full.tb"} dbs.buffers[dbPhoton] = dbBuffer{namespace: "photon", indexFile: "photon_index.tb", fullFile: "photon_full.tb"} dbs.buffers[dbRocky] = dbBuffer{namespace: "rocky", indexFile: "rocky_index.tb", fullFile: "rocky_full.tb"} + dbs.buffers[dbWolfi] = dbBuffer{namespace: "wolfi", indexFile: "wolfi_index.tb", fullFile: "wolfi_full.tb"} + dbs.buffers[dbChainguard] = dbBuffer{namespace: "chainguard", indexFile: "chainguard_index.tb", fullFile: "chainguard_full.tb"} dbs.rawSHA = make([][sha256.Size]byte, len(db.rawFiles)) diff --git a/updater/fetchers/chainguard/chainguard.go b/updater/fetchers/chainguard/chainguard.go new file mode 100644 index 0000000..e4e1cd1 --- /dev/null +++ b/updater/fetchers/chainguard/chainguard.go @@ -0,0 +1,140 @@ +package chainguard + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "regexp" + "strings" + + log "github.com/sirupsen/logrus" + + "github.com/vul-dbgen/common" + "github.com/vul-dbgen/updater" +) + +const ( + securityURL = "https://packages.cgr.dev/chainguard/security.json" + updaterFlag = "chainguard-secdbUpdater" + cveURLPrefix = "https://cve.mitre.org/cgi-bin/cvename.cgi?name=" + // Chainguard uses rolling releases, so we use a generic version identifier + chainguardVersion = "rolling" +) + +var cveRegex = regexp.MustCompile(`^CVE-\d{4}-\d{4,}$`) + +type ChainguardFetcher struct{} + +type secDBData struct { + APKUrl string `json:"apkurl"` + Archs []string `json:"archs"` + RepoName string `json:"reponame"` + URLPrefix string `json:"urlprefix"` + Packages []struct { + Pkg struct { + Name string `json:"name"` + SecFixes map[string]json.RawMessage `json:"secfixes"` + } `json:"pkg"` + } `json:"packages"` +} + +func init() { + updater.RegisterFetcher("chainguard", &ChainguardFetcher{}) +} + +func parseSecDB(body []byte, url string) ([]common.Vulnerability, error) { + var data secDBData + if err := json.Unmarshal(body, &data); err != nil { + log.WithError(err).WithFields(log.Fields{"url": url}).Warn("Failed to unmarshal chainguard db") + return nil, err + } + + var vulns []common.Vulnerability + for _, pkg := range data.Packages { + for version, raw := range pkg.Pkg.SecFixes { + // Skip version "0" entries as they represent false positives + if version == "0" { + continue + } + + ver, err := common.NewVersion(version) + if err != nil { + log.WithError(err).WithField("version", version).Warn("Failed to parse package version. skipping") + continue + } + + var cves []string + if err = json.Unmarshal(raw, &cves); err != nil { + continue + } + + for _, cveName := range cves { + // Filter out GHSA identifiers, only process CVEs + if !cveRegex.MatchString(cveName) { + continue + } + + if year, err := common.ParseYear(cveName[4:]); err != nil { + log.WithField("cve", cveName).Warn("Unable to parse year from CVE name") + continue + } else if year < common.FirstYear { + continue + } + + if s := strings.Index(cveName, " "); s != -1 { + cveName = cveName[:s] + } + + var vuln common.Vulnerability + vuln.Name = cveName + vuln.Link = cveURLPrefix + cveName + + featureVersion := common.FeatureVersion{ + Feature: common.Feature{ + Namespace: "chainguard:" + chainguardVersion, + Name: pkg.Pkg.Name, + }, + Version: ver, + } + vuln.FixedIn = append(vuln.FixedIn, featureVersion) + + vulns = append(vulns, vuln) + + common.DEBUG_VULN(&vuln, "chainguard") + } + } + } + + return vulns, nil +} + +func (u *ChainguardFetcher) downloadSecDB(url string) ([]common.Vulnerability, error) { + r, err := http.Get(url) + if err != nil { + log.WithError(err).WithFields(log.Fields{"url": url}).Error("Failed to download chainguard db") + return nil, err + } + + body, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + return parseSecDB(body, url) +} + +func (u *ChainguardFetcher) FetchUpdate() (resp updater.FetcherResponse, err error) { + log.WithField("package", "Chainguard").Info("Start fetching vulnerabilities") + + // Download security.json + if vulns, err := u.downloadSecDB(securityURL); err == nil { + resp.Vulnerabilities = append(resp.Vulnerabilities, vulns...) + } else { + return resp, err + } + + log.WithFields(log.Fields{"Vulnerabilities": len(resp.Vulnerabilities)}).Info("fetching chainguard done") + return resp, nil +} + +func (u *ChainguardFetcher) Clean() { + // No cleanup needed for Chainguard fetcher +} diff --git a/updater/fetchers/wolfi/wolfi.go b/updater/fetchers/wolfi/wolfi.go new file mode 100644 index 0000000..1b8134b --- /dev/null +++ b/updater/fetchers/wolfi/wolfi.go @@ -0,0 +1,140 @@ +package wolfi + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "regexp" + "strings" + + log "github.com/sirupsen/logrus" + + "github.com/vul-dbgen/common" + "github.com/vul-dbgen/updater" +) + +const ( + securityURL = "https://packages.wolfi.dev/os/security.json" + updaterFlag = "wolfi-secdbUpdater" + cveURLPrefix = "https://cve.mitre.org/cgi-bin/cvename.cgi?name=" + // Wolfi uses rolling releases, so we use a generic version identifier + wolfiVersion = "rolling" +) + +var cveRegex = regexp.MustCompile(`^CVE-\d{4}-\d{4,}$`) + +type WolfiFetcher struct{} + +type secDBData struct { + APKUrl string `json:"apkurl"` + Archs []string `json:"archs"` + RepoName string `json:"reponame"` + URLPrefix string `json:"urlprefix"` + Packages []struct { + Pkg struct { + Name string `json:"name"` + SecFixes map[string]json.RawMessage `json:"secfixes"` + } `json:"pkg"` + } `json:"packages"` +} + +func init() { + updater.RegisterFetcher("wolfi", &WolfiFetcher{}) +} + +func parseSecDB(body []byte, url string) ([]common.Vulnerability, error) { + var data secDBData + if err := json.Unmarshal(body, &data); err != nil { + log.WithError(err).WithFields(log.Fields{"url": url}).Warn("Failed to unmarshal wolfi db") + return nil, err + } + + var vulns []common.Vulnerability + for _, pkg := range data.Packages { + for version, raw := range pkg.Pkg.SecFixes { + // Skip version "0" entries as they represent false positives + if version == "0" { + continue + } + + ver, err := common.NewVersion(version) + if err != nil { + log.WithError(err).WithField("version", version).Warn("Failed to parse package version. skipping") + continue + } + + var cves []string + if err = json.Unmarshal(raw, &cves); err != nil { + continue + } + + for _, cveName := range cves { + // Filter out GHSA identifiers, only process CVEs + if !cveRegex.MatchString(cveName) { + continue + } + + if year, err := common.ParseYear(cveName[4:]); err != nil { + log.WithField("cve", cveName).Warn("Unable to parse year from CVE name") + continue + } else if year < common.FirstYear { + continue + } + + if s := strings.Index(cveName, " "); s != -1 { + cveName = cveName[:s] + } + + var vuln common.Vulnerability + vuln.Name = cveName + vuln.Link = cveURLPrefix + cveName + + featureVersion := common.FeatureVersion{ + Feature: common.Feature{ + Namespace: "wolfi:" + wolfiVersion, + Name: pkg.Pkg.Name, + }, + Version: ver, + } + vuln.FixedIn = append(vuln.FixedIn, featureVersion) + + vulns = append(vulns, vuln) + + common.DEBUG_VULN(&vuln, "wolfi") + } + } + } + + return vulns, nil +} + +func (u *WolfiFetcher) downloadSecDB(url string) ([]common.Vulnerability, error) { + r, err := http.Get(url) + if err != nil { + log.WithError(err).WithFields(log.Fields{"url": url}).Error("Failed to download wolfi db") + return nil, err + } + + body, _ := ioutil.ReadAll(r.Body) + defer r.Body.Close() + + return parseSecDB(body, url) +} + +func (u *WolfiFetcher) FetchUpdate() (resp updater.FetcherResponse, err error) { + log.WithField("package", "Wolfi").Info("Start fetching vulnerabilities") + + // Download security.json + if vulns, err := u.downloadSecDB(securityURL); err == nil { + resp.Vulnerabilities = append(resp.Vulnerabilities, vulns...) + } else { + return resp, err + } + + log.WithFields(log.Fields{"Vulnerabilities": len(resp.Vulnerabilities)}).Info("fetching wolfi done") + return resp, nil +} + +func (u *WolfiFetcher) Clean() { + // No cleanup needed for Wolfi fetcher +}