diff --git a/pkg/vulnsrc/fedora/fedora.go b/pkg/vulnsrc/fedora/fedora.go new file mode 100644 index 00000000..b9b5589c --- /dev/null +++ b/pkg/vulnsrc/fedora/fedora.go @@ -0,0 +1,184 @@ +package fedora + +import ( + "encoding/json" + "fmt" + "io" + "log" + "path/filepath" + "strings" + + bolt "go.etcd.io/bbolt" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/utils" + ustrings "github.com/aquasecurity/trivy-db/pkg/utils/strings" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" +) + +const ( + fedoraDir = "fedora/fedora" +) + +var ( + platformFormat = "fedora %s" + targetRepository = []string{"Everything", "Modular"} + targetArches = []string{"x86_64"} + + source = types.DataSource{ + ID: vulnerability.Fedora, + Name: "Fedora UpdateInfo", + URL: "https://dl.fedoraproject.org/pub/fedora/linux/updates", + } +) + +type VulnSrc struct { + dbc db.Operation +} + +func NewVulnSrc() VulnSrc { + return VulnSrc{ + dbc: db.Config{}, + } +} + +func (vs VulnSrc) Name() types.SourceID { + return source.ID +} + +func (vs VulnSrc) Update(dir string) error { + rootDir := filepath.Join(dir, "vuln-list", fedoraDir) + errata := map[string][]UpdateInfo{} + err := utils.FileWalk(rootDir, func(r io.Reader, path string) error { + var erratum UpdateInfo + if err := json.NewDecoder(r).Decode(&erratum); err != nil { + return xerrors.Errorf("failed to decode Fedora erratum: %w", err) + } + + dirs := strings.Split(strings.TrimPrefix(path, rootDir), string(filepath.Separator))[1:] + majorVer := dirs[0] + repo := dirs[1] + if !ustrings.InSlice(repo, targetRepository) { + log.Printf("unsupported Fedora Repository: %s\n", repo) + return nil + } + + arch := dirs[2] + if !ustrings.InSlice(arch, targetArches) { + switch arch { + case "aarch64": + default: + log.Printf("unsupported Fedora arch: %s\n", arch) + } + return nil + } + + errata[majorVer] = append(errata[majorVer], erratum) + return nil + }) + if err != nil { + return xerrors.Errorf("error in Fedora walk: %w", err) + } + + if err := vs.save(errata); err != nil { + return xerrors.Errorf("error in Fedora save: %w", err) + } + + return nil +} + +func (vs VulnSrc) save(errataVer map[string][]UpdateInfo) error { + err := vs.dbc.BatchUpdate(func(tx *bolt.Tx) error { + for majorVer, errata := range errataVer { + platformName := fmt.Sprintf(platformFormat, majorVer) + if err := vs.commit(tx, platformName, errata); err != nil { + return xerrors.Errorf("error in save Fedora %s: %w", majorVer, err) + } + } + return nil + }) + if err != nil { + return xerrors.Errorf("error in db batch update: %w", err) + } + return nil +} + +func (vs VulnSrc) commit(tx *bolt.Tx, platformName string, errata []UpdateInfo) error { + for _, erratum := range errata { + for _, cveID := range erratum.CveIDs { + for _, pkg := range erratum.Packages { + advisory := types.Advisory{ + FixedVersion: constructVersion(pkg.Epoch, pkg.Version, pkg.Release), + } + + pkgName := pkg.Name + if erratum.Module.Name != "" && erratum.Module.Stream != "" { + pkgName = fmt.Sprintf("%s:%s::%s", erratum.Module.Name, erratum.Module.Stream, pkg.Name) + } + + if err := vs.dbc.PutAdvisoryDetail(tx, cveID, pkgName, []string{platformName}, advisory); err != nil { + return xerrors.Errorf("failed to save Fedora advisory: %w", err) + } + + } + var references []string + for _, ref := range erratum.References { + references = append(references, ref.Href) + } + + vuln := types.VulnerabilityDetail{ + Severity: generalizeSeverity(erratum.Severity), + References: references, + Title: erratum.Title, + Description: erratum.Description, + } + if err := vs.dbc.PutVulnerabilityDetail(tx, cveID, vulnerability.Fedora, vuln); err != nil { + return xerrors.Errorf("failed to save Fedora vulnerability: %w", err) + } + + if err := vs.dbc.PutVulnerabilityID(tx, cveID); err != nil { + return xerrors.Errorf("failed to save the vulnerability ID: %w", err) + } + } + } + return nil +} + +func (vs VulnSrc) Get(release, pkgName string) ([]types.Advisory, error) { + bucket := fmt.Sprintf(platformFormat, release) + advisories, err := vs.dbc.GetAdvisories(bucket, pkgName) + if err != nil { + return nil, xerrors.Errorf("failed to get Fedora advisories: %w", err) + } + return advisories, nil +} + +func constructVersion(epoch, version, release string) string { + verStr := "" + if epoch != "0" && epoch != "" { + verStr += fmt.Sprintf("%s:", epoch) + } + verStr += version + + if release != "" { + verStr += fmt.Sprintf("-%s", release) + + } + return verStr +} + +func generalizeSeverity(severity string) types.Severity { + switch strings.ToLower(severity) { + case "low": + return types.SeverityLow + case "moderate": + return types.SeverityMedium + case "important": + return types.SeverityHigh + case "critical": + return types.SeverityCritical + } + return types.SeverityUnknown +} diff --git a/pkg/vulnsrc/fedora/fedora_test.go b/pkg/vulnsrc/fedora/fedora_test.go new file mode 100644 index 00000000..e497c53e --- /dev/null +++ b/pkg/vulnsrc/fedora/fedora_test.go @@ -0,0 +1,109 @@ +package fedora + +import ( + "path/filepath" + "testing" + + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy-db/pkg/dbtest" + "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVulnSrc_Update(t *testing.T) { + type want struct { + key []string + value interface{} + } + tests := []struct { + name string + dir string + wantValues []want + wantErr string + }{ + { + name: "everything package", + dir: filepath.Join("testdata", "everything"), + wantValues: []want{ + { + key: []string{"advisory-detail", "CVE-2021-41159", "fedora 35", "freerdp-libs-debuginfo"}, + value: types.Advisory{ + FixedVersion: "2:2.4.1-1.fc35", + }, + }, + { + key: []string{"vulnerability-detail", "CVE-2021-41159", string(vulnerability.Fedora)}, + value: types.VulnerabilityDetail{ + Severity: types.SeverityHigh, + References: []string{ + "https://bugzilla.redhat.com/show_bug.cgi?id=2015189", + }, + Title: "freerdp-2.4.1-1.fc35 guacamole-server-1.3.0-9.fc35 remmina-1.4.21-1.fc35", + Description: "- Update to 2.4.1 containing security fixes for CVE-2021-41159 and CVE-2021-41160.\n- Remmina 1.4.21 with bugfixes.\n\n", + }, + }, + { + key: []string{"vulnerability-id", "CVE-2021-41159"}, + value: map[string]interface{}{}, + }, + }, + }, + { + name: "modular package", + dir: filepath.Join("testdata", "module"), + wantValues: []want{ + { + key: []string{"advisory-detail", "CVE-2021-35623", "fedora 35", "mysql:8.0::community-mysql"}, + value: types.Advisory{ + FixedVersion: "8.0.27-1.module_f35+13269+c9322734", + }, + }, + { + key: []string{"vulnerability-detail", "CVE-2021-35623", string(vulnerability.Fedora)}, + value: types.VulnerabilityDetail{ + Severity: types.SeverityMedium, + References: []string{ + "https://bugzilla.redhat.com/show_bug.cgi?id=2016142", + }, + Title: "mysql-8.0-3520211031142409.f27b74a8", + Description: "**MySQL 8.0.27**\n\nRelease notes:\n\n https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-27.html", + }, + }, + { + key: []string{"vulnerability-id", "CVE-2021-35623"}, + value: map[string]interface{}{}, + }, + }, + }, + { + name: "sad path", + dir: filepath.Join("testdata", "sad"), + wantErr: "failed to decode Fedora erratum", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + + err := db.Init(tempDir) + require.NoError(t, err) + defer db.Close() + + vs := NewVulnSrc() + err = vs.Update(tt.dir) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + require.NoError(t, err) + require.NoError(t, db.Close()) // Need to close before dbtest.JSONEq is called + for _, want := range tt.wantValues { + dbtest.JSONEq(t, db.Path(tempDir), want.key, want.value) + } + }) + } +} diff --git a/pkg/vulnsrc/fedora/testdata/everything/vuln-list/fedora/fedora/35/Everything/x86_64/2021/FEDORA-2021-2c25f03d0b.json b/pkg/vulnsrc/fedora/testdata/everything/vuln-list/fedora/fedora/35/Everything/x86_64/2021/FEDORA-2021-2c25f03d0b.json new file mode 100644 index 00000000..34d9185f --- /dev/null +++ b/pkg/vulnsrc/fedora/testdata/everything/vuln-list/fedora/fedora/35/Everything/x86_64/2021/FEDORA-2021-2c25f03d0b.json @@ -0,0 +1,36 @@ +{ + "id": "FEDORA-2021-2c25f03d0b", + "title": "freerdp-2.4.1-1.fc35 guacamole-server-1.3.0-9.fc35 remmina-1.4.21-1.fc35", + "type": "security", + "issued": { + "date": "2021-11-17 01:12:41" + }, + "updated": { + "date": "2021-11-10 20:45:11" + }, + "severity": "Important", + "description": "- Update to 2.4.1 containing security fixes for CVE-2021-41159 and CVE-2021-41160.\n- Remmina 1.4.21 with bugfixes.\n\n", + "packages": [ + { + "name": "freerdp-libs-debuginfo", + "epoch": "2", + "version": "2.4.1", + "release": "1.fc35", + "arch": "x86_64", + "src": "https://download.fedoraproject.org/pub/fedora/linux/updates/35/x86_64/f/freerdp-libs-debuginfo-2.4.1-1.fc35.x86_64.rpm", + "filename": "freerdp-libs-debuginfo-2.4.1-1.fc35.x86_64.rpm" + } + ], + "module": {}, + "references": [ + { + "href": "https://bugzilla.redhat.com/show_bug.cgi?id=2015189", + "id": "2015189", + "title": "remmina-1.4.21 is available", + "type": "bugzilla" + } + ], + "cveids": [ + "CVE-2021-41159" + ] +} \ No newline at end of file diff --git a/pkg/vulnsrc/fedora/testdata/module/vuln-list/fedora/fedora/35/Modular/x86_64/2021/FEDORA-MODULAR-2021-217f84c072.json b/pkg/vulnsrc/fedora/testdata/module/vuln-list/fedora/fedora/35/Modular/x86_64/2021/FEDORA-MODULAR-2021-217f84c072.json new file mode 100644 index 00000000..32906d45 --- /dev/null +++ b/pkg/vulnsrc/fedora/testdata/module/vuln-list/fedora/fedora/35/Modular/x86_64/2021/FEDORA-MODULAR-2021-217f84c072.json @@ -0,0 +1,41 @@ +{ + "id": "FEDORA-MODULAR-2021-217f84c072", + "title": "mysql-8.0-3520211031142409.f27b74a8", + "type": "security", + "issued": { + "date": "2021-11-10 00:48:52" + }, + "updated": { + "date": "2021-10-31 17:53:03" + }, + "severity": "Moderate", + "description": "**MySQL 8.0.27**\n\nRelease notes:\n\n https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-27.html", + "packages": [ + { + "name": "community-mysql", + "epoch": "0", + "version": "8.0.27", + "release": "1.module_f35+13269+c9322734", + "arch": "x86_64", + "filename": "community-mysql-8.0.27-1.module_f35+13269+c9322734.x86_64.rpm" + } + ], + "module": { + "stream": "8.0", + "name": "mysql", + "version": 3520211031142409, + "arch": "x86_64", + "context": "f27b74a8" + }, + "references": [ + { + "href": "https://bugzilla.redhat.com/show_bug.cgi?id=2016142", + "id": "2016142", + "title": "CVE-2021-2478 CVE-2021-2479 CVE-2021-2481 CVE-2021-35546 CVE-2021-35575 CVE-2021-35577 CVE-2021-35591 CVE-2021-35596 CVE-2021-35597 CVE-2021-35602 CVE-2021-35604 CVE-2021-35607 CVE-2021-35608 ... mysql:8.0/community-mysql: various flaws [fedora-all]", + "type": "bugzilla" + } + ], + "cveids": [ + "CVE-2021-35623" + ] +} \ No newline at end of file diff --git a/pkg/vulnsrc/fedora/testdata/sad/vuln-list/fedora/fedora/35/Everything/x86_64/2021/FEDORA-2021-0b8814db99.json b/pkg/vulnsrc/fedora/testdata/sad/vuln-list/fedora/fedora/35/Everything/x86_64/2021/FEDORA-2021-0b8814db99.json new file mode 100644 index 00000000..25d643cc --- /dev/null +++ b/pkg/vulnsrc/fedora/testdata/sad/vuln-list/fedora/fedora/35/Everything/x86_64/2021/FEDORA-2021-0b8814db99.json @@ -0,0 +1,90 @@ +{ + "id": "FEDORA-2021-0b8814db99" + "title": "cacti-1.2.19-1.fc35 cacti-spine-1.2.19-1.fc35", + "type": "security", + "issued": { + "date": "2021-11-11 01:17:54" + }, + "updated": { + "date": "2021-11-02 09:38:12" + }, + "severity": "Moderate", + "description": "- Update to 1.2.19\n\nRelease notes: https://www.cacti.net/info/changelog/1.2.19", + "packages": [ + { + "name": "cacti", + "epoch": "0", + "version": "1.2.19", + "release": "1.fc35", + "arch": "noarch", + "src": "https://download.fedoraproject.org/pub/fedora/linux/updates/35/i386/c/cacti-1.2.19-1.fc35.noarch.rpm", + "filename": "cacti-1.2.19-1.fc35.noarch.rpm" + }, + { + "name": "cacti-spine-debugsource", + "epoch": "0", + "version": "1.2.19", + "release": "1.fc35", + "arch": "i686", + "src": "https://download.fedoraproject.org/pub/fedora/linux/updates/35/i386/c/cacti-spine-debugsource-1.2.19-1.fc35.i686.rpm", + "filename": "cacti-spine-debugsource-1.2.19-1.fc35.i686.rpm" + }, + { + "name": "cacti-spine", + "epoch": "0", + "version": "1.2.19", + "release": "1.fc35", + "arch": "i686", + "src": "https://download.fedoraproject.org/pub/fedora/linux/updates/35/i386/c/cacti-spine-1.2.19-1.fc35.i686.rpm", + "filename": "cacti-spine-1.2.19-1.fc35.i686.rpm" + }, + { + "name": "cacti-spine-debuginfo", + "epoch": "0", + "version": "1.2.19", + "release": "1.fc35", + "arch": "i686", + "src": "https://download.fedoraproject.org/pub/fedora/linux/updates/35/i386/c/cacti-spine-debuginfo-1.2.19-1.fc35.i686.rpm", + "filename": "cacti-spine-debuginfo-1.2.19-1.fc35.i686.rpm" + }, + { + "name": "cacti-spine-debugsource", + "epoch": "0", + "version": "1.2.19", + "release": "1.fc35", + "arch": "x86_64", + "src": "https://download.fedoraproject.org/pub/fedora/linux/updates/35/x86_64/c/cacti-spine-debugsource-1.2.19-1.fc35.x86_64.rpm", + "filename": "cacti-spine-debugsource-1.2.19-1.fc35.x86_64.rpm" + }, + { + "name": "cacti-spine-debuginfo", + "epoch": "0", + "version": "1.2.19", + "release": "1.fc35", + "arch": "x86_64", + "src": "https://download.fedoraproject.org/pub/fedora/linux/updates/35/x86_64/c/cacti-spine-debuginfo-1.2.19-1.fc35.x86_64.rpm", + "filename": "cacti-spine-debuginfo-1.2.19-1.fc35.x86_64.rpm" + }, + { + "name": "cacti-spine", + "epoch": "0", + "version": "1.2.19", + "release": "1.fc35", + "arch": "x86_64", + "src": "https://download.fedoraproject.org/pub/fedora/linux/updates/35/x86_64/c/cacti-spine-1.2.19-1.fc35.x86_64.rpm", + "filename": "cacti-spine-1.2.19-1.fc35.x86_64.rpm" + } + ], + "module": {}, + "references": [ + { + "href": "https://bugzilla.redhat.com/show_bug.cgi?id=2001017", + "id": "2001017", + "title": "CVE-2020-14424 cacti: lack of escaping on template import can lead to XSS [fedora-all]", + "type": "bugzilla" + } + ], + "cveids": [ + "CVE-2020-14424" + ] +} \ No newline at end of file diff --git a/pkg/vulnsrc/fedora/types.go b/pkg/vulnsrc/fedora/types.go new file mode 100644 index 00000000..029abb90 --- /dev/null +++ b/pkg/vulnsrc/fedora/types.go @@ -0,0 +1,48 @@ +package fedora + +// UpdateInfo has detailed data of Fedora Security Advisory +type UpdateInfo struct { + ID string `xml:"id" json:"id,omitempty"` + Title string `xml:"title" json:"title,omitempty"` + Type string `xml:"type,attr" json:"type,omitempty"` + Issued Date `xml:"issued" json:"issued,omitempty"` + Updated Date `xml:"updated" json:"updated,omitempty"` + Severity string `xml:"severity" json:"severity,omitempty"` + Description string `xml:"description" json:"description,omitempty"` + Packages []Package `xml:"pkglist>collection>package" json:"packages,omitempty"` + Module Module `json:"module,omitempty"` + References []Reference `xml:"references>reference" json:"references,omitempty"` + CveIDs []string `json:"cveids,omitempty"` +} + +// Date has issued at, updated at +type Date struct { + Date string `xml:"date,attr" json:"date,omitempty"` +} + +// Reference has reference information +type Reference struct { + Href string `xml:"href,attr" json:"href,omitempty"` + ID string `xml:"id,attr" json:"id,omitempty"` + Title string `xml:"title,attr" json:"title,omitempty"` + Type string `xml:"type,attr" json:"type,omitempty"` +} + +// Package has affected package information +type Package struct { + Name string `xml:"name,attr" json:"name,omitempty"` + Epoch string `xml:"epoch,attr" json:"epoch,omitempty"` + Version string `xml:"version,attr" json:"version,omitempty"` + Release string `xml:"release,attr" json:"release,omitempty"` + Arch string `xml:"arch,attr" json:"arch,omitempty"` + Filename string `xml:"filename" json:"filename,omitempty"` +} + +// Module has modular package information +type Module struct { + Stream string `json:"stream,omitempty"` + Name string `json:"name,omitempty"` + Version int64 `json:"version,omitempty"` + Arch string `json:"arch,omitempty"` + Context string `json:"context,omitempty"` +} diff --git a/pkg/vulnsrc/vulnerability/vulnerability.go b/pkg/vulnsrc/vulnerability/vulnerability.go index c292fa47..d3f61b8d 100644 --- a/pkg/vulnsrc/vulnerability/vulnerability.go +++ b/pkg/vulnsrc/vulnerability/vulnerability.go @@ -15,7 +15,7 @@ const ( var ( sources = []types.SourceID{NVD, RedHat, Debian, Ubuntu, Alpine, Amazon, OracleOVAL, SuseCVRF, Photon, - ArchLinux, Alma, Rocky, CBLMariner, RubySec, PhpSecurityAdvisories, NodejsSecurityWg, GoVulnDB, GHSA, GLAD, OSV, + ArchLinux, Alma, Rocky, Fedora, CBLMariner, RubySec, PhpSecurityAdvisories, NodejsSecurityWg, GoVulnDB, GHSA, GLAD, OSV, } ) diff --git a/pkg/vulnsrc/vulnsrc.go b/pkg/vulnsrc/vulnsrc.go index a4574fbe..bba77952 100644 --- a/pkg/vulnsrc/vulnsrc.go +++ b/pkg/vulnsrc/vulnsrc.go @@ -9,6 +9,7 @@ import ( "github.com/aquasecurity/trivy-db/pkg/vulnsrc/bundler" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/composer" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/debian" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/fedora" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/glad" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/govulndb" @@ -42,6 +43,7 @@ var ( archlinux.NewVulnSrc(), redhat.NewVulnSrc(), redhatoval.NewVulnSrc(), + fedora.NewVulnSrc(), debian.NewVulnSrc(), ubuntu.NewVulnSrc(), amazon.NewVulnSrc(),