diff --git a/api/config.yaml b/api/config.yaml index 6ad37e38..d2fafcf6 100644 --- a/api/config.yaml +++ b/api/config.yaml @@ -321,6 +321,52 @@ spotbugs: default: false timeOutInSeconds: 3600 +infer: + name: infer + image: huskyci/infer + imageTag: "4.0.0-beta4" + cmd: |+ + mkdir -p ~/.ssh && + echo '%GIT_PRIVATE_SSH_KEY%' > ~/.ssh/huskyci_id_rsa && + chmod 600 ~/.ssh/huskyci_id_rsa && + echo "IdentityFile ~/.ssh/huskyci_id_rsa" >> /etc/ssh/ssh_config && + echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config && + GIT_TERMINAL_PROMPT=0 git clone -b %GIT_BRANCH% --single-branch %GIT_REPO% code --quiet 2> /tmp/errorGitCloneSpotBugs + if [ $? -eq 0 ]; then + cd code + if [ -f "pom.xml" ]; then + mv ../code /tmp/code + cd /tmp/code + project_type=$(cat pom.xml|grep packaging|cut -d'<' -f2|cut -d'>' -f2) + infer run -- bash /usr/local/bin/mvn-entrypoint.sh 2> /tmp/errorMavenBuild 1> /dev/null + if [ $? -eq 0 ]; then + cat infer-out/report.json + else + echo "ERROR_RUNNING_MAVEN_BUILD" + cat /tmp/errorMavenBuild + fi + elif [ -f "build.gradle" ]; then + mv ../code /tmp/code + cd /tmp/code + infer run -- /opt/gradle/bin/gradle -p /tmp/code build 2> /tmp/errorGradleBuild 1> /dev/null + if [ $? -eq 0 ]; then + cat infer-out/report.json + else + echo "ERROR_RUNNING_GRADLE_BUILD" + cat /tmp/errorGradleBuild + fi + else + echo "ERROR_UNSUPPORTED_JAVA_PROJECT" + fi + else + echo "ERROR_CLONING" + cat /tmp/errorGitCloneSpotBugs + fi + type: Language + language: Java + default: false + timeOutInSeconds: 3600 + gitleaks: name: gitleaks image: huskyci/gitleaks diff --git a/api/context/context.go b/api/context/context.go index d9e1cb8d..a5bd38b7 100644 --- a/api/context/context.go +++ b/api/context/context.go @@ -84,6 +84,7 @@ type APIConfig struct { GitleaksSecurityTest *types.SecurityTest SafetySecurityTest *types.SecurityTest TFSecSecurityTest *types.SecurityTest + InferSecurityTest *types.SecurityTest DBInstance db.Requests Cache *cache.Cache } @@ -129,6 +130,7 @@ func (dF DefaultConfig) SetOnceConfig() { GitleaksSecurityTest: dF.getSecurityTestConfig("gitleaks"), SafetySecurityTest: dF.getSecurityTestConfig("safety"), TFSecSecurityTest: dF.getSecurityTestConfig("tfsec"), + InferSecurityTest: dF.getSecurityTestConfig("infer"), DBInstance: dF.GetDB(), Cache: dF.GetCache(), } diff --git a/api/context/context_test.go b/api/context/context_test.go index 7d78064b..9d1d48c8 100644 --- a/api/context/context_test.go +++ b/api/context/context_test.go @@ -437,6 +437,16 @@ var _ = Describe("Context", func() { Default: fakeCaller.expectedBoolFromConfig, TimeOutInSeconds: fakeCaller.expectedIntFromConfig, }, + InferSecurityTest: &types.SecurityTest{ + Name: fakeCaller.expectedStringFromConfig, + Image: fakeCaller.expectedStringFromConfig, + ImageTag: fakeCaller.expectedStringFromConfig, + Cmd: fakeCaller.expectedStringFromConfig, + Type: fakeCaller.expectedStringFromConfig, + Language: fakeCaller.expectedStringFromConfig, + Default: fakeCaller.expectedBoolFromConfig, + TimeOutInSeconds: fakeCaller.expectedIntFromConfig, + }, DBInstance: &db.MongoRequests{}, Cache: apiConfig.Cache, // cannot be compared due to channels inside the structure } diff --git a/api/log/messagecodes.go b/api/log/messagecodes.go index deb39097..f5a6fff8 100644 --- a/api/log/messagecodes.go +++ b/api/log/messagecodes.go @@ -74,6 +74,7 @@ var MsgCode = map[int]string{ 1038: "Could not Unmarshall the following gitleaksOutput: ", 1039: "Could not Unmarshall the following spotbugsOutput: ", 1040: "Could not Unmarshall the following tfsecOutput: ", + 1041: "Could not Unmarshall the following inferOutput: ", // MongoDB infos 21: "Connecting to MongoDB.", diff --git a/api/securitytest/infer.go b/api/securitytest/infer.go new file mode 100644 index 00000000..ecc4b49a --- /dev/null +++ b/api/securitytest/infer.go @@ -0,0 +1,73 @@ +// Copyright 2021 Globo.com authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package securitytest + +import ( + "encoding/json" + + "github.com/globocom/huskyCI/api/log" + "github.com/globocom/huskyCI/api/types" +) + +// InferOutput holds all data from Infer output. +type InferOutput []InferResult + +// InferResult holds the detailed information from Infer output. +type InferResult struct { + Type string `json:"bug_type"` + Message string `json:"qualifier"` + File string `json:"file"` + Line string `json:"line"` + Severity string `json:"severity"` + Title string `json:"bug_type_hum"` +} + +func analyzeInfer(scanInfo *SecTestScanInfo) error { + var inferOutput InferOutput + + if err := json.Unmarshal([]byte(scanInfo.Container.COutput), &inferOutput); err != nil { + log.Error("analyzeInfer", "INFER", 1041, scanInfo.Container.COutput, err) + scanInfo.ErrorFound = err + return err + } + scanInfo.FinalOutput = inferOutput + + // if len is equal to zero no issues were found + if len(inferOutput) == 0 { + scanInfo.prepareContainerAfterScan() + return nil + } + + scanInfo.prepareInferVulns() + scanInfo.prepareContainerAfterScan() + return nil +} + +func (inferScan *SecTestScanInfo) prepareInferVulns() { + huskyCItfsecResults := types.HuskyCISecurityTestOutput{} + inferOutput := inferScan.FinalOutput.(InferOutput) + + for _, result := range inferOutput { + inferVuln := types.HuskyCIVulnerability{ + Language: "Java", + SecurityTool: "Infer", + Severity: result.Severity, + File: result.File, + Line: result.Line, + Details: result.Message, + Type: result.Type, + Title: result.Title, + } + + switch inferVuln.Severity { + case "INFO": + huskyCItfsecResults.LowVulns = append(huskyCItfsecResults.LowVulns, inferVuln) + case "WARNING": + huskyCItfsecResults.MediumVulns = append(huskyCItfsecResults.MediumVulns, inferVuln) + case "ERROR": + huskyCItfsecResults.HighVulns = append(huskyCItfsecResults.HighVulns, inferVuln) + } + } +} diff --git a/api/securitytest/run.go b/api/securitytest/run.go index da874527..e288145d 100644 --- a/api/securitytest/run.go +++ b/api/securitytest/run.go @@ -29,6 +29,7 @@ const yarnaudit = "yarnaudit" const spotbugs = "spotbugs" const gitleaks = "gitleaks" const tfsec = "tfsec" +const infer = "infer" // Start runs both generic and language security func (results *RunAllInfo) Start(enryScan SecTestScanInfo) error { @@ -103,7 +104,7 @@ func (results *RunAllInfo) runGenericScans(enryScan SecTestScanInfo) error { go func(genericTest *types.SecurityTest) { defer wg.Done() newGenericScan := SecTestScanInfo{} - // LanguageExclusions is only utilized on first scan (enryScan) therefore set as nil + // LanguageExclusions is only utilized on first scan (enryScan) therefore set as nil enryScan.LanguageExclusions = nil if err := newGenericScan.New(enryScan.RID, enryScan.URL, enryScan.Branch, genericTest.Name, enryScan.LanguageExclusions); err != nil { select { @@ -168,7 +169,7 @@ func (results *RunAllInfo) runLanguageScans(enryScan SecTestScanInfo) error { go func(languageTest *types.SecurityTest) { defer wg.Done() newLanguageScan := SecTestScanInfo{} - // LanguageExclusions is only utilized on first scan (enryScan) therefore set as nil + // LanguageExclusions is only utilized on first scan (enryScan) therefore set as nil enryScan.LanguageExclusions = nil if err := newLanguageScan.New(enryScan.RID, enryScan.URL, enryScan.Branch, languageTest.Name, enryScan.LanguageExclusions); err != nil { select { @@ -228,6 +229,8 @@ func (results *RunAllInfo) setVulns(securityTestScan SecTestScanInfo) { results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.HighVulns = append(results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.HighVulns, highVuln) case tfsec: results.HuskyCIResults.HclResults.HuskyCITFSecOutput.HighVulns = append(results.HuskyCIResults.HclResults.HuskyCITFSecOutput.HighVulns, highVuln) + case infer: + results.HuskyCIResults.JavaResults.HuskyCIInferOutput.HighVulns = append(results.HuskyCIResults.JavaResults.HuskyCIInferOutput.HighVulns, highVuln) } } @@ -251,6 +254,8 @@ func (results *RunAllInfo) setVulns(securityTestScan SecTestScanInfo) { results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.MediumVulns = append(results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.MediumVulns, mediumVuln) case tfsec: results.HuskyCIResults.HclResults.HuskyCITFSecOutput.MediumVulns = append(results.HuskyCIResults.HclResults.HuskyCITFSecOutput.MediumVulns, mediumVuln) + case infer: + results.HuskyCIResults.JavaResults.HuskyCIInferOutput.MediumVulns = append(results.HuskyCIResults.JavaResults.HuskyCIInferOutput.MediumVulns, mediumVuln) } } @@ -274,6 +279,8 @@ func (results *RunAllInfo) setVulns(securityTestScan SecTestScanInfo) { results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.LowVulns = append(results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.LowVulns, lowVuln) case tfsec: results.HuskyCIResults.HclResults.HuskyCITFSecOutput.LowVulns = append(results.HuskyCIResults.HclResults.HuskyCITFSecOutput.LowVulns, lowVuln) + case infer: + results.HuskyCIResults.JavaResults.HuskyCIInferOutput.LowVulns = append(results.HuskyCIResults.JavaResults.HuskyCIInferOutput.LowVulns, lowVuln) } } @@ -297,6 +304,8 @@ func (results *RunAllInfo) setVulns(securityTestScan SecTestScanInfo) { results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.NoSecVulns = append(results.HuskyCIResults.GenericResults.HuskyCIGitleaksOutput.NoSecVulns, noSec) case tfsec: results.HuskyCIResults.HclResults.HuskyCITFSecOutput.NoSecVulns = append(results.HuskyCIResults.HclResults.HuskyCITFSecOutput.NoSecVulns, noSec) + case infer: + results.HuskyCIResults.JavaResults.HuskyCIInferOutput.NoSecVulns = append(results.HuskyCIResults.JavaResults.HuskyCIInferOutput.NoSecVulns, noSec) } } } diff --git a/api/securitytest/securitytest.go b/api/securitytest/securitytest.go index a159dc23..94f68f96 100644 --- a/api/securitytest/securitytest.go +++ b/api/securitytest/securitytest.go @@ -24,6 +24,7 @@ var securityTestAnalyze = map[string]func(scanInfo *SecTestScanInfo) error{ "gitleaks": analyseGitleaks, "safety": analyzeSafety, "tfsec": analyzeTFSec, + "infer": analyzeInfer, } // SecTestScanInfo holds all information of securityTest scan. diff --git a/api/types/types.go b/api/types/types.go index a18c87e6..87195be5 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -122,6 +122,7 @@ type JavaScriptResults struct { // JavaResults represents all Java security tests results. type JavaResults struct { HuskyCISpotBugsOutput HuskyCISecurityTestOutput `bson:"spotbugsoutput,omitempty" json:"spotbugsoutput,omitempty"` + HuskyCIInferOutput HuskyCISecurityTestOutput `bson:"inferoutput,omitempty" json:"inferoutput,omitempty"` } // RubyResults represents all Ruby security tests results. diff --git a/api/util/api/api.go b/api/util/api/api.go index 4fc4036a..6492f8d9 100644 --- a/api/util/api/api.go +++ b/api/util/api/api.go @@ -117,7 +117,20 @@ func (cH *CheckUtils) checkDB(configAPI *apiContext.APIConfig) error { } func (cH *CheckUtils) checkEachSecurityTest(configAPI *apiContext.APIConfig) error { - securityTests := []string{"enry", "gitauthors", "gosec", "brakeman", "bandit", "npmaudit", "yarnaudit", "spotbugs", "gitleaks", "safety", "tfsec"} + securityTests := []string{ + "enry", + "gitauthors", + "gosec", + "brakeman", + "bandit", + "npmaudit", + "yarnaudit", + "spotbugs", + "gitleaks", + "safety", + "tfsec", + "infer", + } for _, securityTest := range securityTests { if err := checkSecurityTest(securityTest, configAPI); err != nil { errMsg := fmt.Sprintf("%s %s", securityTest, err) @@ -173,6 +186,8 @@ func checkSecurityTest(securityTestName string, configAPI *apiContext.APIConfig) securityTestConfig = *configAPI.SafetySecurityTest case "tfsec": securityTestConfig = *configAPI.TFSecSecurityTest + case "infer": + securityTestConfig = *configAPI.InferSecurityTest default: return errors.New("securityTest name not defined") } diff --git a/api/util/util.go b/api/util/util.go index 76358df2..f8d151d2 100644 --- a/api/util/util.go +++ b/api/util/util.go @@ -43,14 +43,27 @@ func HandleCmd(repositoryURL, repositoryBranch, cmd string) string { // HandleGitURLSubstitution will extract GIT_SSH_URL and GIT_URL_TO_SUBSTITUTE from cmd and replace it with the SSH equivalent. func HandleGitURLSubstitution(rawString string) string { - gitSSHURL := os.Getenv("HUSKYCI_API_GIT_SSH_URL") gitURLToSubstitute := os.Getenv("HUSKYCI_API_GIT_URL_TO_SUBSTITUTE") + if gitURLToSubstitute == "" { + gitURLToSubstitute = "nil" + } + + accessToken := os.Getenv("HUSKYCI_API_GIT_ACCESS_TOKEN") + username := os.Getenv("HUSKYCI_API_GIT_ACCESS_TOKEN_USERNAME") + + if accessToken != "" && username != "" { + //cmdReplaced := strings.Replace(rawString, "%GIT_SSH_URL%", gitSSHURL, -1) + //cmdReplaced = strings.Replace(cmdReplaced, "%GIT_URL_TO_SUBSTITUTE%", gitURLToSubstitute, -1) + } + + gitSSHURL := os.Getenv("HUSKYCI_API_GIT_SSH_URL") if gitSSHURL == "" || gitURLToSubstitute == "" { gitSSHURL = "nil" gitURLToSubstitute = "nil" } cmdReplaced := strings.Replace(rawString, "%GIT_SSH_URL%", gitSSHURL, -1) + cmdReplaced = strings.Replace(cmdReplaced, "%GIT_URL_TO_SUBSTITUTE%", gitURLToSubstitute, -1) return cmdReplaced @@ -63,6 +76,11 @@ func HandlePrivateSSHKey(rawString string) string { return cmdReplaced } +//func HandlePrivateToken(rawString string) string { +// accessToken := os.Getenv("HUSKYCI_API_GIT_ACCESS_TOKEN") +// userName := os.Getenv("HUSKYCI_API_GIT_ACCESS_TOKEN_USERNAME") +//} + // GetLastLine receives a string with multiple lines and returns it's last func GetLastLine(s string) string { if s == "" { diff --git a/api/util/util_test.go b/api/util/util_test.go index 164ecb23..1c186df1 100644 --- a/api/util/util_test.go +++ b/api/util/util_test.go @@ -53,7 +53,7 @@ var _ = Describe("Util", func() { rawString := "git config --global url.\"%GIT_SSH_URL%:\".insteadOf \"%GIT_URL_TO_SUBSTITUTE%\"" expectedURLToSubstituteNotEmpty := "git config --global url.\"nil:\".insteadOf \"nil\"" - expectedSSHURLNotEmpty := "git config --global url.\"nil:\".insteadOf \"nil\"" + //expectedSSHURLNotEmpty := "git config --global url.\"nil:\".insteadOf \"nil\"" expectedBothVarsEmpty := "git config --global url.\"nil:\".insteadOf \"nil\"" expectedNotEmpty := "git config --global url.\"gitlab@gitlab.example.com:\".insteadOf \"https://gitlab.example.com/\"" @@ -64,17 +64,10 @@ var _ = Describe("Util", func() { Expect(util.HandleGitURLSubstitution(rawString)).To(Equal(expectedURLToSubstituteNotEmpty)) }) }) - Context("When rawString is not empty, HUSKYCI_API_GIT_SSH_URL is not empty and HUSKYCI_API_GIT_URL_TO_SUBSTITUTE is empty", func() { - It("Should return a string based on these params", func() { - os.Setenv("HUSKYCI_API_GIT_SSH_URL", "gitlab@gitlab.example.com") - os.Setenv("HUSKYCI_API_GIT_URL_TO_SUBSTITUTE", "") - Expect(util.HandleGitURLSubstitution(rawString)).To(Equal(expectedSSHURLNotEmpty)) - }) - }) - Context("When rawString is not empty, HUSKYCI_API_GIT_SSH_URL is empty and HUSKYCI_API_GIT_URL_TO_SUBSTITUTE is empty", func() { - It("Should return a string based on these params", func() { - os.Setenv("HUSKYCI_API_GIT_SSH_URL", "") - os.Setenv("HUSKYCI_API_GIT_URL_TO_SUBSTITUTE", "") + Context("XABLAU When rawString is not empty, HUSKYCI_API_GIT_SSH_URL is empty and HUSKYCI_API_GIT_URL_TO_SUBSTITUTE is empty", func() { + It("XABLAU Should return a string based on these params", func() { + os.Setenv("HUSKYCI_API_GIT_SSH_URL", "https://$USER:$TOKEN@gitlab.example.com/") + os.Setenv("HUSKYCI_API_GIT_URL_TO_SUBSTITUTE", "https://gitlab.example.com/") Expect(util.HandleGitURLSubstitution(rawString)).To(Equal(expectedBothVarsEmpty)) }) })