From 0ededc7cb4a28c8131aacfd0ac6c9233677f25e8 Mon Sep 17 00:00:00 2001 From: Spyros Date: Thu, 17 Jun 2021 16:43:44 +0100 Subject: [PATCH] added optional flag and functionality for combined trivy output parsing (#115) * added optional flag and functionality for combined trivy output parsing * address comments --- producers/docker_trivy/main.go | 145 +++++++------ producers/docker_trivy/main_test.go | 215 ++++++++++++-------- producers/docker_trivy/types/trivy-issue.go | 3 + 3 files changed, 219 insertions(+), 144 deletions(-) diff --git a/producers/docker_trivy/main.go b/producers/docker_trivy/main.go index bf0e428..b029d1f 100644 --- a/producers/docker_trivy/main.go +++ b/producers/docker_trivy/main.go @@ -1,77 +1,106 @@ package main import ( - v1 "github.com/thought-machine/dracon/api/proto/v1" - "github.com/thought-machine/dracon/producers/docker_trivy/types" + "fmt" + "log" + "strings" + "flag" - "fmt" - "log" - "strings" - - "github.com/thought-machine/dracon/producers" + v1 "github.com/thought-machine/dracon/api/proto/v1" + "github.com/thought-machine/dracon/producers/docker_trivy/types" + "github.com/thought-machine/dracon/producers" ) +var Combined bool + func main() { - if err := producers.ParseFlags(); err != nil { - log.Fatal(err) - } + flag.BoolVar(&Combined, "combinedout", false, "Output is the combined output of Trivy against multiple images, expects {:[],:[]}") + + if err := producers.ParseFlags(); err != nil { + log.Fatal(err) + } - inFile, err := producers.ReadInFile() - if err != nil { - log.Fatal(err) - } + inFile, err := producers.ReadInFile() + if err != nil { + log.Fatal(err) + } - var results []types.TrivyOut - if err := producers.ParseJSON(inFile, &results); err != nil { - log.Fatal(err) - } + if Combined { + var results types.CombinedOut + if err := producers.ParseJSON(inFile, &results); err != nil { + log.Fatal(err) + } + if err := producers.WriteDraconOut( + "trivy", + parseCombinedOut(results), + ); err != nil { + log.Fatal(err) + } + } else { + var results []types.TrivyOut + if err := producers.ParseJSON(inFile, &results); err != nil { + log.Fatal(err) + } + if err := producers.WriteDraconOut( + "trivy", + parseSingleOut(results), + ); err != nil { + log.Fatal(err) + } - if err := producers.WriteDraconOut( - "trivy", - parseOut(results), - ); err != nil { - log.Fatal(err) - } + } +} +func parseCombinedOut(results types.CombinedOut) []*v1.Issue { + issues := []*v1.Issue{} + for img, output := range results { + log.Printf("Parsing Combined Output for %s\n", img) + for _, res := range output { + target := res.Target + for _, vuln := range res.Vulnerable { + issues = append(issues, parseResult(&vuln, target)) + } + } + } + return issues } -func parseOut(results []types.TrivyOut) []*v1.Issue { - issues := []*v1.Issue{} - for _, res := range results { - target := res.Target +func parseSingleOut(results []types.TrivyOut) []*v1.Issue { + issues := []*v1.Issue{} + for _, res := range results { + target := res.Target - for _, vuln := range res.Vulnerable { - issues = append(issues, parseResult(&vuln, target)) - } - } - return issues -} + for _, vuln := range res.Vulnerable { + issues = append(issues, parseResult(&vuln, target)) + } + } + return issues + } // TrivySeverityToDracon maps Trivy Severity Strings to dracon struct func TrivySeverityToDracon(severity string) v1.Severity { - switch severity { - case "LOW": - return v1.Severity_SEVERITY_LOW - case "MEDIUM": - return v1.Severity_SEVERITY_MEDIUM - case "HIGH": - return v1.Severity_SEVERITY_HIGH - case "CRITICAL": - return v1.Severity_SEVERITY_CRITICAL - default: - return v1.Severity_SEVERITY_INFO - } -} + switch severity { + case "LOW": + return v1.Severity_SEVERITY_LOW + case "MEDIUM": + return v1.Severity_SEVERITY_MEDIUM + case "HIGH": + return v1.Severity_SEVERITY_HIGH + case "CRITICAL": + return v1.Severity_SEVERITY_CRITICAL + default: + return v1.Severity_SEVERITY_INFO + } + } func parseResult(r *types.TrivyVulnerability, target string) *v1.Issue { - cvss := r.CVSS.Nvd.V3Score - return &v1.Issue{ - Target: target, - Type: "Container image vulnerability", - Title: fmt.Sprintf("[%s][%s] %s", target, r.CVE, r.Title), - Severity: TrivySeverityToDracon(r.Severity), - Confidence: v1.Confidence_CONFIDENCE_MEDIUM, - Cvss: cvss, - Description: fmt.Sprintf("CVSS Score: %v\nCvssVector: %s\nCve: %s\nCwe: %s\nReference: %s\nOriginal Description:%s\n", - r.CVSS.Nvd.V3Score, r.CVSS.Nvd.V3Vector, r.CVE, strings.Join(r.CweIDs[:], ","), r.PrimaryURL, r.Description), - } + return &v1.Issue{ + Target: target, + Type: "Container image vulnerability", + Title: fmt.Sprintf("[%s][%s] %s", target, r.CVE, r.Title), + Severity: TrivySeverityToDracon(r.Severity), + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Cvss: r.CVSS.Nvd.V3Score, + Description: fmt.Sprintf("CVSS Score: %v\nCvssVector: %s\nCve: %s\nCwe: %s\nReference: %s\nOriginal Description:%s\n", + r.CVSS.Nvd.V3Score, r.CVSS.Nvd.V3Vector, r.CVE, strings.Join(r.CweIDs[:], ","), r.PrimaryURL, r.Description), + } } diff --git a/producers/docker_trivy/main_test.go b/producers/docker_trivy/main_test.go index ef52260..d56d593 100644 --- a/producers/docker_trivy/main_test.go +++ b/producers/docker_trivy/main_test.go @@ -1,102 +1,145 @@ package main import ( - v1 "github.com/thought-machine/dracon/api/proto/v1" - "github.com/thought-machine/dracon/producers/docker_trivy/types" + "encoding/json" + "fmt" + "testing" - "encoding/json" - "fmt" - "testing" + "github.com/stretchr/testify/assert" + + v1 "github.com/thought-machine/dracon/api/proto/v1" + "github.com/thought-machine/dracon/producers/docker_trivy/types" - "github.com/stretchr/testify/assert" ) -func TestParseOut(t *testing.T) { - var results []types.TrivyOut - err := json.Unmarshal([]byte(exampleOutput), &results) - if err != nil { - t.Logf(err.Error()) - t.Fail() - } - issues := parseOut(results) +func TestParseCombinedOut(t *testing.T) { + var results types.CombinedOut + combinedOutput := fmt.Sprintf(`{"ubuntu:latest":%s,"alpine:latest":%s}`,exampleOutput,exampleOutput) + err := json.Unmarshal([]byte(combinedOutput), &results) + if err != nil { + t.Logf(err.Error()) + t.Fail() + } + issues := parseCombinedOut(results) + + expectedIssues := []*v1.Issue{&v1.Issue{ + Target: "ubuntu (ubuntu 18.04)", + Type: "Container image vulnerability", + Title: "[ubuntu (ubuntu 18.04)][CVE-2020-27350] apt: integer overflows and underflows while parsing .deb packages", + Severity: v1.Severity_SEVERITY_MEDIUM, + Cvss: 5.7, + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Description: fmt.Sprintf("CVSS Score: %v\nCvssVector: %s\nCve: %s\nCwe: %s\nReference: %s\nOriginal Description:%s\n", + "5.7", "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", "CVE-2020-27350", + "CWE-190", "https://avd.aquasec.com/nvd/cve-2020-27350", "APT had several integer overflows and underflows while parsing .deb packages, aka GHSL-2020-168 GHSL-2020-169, in files apt-pkg/contrib/extracttar.cc, apt-pkg/deb/debfile.cc, and apt-pkg/contrib/arfile.cc. This issue affects: apt 1.2.32ubuntu0 versions prior to 1.2.32ubuntu0.2; 1.6.12ubuntu0 versions prior to 1.6.12ubuntu0.2; 2.0.2ubuntu0 versions prior to 2.0.2ubuntu0.2; 2.1.10ubuntu0 versions prior to 2.1.10ubuntu0.1;"), + }} + + found := 0 + assert.Equal(t, 2, len(issues)) + for _, issue := range issues { + singleMatch := 0 + for _, expected := range expectedIssues { + if expected.Title == issue.Title { + singleMatch++ + found++ + assert.Equal(t, singleMatch, 1) //assert no duplicates + assert.EqualValues(t, expected.Type, issue.Type) + assert.EqualValues(t, expected.Title, issue.Title) + assert.EqualValues(t, expected.Severity, issue.Severity) + assert.EqualValues(t, expected.Cvss, issue.Cvss) + assert.EqualValues(t, expected.Confidence, issue.Confidence) + assert.EqualValues(t, expected.Description, issue.Description) + } + } + } + assert.Equal(t, found, len(issues)) //assert everything has been found +} + +func TestParseSingleOut(t *testing.T) { + var results []types.TrivyOut + err := json.Unmarshal([]byte(exampleOutput), &results) + if err != nil { + t.Logf(err.Error()) + t.Fail() + } + issues := parseSingleOut(results) - expectedIssues := make([]*v1.Issue, 1) - expectedIssues[0] = &v1.Issue{ - Target: "ubuntu (ubuntu 18.04)", - Type: "Container image vulnerability", - Title: "[ubuntu (ubuntu 18.04)][CVE-2020-27350] apt: integer overflows and underflows while parsing .deb packages", - Severity: v1.Severity_SEVERITY_MEDIUM, - Cvss: 5.7, - Confidence: v1.Confidence_CONFIDENCE_MEDIUM, - Description: fmt.Sprintf("CVSS Score: %v\nCvssVector: %s\nCve: %s\nCwe: %s\nReference: %s\nOriginal Description:%s\n", - "5.7", "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", "CVE-2020-27350", - "CWE-190", "https://avd.aquasec.com/nvd/cve-2020-27350", "APT had several integer overflows and underflows while parsing .deb packages, aka GHSL-2020-168 GHSL-2020-169, in files apt-pkg/contrib/extracttar.cc, apt-pkg/deb/debfile.cc, and apt-pkg/contrib/arfile.cc. This issue affects: apt 1.2.32ubuntu0 versions prior to 1.2.32ubuntu0.2; 1.6.12ubuntu0 versions prior to 1.6.12ubuntu0.2; 2.0.2ubuntu0 versions prior to 2.0.2ubuntu0.2; 2.1.10ubuntu0 versions prior to 2.1.10ubuntu0.1;"), - } + expectedIssues := []*v1.Issue{&v1.Issue{ + Target: "ubuntu (ubuntu 18.04)", + Type: "Container image vulnerability", + Title: "[ubuntu (ubuntu 18.04)][CVE-2020-27350] apt: integer overflows and underflows while parsing .deb packages", + Severity: v1.Severity_SEVERITY_MEDIUM, + Cvss: 5.7, + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Description: fmt.Sprintf("CVSS Score: %v\nCvssVector: %s\nCve: %s\nCwe: %s\nReference: %s\nOriginal Description:%s\n", + "5.7", "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", "CVE-2020-27350", + "CWE-190", "https://avd.aquasec.com/nvd/cve-2020-27350", "APT had several integer overflows and underflows while parsing .deb packages, aka GHSL-2020-168 GHSL-2020-169, in files apt-pkg/contrib/extracttar.cc, apt-pkg/deb/debfile.cc, and apt-pkg/contrib/arfile.cc. This issue affects: apt 1.2.32ubuntu0 versions prior to 1.2.32ubuntu0.2; 1.6.12ubuntu0 versions prior to 1.6.12ubuntu0.2; 2.0.2ubuntu0 versions prior to 2.0.2ubuntu0.2; 2.1.10ubuntu0 versions prior to 2.1.10ubuntu0.1;"), + }} - found := 0 - assert.Equal(t, len(expectedIssues), len(issues)) - for _, issue := range issues { - singleMatch := 0 - for _, expected := range expectedIssues { - if expected.Title == issue.Title { - singleMatch++ - found++ - assert.Equal(t, singleMatch, 1) //assert no duplicates - assert.EqualValues(t, expected.Type, issue.Type) - assert.EqualValues(t, expected.Title, issue.Title) - assert.EqualValues(t, expected.Severity, issue.Severity) - assert.EqualValues(t, expected.Cvss, issue.Cvss) - assert.EqualValues(t, expected.Confidence, issue.Confidence) - assert.EqualValues(t, expected.Description, issue.Description) - } - } - } - assert.Equal(t, found, len(issues)) //assert everything has been found + found := 0 + assert.Equal(t, len(expectedIssues), len(issues)) + for _, issue := range issues { + singleMatch := 0 + for _, expected := range expectedIssues { + if expected.Title == issue.Title { + singleMatch++ + found++ + assert.Equal(t, singleMatch, 1) //assert no duplicates + assert.EqualValues(t, expected.Type, issue.Type) + assert.EqualValues(t, expected.Title, issue.Title) + assert.EqualValues(t, expected.Severity, issue.Severity) + assert.EqualValues(t, expected.Cvss, issue.Cvss) + assert.EqualValues(t, expected.Confidence, issue.Confidence) + assert.EqualValues(t, expected.Description, issue.Description) + } + } + } + assert.Equal(t, found, len(issues)) //assert everything has been found } var exampleOutput = ` [ - { +{ "Target": "ubuntu (ubuntu 18.04)", "Type": "ubuntu", "Vulnerabilities": [ - { - "VulnerabilityID": "CVE-2020-27350", - "PkgName": "apt", - "InstalledVersion": "1.6.12", - "FixedVersion": "1.6.12ubuntu0.2", - "Layer": { - "DiffID": "sha256:a090697502b8d19fbc83afb24d8fb59b01e48bf87763a00ca55cfff42423ad36" - }, - "SeveritySource": "ubuntu", - "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-27350", - "Title": "apt: integer overflows and underflows while parsing .deb packages", - "Description": "APT had several integer overflows and underflows while parsing .deb packages, aka GHSL-2020-168 GHSL-2020-169, in files apt-pkg/contrib/extracttar.cc, apt-pkg/deb/debfile.cc, and apt-pkg/contrib/arfile.cc. This issue affects: apt 1.2.32ubuntu0 versions prior to 1.2.32ubuntu0.2; 1.6.12ubuntu0 versions prior to 1.6.12ubuntu0.2; 2.0.2ubuntu0 versions prior to 2.0.2ubuntu0.2; 2.1.10ubuntu0 versions prior to 2.1.10ubuntu0.1;", - "Severity": "MEDIUM", - "CweIDs": [ - "CWE-190" - ], - "CVSS": { - "nvd": { - "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", - "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", - "V2Score": 4.6, - "V3Score": 5.7 - }, - "redhat": { - "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", - "V3Score": 5.7 - } - }, - "References": [ - "https://bugs.launchpad.net/bugs/1899193", - "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-27350", - "https://security.netapp.com/advisory/ntap-20210108-0005/", - "https://usn.ubuntu.com/usn/usn-4667-1", - "https://usn.ubuntu.com/usn/usn-4667-2", - "https://www.debian.org/security/2020/dsa-4808" - ], - "PublishedDate": "2020-12-10T04:15:00Z", - "LastModifiedDate": "2021-01-08T12:15:00Z" - }]}] -` + { + "VulnerabilityID": "CVE-2020-27350", + "PkgName": "apt", + "InstalledVersion": "1.6.12", + "FixedVersion": "1.6.12ubuntu0.2", + "Layer": { + "DiffID": "sha256:a090697502b8d19fbc83afb24d8fb59b01e48bf87763a00ca55cfff42423ad36" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-27350", + "Title": "apt: integer overflows and underflows while parsing .deb packages", + "Description": "APT had several integer overflows and underflows while parsing .deb packages, aka GHSL-2020-168 GHSL-2020-169, in files apt-pkg/contrib/extracttar.cc, apt-pkg/deb/debfile.cc, and apt-pkg/contrib/arfile.cc. This issue affects: apt 1.2.32ubuntu0 versions prior to 1.2.32ubuntu0.2; 1.6.12ubuntu0 versions prior to 1.6.12ubuntu0.2; 2.0.2ubuntu0 versions prior to 2.0.2ubuntu0.2; 2.1.10ubuntu0 versions prior to 2.1.10ubuntu0.1;", + "Severity": "MEDIUM", + "CweIDs": [ + "CWE-190" + ], + "CVSS": { + "nvd": { + "V2Vector": "AV:L/AC:L/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", + "V2Score": 4.6, + "V3Score": 5.7 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:L", + "V3Score": 5.7 + } + }, + "References": [ + "https://bugs.launchpad.net/bugs/1899193", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-27350", + "https://security.netapp.com/advisory/ntap-20210108-0005/", + "https://usn.ubuntu.com/usn/usn-4667-1", + "https://usn.ubuntu.com/usn/usn-4667-2", + "https://www.debian.org/security/2020/dsa-4808" + ], + "PublishedDate": "2020-12-10T04:15:00Z", + "LastModifiedDate": "2021-01-08T12:15:00Z" + }]}] + ` diff --git a/producers/docker_trivy/types/trivy-issue.go b/producers/docker_trivy/types/trivy-issue.go index 2181544..b87bb20 100644 --- a/producers/docker_trivy/types/trivy-issue.go +++ b/producers/docker_trivy/types/trivy-issue.go @@ -1,5 +1,8 @@ package types +// CombinedOut represents the output of multiple Trivy runs (useful when using the Trivy Dracon tool to scan multiple images); the key is the name of the image file that was scanned +type CombinedOut map[string][]TrivyOut + // TrivyOut represents the output of a trivy run that we care about type TrivyOut struct { Vulnerable []TrivyVulnerability `json:"Vulnerabilities"`