diff --git a/deployments/dockerfiles/semgrep/.semver.yaml b/deployments/dockerfiles/semgrep/.semver.yaml new file mode 100644 index 000000000..36a2accc3 --- /dev/null +++ b/deployments/dockerfiles/semgrep/.semver.yaml @@ -0,0 +1,4 @@ +alpha: 0 +beta: 0 +rc: 0 +release: v1.0.0 diff --git a/deployments/dockerfiles/semgrep/Dockerfile b/deployments/dockerfiles/semgrep/Dockerfile new file mode 100644 index 000000000..3a8a85b4d --- /dev/null +++ b/deployments/dockerfiles/semgrep/Dockerfile @@ -0,0 +1,18 @@ +# Copyright 2020 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. + +FROM python:3.6-alpine + +RUN apk add --no-cache git bash +RUN python3 -m pip install semgrep diff --git a/development-kit/pkg/entities/analyser/general/semgrep/analysis.go b/development-kit/pkg/entities/analyser/general/semgrep/analysis.go new file mode 100644 index 000000000..132f0f373 --- /dev/null +++ b/development-kit/pkg/entities/analyser/general/semgrep/analysis.go @@ -0,0 +1,20 @@ +// Copyright 2020 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 semgrep + +type Analysis struct { + Results []Result `json:"results"` + Errors []string `json:"errors"` +} diff --git a/development-kit/pkg/entities/analyser/general/semgrep/extra.go b/development-kit/pkg/entities/analyser/general/semgrep/extra.go new file mode 100644 index 000000000..e1df45109 --- /dev/null +++ b/development-kit/pkg/entities/analyser/general/semgrep/extra.go @@ -0,0 +1,21 @@ +// Copyright 2020 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 semgrep + +type Extra struct { + Message string `json:"message"` + Severity string `json:"severity"` + Code string `json:"lines"` +} diff --git a/development-kit/pkg/entities/analyser/general/semgrep/position.go b/development-kit/pkg/entities/analyser/general/semgrep/position.go new file mode 100644 index 000000000..1d02ae1f7 --- /dev/null +++ b/development-kit/pkg/entities/analyser/general/semgrep/position.go @@ -0,0 +1,20 @@ +// Copyright 2020 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 semgrep + +type Position struct { + Line int `json:"line"` + Col int `json:"col"` +} diff --git a/development-kit/pkg/entities/analyser/general/semgrep/result.go b/development-kit/pkg/entities/analyser/general/semgrep/result.go new file mode 100644 index 000000000..2a8544fea --- /dev/null +++ b/development-kit/pkg/entities/analyser/general/semgrep/result.go @@ -0,0 +1,23 @@ +// Copyright 2020 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 semgrep + +type Result struct { + CheckID string `json:"check_id"` + Path string `json:"path"` + Start Position `json:"start"` + End Position `json:"end"` + Extra Extra `json:"extra"` +} diff --git a/development-kit/pkg/enums/languages/languages.go b/development-kit/pkg/enums/languages/languages.go index edc7820b7..f3fa98514 100644 --- a/development-kit/pkg/enums/languages/languages.go +++ b/development-kit/pkg/enums/languages/languages.go @@ -26,8 +26,13 @@ const ( Java Language = "Java" Kotlin Language = "Kotlin" Javascript Language = "JavaScript" + TypeScript Language = "TypeScript" Leaks Language = "Leaks" HCL Language = "HCL" + C Language = "C" + PHP Language = "PHP" + HTML Language = "HTML" + Generic Language = "Generic" Unknown Language = "Unknown" ) @@ -51,6 +56,7 @@ func SupportedLanguages() []Language { Javascript, Leaks, HCL, + Generic, Unknown, } } @@ -66,6 +72,7 @@ func (l Language) MapEnableLanguages() map[string]Language { Kotlin.ToString(): Kotlin, Javascript.ToString(): Javascript, HCL.ToString(): HCL, + Generic.ToString(): Generic, } } diff --git a/development-kit/pkg/enums/languages/languages_test.go b/development-kit/pkg/enums/languages/languages_test.go index a8ef9b3c1..e1ac9599d 100644 --- a/development-kit/pkg/enums/languages/languages_test.go +++ b/development-kit/pkg/enums/languages/languages_test.go @@ -27,7 +27,7 @@ func TestToString(t *testing.T) { func TestMapEnableLanguages(t *testing.T) { t.Run("should map enable languages", func(t *testing.T) { - assert.Len(t, DotNet.MapEnableLanguages(), 9) + assert.Len(t, DotNet.MapEnableLanguages(), 10) }) } @@ -43,6 +43,6 @@ func TestParseStringToLanguage(t *testing.T) { func TestSupportedLanguages(t *testing.T) { t.Run("should return supported languages", func(t *testing.T) { - assert.Len(t, SupportedLanguages(), 10) + assert.Len(t, SupportedLanguages(), 11) }) } diff --git a/development-kit/pkg/enums/tools/tools.go b/development-kit/pkg/enums/tools/tools.go index 25c4a7278..d56c49a2c 100644 --- a/development-kit/pkg/enums/tools/tools.go +++ b/development-kit/pkg/enums/tools/tools.go @@ -30,6 +30,7 @@ const ( HorusecLeaks Tool = "HorusecLeaks" GitLeaks Tool = "GitLeaks" TfSec Tool = "TfSec" + Semgrep Tool = "Semgrep" ) func (t Tool) ToString() string { diff --git a/development-kit/pkg/usecases/analysis/analysis.go b/development-kit/pkg/usecases/analysis/analysis.go index 6020c74f9..faaf7e143 100644 --- a/development-kit/pkg/usecases/analysis/analysis.go +++ b/development-kit/pkg/usecases/analysis/analysis.go @@ -136,6 +136,7 @@ func (au *UseCases) setupValidationVulnerabilities(vulnerability *horusecEntitie ) } +// nolint func (au *UseCases) sliceTools() []interface{} { return []interface{}{ tools.GoSec, @@ -151,8 +152,11 @@ func (au *UseCases) sliceTools() []interface{} { tools.HorusecJava, tools.HorusecKotlin, tools.HorusecLeaks, + tools.Semgrep, } } + +// nolint func (au *UseCases) sliceLanguages() []interface{} { return []interface{}{ languages.Go, @@ -164,6 +168,12 @@ func (au *UseCases) sliceLanguages() []interface{} { languages.Javascript, languages.Leaks, languages.HCL, + languages.PHP, + languages.TypeScript, + languages.C, + languages.HTML, + languages.Generic, + languages.Unknown, } } func (au *UseCases) sliceSeverities() []interface{} { diff --git a/horusec-cli/README.md b/horusec-cli/README.md index 7aaf4d77c..6f3e34161 100644 --- a/horusec-cli/README.md +++ b/horusec-cli/README.md @@ -175,6 +175,7 @@ The configuration file receive an object with the content follow: "kotlin": [], "javaScript": [], "leaks": [], + "generic": [], "hlc": [] } } @@ -300,6 +301,7 @@ The interface of languages accepts is: javaScript []string leaks []string hlc []string + generic []string } ``` diff --git a/horusec-cli/cmd/horusec/start/start_test.go b/horusec-cli/cmd/horusec/start/start_test.go index b773634bf..94347e5f1 100644 --- a/horusec-cli/cmd/horusec/start/start_test.go +++ b/horusec-cli/cmd/horusec/start/start_test.go @@ -443,7 +443,7 @@ func TestStartCommand_Execute(t *testing.T) { assert.Contains(t, output, "FOLDER BEFORE THE ANALYSIS FINISH! Don’t worry, we’ll remove it after the analysis ends automatically! Project sent to folder in location:") assert.Contains(t, output, "Hold on! Horusec still analysis your code. Timeout in: 600s") assert.Contains(t, output, "{HORUSEC_CLI} No authorization token was found, your code it is not going to be sent to horusec. Please enter a token with the -a flag to configure and save your analysis") - assert.Contains(t, output, "[HORUSEC] 6 VULNERABILITIES WERE FOUND IN YOUR CODE SENT TO HORUSEC, SEE MORE DETAILS IN DEBUG LEVEL AND TRY AGAIN") + assert.Contains(t, output, "[HORUSEC] 6 VULNERABILITIES WERE FOUND IN YOUR CODE SENT TO HORUSEC, TO SEE MORE DETAILS USE THE LOG LEVEL AS DEBUG AND TRY AGAIN") promptMock.AssertNotCalled(t, "Ask") assert.NoError(t, os.RemoveAll(dstZip)) }) diff --git a/horusec-cli/config/config.go b/horusec-cli/config/config.go index d64f984fc..ba5782055 100644 --- a/horusec-cli/config/config.go +++ b/horusec-cli/config/config.go @@ -113,6 +113,7 @@ const ( // kotlin string // javaScript string // git string + // generic string // } // Validation: It is mandatory to be valid interface of workdir to proceed EnvWorkDirPath = "HORUSEC_CLI_WORK_DIR" diff --git a/horusec-cli/internal/controllers/analyser/analyser.go b/horusec-cli/internal/controllers/analyser/analyser.go index 500c6a5c1..a16d2ae71 100644 --- a/horusec-cli/internal/controllers/analyser/analyser.go +++ b/horusec-cli/internal/controllers/analyser/analyser.go @@ -16,7 +16,6 @@ package analyser import ( "fmt" - "github.com/google/uuid" "log" "os" "os/signal" @@ -25,6 +24,8 @@ import ( "strings" "time" + "github.com/google/uuid" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/java/horusecjava" "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/kotlin/horuseckotlin" @@ -41,6 +42,7 @@ import ( dockerClient "github.com/ZupIT/horusec/horusec-cli/internal/services/docker/client" "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters" "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/dotnet/scs" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/generic/semgrep" "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/golang/gosec" "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/hcl" "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters/javascript/npmaudit" @@ -180,6 +182,7 @@ func (a *Analyser) mapDetectVulnerabilityByLanguage() map[languages.Language]fun languages.Python: a.detectVulnerabilityPython, languages.Ruby: a.detectVulnerabilityRuby, languages.HCL: a.detectVulnerabilityHCL, + languages.Generic: a.detectVulnerabilityGeneric, } } @@ -236,6 +239,11 @@ func (a *Analyser) detectVulnerabilityHCL(projectSubPath string) { go hcl.NewFormatter(a.formatterService).StartAnalysis(projectSubPath) } +func (a *Analyser) detectVulnerabilityGeneric(projectSubPath string) { + a.monitor.AddProcess(1) + go semgrep.NewFormatter(a.formatterService).StartAnalysis(projectSubPath) +} + func (a *Analyser) shouldAnalysePath(projectSubPath string) bool { pathToFilter := a.config.GetFilterPath() if pathToFilter == "" { diff --git a/horusec-cli/internal/controllers/language_detect/language_detect.go b/horusec-cli/internal/controllers/language_detect/language_detect.go index 38653c76f..5a5fbd3fd 100644 --- a/horusec-cli/internal/controllers/language_detect/language_detect.go +++ b/horusec-cli/internal/controllers/language_detect/language_detect.go @@ -30,7 +30,7 @@ import ( "github.com/ZupIT/horusec/development-kit/pkg/utils/logger" "github.com/ZupIT/horusec/horusec-cli/config" "github.com/ZupIT/horusec/horusec-cli/internal/helpers/messages" - doublestar "github.com/bmatcuk/doublestar/v2" + "github.com/bmatcuk/doublestar/v2" "github.com/google/uuid" ) @@ -51,7 +51,7 @@ func NewLanguageDetect(configs *config.Config, analysisID uuid.UUID) Interface { } func (ld *LanguageDetect) LanguageDetect(directory string) ([]languages.Language, error) { - langs := []string{languages.Leaks.ToString()} + langs := []string{languages.Leaks.ToString(), languages.Generic.ToString()} languagesFound, err := ld.getLanguages(directory) if err != nil { logger.LogErrorWithLevel(messages.MsgErrorDetectLanguage, err, logger.ErrorLevel) diff --git a/horusec-cli/internal/controllers/language_detect/language_detect_test.go b/horusec-cli/internal/controllers/language_detect/language_detect_test.go index 983f6d91a..d138484e0 100644 --- a/horusec-cli/internal/controllers/language_detect/language_detect_test.go +++ b/horusec-cli/internal/controllers/language_detect/language_detect_test.go @@ -81,7 +81,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.NoError(t, err) assert.Contains(t, langs, languages.Leaks) - assert.Len(t, langs, 1) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 2) }) t.Run("Should ignore additional folder setup in configs", func(t *testing.T) { @@ -97,7 +98,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Go) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) t.Run("Should ignore additional specific file name setup in configs", func(t *testing.T) { @@ -113,7 +115,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Go) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) t.Run("Should run language detect and return GO and GITLEAKS", func(t *testing.T) { configs := &config.Config{} @@ -128,7 +131,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Go) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) t.Run("Should run language detect and return GITLEAKS", func(t *testing.T) { @@ -143,6 +147,7 @@ func TestNewLanguageDetect(t *testing.T) { langs, _ := controller.LanguageDetect(getSourcePath(analysis.ID)) assert.Contains(t, langs, languages.Leaks) + assert.Contains(t, langs, languages.Generic) }) t.Run("Should run language detect and return JAVA and GITLEAKS", func(t *testing.T) { @@ -158,7 +163,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Java) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) t.Run("Should run language detect and return JAVASCRIPT and GITLEAKS", func(t *testing.T) { @@ -174,7 +180,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Javascript) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) t.Run("Should run language detect and return JAVASCRIPT and GITLEAKS", func(t *testing.T) { @@ -190,7 +197,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Javascript) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) //t.Run("Should run language detect and return KOTLIN and GITLEAKS", func(t *testing.T) { @@ -226,7 +234,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.DotNet) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) t.Run("Should run language detect and return PYTHON and GITLEAKS", func(t *testing.T) { @@ -242,7 +251,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Python) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) t.Run("Should run language detect and return PYTHON and GITLEAKS", func(t *testing.T) { @@ -258,7 +268,8 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Python) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) t.Run("Should run language detect and return RUBY and GITLEAKS", func(t *testing.T) { @@ -274,6 +285,7 @@ func TestNewLanguageDetect(t *testing.T) { assert.Contains(t, langs, languages.Leaks) assert.Contains(t, langs, languages.Ruby) - assert.Len(t, langs, 2) + assert.Contains(t, langs, languages.Generic) + assert.Len(t, langs, 3) }) } diff --git a/horusec-cli/internal/entities/workdir/workdir.go b/horusec-cli/internal/entities/workdir/workdir.go index 3bf333f81..0944de596 100644 --- a/horusec-cli/internal/entities/workdir/workdir.go +++ b/horusec-cli/internal/entities/workdir/workdir.go @@ -32,6 +32,7 @@ type WorkDir struct { JavaScript []string `json:"javaScript"` Leaks []string `json:"leaks"` HCL []string `json:"hcl"` + Generic []string `json:"generic"` } func (w *WorkDir) String() string { @@ -67,6 +68,7 @@ func (w *WorkDir) Map() map[languages.Language][]string { languages.Javascript: w.JavaScript, languages.Leaks: w.Leaks, languages.HCL: w.HCL, + languages.Generic: w.Generic, } } diff --git a/horusec-cli/internal/services/formatters/generic/semgrep/config.go b/horusec-cli/internal/services/formatters/generic/semgrep/config.go new file mode 100644 index 000000000..eefe3afee --- /dev/null +++ b/horusec-cli/internal/services/formatters/generic/semgrep/config.go @@ -0,0 +1,26 @@ +// Copyright 2020 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 semgrep + +const ( + ImageName = "horuszup/horusec-semgrep" + ImageTag = "v1.0.0" + // nolint + ImageCmd = ` + {{WORK_DIR}} + semgrep --config=p/r2c-ci -q --json . + chmod -R 777 . + ` +) diff --git a/horusec-cli/internal/services/formatters/generic/semgrep/formatter.go b/horusec-cli/internal/services/formatters/generic/semgrep/formatter.go new file mode 100644 index 000000000..15ce0752e --- /dev/null +++ b/horusec-cli/internal/services/formatters/generic/semgrep/formatter.go @@ -0,0 +1,184 @@ +// Copyright 2020 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 semgrep + +import ( + "encoding/json" + "github.com/ZupIT/horusec/development-kit/pkg/entities/analyser/general/semgrep" + "github.com/ZupIT/horusec/development-kit/pkg/entities/horusec" + "github.com/ZupIT/horusec/development-kit/pkg/enums/languages" + "github.com/ZupIT/horusec/development-kit/pkg/enums/severity" + "github.com/ZupIT/horusec/development-kit/pkg/enums/tools" + vulnhash "github.com/ZupIT/horusec/development-kit/pkg/utils/vuln_hash" + dockerEntities "github.com/ZupIT/horusec/horusec-cli/internal/entities/docker" + "github.com/ZupIT/horusec/horusec-cli/internal/helpers/messages" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters" + "path/filepath" + "strconv" +) + +type Formatter struct { + formatters.IService +} + +func NewFormatter(service formatters.IService) formatters.IFormatter { + return &Formatter{ + service, + } +} + +func (f *Formatter) StartAnalysis(projectSubPath string) { + err := f.startSecurityCodeScanAnalysis(projectSubPath) + f.SetLanguageIsFinished() + f.LogAnalysisError(err, tools.SecurityCodeScan, projectSubPath) +} + +func (f *Formatter) startSecurityCodeScanAnalysis(projectSubPath string) error { + f.LogDebugWithReplace(messages.MsgDebugToolStartAnalysis, tools.Semgrep) + + output, err := f.ExecuteContainer(f.getConfigData(projectSubPath)) + if err != nil { + f.SetAnalysisError(err) + return err + } + + err = f.parseOutput(output) + if err != nil { + f.SetAnalysisError(err) + } + f.LogDebugWithReplace(messages.MsgDebugToolFinishAnalysis, tools.Semgrep) + return err +} + +func (f *Formatter) getConfigData(projectSubPath string) *dockerEntities.AnalysisData { + return &dockerEntities.AnalysisData{ + Image: ImageName, + Tag: ImageTag, + CMD: f.AddWorkDirInCmd(ImageCmd, projectSubPath, tools.Semgrep), + Language: languages.Generic, + } +} + +func (f *Formatter) parseOutput(output string) error { + var analysis *semgrep.Analysis + + err := json.Unmarshal([]byte(output), &analysis) + if err != nil { + return err + } + + for _, result := range analysis.Results { + item := result + f.setAnalysisResults(f.setVulnerabilityData(&item)) + } + + return nil +} + +func (f *Formatter) setVulnerabilityData(result *semgrep.Result) *horusec.Vulnerability { + data := f.getDefaultVulnerabilityData() + data.Details = result.Extra.Message + data.Severity = f.getSeverity(result.Extra.Severity) + data.Line = strconv.Itoa(result.Start.Line) + data.Column = strconv.Itoa(result.Start.Col) + data.File = result.Path + data.Code = f.GetCodeWithMaxCharacters(result.Extra.Code, 0) + data.Language = f.getLanguageByFile(result.Path) + + data = vulnhash.Bind(data) + + return f.setCommitAuthor(data) +} + +func (f *Formatter) setCommitAuthor(vulnerability *horusec.Vulnerability) *horusec.Vulnerability { + commitAuthor := f.GetCommitAuthor(vulnerability.Line, vulnerability.File) + + vulnerability.CommitAuthor = commitAuthor.Author + vulnerability.CommitHash = commitAuthor.CommitHash + vulnerability.CommitDate = commitAuthor.Date + vulnerability.CommitEmail = commitAuthor.Email + vulnerability.CommitMessage = commitAuthor.Message + + return vulnerability +} + +func (f *Formatter) getDefaultVulnerabilityData() *horusec.Vulnerability { + vulnerabilitySeverity := &horusec.Vulnerability{} + vulnerabilitySeverity.SecurityTool = tools.Semgrep + return vulnerabilitySeverity +} + +func (f *Formatter) getLanguageByFile(file string) languages.Language { + languagesMap := f.getLanguagesMap() + return languagesMap[f.getExtension(file)] +} + +func (f *Formatter) getExtension(file string) string { + ext := filepath.Ext(file) + for _, item := range f.getExtensionList() { + if item == ext { + return ext + } + } + + return "" +} + +func (f *Formatter) getLanguagesMap() map[string]languages.Language { + return map[string]languages.Language{ + ".go": languages.Go, + ".java": languages.Java, + ".js": languages.Javascript, + ".tsx": languages.TypeScript, + ".ts": languages.TypeScript, + ".py": languages.Python, + ".rb": languages.Ruby, + ".c": languages.C, + ".html": languages.HTML, + "": languages.Unknown, + } +} + +func (f *Formatter) getExtensionList() []string { + return []string{ + ".go", + ".java", + ".js", + ".tsx", + ".ts", + ".py", + ".rb", + ".c", + ".html", + } +} + +func (f *Formatter) setAnalysisResults(vulnerability *horusec.Vulnerability) { + f.GetAnalysis().AnalysisVulnerabilities = append(f.GetAnalysis().AnalysisVulnerabilities, + horusec.AnalysisVulnerabilities{ + Vulnerability: *vulnerability, + }) +} + +func (f *Formatter) getSeverity(resultSeverity string) severity.Severity { + switch resultSeverity { + case "ERROR": + return severity.High + case "WARNING": + return severity.Medium + } + + return severity.Low +} diff --git a/horusec-cli/internal/services/formatters/generic/semgrep/formatter_test.go b/horusec-cli/internal/services/formatters/generic/semgrep/formatter_test.go new file mode 100644 index 000000000..4e2668ae0 --- /dev/null +++ b/horusec-cli/internal/services/formatters/generic/semgrep/formatter_test.go @@ -0,0 +1,136 @@ +// Copyright 2020 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 semgrep + +import ( + "errors" + "github.com/ZupIT/horusec/development-kit/pkg/entities/horusec" + cliConfig "github.com/ZupIT/horusec/horusec-cli/config" + "github.com/ZupIT/horusec/horusec-cli/internal/entities/workdir" + "github.com/ZupIT/horusec/horusec-cli/internal/services/docker" + "github.com/ZupIT/horusec/horusec-cli/internal/services/formatters" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestParseOutput(t *testing.T) { + t.Run("Should return 1 vulnerabilities with no errors", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + dockerAPIControllerMock.On("SetAnalysisID") + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + output := "{ \"results\":[ { \"check_id\":\"python.lang.correctness.useless-comparison.no-strings-as-booleans\"," + + " \"path\":\"bad/vulpy.py\", \"start\":{ \"line\":36, \"col\":1 }, \"end\":{ \"line\":37, \"col\":23 }, " + + "\"extra\":{ \"message\":\"Using strings as booleans in Python has unexpected results.\\n`\\\"one\\\" and " + + "\\\"two\\\"` will return \\\"two\\\".\\n`\\\"one\\\" or \\\"two\\\"` will return \\\"one\\\".\\n In Python" + + ", strings are truthy, evaluating to True.\\n\", \"metavars\":{ }, \"metadata\":{ }, \"severity\":\"ERROR\"" + + ", \"lines\":\"if csp:\\n print('CSP:', csp)\" } } ] }" + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + assert.Len(t, analysis.AnalysisVulnerabilities, 1) + }) + + t.Run("Should return 1 vulnerabilities with no errors", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + dockerAPIControllerMock.On("SetAnalysisID") + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + output := "{ \"results\":[ { \"check_id\":\"python.lang.correctness.useless-comparison.no-strings-as-booleans\"," + + " \"path\":\"bad/vulpy.py\", \"start\":{ \"line\":36, \"col\":1 }, \"end\":{ \"line\":37, \"col\":23 }, " + + "\"extra\":{ \"message\":\"Using strings as booleans in Python has unexpected results.\\n`\\\"one\\\" and " + + "\\\"two\\\"` will return \\\"two\\\".\\n`\\\"one\\\" or \\\"two\\\"` will return \\\"one\\\".\\n In Python" + + ", strings are truthy, evaluating to True.\\n\", \"metavars\":{ }, \"metadata\":{ }, \"severity\":\"WARNING\"" + + ", \"lines\":\"if csp:\\n print('CSP:', csp)\" } } ] }" + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + assert.Len(t, analysis.AnalysisVulnerabilities, 1) + }) + + t.Run("Should return 1 vulnerabilities with no errors", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + dockerAPIControllerMock.On("SetAnalysisID") + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + output := "{ \"results\":[ { \"check_id\":\"python.lang.correctness.useless-comparison.no-strings-as-booleans\"," + + " \"path\":\"bad\", \"start\":{ \"line\":36, \"col\":1 }, \"end\":{ \"line\":37, \"col\":23 }, " + + "\"extra\":{ \"message\":\"Using strings as booleans in Python has unexpected results.\\n`\\\"one\\\" and " + + "\\\"two\\\"` will return \\\"two\\\".\\n`\\\"one\\\" or \\\"two\\\"` will return \\\"one\\\".\\n In Python" + + ", strings are truthy, evaluating to True.\\n\", \"metavars\":{ }, \"metadata\":{ }, \"severity\":\"test\"" + + ", \"lines\":\"if csp:\\n print('CSP:', csp)\" } } ] }" + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + assert.Len(t, analysis.AnalysisVulnerabilities, 1) + }) + + t.Run("Should return error when invalid output", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + dockerAPIControllerMock.On("SetAnalysisID") + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + output := "!!" + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + assert.NotEmpty(t, analysis.Errors) + }) + + t.Run("Should return error when executing container", func(t *testing.T) { + dockerAPIControllerMock := &docker.Mock{} + dockerAPIControllerMock.On("SetAnalysisID") + analysis := &horusec.Analysis{} + config := &cliConfig.Config{ + WorkDir: &workdir.WorkDir{}, + } + + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("", errors.New("test")) + + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config, &horusec.Monitor{}) + formatter := NewFormatter(service) + + formatter.StartAnalysis("") + assert.NotEmpty(t, analysis.Errors) + }) +}