diff --git a/go.mod b/go.mod index 43f2c0981..b2fa2d6b7 100644 --- a/go.mod +++ b/go.mod @@ -130,7 +130,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -// replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security dev +// attiasas:services-sub-scan-source-code +replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-security v0.0.0-20260806060325-9a4c41b4285b // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev @@ -138,6 +139,7 @@ require ( // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go master +// orto17:missconfiguration-service +replace github.com/jfrog/jfrog-client-go => github.com/orto17/jfrog-client-go v0.0.0-20260803124813-bb92d09fecf2 // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go master diff --git a/go.sum b/go.sum index 06d717445..bd316c64e 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/attiasas/jfrog-cli-security v0.0.0-20260806060325-9a4c41b4285b h1:6c8YDC2VuDY8c90dZ2gCz9Vt9apJUz+mRqCgynJAKNE= +github.com/attiasas/jfrog-cli-security v0.0.0-20260806060325-9a4c41b4285b/go.mod h1:YhlMkzTmOlKl9tgm2l9ye22mt6S7eBv5DU4g3Tu5y1M= github.com/beevik/etree v1.7.0 h1:xjBk9O4p4x7D1YajePjfLzdaFC4/uYUENA7P0pv6gXA= github.com/beevik/etree v1.7.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= @@ -154,10 +156,6 @@ github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260803134309-cea6be60a1f2 h1:I github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260803134309-cea6be60a1f2/go.mod h1:1vxzqW7jHBSuTNqO2vxEnhbniwq4dj5wveDuCMJX7Yo= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260728123939-34b27f070f2e h1:K0IK3w5a5h6SIi9yoOJ6a7DL+kuFjs5acypOxKyT2OM= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260728123939-34b27f070f2e/go.mod h1:MygQx8pekgPCXyXnejIAVG9S4ImGcDFmcfRPUug/0d0= -github.com/jfrog/jfrog-cli-security v1.32.1 h1:GQ89waCbRZSL6fEpFwGWpIeiy2c8SNUTzccyec2QC1Y= -github.com/jfrog/jfrog-cli-security v1.32.1/go.mod h1:BKhIR0RkW0KMIdAeJbufG4rl44wZbpn+5bfE2jEN338= -github.com/jfrog/jfrog-client-go v1.55.1-0.20260803094922-a87c05639195 h1:1h6qSM8fanMsy9xT7zrRn/lrAMclzQzT5OJt4a9X4Qo= -github.com/jfrog/jfrog-client-go v1.55.1-0.20260803094922-a87c05639195/go.mod h1:FHpjN1nTDoj96xd6obe27EOgGErqzU0rQgC96L3Ch9E= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= @@ -215,6 +213,8 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/orto17/jfrog-client-go v0.0.0-20260803124813-bb92d09fecf2 h1:CVSdxDuYR3F7WXTXO4Pn1eSFRKE3X/bKeHhvGL+xhlY= +github.com/orto17/jfrog-client-go v0.0.0-20260803124813-bb92d09fecf2/go.mod h1:FHpjN1nTDoj96xd6obe27EOgGErqzU0rQgC96L3Ch9E= github.com/owenrumney/go-sarif/v3 v3.2.3 h1:n6mdX5ugKwCrZInvBsf6WumXmpAe3mbmQXgkXlIq34U= github.com/owenrumney/go-sarif/v3 v3.2.3/go.mod h1:1bV7t8SZg7pX41spaDkEUs8/yEjzk9JapztMoX1XNjg= github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= diff --git a/scanpullrequest/scanpullrequest.go b/scanpullrequest/scanpullrequest.go index 90a156fe1..af7c4c40c 100644 --- a/scanpullrequest/scanpullrequest.go +++ b/scanpullrequest/scanpullrequest.go @@ -195,6 +195,7 @@ func filterFailedResultsIfScannersFailuresAreAllowed(targetResults, sourceResult filterScaResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepContextualAnalysis) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepSecrets) + filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepServices) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepIaC) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepSast) } @@ -204,6 +205,7 @@ func filterFailedResultsIfScannersFailuresAreAllowed(targetResults, sourceResult filterScaResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepContextualAnalysis) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepSecrets) + filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepServices) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepIaC) filterJasResultsIfScanFailed(targetSourceResultsPair.target, targetSourceResultsPair.source, results.CmdStepSast) } @@ -213,6 +215,7 @@ func filterFailedResultsIfScannersFailuresAreAllowed(targetResults, sourceResult filterScaResultsIfScanFailed(nil, sourceResult) filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepContextualAnalysis) filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepSecrets) + filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepServices) filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepIaC) filterJasResultsIfScanFailed(nil, sourceResult, results.CmdStepSast) } @@ -256,6 +259,12 @@ func filterSpecificScannersViolationsIfScanFailed(sourceResults *results.Securit sourceResults.Violations.Secrets = nil } + if (sourceStatusCodes.ServicesScanStatusCode != nil && *sourceStatusCodes.ServicesScanStatusCode != 0) || + (targetStatusCodes.ServicesScanStatusCode != nil && *targetStatusCodes.ServicesScanStatusCode != 0) { + log.Debug(fmt.Sprintf(violationsFilteringErrorMessage, results.CmdStepServices)) + sourceResults.Violations.Services = nil + } + if (sourceStatusCodes.IacScanStatusCode != nil && *sourceStatusCodes.IacScanStatusCode != 0) || (targetStatusCodes.IacScanStatusCode != nil && *targetStatusCodes.IacScanStatusCode != 0) { log.Debug(fmt.Sprintf(violationsFilteringErrorMessage, results.CmdStepIaC)) @@ -354,6 +363,10 @@ func filterJasResultsIfScanFailed(targetResult, sourceResult *results.TargetResu if sourceResult.JasResults != nil { sourceResult.JasResults.JasVulnerabilities.SecretsScanResults = nil } + case results.CmdStepServices: + if sourceResult.JasResults != nil { + sourceResult.JasResults.JasVulnerabilities.ServicesScanResults = nil + } case results.CmdStepIaC: if sourceResult.JasResults != nil { sourceResult.JasResults.JasVulnerabilities.IacScanResults = nil @@ -445,6 +458,9 @@ func scanResultsToIssuesCollection(scanResults *results.SecurityCommandResults, IacVulnerabilities: simpleJsonResults.IacsVulnerabilities, IacViolations: simpleJsonResults.IacsViolations, + ServicesVulnerabilities: simpleJsonResults.ServicesVulnerabilities, + ServicesViolations: simpleJsonResults.ServicesViolations, + SecretsVulnerabilities: simpleJsonResults.SecretsVulnerabilities, SecretsViolations: simpleJsonResults.SecretsViolations, @@ -484,6 +500,7 @@ func getScanStatus(cmdResults ...formats.SimpleJsonResults) formats.ScanStatus { for _, sourceResults := range cmdResults[1:] { statuses.ScaStatusCode = getWorstScanStatus(statuses.ScaStatusCode, sourceResults.Statuses.ScaStatusCode) statuses.IacStatusCode = getWorstScanStatus(statuses.IacStatusCode, sourceResults.Statuses.IacStatusCode) + statuses.ServicesStatusCode = getWorstScanStatus(statuses.ServicesStatusCode, sourceResults.Statuses.ServicesStatusCode) statuses.SecretsStatusCode = getWorstScanStatus(statuses.SecretsStatusCode, sourceResults.Statuses.SecretsStatusCode) statuses.SastStatusCode = getWorstScanStatus(statuses.SastStatusCode, sourceResults.Statuses.SastStatusCode) statuses.ApplicabilityStatusCode = getWorstScanStatus(statuses.ApplicabilityStatusCode, sourceResults.Statuses.ApplicabilityStatusCode) diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index e79fbfbc4..faf5288dc 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -75,6 +75,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { ScaScanStatusCode: securityutils.NewIntPtr(0), ContextualAnalysisStatusCode: securityutils.NewIntPtr(0), IacScanStatusCode: securityutils.NewIntPtr(0), + ServicesScanStatusCode: securityutils.NewIntPtr(0), SecretsScanStatusCode: securityutils.NewIntPtr(0), SastScanStatusCode: securityutils.NewIntPtr(0), }, @@ -107,6 +108,13 @@ func TestScanResultsToIssuesCollection(t *testing.T) { ), ), }, + ServicesScanResults: []*sarif.Run{ + sarifutils.CreateRunWithDummyResults( + sarifutils.CreateResultWithLocations("Exposed service endpoint detected", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.Medium).String(), + sarifutils.CreateLocation("service.yaml", 1, 10, 2, 11, "services-snippet"), + ), + ), + }, SecretsScanResults: []*sarif.Run{ sarifutils.CreateRunWithDummyResults( sarifutils.CreateResultWithLocations("Secret", "rule", severityutils.SeverityToSarifSeverityLevel(severityutils.High).String(), @@ -166,6 +174,27 @@ func TestScanResultsToIssuesCollection(t *testing.T) { }, }, }, + ServicesVulnerabilities: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 26, + }, + ScannerInfo: formats.ScannerInfo{ + ScannerDescription: "rule-msg", + RuleId: "rule", + }, + Finding: "Exposed service endpoint detected", + Location: formats.Location{ + File: "service.yaml", + StartLine: 1, + StartColumn: 10, + EndLine: 2, + EndColumn: 11, + Snippet: "services-snippet", + }, + }, + }, SecretsVulnerabilities: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{ @@ -215,6 +244,7 @@ func TestScanResultsToIssuesCollection(t *testing.T) { if assert.NoError(t, err) { assert.ElementsMatch(t, expectedOutput.ScaVulnerabilities, issuesRows.ScaVulnerabilities) assert.ElementsMatch(t, expectedOutput.IacVulnerabilities, issuesRows.IacVulnerabilities) + assert.ElementsMatch(t, expectedOutput.ServicesVulnerabilities, issuesRows.ServicesVulnerabilities) assert.ElementsMatch(t, expectedOutput.SecretsVulnerabilities, issuesRows.SecretsVulnerabilities) assert.ElementsMatch(t, expectedOutput.SastVulnerabilities, issuesRows.SastVulnerabilities) assert.ElementsMatch(t, expectedOutput.LicensesViolations, issuesRows.LicensesViolations) @@ -584,6 +614,31 @@ func TestFilterJasResultsIfScanFailed(t *testing.T) { }, hasFailure: true, }, + { + name: "Services scanner failed - should remove Services vulnerabilities", + cmdStep: results.CmdStepServices, + targetResult: &results.TargetResults{ + JasResults: &results.JasScansResults{ + JasVulnerabilities: results.JasScanResults{ + ServicesScanResults: []*sarif.Run{}, + }, + }, + ResultsStatus: results.ResultsStatus{ + ServicesScanStatusCode: intPtr(0), + }, + }, + sourceResult: &results.TargetResults{ + JasResults: &results.JasScansResults{ + JasVulnerabilities: results.JasScanResults{ + ServicesScanResults: []*sarif.Run{}, + }, + }, + ResultsStatus: results.ResultsStatus{ + ServicesScanStatusCode: intPtr(1), + }, + }, + hasFailure: true, + }, { name: "SAST scanner failed - should remove SAST vulnerabilities", cmdStep: results.CmdStepSast, @@ -699,6 +754,8 @@ func TestFilterJasResultsIfScanFailed(t *testing.T) { assert.Nil(t, test.sourceResult.JasResults.JasVulnerabilities.SecretsScanResults, "Secrets vulnerability scan results should be removed when scan failed") case results.CmdStepIaC: assert.Nil(t, test.sourceResult.JasResults.JasVulnerabilities.IacScanResults, "IaC vulnerability scan results should be removed when scan failed") + case results.CmdStepServices: + assert.Nil(t, test.sourceResult.JasResults.JasVulnerabilities.ServicesScanResults, "Services vulnerability scan results should be removed when scan failed") case results.CmdStepSast: assert.Nil(t, test.sourceResult.JasResults.JasVulnerabilities.SastScanResults, "SAST vulnerability scan results should be removed when scan failed") } @@ -1138,6 +1195,7 @@ func TestFilterViolationsResults(t *testing.T) { shouldRemoveSca bool shouldRemoveSecrets bool shouldRemoveIac bool + shouldRemoveServices bool shouldRemoveSast bool shouldRemoveAllViolations bool }{ @@ -1171,6 +1229,20 @@ func TestFilterViolationsResults(t *testing.T) { targetResults: createSecurityCommandResultsForTest("test-target", "", false, false, false, false, false, false, 0, 0, 0, 1, 0, 0), shouldRemoveIac: true, }, + { + name: "Violations scan succeeded, Services scan failed in source - should remove only Services violations", + sourceResults: func() *results.SecurityCommandResults { + result := createSecurityCommandResultsForTest("test-target", "", false, false, false, false, false, true, 0, 0, 0, 0, 0, 0) + result.Targets[0].ResultsStatus.ServicesScanStatusCode = intPtr(1) + return result + }(), + targetResults: func() *results.SecurityCommandResults { + result := createSecurityCommandResultsForTest("test-target", "", false, false, false, false, false, false, 0, 0, 0, 0, 0, 0) + result.Targets[0].ResultsStatus.ServicesScanStatusCode = intPtr(0) + return result + }(), + shouldRemoveServices: true, + }, { name: "Violations scan succeeded, SAST scan failed in source - should remove only SAST violations", sourceResults: createSecurityCommandResultsForTest("test-target", "", false, false, false, false, false, true, 0, 0, 0, 0, 1, 0), @@ -1230,6 +1302,12 @@ func TestFilterViolationsResults(t *testing.T) { assert.NotNil(t, test.sourceResults.Violations.Iac, "IaC violations should NOT be removed") } + if test.shouldRemoveServices { + assert.Nil(t, test.sourceResults.Violations.Services, "Services violations should be removed") + } else { + assert.NotNil(t, test.sourceResults.Violations.Services, "Services violations should NOT be removed") + } + if test.shouldRemoveSast { assert.Nil(t, test.sourceResults.Violations.Sast, "SAST violations should be removed") } else { @@ -1315,6 +1393,21 @@ func TestIsScanFailedInSourceOrTarget(t *testing.T) { step: results.CmdStepIaC, expected: true, }, + { + name: "Services scan failed in source - should return true", + sourceResult: &results.TargetResults{ + ResultsStatus: results.ResultsStatus{ + ServicesScanStatusCode: intPtr(1), + }, + }, + targetResult: &results.TargetResults{ + ResultsStatus: results.ResultsStatus{ + ServicesScanStatusCode: intPtr(0), + }, + }, + step: results.CmdStepServices, + expected: true, + }, { name: "Both are nil - should return false", sourceResult: nil, @@ -1500,10 +1593,11 @@ func createSecurityCommandResultsForTest(targetLocation string, targetName strin if withViolations { result.Violations = &violationutils.Violations{ - Sca: []violationutils.CveViolation{{}}, - Secrets: []violationutils.JasViolation{{}}, - Iac: []violationutils.JasViolation{{}}, - Sast: []violationutils.JasViolation{{}}, + Sca: []violationutils.CveViolation{{}}, + Secrets: []violationutils.JasViolation{{}}, + Iac: []violationutils.JasViolation{{}}, + Services: []violationutils.JasViolation{{}}, + Sast: []violationutils.JasViolation{{}}, } } diff --git a/testdata/messages/integration/test_proj_pip_with_vulnerability.md b/testdata/messages/integration/test_proj_pip_with_vulnerability.md index 994986d58..236df3c5f 100644 --- a/testdata/messages/integration/test_proj_pip_with_vulnerability.md +++ b/testdata/messages/integration/test_proj_pip_with_vulnerability.md @@ -19,6 +19,7 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | Not Found | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ✅ Done | Not Found | ### 📦 Vulnerable Dependencies diff --git a/testdata/messages/integration/test_proj_with_vulnerability_simplified.md b/testdata/messages/integration/test_proj_with_vulnerability_simplified.md index 18b3e3f64..0385016e7 100644 --- a/testdata/messages/integration/test_proj_with_vulnerability_simplified.md +++ b/testdata/messages/integration/test_proj_with_vulnerability_simplified.md @@ -18,6 +18,7 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | Not Found | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ✅ Done | Not Found | diff --git a/testdata/messages/integration/test_proj_with_vulnerability_standard.md b/testdata/messages/integration/test_proj_with_vulnerability_standard.md index 9b6750fc1..0335e774a 100644 --- a/testdata/messages/integration/test_proj_with_vulnerability_standard.md +++ b/testdata/messages/integration/test_proj_with_vulnerability_standard.md @@ -19,6 +19,7 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | Not Found | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ✅ Done | Not Found | ### 📦 Vulnerable Dependencies diff --git a/testdata/messages/reviewcomment/services/services_review_content_simplified.md b/testdata/messages/reviewcomment/services/services_review_content_simplified.md new file mode 100644 index 000000000..9ee116a11 --- /dev/null +++ b/testdata/messages/reviewcomment/services/services_review_content_simplified.md @@ -0,0 +1,28 @@ + + +--- +## 🔌 Services Vulnerability + +--- +| Severity | Finding | +| :---------------------: | :-----------------------------------: | +| Medium | Exposed service endpoint detected | + + +--- +### Full description + +--- + + + +--- +### Vulnerability Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Rule ID:** | rule-id | + +Scanner Description.... + diff --git a/testdata/messages/reviewcomment/services/services_review_content_standard.md b/testdata/messages/reviewcomment/services/services_review_content_standard.md new file mode 100644 index 000000000..3307a6fb4 --- /dev/null +++ b/testdata/messages/reviewcomment/services/services_review_content_standard.md @@ -0,0 +1,21 @@ + +## 🔌 Services Vulnerability +
+ +| Severity | Finding | +| :---------------------: | :-----------------------------------: | +| ![medium](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | Exposed service endpoint detected | + +
+ + +
Full description + +### Vulnerability Details +| | | +| --------------------- | :-----------------------------------: | +| **Rule ID:** | rule-id | + +Scanner Description.... + +
\ No newline at end of file diff --git a/testdata/messages/reviewcomment/services/services_violation_review_content_simplified.md b/testdata/messages/reviewcomment/services/services_violation_review_content_simplified.md new file mode 100644 index 000000000..2bc0800b6 --- /dev/null +++ b/testdata/messages/reviewcomment/services/services_violation_review_content_simplified.md @@ -0,0 +1,28 @@ + + +--- +## 🔌 Services Violation + +--- +| Severity | ID | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| Medium | services-violation-id | Exposed service endpoint detected | jas-watch | policy1, policy2 | + + +--- +### Full description + +--- + + + +--- +### Violation Details + +--- +| | | +| --------------------- | :-----------------------------------: | +| **Rule ID:** | rule-id | + +Scanner Description.... + diff --git a/testdata/messages/reviewcomment/services/services_violation_review_content_standard.md b/testdata/messages/reviewcomment/services/services_violation_review_content_standard.md new file mode 100644 index 000000000..7491dbc08 --- /dev/null +++ b/testdata/messages/reviewcomment/services/services_violation_review_content_standard.md @@ -0,0 +1,21 @@ + +## 🔌 Services Violation +
+ +| Severity | ID | Finding | Watch Name | Policies | +| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | +| ![medium](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableMediumSeverity.png)
Medium | services-violation-id | Exposed service endpoint detected | jas-watch | policy1
policy2 | + +
+ + +
Full description + +### Violation Details +| | | +| --------------------- | :-----------------------------------: | +| **Rule ID:** | rule-id | + +Scanner Description.... + +
\ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_both_simplified.md b/testdata/messages/summarycomment/summary/summary_both_simplified.md index 9512be400..779a1bba0 100644 --- a/testdata/messages/summarycomment/summary/summary_both_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_both_simplified.md @@ -12,4 +12,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | 4 Issues Found: 🔴 3 High, 🟡 1 Low | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_both_standard.md b/testdata/messages/summarycomment/summary/summary_both_standard.md index 30f1b29a4..cd434cd8d 100644 --- a/testdata/messages/summarycomment/summary/summary_both_standard.md +++ b/testdata/messages/summarycomment/summary/summary_both_standard.md @@ -8,4 +8,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done |
4 Issues Found 3 High
1 Low
| | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_simplified.md b/testdata/messages/summarycomment/summary/summary_simplified.md index 9a073c047..a06fe8d34 100644 --- a/testdata/messages/summarycomment/summary/summary_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_simplified.md @@ -12,4 +12,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | 3 Issues Found: 🔴 2 High, 🟡 1 Low | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_standard.md b/testdata/messages/summarycomment/summary/summary_standard.md index 5244ba6bf..34be3271c 100644 --- a/testdata/messages/summarycomment/summary/summary_standard.md +++ b/testdata/messages/summarycomment/summary/summary_standard.md @@ -8,4 +8,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done |
3 Issues Found 2 High
1 Low
| | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_violation_simplified.md b/testdata/messages/summarycomment/summary/summary_violation_simplified.md index 58185dd84..738debc4d 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_simplified.md +++ b/testdata/messages/summarycomment/summary/summary_violation_simplified.md @@ -12,4 +12,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | 1 Issues Found: 🔴 1 High | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/messages/summarycomment/summary/summary_violation_standard.md b/testdata/messages/summarycomment/summary/summary_violation_standard.md index 245da163e..47c9302ad 100644 --- a/testdata/messages/summarycomment/summary/summary_violation_standard.md +++ b/testdata/messages/summarycomment/summary/summary_violation_standard.md @@ -8,4 +8,5 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done |
1 Issues Found 1 High
| | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ℹ️ Not Scanned | - | \ No newline at end of file diff --git a/testdata/scanpullrequest/expected_response.md b/testdata/scanpullrequest/expected_response.md index 1e41a6f8d..f596233bd 100644 --- a/testdata/scanpullrequest/expected_response.md +++ b/testdata/scanpullrequest/expected_response.md @@ -19,6 +19,7 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | Not Found | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ✅ Done | Not Found | ### 📦 Vulnerable Dependencies diff --git a/testdata/scanpullrequest/expected_response_multi_dir.md b/testdata/scanpullrequest/expected_response_multi_dir.md index 8d135ea33..e03dea085 100644 --- a/testdata/scanpullrequest/expected_response_multi_dir.md +++ b/testdata/scanpullrequest/expected_response_multi_dir.md @@ -19,6 +19,7 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | Not Found | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ✅ Done | Not Found | ### 📦 Vulnerable Dependencies diff --git a/testdata/scanpullrequest/expected_response_subdir.md b/testdata/scanpullrequest/expected_response_subdir.md index 22e212c4d..939b48e78 100644 --- a/testdata/scanpullrequest/expected_response_subdir.md +++ b/testdata/scanpullrequest/expected_response_subdir.md @@ -19,6 +19,7 @@ | **Contextual Analysis** | ✅ Done | - | | **Static Application Security Testing (SAST)** | ✅ Done | Not Found | | **Secrets** | ✅ Done | - | +| **Services** | ℹ️ Not Scanned | - | | **Infrastructure as Code (IaC)** | ✅ Done | Not Found | ### 📦 Vulnerable Dependencies diff --git a/utils/comment.go b/utils/comment.go index 5faea0216..38eb2a59e 100644 --- a/utils/comment.go +++ b/utils/comment.go @@ -32,6 +32,7 @@ const ( ApplicableComment ReviewCommentType = "Applicable" IacComment ReviewCommentType = "Iac" SastComment ReviewCommentType = "Sast" + ServicesComment ReviewCommentType = "Services" SecretComment ReviewCommentType = "Secrets" SnippetComment ReviewCommentType = "Snippet" @@ -221,7 +222,15 @@ func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, similarSastIssues.Location, generateSourceCodeReviewContent(SastComment, true, writer, similarSastIssues.issues...))) } } - + // Services review comments + for _, services := range issues.ServicesVulnerabilities { + commentsToAdd = append(commentsToAdd, generateReviewComment(ServicesComment, services.Location, generateSourceCodeReviewContent(ServicesComment, false, writer, services))) + } + if len(issues.ServicesViolations) > 0 { + for _, similarServicesIssues := range groupSimilarJasIssues(issues.ServicesViolations) { + commentsToAdd = append(commentsToAdd, generateReviewComment(ServicesComment, similarServicesIssues.Location, generateSourceCodeReviewContent(ServicesComment, true, writer, similarServicesIssues.issues...))) + } + } // Secrets review comments if !repo.FrogbotConfig.ShowSecretsAsPrComment { return @@ -386,6 +395,8 @@ func generateSourceCodeReviewContent(commentType ReviewCommentType, violation bo return outputwriter.GenerateReviewCommentContent(outputwriter.SastReviewContent(violation, writer, similarIssues...), writer) case SecretComment: return outputwriter.GenerateReviewCommentContent(outputwriter.SecretReviewContent(violation, writer, similarIssues...), writer) + case ServicesComment: + return outputwriter.GenerateReviewCommentContent(outputwriter.ServicesReviewContent(violation, writer, similarIssues...), writer) } return } diff --git a/utils/comment_test.go b/utils/comment_test.go index f6c95d943..889774914 100644 --- a/utils/comment_test.go +++ b/utils/comment_test.go @@ -573,6 +573,26 @@ func TestGetNewReviewComments(t *testing.T) { }, }, }, + ServicesVulnerabilities: []formats.SourceCodeRow{ + { + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 10, + }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "services-rule", + }, + Finding: "Exposed service endpoint detected", + Location: formats.Location{ + File: "file1", + StartLine: 1, + StartColumn: 10, + EndLine: 2, + EndColumn: 11, + Snippet: "services-snippet", + }, + }, + }, SastVulnerabilities: []formats.SourceCodeRow{ { SeverityDetails: formats.SeverityDetails{ @@ -663,6 +683,43 @@ func TestGetNewReviewComments(t *testing.T) { }, }, }, + { + Location: formats.Location{ + File: "file1", + StartLine: 1, + StartColumn: 10, + EndLine: 2, + EndColumn: 11, + Snippet: "services-snippet", + }, + Type: ServicesComment, + CommentInfo: vcsclient.PullRequestComment{ + CommentInfo: vcsclient.CommentInfo{ + Content: outputwriter.GenerateReviewCommentContent(outputwriter.ServicesReviewContent(false, writer, formats.SourceCodeRow{ + SeverityDetails: formats.SeverityDetails{ + Severity: "Medium", + SeverityNumValue: 10, + }, + ScannerInfo: formats.ScannerInfo{ + RuleId: "services-rule", + }, + Finding: "Exposed service endpoint detected", + }), writer), + }, + PullRequestDiff: vcsclient.PullRequestDiff{ + OriginalFilePath: "file1", + OriginalStartLine: 1, + OriginalStartColumn: 10, + OriginalEndLine: 2, + OriginalEndColumn: 11, + NewFilePath: "file1", + NewStartLine: 1, + NewStartColumn: 10, + NewEndLine: 2, + NewEndColumn: 11, + }, + }, + }, { Location: formats.Location{ File: "file1", diff --git a/utils/issues/issuescollection.go b/utils/issues/issuescollection.go index f87d8ecf4..464de9793 100644 --- a/utils/issues/issuescollection.go +++ b/utils/issues/issuescollection.go @@ -28,6 +28,9 @@ type ScansIssuesCollection struct { IacVulnerabilities []formats.SourceCodeRow IacViolations []formats.SourceCodeRow + ServicesVulnerabilities []formats.SourceCodeRow + ServicesViolations []formats.SourceCodeRow + SecretsVulnerabilities []formats.SourceCodeRow SecretsViolations []formats.SourceCodeRow @@ -83,6 +86,13 @@ func (ic *ScansIssuesCollection) Append(issues *ScansIssuesCollection) { if len(issues.IacViolations) > 0 { ic.IacViolations = append(ic.IacViolations, issues.IacViolations...) } + // Services + if len(issues.ServicesVulnerabilities) > 0 { + ic.ServicesVulnerabilities = append(ic.ServicesVulnerabilities, issues.ServicesVulnerabilities...) + } + if len(issues.ServicesViolations) > 0 { + ic.ServicesViolations = append(ic.ServicesViolations, issues.ServicesViolations...) + } // Last-write-wins, not a true merge: today only one non-nil Violations source is ever appended // (scan-pr calls Append exactly once, from the source-branch scan). Revisit if that changes. if issues.Violations != nil { @@ -97,6 +107,9 @@ func (ic *ScansIssuesCollection) AppendStatus(scanStatus formats.ScanStatus) { if ic.IacStatusCode == nil || (*ic.IacStatusCode == 0 && scanStatus.IacStatusCode != nil) { ic.IacStatusCode = scanStatus.IacStatusCode } + if ic.ServicesStatusCode == nil || (*ic.ServicesStatusCode == 0 && scanStatus.ServicesStatusCode != nil) { + ic.ServicesStatusCode = scanStatus.ServicesStatusCode + } if ic.SecretsStatusCode == nil || (*ic.SecretsStatusCode == 0 && scanStatus.SecretsStatusCode != nil) { ic.SecretsStatusCode = scanStatus.SecretsStatusCode } @@ -120,6 +133,8 @@ func (ic *ScansIssuesCollection) GetScanStatus(scanType utils.SubScanType) *int return ic.ScaStatusCode case utils.IacScan: return ic.IacStatusCode + case utils.ServicesScan: + return ic.ServicesStatusCode case utils.SecretsScan: return ic.SecretsStatusCode case utils.SastScan: @@ -141,6 +156,9 @@ func (ic *ScansIssuesCollection) HasErrors() bool { if iacStatus := ic.GetScanStatus(utils.IacScan); iacStatus != nil && *iacStatus != 0 { return true } + if servicesStatus := ic.GetScanStatus(utils.ServicesScan); servicesStatus != nil && *servicesStatus != 0 { + return true + } if secretsStatus := ic.GetScanStatus(utils.SecretsScan); secretsStatus != nil && *secretsStatus != 0 { return true } @@ -180,6 +198,14 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc if vulnerabilities { jasVulnerabilities = ic.IacVulnerabilities } + case utils.ServicesScan: + // Count Services issues only if requested + if isViolation { + jasViolations = ic.ServicesViolations + } + if vulnerabilities { + jasVulnerabilities = ic.ServicesVulnerabilities + } case utils.SecretsScan: // Count Secrets issues only if requested if isViolation { @@ -208,7 +234,7 @@ func (ic *ScansIssuesCollection) GetScanIssuesSeverityCount(scanType utils.SubSc } func (ic *ScansIssuesCollection) IssuesExists(includeSecrets bool) bool { - return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.SastIssuesExists() || (includeSecrets && ic.SecretsIssuesExists()) + return ic.ScaIssuesExists() || ic.IacIssuesExists() || ic.SastIssuesExists() || ic.ServicesIssuesExists() || (includeSecrets && ic.SecretsIssuesExists()) } func (ic *ScansIssuesCollection) ScaIssuesExists() bool { @@ -219,6 +245,10 @@ func (ic *ScansIssuesCollection) IacIssuesExists() bool { return len(ic.IacVulnerabilities) > 0 || len(ic.IacViolations) > 0 } +func (ic *ScansIssuesCollection) ServicesIssuesExists() bool { + return len(ic.ServicesVulnerabilities) > 0 || len(ic.ServicesViolations) > 0 +} + func (ic *ScansIssuesCollection) SecretsIssuesExists() bool { return len(ic.SecretsVulnerabilities) > 0 || len(ic.SecretsViolations) > 0 } @@ -265,6 +295,12 @@ func (ic *ScansIssuesCollection) IsFailPrRuleApplied() bool { return true } } + for _, servicesViolation := range ic.ServicesViolations { + if servicesViolation.FailPr { + log.Debug(fmt.Sprintf(FailPrRuleMessage, servicesViolation.ViolationContext.Watch)) + return true + } + } return false } @@ -344,7 +380,7 @@ func (ic *ScansIssuesCollection) GetApplicableEvidences() (evidences []Applicabl // Violations func (ic *ScansIssuesCollection) GetTotalViolations(includeSecrets bool) int { - total := ic.GetTotalScaViolations() + len(ic.IacViolations) + len(ic.SastViolations) + total := ic.GetTotalScaViolations() + len(ic.IacViolations) + len(ic.SastViolations) + len(ic.ServicesViolations) if includeSecrets { total += len(ic.SecretsViolations) } @@ -358,7 +394,7 @@ func (ic *ScansIssuesCollection) GetTotalScaViolations() int { // Vulnerabilities func (ic *ScansIssuesCollection) GetTotalVulnerabilities(includeSecrets bool) int { - total := len(ic.ScaVulnerabilities) + len(ic.IacVulnerabilities) + len(ic.SastVulnerabilities) + total := len(ic.ScaVulnerabilities) + len(ic.IacVulnerabilities) + len(ic.SastVulnerabilities) + len(ic.ServicesVulnerabilities) if includeSecrets { total += len(ic.SecretsVulnerabilities) } diff --git a/utils/issues/issuescollection_test.go b/utils/issues/issuescollection_test.go index 75c268e37..1cb208aa6 100644 --- a/utils/issues/issuescollection_test.go +++ b/utils/issues/issuescollection_test.go @@ -128,8 +128,9 @@ func getTestData() ScansIssuesCollection { }, }}, - IacVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "Low"}}}, - SecretsVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}, + IacVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "Low"}}}, + ServicesVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "Medium"}}}, + SecretsVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}, SecretsViolations: []formats.SourceCodeRow{{ SeverityDetails: formats.SeverityDetails{Severity: "High"}, ViolationContext: formats.ViolationContext{ @@ -158,12 +159,12 @@ func TestGetAllIssuesCount(t *testing.T) { { name: "With Secrets", includeSecrets: true, - expectedFindings: 9, + expectedFindings: 10, }, { name: "No Secrets", includeSecrets: false, - expectedFindings: 7, + expectedFindings: 8, }, } issuesCollection := getTestData() @@ -184,12 +185,12 @@ func TestGetTotalVulnerabilities(t *testing.T) { { name: "With Secrets", includeSecrets: true, - expectedFindings: 6, + expectedFindings: 7, }, { name: "No Secrets", includeSecrets: false, - expectedFindings: 5, + expectedFindings: 6, }, } issuesCollection := getTestData() @@ -273,6 +274,25 @@ func TestGetScanIssuesSeverityCount(t *testing.T) { violation: true, expectedSeverityCount: map[string]int{"Low": 1}, }, + { + name: "Services Vulnerabilities", + scanType: utils.ServicesScan, + vulnerabilities: true, + expectedSeverityCount: map[string]int{"Medium": 1}, + }, + { + name: "Services Violations", + scanType: utils.ServicesScan, + violation: true, + expectedSeverityCount: map[string]int{}, + }, + { + name: "Services Vulnerabilities and Violations", + scanType: utils.ServicesScan, + vulnerabilities: true, + violation: true, + expectedSeverityCount: map[string]int{"Medium": 1}, + }, { name: "Secrets Vulnerabilities", scanType: utils.SecretsScan, @@ -378,6 +398,13 @@ func TestIssuesExists(t *testing.T) { includeSecrets: true, expected: true, }, + { + name: "With Services only", + issues: ScansIssuesCollection{ + ServicesVulnerabilities: []formats.SourceCodeRow{{SeverityDetails: formats.SeverityDetails{Severity: "High"}}}, + }, + expected: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -434,6 +461,12 @@ func TestIsFailPrRuleApplied(t *testing.T) { scannerToAddFailPrRule: "iac", expected: true, }, + { + name: "Services violations with fail PR rule", + issues: getTestData(), + scannerToAddFailPrRule: "services", + expected: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -466,6 +499,13 @@ func TestIsFailPrRuleApplied(t *testing.T) { FailPr: true, }, }) + case "services": + tc.issues.ServicesViolations = append(tc.issues.ServicesViolations, formats.SourceCodeRow{ + ViolationContext: formats.ViolationContext{ + Watch: "services-watch", + FailPr: true, + }, + }) } assert.Equal(t, tc.expected, tc.issues.IsFailPrRuleApplied()) @@ -508,6 +548,18 @@ func TestHasErrors(t *testing.T) { }, expected: true, }, + { + name: "With Services Errors", + status: formats.ScanStatus{ + ScaStatusCode: utils.NewIntPtr(0), + SastStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(0), + IacStatusCode: utils.NewIntPtr(0), + ServicesStatusCode: utils.NewIntPtr(1), + ApplicabilityStatusCode: utils.NewIntPtr(0), + }, + expected: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -519,9 +571,10 @@ func TestHasErrors(t *testing.T) { func TestIsScanNotCompleted(t *testing.T) { issues := ScansIssuesCollection{ScanStatus: formats.ScanStatus{ - ScaStatusCode: utils.NewIntPtr(-1), - SastStatusCode: utils.NewIntPtr(0), - SecretsStatusCode: utils.NewIntPtr(33), + ScaStatusCode: utils.NewIntPtr(-1), + SastStatusCode: utils.NewIntPtr(0), + SecretsStatusCode: utils.NewIntPtr(33), + ServicesStatusCode: utils.NewIntPtr(0), }} testCases := []struct { name string @@ -532,6 +585,10 @@ func TestIsScanNotCompleted(t *testing.T) { name: "Scanned and Passed", scan: utils.SastScan, }, + { + name: "Services Scanned and Passed", + scan: utils.ServicesScan, + }, { name: "Scanned and unknown Failed", scan: utils.ScaScan, @@ -565,12 +622,14 @@ func TestAppendStatus(t *testing.T) { SastStatusCode: utils.NewIntPtr(33), ApplicabilityStatusCode: utils.NewIntPtr(0), SecretsStatusCode: utils.NewIntPtr(51), + ServicesStatusCode: utils.NewIntPtr(7), } expectedStatus := formats.ScanStatus{ ScaStatusCode: utils.NewIntPtr(-1), SastStatusCode: utils.NewIntPtr(33), ApplicabilityStatusCode: utils.NewIntPtr(0), SecretsStatusCode: utils.NewIntPtr(51), + ServicesStatusCode: utils.NewIntPtr(7), } issues := ScansIssuesCollection{ScanStatus: oldStatus} issues.AppendStatus(newStatus) diff --git a/utils/outputwriter/outputcontent.go b/utils/outputwriter/outputcontent.go index 7683378a2..f0b9c066f 100644 --- a/utils/outputwriter/outputcontent.go +++ b/utils/outputwriter/outputcontent.go @@ -35,6 +35,7 @@ const ( //#nosec G101 -- not a secret secretsTitle = "🤫 Secret" + servicesTitle = "🔌 Services" contextualAnalysisTitle = "📦🔍 Contextual Analysis CVE" iacTitle = "🛠️ Infrastructure as Code" sastTitle = "🎯 Static Application Security Testing (SAST)" @@ -186,6 +187,7 @@ func ScanSummaryContent(issues issues.ScansIssuesCollection, context results.Res table.AddRow(MarkAsBold("Contextual Analysis"), getSubScanResultStatus(issues.GetScanStatus(utils.ContextualAnalysisScan)), "") table.AddRow(MarkAsBold("Static Application Security Testing (SAST)"), getSubScanResultStatus(issues.GetScanStatus(utils.SastScan)), getScanSecurityIssuesDetails(issues, context, utils.SastScan, writer)) table.AddRow(MarkAsBold("Secrets"), getSubScanResultStatus(issues.GetScanStatus(utils.SecretsScan)), secretsDetails) + table.AddRow(MarkAsBold("Services"), getSubScanResultStatus(issues.GetScanStatus(utils.ServicesScan)), getScanSecurityIssuesDetails(issues, context, utils.ServicesScan, writer)) table.AddRow(MarkAsBold("Infrastructure as Code (IaC)"), getSubScanResultStatus(issues.GetScanStatus(utils.IacScan)), getScanSecurityIssuesDetails(issues, context, utils.IacScan, writer)) WriteContent(&contentBuilder, table.Build()) return contentBuilder.String() @@ -232,6 +234,8 @@ func getScanSecurityIssuesDetails(issues issues.ScansIssuesCollection, context r severityCountMap = issues.GetScanIssuesSeverityCount(utils.SecretsScan, countVulnerabilities, countViolations) case utils.IacScan: severityCountMap = issues.GetScanIssuesSeverityCount(utils.IacScan, countVulnerabilities, countViolations) + case utils.ServicesScan: + severityCountMap = issues.GetScanIssuesSeverityCount(utils.ServicesScan, countVulnerabilities, countViolations) } totalIssues := getTotalIssues(severityCountMap) if totalIssues == 0 { @@ -502,6 +506,22 @@ func IacReviewContent(violation bool, writer OutputWriter, issues ...formats.Sou return contentBuilder.String() } +func ServicesReviewContent(violation bool, writer OutputWriter, issues ...formats.SourceCodeRow) string { + var contentBuilder strings.Builder + WriteContent(&contentBuilder, + writer.MarkAsTitle(fmt.Sprintf("%s %s", servicesTitle, getIssueType(violation)), 2), + writer.MarkInCenter(getJasIssueDescriptionTable(writer, issues...)), + getJasFullDescription(violation, writer, getServicesRuleFullDescriptionTable, issues...), + ) + return contentBuilder.String() +} + +func getServicesRuleFullDescriptionTable(info formats.ScannerInfo, writer OutputWriter) *MarkdownTableBuilder { + table := getBaseJasDetailsTable(info, writer) + table.AddRow(MarkAsBold("Rule ID:"), info.RuleId) + return table +} + func SastReviewContent(violation bool, writer OutputWriter, issues ...formats.SourceCodeRow) string { var contentBuilder strings.Builder WriteContent(&contentBuilder, diff --git a/utils/outputwriter/outputcontent_test.go b/utils/outputwriter/outputcontent_test.go index ca626aee5..3a854fc02 100644 --- a/utils/outputwriter/outputcontent_test.go +++ b/utils/outputwriter/outputcontent_test.go @@ -1247,6 +1247,84 @@ func TestIacReviewContent(t *testing.T) { } } +func TestServicesReviewContent(t *testing.T) { + testCases := []struct { + name string + issues []formats.SourceCodeRow + cases []OutputTestCase + }{ + { + name: "Services review comment content", + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Finding: "Exposed service endpoint detected", + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule-id", + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Scanner Short Description", + }, + }}, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "services", "services_review_content_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "services", "services_review_content_simplified.md")}, + }, + }, + }, + { + name: "Services violation review comment content", + issues: []formats.SourceCodeRow{{ + SeverityDetails: formats.SeverityDetails{Severity: "Medium"}, + Finding: "Exposed service endpoint detected", + ScannerInfo: formats.ScannerInfo{ + RuleId: "rule-id", + ScannerDescription: "Scanner Description....", + ScannerShortDescription: "Scanner Short Description", + }, + ViolationContext: formats.ViolationContext{ + IssueId: "services-violation-id", + Watch: "jas-watch", + Policies: []string{"policy1", "policy2"}, + }, + }}, + cases: []OutputTestCase{ + { + name: "Standard output", + writer: &StandardOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "services", "services_violation_review_content_standard.md")}, + }, + { + name: "Simplified output", + writer: &SimplifiedOutput{MarkdownOutput{hasInternetConnection: true}}, + expectedOutputPath: []string{filepath.Join(testReviewCommentDir, "services", "services_violation_review_content_simplified.md")}, + }, + }, + }, + } + + for _, tc := range testCases { + for _, test := range tc.cases { + t.Run(tc.name+"_"+test.name, func(t *testing.T) { + expectedOutput := GetExpectedTestOutput(t, test) + violations := false + for _, issue := range tc.issues { + if issue.Watch != "" { + violations = true + break + } + } + assert.Equal(t, expectedOutput, ServicesReviewContent(violations, test.writer, tc.issues...)) + }) + } + } +} + func TestSastReviewContent(t *testing.T) { testCases := []struct { name string diff --git a/utils/utils.go b/utils/utils.go index 5f9367584..f0e7231fa 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -365,10 +365,12 @@ func GetVulnerabiltiesUniqueID(vulnerability formats.VulnerabilityOrViolationRow func ConvertSarifPathsToRelative(issues *issues.ScansIssuesCollection, workingDirs ...string) { convertSarifPathsInCveApplicability(issues.ScaVulnerabilities, workingDirs...) convertSarifPathsInIacs(issues.IacVulnerabilities, workingDirs...) + convertSarifPathsInServices(issues.ServicesVulnerabilities, workingDirs...) convertSarifPathsInSecrets(issues.SecretsVulnerabilities, workingDirs...) convertSarifPathsInSast(issues.SastVulnerabilities, workingDirs...) convertSarifPathsInCveApplicability(issues.ScaViolations, workingDirs...) convertSarifPathsInIacs(issues.IacViolations, workingDirs...) + convertSarifPathsInServices(issues.ServicesViolations, workingDirs...) convertSarifPathsInSecrets(issues.SecretsViolations, workingDirs...) convertSarifPathsInSast(issues.SastViolations, workingDirs...) } @@ -396,6 +398,15 @@ func convertSarifPathsInIacs(iacs []formats.SourceCodeRow, workingDirs ...string } } +func convertSarifPathsInServices(services []formats.SourceCodeRow, workingDirs ...string) { + for i := range services { + service := &services[i] + for _, wd := range workingDirs { + service.Location.File = utils.GetRelativePath(service.Location.File, wd) + } + } +} + func convertSarifPathsInSecrets(secrets []formats.SourceCodeRow, workingDirs ...string) { for i := range secrets { secret := &secrets[i] diff --git a/utils/utils_test.go b/utils/utils_test.go index 6542b714b..586789ff2 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/jfrog/frogbot/v3/utils/gitlabreport" + "github.com/jfrog/frogbot/v3/utils/issues" "github.com/jfrog/frogbot/v3/utils/outputwriter" ) @@ -34,6 +35,22 @@ const ( dependencySubmissionTestSha = "abc123def456" ) +func TestConvertSarifPathsToRelativeServices(t *testing.T) { + issuesCollection := &issues.ScansIssuesCollection{ + ServicesVulnerabilities: []formats.SourceCodeRow{{ + Location: formats.Location{File: filepath.Join("tmp", "wd", "service.yaml")}, + }}, + ServicesViolations: []formats.SourceCodeRow{{ + Location: formats.Location{File: filepath.Join("tmp", "wd", "api.yaml")}, + }}, + } + + ConvertSarifPathsToRelative(issuesCollection, filepath.Join("tmp", "wd")) + + assert.Equal(t, "service.yaml", issuesCollection.ServicesVulnerabilities[0].Location.File) + assert.Equal(t, "api.yaml", issuesCollection.ServicesViolations[0].Location.File) +} + func TestChdir(t *testing.T) { originCwd, err := os.Getwd() assert.NoError(t, err)