From 82c04f95dad7dc92a24cf35a496e96cddab99740 Mon Sep 17 00:00:00 2001 From: Dimitris Kavvathas Date: Thu, 29 Sep 2022 15:00:10 +0200 Subject: [PATCH] SONAR-17393 Add OWASP ASVS report to 'api/security_reports/show' API call. --- .../sonar/server/issue/index/IssueIndex.java | 27 ++-- .../index/IssueIndexSecurityReportsTest.java | 122 +++++++++++++++++- .../ws/client/issue/IssuesWsParameters.java | 1 + 3 files changed, 139 insertions(+), 11 deletions(-) diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java index aac88c660fc..bfabfb5af6a 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -68,6 +68,7 @@ import org.joda.time.Duration; import org.sonar.api.issue.Issue; import org.sonar.api.rule.Severity; import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion; import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version; import org.sonar.api.server.rule.RulesDefinition.PciDssVersion; import org.sonar.api.utils.DateUtils; @@ -90,6 +91,7 @@ import org.sonar.server.issue.index.IssueQuery.PeriodStart; import org.sonar.server.permission.index.AuthorizationDoc; import org.sonar.server.permission.index.WebAuthorizationTypeSupport; import org.sonar.server.security.SecurityStandards; +import org.sonar.server.security.SecurityStandards.OwaspAsvs; import org.sonar.server.security.SecurityStandards.PciDss; import org.sonar.server.security.SecurityStandards.SQCategory; import org.sonar.server.user.UserSession; @@ -1154,9 +1156,18 @@ public class IssueIndex { SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp); Arrays.stream(PciDss.values()) .forEach(pciDss -> request.aggregation( - newPciDssSecurityReportSubAggregations( - AggregationBuilders.filter(pciDss.category(), boolQuery().filter(prefixQuery(version.prefix(), pciDss.category() + "."))), version))); - return searchPciDss(request, version.label()); + newSecurityReportSubAggregations( + AggregationBuilders.filter(pciDss.category(), boolQuery().filter(prefixQuery(version.prefix(), pciDss.category() + "."))), version.prefix()))); + return searchWithDistribution(request, version.label()); + } + + public List getOwaspAsvsReport(String projectUuid, boolean isViewOrApp, OwaspAsvsVersion version) { + SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp); + Arrays.stream(OwaspAsvs.values()) + .forEach(owaspAsvs -> request.aggregation( + newSecurityReportSubAggregations( + AggregationBuilders.filter(owaspAsvs.category(), boolQuery().filter(prefixQuery(version.prefix(), owaspAsvs.category() + "."))), version.prefix()))); + return searchWithDistribution(request, version.label()); } public List getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe, OwaspTop10Version version) { @@ -1170,12 +1181,12 @@ public class IssueIndex { return search(request, includeCwe, version.label()); } - private List searchPciDss(SearchSourceBuilder sourceBuilder, String version) { + private List searchWithDistribution(SearchSourceBuilder sourceBuilder, String version) { SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType()) .source(sourceBuilder); SearchResponse response = client.search(request); return response.getAggregations().asList().stream() - .map(c -> processPciDssSecurityReportIssueSearchResults((ParsedFilter) c, version)) + .map(c -> processSecurityReportIssueSearchResultsWithDistribution((ParsedFilter) c, version)) .collect(MoreCollectors.toList()); } @@ -1188,7 +1199,7 @@ public class IssueIndex { .collect(MoreCollectors.toList()); } - private static SecurityStandardCategoryStatistics processPciDssSecurityReportIssueSearchResults(ParsedFilter categoryFilter, String version) { + private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResultsWithDistribution(ParsedFilter categoryFilter, String version) { Stream stream = ((ParsedStringTerms) categoryFilter.getAggregations().get(AGG_DISTRIBUTION)).getBuckets().stream(); var children = stream.filter(categoryBucket -> StringUtils.startsWith(categoryBucket.getKeyAsString(), categoryFilter.getName() + ".")) .map(categoryBucket -> processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getKeyAsString(), null, null)).collect(toList()); @@ -1229,10 +1240,10 @@ public class IssueIndex { reviewedSecurityHotspots, securityReviewRating, children, version); } - private static AggregationBuilder newPciDssSecurityReportSubAggregations(AggregationBuilder categoriesAggs, PciDssVersion version) { + private static AggregationBuilder newSecurityReportSubAggregations(AggregationBuilder categoriesAggs, String securityStandardVersionPrefix) { AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs); final TermsAggregationBuilder distributionAggregation = AggregationBuilders.terms(AGG_DISTRIBUTION) - .field(version.prefix()) + .field(securityStandardVersionPrefix) // 100 should be enough to display all the requirements per category. If not, the UI will be broken anyway .size(MAX_FACET_SIZE); categoriesAggs.subAggregation(addSecurityReportIssueCountAggregations(distributionAggregation)); diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java index 8a7952c6e1c..ba26775b3e9 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java @@ -27,7 +27,6 @@ import org.junit.Test; import org.sonar.api.issue.Issue; import org.sonar.api.rule.Severity; import org.sonar.api.rules.RuleType; -import org.sonar.api.server.rule.RulesDefinition; import org.sonar.db.component.ComponentDto; import org.sonar.server.view.index.ViewDoc; @@ -38,6 +37,8 @@ import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion; +import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion; import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2017; import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021; import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; @@ -203,6 +204,29 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { assertThat(pciDss32Report.get(11).getChildren()).isEmpty(); } + @Test + public void getOwaspAsvs40Report_aggregation() { + List owaspAsvsReport = indexIssuesAndAssertOwaspAsvsReport(); + + assertThat(owaspAsvsReport) + .isNotEmpty(); + + assertThat(owaspAsvsReport.get(0).getChildren()).hasSize(2); + assertThat(owaspAsvsReport.get(1).getChildren()).isEmpty(); + assertThat(owaspAsvsReport.get(2).getChildren()).hasSize(4); + assertThat(owaspAsvsReport.get(3).getChildren()).isEmpty(); + assertThat(owaspAsvsReport.get(4).getChildren()).isEmpty(); + assertThat(owaspAsvsReport.get(5).getChildren()).hasSize(2); + assertThat(owaspAsvsReport.get(6).getChildren()).isEmpty(); + assertThat(owaspAsvsReport.get(7).getChildren()).hasSize(1); + assertThat(owaspAsvsReport.get(8).getChildren()).isEmpty(); + assertThat(owaspAsvsReport.get(9).getChildren()).hasSize(1); + assertThat(owaspAsvsReport.get(10).getChildren()).isEmpty(); + assertThat(owaspAsvsReport.get(11).getChildren()).isEmpty(); + assertThat(owaspAsvsReport.get(12).getChildren()).isEmpty(); + assertThat(owaspAsvsReport.get(13).getChildren()).isEmpty(); + } + @Test public void getOwaspTop10Report_aggregation_with_cwe() { List owaspTop10Report = indexIssuesAndAssertOwaspReport(true); @@ -300,7 +324,7 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { .setResolution(Issue.RESOLUTION_FIXED), newDoc("notpcidsshotspot", project).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW)); - List pciDssReport = underTest.getPciDssReport(project.uuid(), false, RulesDefinition.PciDssVersion.V3_2).stream() + List pciDssReport = underTest.getPciDssReport(project.uuid(), false, PciDssVersion.V3_2).stream() .sorted(comparing(s -> parseInt(s.getCategory()))) .collect(toList()); assertThat(pciDssReport) @@ -324,6 +348,49 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { return pciDssReport; } + private List indexIssuesAndAssertOwaspAsvsReport() { + ComponentDto project = newPrivateProjectDto(); + indexIssues( + newDoc("openvul1", project).setOwaspAsvs40(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN) + .setSeverity(Severity.MAJOR), + newDoc("openvul2", project).setOwaspAsvs40(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED) + .setSeverity(Severity.MINOR), + newDoc("openvul3", project).setOwaspAsvs40(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED) + .setSeverity(Severity.MINOR), + newDoc("notowaspasvsvul", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL), + newDoc("toreviewhotspot1", project).setOwaspAsvs40(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT) + .setStatus(Issue.STATUS_TO_REVIEW), + newDoc("toreviewhotspot2", project).setOwaspAsvs40(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW), + newDoc("reviewedHotspot", project).setOwaspAsvs40(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED) + .setResolution(Issue.RESOLUTION_FIXED), + newDoc("notowaspasvshotspot", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW)); + + List owaspAsvsReport = underTest.getOwaspAsvsReport(project.uuid(), false, OwaspAsvsVersion.V4_0).stream() + .sorted(comparing(s -> parseInt(s.getCategory()))) + .collect(toList()); + assertThat(owaspAsvsReport) + .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities, + SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, + SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating) + .containsExactlyInAnyOrder( + tuple("1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5), + tuple("2", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4), + tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("6", 2L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5), + tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1), + tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("10", 1L, OptionalInt.of(2), 0L, 0L, 1), + tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("13", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("14", 0L, OptionalInt.empty(), 0L, 0L, 1)); + + return owaspAsvsReport; + } + private List indexIssuesAndAssertOwasp2021Report(boolean includeCwe) { ComponentDto project = newPrivateProjectDto(); indexIssues( @@ -458,7 +525,7 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { indexView(portfolio1.uuid(), singletonList(project1.uuid())); indexView(portfolio2.uuid(), singletonList(project2.uuid())); - List pciDssReport = underTest.getPciDssReport(portfolio1.uuid(), true, RulesDefinition.PciDssVersion.V3_2).stream() + List pciDssReport = underTest.getPciDssReport(portfolio1.uuid(), true, PciDssVersion.V3_2).stream() .sorted(comparing(s -> parseInt(s.getCategory()))) .collect(toList()); assertThat(pciDssReport) @@ -480,6 +547,55 @@ public class IssueIndexSecurityReportsTest extends IssueIndexTestCommon { tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1)); } + @Test + public void getOwaspAsvsReport_aggregation_on_portfolio() { + ComponentDto portfolio1 = db.components().insertPrivateApplication(); + ComponentDto portfolio2 = db.components().insertPrivateApplication(); + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + + indexIssues( + newDoc("openvul1", project1).setOwaspAsvs40(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN) + .setSeverity(Severity.MAJOR), + newDoc("openvul2", project2).setOwaspAsvs40(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED) + .setSeverity(Severity.MINOR), + newDoc("openvul3", project1).setOwaspAsvs40(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED) + .setSeverity(Severity.MINOR), + newDoc("notowaspvul", project1).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL), + newDoc("toreviewhotspot1", project2).setOwaspAsvs40(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT) + .setStatus(Issue.STATUS_TO_REVIEW), + newDoc("toreviewhotspot2", project1).setOwaspAsvs40(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW), + newDoc("reviewedHotspot", project2).setOwaspAsvs40(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED) + .setResolution(Issue.RESOLUTION_FIXED), + newDoc("notowasphotspot", project1).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW)); + + indexView(portfolio1.uuid(), singletonList(project1.uuid())); + indexView(portfolio2.uuid(), singletonList(project2.uuid())); + + List owaspAsvsReport = underTest.getOwaspAsvsReport(portfolio1.uuid(), true, OwaspAsvsVersion.V4_0).stream() + .sorted(comparing(s -> parseInt(s.getCategory()))) + .collect(toList()); + assertThat(owaspAsvsReport) + .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities, + SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, + SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating) + .containsExactlyInAnyOrder( + tuple("1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1), + tuple("2", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("3", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot2 */, 0L, 5), + tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("6", 1L /* openvul3 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5), + tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("8", 0L, OptionalInt.empty(), 0L, 0L /* reviewedHotspot */, 1), + tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("10", 1L /* openvul3 */, OptionalInt.of(2), 0L, 0L, 1), + tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("13", 0L, OptionalInt.empty(), 0L, 0L, 1), + tuple("14", 0L, OptionalInt.empty(), 0L, 0L, 1)); + } + @Test public void getCWETop25Report_aggregation() { ComponentDto project = newPrivateProjectDto(); diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java index 1ca2b86ce5c..63459ace050 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/issue/IssuesWsParameters.java @@ -82,6 +82,7 @@ public class IssuesWsParameters { public static final String PARAM_PCI_DSS = "pciDss"; public static final String PARAM_PCI_DSS_32 = "pciDss-3.2"; public static final String PARAM_PCI_DSS_40 = "pciDss-4.0"; + public static final String PARAM_OWASP_ASVS = "owaspAsvs"; public static final String PARAM_OWASP_ASVS_40 = "owaspAsvs-4.0"; public static final String PARAM_OWASP_TOP_10 = "owaspTop10"; public static final String PARAM_OWASP_TOP_10_2021 = "owaspTop10-2021"; -- 2.39.5