diff --git a/internal/services/formatters/generic/trivy/entities/misconfiguration.go b/internal/services/formatters/generic/trivy/entities/misconfiguration.go deleted file mode 100644 index 9a9343fd0..000000000 --- a/internal/services/formatters/generic/trivy/entities/misconfiguration.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2021 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package entities - -type Misconfiguration struct { - Title string `json:"title"` - Description string `json:"description"` - Message string `json:"message"` - Resolution string `json:"resolution"` - References []string `json:"references"` - Severity string `json:"severity"` -} diff --git a/internal/services/formatters/generic/trivy/entities/output.go b/internal/services/formatters/generic/trivy/entities/output.go deleted file mode 100644 index d1429e52b..000000000 --- a/internal/services/formatters/generic/trivy/entities/output.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2021 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package entities - -type Output struct { - ArtifactName string `json:"artifactName"` - ArtifactType string `json:"artifactType"` - Results []*Result `json:"results"` -} diff --git a/internal/services/formatters/generic/trivy/entities/result.go b/internal/services/formatters/generic/trivy/entities/result.go deleted file mode 100644 index 330a7baee..000000000 --- a/internal/services/formatters/generic/trivy/entities/result.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2021 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package entities - -type Result struct { - Target string `json:"target"` - Class string `json:"class"` - Type string `json:"type"` - Vulnerabilities []*Vulnerability `json:"vulnerabilities"` - Misconfigurations []*Misconfiguration `json:"misconfigurations"` -} diff --git a/internal/services/formatters/generic/trivy/formatter.go b/internal/services/formatters/generic/trivy/formatter.go index 64d281919..eab8a3137 100644 --- a/internal/services/formatters/generic/trivy/formatter.go +++ b/internal/services/formatters/generic/trivy/formatter.go @@ -25,15 +25,14 @@ import ( "github.com/ZupIT/horusec-devkit/pkg/enums/languages" "github.com/ZupIT/horusec-devkit/pkg/enums/severities" "github.com/ZupIT/horusec-devkit/pkg/enums/tools" - enumsVulnerability "github.com/ZupIT/horusec-devkit/pkg/enums/vulnerability" + enumvulnerability "github.com/ZupIT/horusec-devkit/pkg/enums/vulnerability" "github.com/ZupIT/horusec-devkit/pkg/utils/logger" "github.com/google/uuid" - dockerEntities "github.com/ZupIT/horusec/internal/entities/docker" + "github.com/ZupIT/horusec/internal/entities/docker" "github.com/ZupIT/horusec/internal/enums/images" "github.com/ZupIT/horusec/internal/helpers/messages" "github.com/ZupIT/horusec/internal/services/formatters" - "github.com/ZupIT/horusec/internal/services/formatters/generic/trivy/entities" "github.com/ZupIT/horusec/internal/utils/file" vulnhash "github.com/ZupIT/horusec/internal/utils/vuln_hash" ) @@ -125,8 +124,8 @@ func (f *Formatter) parse(projectSubPath, configOutput, fileSystemOutput string) return fileSystemOutput, f.parseOutput(fileSystemOutput, CmdFs, projectSubPath) } -func (f *Formatter) getDockerConfig(cmd, projectSubPath string) *dockerEntities.AnalysisData { - analysisData := &dockerEntities.AnalysisData{ +func (f *Formatter) getDockerConfig(cmd, projectSubPath string) *docker.AnalysisData { + analysisData := &docker.AnalysisData{ CMD: f.AddWorkDirInCmd(cmd, projectSubPath, tools.Trivy), Language: languages.Generic, } @@ -135,7 +134,7 @@ func (f *Formatter) getDockerConfig(cmd, projectSubPath string) *dockerEntities. } func (f *Formatter) parseOutput(output, cmd, projectSubPath string) error { - report := &entities.Output{} + report := new(trivyOutput) if output == "" { return nil @@ -146,42 +145,43 @@ func (f *Formatter) parseOutput(output, cmd, projectSubPath string) error { } for _, result := range report.Results { - f.setVulnerabilities(cmd, result, filepath.Join(projectSubPath, result.Target)) + f.addVulnerabilities(cmd, result, filepath.Join(projectSubPath, result.Target)) } return nil } -func (f *Formatter) setVulnerabilities(cmd string, result *entities.Result, path string) { +func (f *Formatter) addVulnerabilities(cmd string, result *trivyOutputResult, path string) { switch cmd { case CmdFs: - f.setVulnerabilitiesOutput(result.Vulnerabilities, path) + f.addVulnerabilitiesOutput(result.Vulnerabilities, path) case CmdConfig: - f.setVulnerabilitiesOutput(result.Vulnerabilities, path) - f.setMisconfigurationOutput(result.Misconfigurations, path) + f.addVulnerabilitiesOutput(result.Vulnerabilities, path) + f.addMisconfigurationOutput(result.Misconfigurations, path) } } -func (f *Formatter) setVulnerabilitiesOutput(vulnerabilities []*entities.Vulnerability, target string) { +func (f *Formatter) addVulnerabilitiesOutput(vulnerabilities []*trivyVulnerability, target string) { for _, vuln := range vulnerabilities { addVuln := f.getVulnBase() addVuln.Code = fmt.Sprintf("%s v%s", vuln.PkgName, vuln.InstalledVersion) _, _, addVuln.Line = file.GetDependencyInfo(addVuln.Code, target) addVuln.File = target - addVuln.Details = vuln.GetDetails() + addVuln.Details = vuln.getDetails() addVuln.Severity = severities.GetSeverityByString(vuln.Severity) addVuln = vulnhash.Bind(addVuln) f.AddNewVulnerabilityIntoAnalysis(addVuln) } } -func (f *Formatter) setMisconfigurationOutput(result []*entities.Misconfiguration, target string) { +func (f *Formatter) addMisconfigurationOutput(result []*trivyMisconfiguration, target string) { for _, vuln := range result { addVuln := f.getVulnBase() addVuln.File = target addVuln.Code = vuln.Title - addVuln.Details = fmt.Sprintf("%s - %s - %s - %s", - vuln.Description, vuln.Message, vuln.Resolution, vuln.References) + addVuln.Details = fmt.Sprintf( + "%s - %s - %s - %s", vuln.Description, vuln.Message, vuln.Resolution, vuln.References, + ) addVuln.Severity = severities.GetSeverityByString(vuln.Severity) addVuln = vulnhash.Bind(addVuln) f.AddNewVulnerabilityIntoAnalysis(addVuln) @@ -196,6 +196,6 @@ func (f *Formatter) getVulnBase() *vulnerability.Vulnerability { Confidence: confidence.Medium, SecurityTool: tools.Trivy, Language: languages.Generic, - Type: enumsVulnerability.Vulnerability, + Type: enumvulnerability.Vulnerability, } } diff --git a/internal/services/formatters/generic/trivy/formatter_test.go b/internal/services/formatters/generic/trivy/formatter_test.go index f23fec38c..729b971b8 100644 --- a/internal/services/formatters/generic/trivy/formatter_test.go +++ b/internal/services/formatters/generic/trivy/formatter_test.go @@ -17,68 +17,129 @@ package trivy import ( "testing" - entitiesAnalysis "github.com/ZupIT/horusec-devkit/pkg/entities/analysis" + "github.com/ZupIT/horusec-devkit/pkg/entities/analysis" + "github.com/ZupIT/horusec-devkit/pkg/enums/languages" "github.com/ZupIT/horusec-devkit/pkg/enums/tools" "github.com/stretchr/testify/assert" "github.com/ZupIT/horusec/config" "github.com/ZupIT/horusec/internal/entities/toolsconfig" - "github.com/ZupIT/horusec/internal/entities/workdir" "github.com/ZupIT/horusec/internal/services/formatters" "github.com/ZupIT/horusec/internal/utils/testutil" ) -func TestParseOutput(t *testing.T) { - t.Run("Should return 2 vulnerabilities with no errors", func(t *testing.T) { +func TestTrivyParseOutput(t *testing.T) { + t.Run("Should add 2 vulnerabilities on analysis without errors", func(t *testing.T) { dockerAPIControllerMock := testutil.NewDockerMock() dockerAPIControllerMock.On("SetAnalysisID") - analysis := &entitiesAnalysis.Analysis{} - c := &config.Config{} - c.WorkDir = &workdir.WorkDir{} + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) - output := `{"SchemaVersion":2,"ArtifactName":"./","ArtifactType":"filesystem","Metadata":{},"Results":[{"Target":"go.sum","Class":"lang-pkgs","Type":"gomod","Vulnerabilities":[{"VulnerabilityID":"CVE-2020-26160","PkgName":"github.com/dgrijalva/jwt-go","InstalledVersion":"3.2.0+incompatible","Layer":{"DiffID":"sha256:f792cd543fb8711f2afbe7990dddf572b57b29f982ea03c11010972b07a28b36"},"SeveritySource":"nvd","PrimaryURL":"https://avd.aquasec.com/nvd/cve-2020-26160","Title":"jwt-go: access restriction bypass vulnerability","Description":"jwt-go before 4.0.0-preview1 allows attackers to bypass intended access restrictions in situations with []string{} for m[\"aud\"] (which is allowed by the specification). Because the type assertion fails, \"\" is the value of aud. This is a security problem if the JWT token is presented to a service that lacks its own audience check.","Severity":"HIGH","CweIDs":["CWE-862"],"CVSS":{"nvd":{"V2Vector":"AV:N/AC:L/Au:N/C:P/I:N/A:N","V3Vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N","V2Score":5,"V3Score":7.5},"redhat":{"V3Vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N","V3Score":7.5}},"References":["https://github.com/dgrijalva/jwt-go/pull/426","https://nvd.nist.gov/vuln/detail/CVE-2020-26160","https://snyk.io/vuln/SNYK-GOLANG-GITHUBCOMDGRIJALVAJWTGO-596515"],"PublishedDate":"2020-09-30T18:15:00Z","LastModifiedDate":"2021-07-21T11:39:00Z"}]}]}` + analysis := new(analysis.Analysis) - dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + cfg := config.New() - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, c) + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, cfg) formatter := NewFormatter(service) - formatter.StartAnalysis("") + assert.Len(t, analysis.AnalysisVulnerabilities, 2) + + for _, v := range analysis.AnalysisVulnerabilities { + vuln := v.Vulnerability + + assert.Equal(t, tools.Trivy, vuln.SecurityTool) + assert.Equal(t, languages.Generic, vuln.Language) + assert.NotEmpty(t, vuln.Details, "Expected not empty details") + assert.NotEmpty(t, vuln.Code, "Expected not empty code") + assert.NotEmpty(t, vuln.File, "Expected not empty file name") + assert.NotEmpty(t, vuln.Severity, "Expected not empty severity") + + } }) - t.Run("Should return error when invalid output", func(t *testing.T) { + t.Run("Should add error on analysis when invalid output", func(t *testing.T) { dockerAPIControllerMock := testutil.NewDockerMock() dockerAPIControllerMock.On("SetAnalysisID") - analysis := &entitiesAnalysis.Analysis{} - c := &config.Config{} - c.WorkDir = &workdir.WorkDir{} + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("invalid", nil) - output := "!!" + analysis := new(analysis.Analysis) - dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + cfg := config.New() - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, c) + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, cfg) formatter := NewFormatter(service) - formatter.StartAnalysis("") - assert.NotEmpty(t, analysis.Errors) + + assert.True(t, analysis.HasErrors(), "Expected errors on analysis") }) t.Run("Should not execute tool because it's ignored", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} + analysis := new(analysis.Analysis) + dockerAPIControllerMock := testutil.NewDockerMock() - c := &config.Config{} - c.WorkDir = &workdir.WorkDir{} - c.ToolsConfig = toolsconfig.ToolsConfig{ + + cfg := config.New() + cfg.ToolsConfig = toolsconfig.ToolsConfig{ tools.Trivy: toolsconfig.Config{ IsToIgnore: true, }, } - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, c) + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, cfg) formatter := NewFormatter(service) - formatter.StartAnalysis("") }) } + +const output = ` +{ + "SchemaVersion": 2, + "ArtifactName": "./", + "ArtifactType": "filesystem", + "Metadata": {}, + "Results": [ + { + "Target": "go.sum", + "Class": "lang-pkgs", + "Type": "gomod", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-26160", + "PkgName": "github.com/dgrijalva/jwt-go", + "InstalledVersion": "3.2.0+incompatible", + "Layer": { + "DiffID": "sha256:f792cd543fb8711f2afbe7990dddf572b57b29f982ea03c11010972b07a28b36" + }, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-26160", + "Title": "jwt-go: access restriction bypass vulnerability", + "Description": "jwt-go before 4.0.0-preview1 allows attackers to bypass intended access restrictions in situations with []string{} for m[\"aud\"] (which is allowed by the specification). Because the type assertion fails, \"\" is the value of aud. This is a security problem if the JWT token is presented to a service that lacks its own audience check.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-862" + ], + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V2Score": 5, + "V3Score": 7.5 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "https://github.com/dgrijalva/jwt-go/pull/426", + "https://nvd.nist.gov/vuln/detail/CVE-2020-26160", + "https://snyk.io/vuln/SNYK-GOLANG-GITHUBCOMDGRIJALVAJWTGO-596515" + ], + "PublishedDate": "2020-09-30T18:15:00Z", + "LastModifiedDate": "2021-07-21T11:39:00Z" + } + ] + } + ] +} +` diff --git a/internal/services/formatters/generic/trivy/entities/vulnerability.go b/internal/services/formatters/generic/trivy/output.go similarity index 65% rename from internal/services/formatters/generic/trivy/entities/vulnerability.go rename to internal/services/formatters/generic/trivy/output.go index d04b212af..8e1b637cb 100644 --- a/internal/services/formatters/generic/trivy/entities/vulnerability.go +++ b/internal/services/formatters/generic/trivy/output.go @@ -12,14 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -package entities +package trivy import ( "fmt" "strings" ) -type Vulnerability struct { +type trivyOutput struct { + ArtifactName string `json:"artifactName"` + ArtifactType string `json:"artifactType"` + Results []*trivyOutputResult `json:"results"` +} + +type trivyOutputResult struct { + Target string `json:"target"` + Class string `json:"class"` + Type string `json:"type"` + Vulnerabilities []*trivyVulnerability `json:"vulnerabilities"` + Misconfigurations []*trivyMisconfiguration `json:"misconfigurations"` +} + +type trivyMisconfiguration struct { + Title string `json:"title"` + Description string `json:"description"` + Message string `json:"message"` + Resolution string `json:"resolution"` + References []string `json:"references"` + Severity string `json:"severity"` +} + +type trivyVulnerability struct { VulnerabilityID string `json:"vulnerabilityID"` PkgName string `json:"pkgName"` InstalledVersion string `json:"installedVersion"` @@ -32,7 +55,7 @@ type Vulnerability struct { CweIDs []string `json:"cweIDs"` } -func (v *Vulnerability) GetDetails() string { +func (v *trivyVulnerability) getDetails() string { details := v.getBaseDetailsWithoutCWEs() if len(v.CweIDs) > 0 { @@ -42,7 +65,7 @@ func (v *Vulnerability) GetDetails() string { return strings.TrimRight(details, "\n") } -func (v *Vulnerability) getBaseDetailsWithoutCWEs() (details string) { +func (v *trivyVulnerability) getBaseDetailsWithoutCWEs() (details string) { if v.Description != "" { details += v.Description + "\n" } @@ -60,7 +83,7 @@ func (v *Vulnerability) getBaseDetailsWithoutCWEs() (details string) { } // nolint:gomnd // magic number "2" is not necessary to check -func (v *Vulnerability) getDetailsWithCWEs(details string) string { +func (v *trivyVulnerability) getDetailsWithCWEs(details string) string { details += "Cwe Links: " for _, ID := range v.CweIDs { @@ -73,7 +96,7 @@ func (v *Vulnerability) getDetailsWithCWEs(details string) string { return strings.TrimRight(details, ",") } -func (v *Vulnerability) addCWELinkInDetails(details, cweID string) string { +func (v *trivyVulnerability) addCWELinkInDetails(details, cweID string) string { basePath := "https://cwe.mitre.org/data/definitions/" cweLink := basePath + cweID + ".html"