aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorMatteo Mara <matteo.mara@sonarsource.com>2022-07-19 18:08:17 +0200
committersonartech <sonartech@sonarsource.com>2022-07-25 20:03:58 +0000
commit9cd44988c23e6533cbf34e5acc6d225e706f1707 (patch)
tree0a38b2a50f51400ceccf1d3b24a586a033dc9ef3 /server
parent3ffa7edb6113b9f51b1f03396827344561953586 (diff)
downloadsonarqube-9cd44988c23e6533cbf34e5acc6d225e706f1707.tar.gz
sonarqube-9cd44988c23e6533cbf34e5acc6d225e706f1707.zip
SONAR-17061 add PCI DSS to security reports show API
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java28
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java4
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java2
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java33
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityStandardsTest.java10
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java96
-rw-r--r--server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java24
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java112
-rw-r--r--server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java26
9 files changed, 303 insertions, 32 deletions
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
index 5ee631b248c..ac194d590ab 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueDoc.java
@@ -276,13 +276,28 @@ public class IssueDoc extends BaseDoc {
}
@CheckForNull
- public Collection<String> getOwaspTop10() {
- return getNullableField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10);
+ public Collection<String> getPciDss32() {
+ return getNullableField(IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32);
+ }
+
+ public IssueDoc setPciDss32(@Nullable Collection<String> o) {
+ setField(IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32, o);
+ return this;
+ }
+
+ public IssueDoc setPciDss40(@Nullable Collection<String> o) {
+ setField(IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_40, o);
+ return this;
}
@CheckForNull
- public Collection<String> getOwaspTop10For2021() {
- return getNullableField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10_2021);
+ public Collection<String> getPciDss40() {
+ return getNullableField(IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_40);
+ }
+
+ @CheckForNull
+ public Collection<String> getOwaspTop10() {
+ return getNullableField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10);
}
public IssueDoc setOwaspTop10(@Nullable Collection<String> o) {
@@ -290,6 +305,11 @@ public class IssueDoc extends BaseDoc {
return this;
}
+ @CheckForNull
+ public Collection<String> getOwaspTop10For2021() {
+ return getNullableField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10_2021);
+ }
+
public IssueDoc setOwaspTop10For2021(@Nullable Collection<String> o) {
setField(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10_2021, o);
return this;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
index d7dd7fbb541..a0bf4593815 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIndexDefinition.java
@@ -96,6 +96,8 @@ public class IssueIndexDefinition implements IndexDefinition {
public static final String FIELD_ISSUE_STATUS = "status";
public static final String FIELD_ISSUE_TAGS = "tags";
public static final String FIELD_ISSUE_TYPE = "type";
+ public static final String FIELD_ISSUE_PCI_DSS_32 = "pciDss-3.2";
+ public static final String FIELD_ISSUE_PCI_DSS_40 = "pciDss-4.0";
public static final String FIELD_ISSUE_OWASP_TOP_10 = "owaspTop10";
public static final String FIELD_ISSUE_OWASP_TOP_10_2021 = "owaspTop10-2021";
public static final String FIELD_ISSUE_SANS_TOP_25 = "sansTop25";
@@ -164,6 +166,8 @@ public class IssueIndexDefinition implements IndexDefinition {
mapping.keywordFieldBuilder(FIELD_ISSUE_STATUS).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
mapping.keywordFieldBuilder(FIELD_ISSUE_TAGS).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_TYPE).disableNorms().build();
+ mapping.keywordFieldBuilder(FIELD_ISSUE_PCI_DSS_32).disableNorms().build();
+ mapping.keywordFieldBuilder(FIELD_ISSUE_PCI_DSS_40).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_OWASP_TOP_10).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_OWASP_TOP_10_2021).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_SANS_TOP_25).disableNorms().build();
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
index 743f962f618..9586ff3cb55 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/index/IssueIteratorForSingleChunk.java
@@ -235,6 +235,8 @@ class IssueIteratorForSingleChunk implements IssueIterator {
SecurityStandards.SQCategory sqCategory = securityStandards.getSqCategory();
doc.setOwaspTop10(securityStandards.getOwaspTop10());
doc.setOwaspTop10For2021(securityStandards.getOwaspTop10For2021());
+ doc.setPciDss32(securityStandards.getPciDss32());
+ doc.setPciDss40(securityStandards.getPciDss40());
doc.setCwe(securityStandards.getCwe());
doc.setSansTop25(securityStandards.getSansTop25());
doc.setSonarSourceSecurityCategory(sqCategory);
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java
index 4569aeecf17..afb13b4271b 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java
@@ -37,6 +37,8 @@ import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
+import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion.V3_2;
+import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion.V4_0;
import static org.sonar.core.util.stream.MoreCollectors.toList;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
@@ -54,6 +56,8 @@ public final class SecurityStandards {
private static final String OWASP_TOP10_PREFIX = "owaspTop10:";
private static final String OWASP_TOP10_2021_PREFIX = "owaspTop10-2021:";
+ private static final String PCI_DSS_32_PREFIX = V3_2.prefix() + ":";
+ private static final String PCI_DSS_40_PREFIX = V4_0.prefix() + ":";
private static final String CWE_PREFIX = "cwe:";
// See https://www.sans.org/top25-software-errors
private static final Set<String> INSECURE_CWE = new HashSet<>(asList("89", "78", "79", "434", "352", "601"));
@@ -159,6 +163,20 @@ public final class SecurityStandards {
}
}
+ public enum PciDss {
+ R1("1"), R2("2"), R3("3"), R4("4"), R5("5"), R6("6"), R7("7"), R8("8"), R9("9"), R10("10"), R11("11"), R12("12");
+
+ private final String category;
+
+ PciDss(String category) {
+ this.category = category;
+ }
+
+ public String category() {
+ return category;
+ }
+ }
+
public static final Map<SQCategory, Set<String>> CWES_BY_SQ_CATEGORY = ImmutableMap.<SQCategory, Set<String>>builder()
.put(SQCategory.BUFFER_OVERFLOW, Set.of("119", "120", "131", "676", "788"))
.put(SQCategory.SQL_INJECTION, Set.of("89", "564", "943"))
@@ -207,6 +225,14 @@ public final class SecurityStandards {
return cwe;
}
+ public Set<String> getPciDss32() {
+ return toPciDss(standards, PCI_DSS_32_PREFIX);
+ }
+
+ public Set<String> getPciDss40() {
+ return toPciDss(standards, PCI_DSS_40_PREFIX);
+ }
+
public Set<String> getOwaspTop10() {
return toOwaspTop10(standards, OWASP_TOP10_PREFIX);
}
@@ -250,6 +276,13 @@ public final class SecurityStandards {
return new SecurityStandards(standards, cwe, sqCategory, ignoredSQCategories);
}
+ private static Set<String> toPciDss(Set<String> securityStandards, String prefix) {
+ return securityStandards.stream()
+ .filter(s -> s.startsWith(prefix))
+ .map(s -> s.substring(prefix.length()))
+ .collect(toSet());
+ }
+
private static Set<String> toOwaspTop10(Set<String> securityStandards, String prefix) {
return securityStandards.stream()
.filter(s -> s.startsWith(prefix))
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityStandardsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityStandardsTest.java
index bfcf245dfe5..665d27fa357 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityStandardsTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityStandardsTest.java
@@ -19,10 +19,13 @@
*/
package org.sonar.server.security;
+import java.util.Arrays;
import java.util.EnumSet;
+import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
+import org.sonar.server.security.SecurityStandards.PciDss;
import org.sonar.server.security.SecurityStandards.SQCategory;
import static java.util.Collections.emptySet;
@@ -115,4 +118,11 @@ public class SecurityStandardsTest {
sqCategories.remove(expected);
}
}
+
+ @Test
+ public void pciDss_categories_check() {
+ List<String> pciDssCategories = Arrays.stream(PciDss.values()).map(PciDss::category).collect(Collectors.toList());
+
+ assertThat(pciDssCategories).hasSize(12).containsExactly("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12");
+ }
}
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 459a89763fb..d9c53eed857 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
@@ -69,6 +69,7 @@ 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.OwaspTop10Version;
+import org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import org.sonar.core.util.stream.MoreCollectors;
@@ -89,6 +90,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.PciDss;
import org.sonar.server.security.SecurityStandards.SQCategory;
import org.sonar.server.user.UserSession;
import org.sonar.server.view.index.ViewIndexDefinition;
@@ -100,12 +102,12 @@ import static java.util.stream.Collectors.toList;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
+import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
-import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.server.es.BaseDoc.epochMillisToEpochSeconds;
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
@@ -124,6 +126,8 @@ import static org.sonar.server.issue.index.IssueIndex.Facet.FILES;
import static org.sonar.server.issue.index.IssueIndex.Facet.LANGUAGES;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10;
import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10_2021;
+import static org.sonar.server.issue.index.IssueIndex.Facet.PCI_DSS_32;
+import static org.sonar.server.issue.index.IssueIndex.Facet.PCI_DSS_40;
import static org.sonar.server.issue.index.IssueIndex.Facet.PROJECT_UUIDS;
import static org.sonar.server.issue.index.IssueIndex.Facet.RESOLUTIONS;
import static org.sonar.server.issue.index.IssueIndex.Facet.RULES;
@@ -153,6 +157,8 @@ import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_MODU
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10_2021;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_40;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RESOLUTION;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RULE_UUID;
@@ -183,6 +189,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10_2021;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_32;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_40;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
@@ -209,7 +217,7 @@ public class IssueIndex {
private static final String AGG_TO_REVIEW_SECURITY_HOTSPOTS = "toReviewSecurityHotspots";
private static final String AGG_IN_REVIEW_SECURITY_HOTSPOTS = "inReviewSecurityHotspots";
private static final String AGG_REVIEWED_SECURITY_HOTSPOTS = "reviewedSecurityHotspots";
- private static final String AGG_CWES = "cwes";
+ private static final String AGG_DISTRIBUTION = "distribution";
private static final BoolQueryBuilder NON_RESOLVED_VULNERABILITIES_FILTER = boolQuery()
.filter(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name()))
.mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
@@ -245,6 +253,8 @@ public class IssueIndex {
DIRECTORIES(PARAM_DIRECTORIES, FIELD_ISSUE_DIRECTORY_PATH, STICKY, MAX_FACET_SIZE),
ASSIGNEES(PARAM_ASSIGNEES, FIELD_ISSUE_ASSIGNEE_UUID, STICKY, MAX_FACET_SIZE),
ASSIGNED_TO_ME(FACET_ASSIGNED_TO_ME, FIELD_ISSUE_ASSIGNEE_UUID, STICKY, 1),
+ PCI_DSS_32(PARAM_PCI_DSS_32, FIELD_ISSUE_PCI_DSS_32, STICKY, DEFAULT_FACET_SIZE),
+ PCI_DSS_40(PARAM_PCI_DSS_40, FIELD_ISSUE_PCI_DSS_40, STICKY, DEFAULT_FACET_SIZE),
OWASP_TOP_10(PARAM_OWASP_TOP_10, FIELD_ISSUE_OWASP_TOP_10, STICKY, DEFAULT_FACET_SIZE),
OWASP_TOP_10_2021(PARAM_OWASP_TOP_10_2021, FIELD_ISSUE_OWASP_TOP_10_2021, STICKY, DEFAULT_FACET_SIZE),
SANS_TOP_25(PARAM_SANS_TOP_25, FIELD_ISSUE_SANS_TOP_25, STICKY, DEFAULT_FACET_SIZE),
@@ -445,6 +455,8 @@ public class IssueIndex {
filters.addFilter(FIELD_ISSUE_STATUS, STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_STATUS, query.statuses()));
// security category
+ addSecurityCategoryFilter(FIELD_ISSUE_PCI_DSS_32, PCI_DSS_32, query.pciDss32(), filters);
+ addSecurityCategoryFilter(FIELD_ISSUE_PCI_DSS_40, PCI_DSS_40, query.pciDss40(), filters);
addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10, OWASP_TOP_10, query.owaspTop10(), filters);
addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10_2021, OWASP_TOP_10_2021, query.owaspTop10For2021(), filters);
addSecurityCategoryFilter(FIELD_ISSUE_SANS_TOP_25, SANS_TOP_25, query.sansTop25(), filters);
@@ -565,11 +577,11 @@ public class IssueIndex {
private static RequestFiltersComputer newFilterComputer(SearchOptions options, AllFilters allFilters) {
Collection<String> facetNames = options.getFacets();
Set<TopAggregationDefinition<?>> facets = Stream.concat(
- Stream.of(EFFORT_TOP_AGGREGATION),
- facetNames.stream()
- .map(FACETS_BY_NAME::get)
- .filter(Objects::nonNull)
- .map(Facet::getTopAggregationDef))
+ Stream.of(EFFORT_TOP_AGGREGATION),
+ facetNames.stream()
+ .map(FACETS_BY_NAME::get)
+ .filter(Objects::nonNull)
+ .map(Facet::getTopAggregationDef))
.collect(MoreCollectors.toSet(facetNames.size()));
return new RequestFiltersComputer(allFilters, facets);
@@ -767,11 +779,11 @@ public class IssueIndex {
RESOLUTIONS.getName(), RESOLUTIONS.getTopAggregationDef(), RESOLUTIONS.getNumberOfTerms(),
NO_EXTRA_FILTER,
t ->
- // add aggregation of type "missing" to return count of unresolved issues in the facet
- t.subAggregation(
- addEffortAggregationIfNeeded(query, AggregationBuilders
- .missing(RESOLUTIONS.getName() + FACET_SUFFIX_MISSING)
- .field(RESOLUTIONS.getFieldName()))));
+ // add aggregation of type "missing" to return count of unresolved issues in the facet
+ t.subAggregation(
+ addEffortAggregationIfNeeded(query, AggregationBuilders
+ .missing(RESOLUTIONS.getName() + FACET_SUFFIX_MISSING)
+ .field(RESOLUTIONS.getFieldName()))));
esRequest.aggregation(aggregation);
}
@@ -891,10 +903,10 @@ public class IssueIndex {
ASSIGNED_TO_ME.getNumberOfTerms(),
NO_EXTRA_FILTER,
t ->
- // add sub-aggregation to return issue count for current user
- aggregationHelper.getSubAggregationHelper()
- .buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[]{uuid})
- .ifPresent(t::subAggregation));
+ // add sub-aggregation to return issue count for current user
+ aggregationHelper.getSubAggregationHelper()
+ .buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[] {uuid})
+ .ifPresent(t::subAggregation));
esRequest.aggregation(aggregation);
}
}
@@ -1084,31 +1096,56 @@ public class IssueIndex {
return search(request, includeCwe, null);
}
+ public List<SecurityStandardCategoryStatistics> getPciDssReport(String projectUuid, boolean isViewOrApp, PciDssVersion version) {
+ 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());
+ }
+
public List<SecurityStandardCategoryStatistics> getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe, OwaspTop10Version version) {
- String queryKey = version.equals(Y2021) ? FIELD_ISSUE_OWASP_TOP_10_2021 : FIELD_ISSUE_OWASP_TOP_10;
SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
IntStream.rangeClosed(1, 10).mapToObj(i -> "a" + i)
.forEach(owaspCategory -> request.aggregation(
newSecurityReportSubAggregations(
- AggregationBuilders.filter(owaspCategory, boolQuery().filter(termQuery(queryKey, owaspCategory))),
+ AggregationBuilders.filter(owaspCategory, boolQuery().filter(termQuery(version.prefix(), owaspCategory))),
includeCwe,
null)));
return search(request, includeCwe, version.label());
}
- private List<SecurityStandardCategoryStatistics> search(SearchSourceBuilder sourceBuilder, boolean includeCwe, @Nullable String version) {
+ private List<SecurityStandardCategoryStatistics> searchPciDss(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))
+ .collect(MoreCollectors.toList());
+ }
+
+ private List<SecurityStandardCategoryStatistics> search(SearchSourceBuilder sourceBuilder, boolean includeDistribution, @Nullable String version) {
SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType())
.source(sourceBuilder);
SearchResponse response = client.search(request);
return response.getAggregations().asList().stream()
- .map(c -> processSecurityReportIssueSearchResults((ParsedFilter) c, includeCwe, version))
+ .map(c -> processSecurityReportIssueSearchResults((ParsedFilter) c, includeDistribution, version))
.collect(MoreCollectors.toList());
}
- private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResults(ParsedFilter categoryBucket, boolean includeCwe, String version) {
+ private static SecurityStandardCategoryStatistics processPciDssSecurityReportIssueSearchResults(ParsedFilter categoryFilter, String version) {
+ Stream<? extends Terms.Bucket> 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());
+
+ return processSecurityReportCategorySearchResults(categoryFilter, categoryFilter.getName(), children, version);
+ }
+
+ private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResults(ParsedFilter categoryBucket, boolean includeDistribution, String version) {
List<SecurityStandardCategoryStatistics> children = new ArrayList<>();
- if (includeCwe) {
- Stream<? extends Terms.Bucket> stream = ((ParsedStringTerms) categoryBucket.getAggregations().get(AGG_CWES)).getBuckets().stream();
+ if (includeDistribution) {
+ Stream<? extends Terms.Bucket> stream = ((ParsedStringTerms) categoryBucket.getAggregations().get(AGG_DISTRIBUTION)).getBuckets().stream();
children = stream.map(cweBucket -> processSecurityReportCategorySearchResults(cweBucket, cweBucket.getKeyAsString(), null, null)).collect(toList());
}
@@ -1138,10 +1175,21 @@ public class IssueIndex {
reviewedSecurityHotspots, securityReviewRating, children, version);
}
+ private static AggregationBuilder newPciDssSecurityReportSubAggregations(AggregationBuilder categoriesAggs, PciDssVersion version) {
+ AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
+ final TermsAggregationBuilder distributionAggregation = AggregationBuilders.terms(AGG_DISTRIBUTION)
+ .field(version.prefix())
+ // 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));
+
+ return aggregationBuilder;
+ }
+
private static AggregationBuilder newSecurityReportSubAggregations(AggregationBuilder categoriesAggs, boolean includeCwe, @Nullable Collection<String> cwesInCategory) {
AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
if (includeCwe) {
- final TermsAggregationBuilder cwesAgg = AggregationBuilders.terms(AGG_CWES)
+ final TermsAggregationBuilder cwesAgg = AggregationBuilders.terms(AGG_DISTRIBUTION)
.field(FIELD_ISSUE_CWE)
// 100 should be enough to display all CWEs. If not, the UI will be broken anyway
.size(MAX_FACET_SIZE);
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
index fb743517548..77fe8d8ab72 100644
--- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
+++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQuery.java
@@ -80,6 +80,8 @@ public class IssueQuery {
private final Collection<String> tags;
private final Collection<String> types;
private final Collection<String> owaspTop10;
+ private final Collection<String> pciDss32;
+ private final Collection<String> pciDss40;
private final Collection<String> owaspTop10For2021;
private final Collection<String> sansTop25;
private final Collection<String> cwe;
@@ -119,6 +121,8 @@ public class IssueQuery {
this.languages = defaultCollection(builder.languages);
this.tags = defaultCollection(builder.tags);
this.types = defaultCollection(builder.types);
+ this.pciDss32 = defaultCollection(builder.pciDss32);
+ this.pciDss40 = defaultCollection(builder.pciDss40);
this.owaspTop10 = defaultCollection(builder.owaspTop10);
this.owaspTop10For2021 = defaultCollection(builder.owaspTop10For2021);
this.sansTop25 = defaultCollection(builder.sansTop25);
@@ -213,6 +217,14 @@ public class IssueQuery {
return types;
}
+ public Collection<String> pciDss32() {
+ return pciDss32;
+ }
+
+ public Collection<String> pciDss40() {
+ return pciDss40;
+ }
+
public Collection<String> owaspTop10() {
return owaspTop10;
}
@@ -333,6 +345,8 @@ public class IssueQuery {
private Collection<String> languages;
private Collection<String> tags;
private Collection<String> types;
+ private Collection<String> pciDss32;
+ private Collection<String> pciDss40;
private Collection<String> owaspTop10;
private Collection<String> owaspTop10For2021;
private Collection<String> sansTop25;
@@ -448,6 +462,16 @@ public class IssueQuery {
return this;
}
+ public Builder pciDss32(@Nullable Collection<String> o) {
+ this.pciDss32 = o;
+ return this;
+ }
+
+ public Builder pciDss40(@Nullable Collection<String> o) {
+ this.pciDss40 = o;
+ return this;
+ }
+
public Builder owaspTop10(@Nullable Collection<String> o) {
this.owaspTop10 = o;
return this;
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 bede12e3cdd..82bf992f1b8 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
@@ -29,6 +29,7 @@ import org.sonar.api.impl.utils.TestSystem2;
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.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
@@ -40,9 +41,11 @@ import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.view.index.ViewDoc;
import org.sonar.server.view.index.ViewIndexer;
+import static java.lang.Integer.parseInt;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.singletonList;
+import static java.util.Comparator.comparing;
import static java.util.TimeZone.getTimeZone;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
@@ -207,6 +210,27 @@ public class IssueIndexSecurityReportsTest {
}
@Test
+ public void getPciDss32Report_aggregation() {
+ List<SecurityStandardCategoryStatistics> pciDss32Report = indexIssuesAndAssertPciDss32Report();
+
+ assertThat(pciDss32Report)
+ .isNotEmpty();
+
+ assertThat(pciDss32Report.get(0).getChildren()).hasSize(2);
+ assertThat(pciDss32Report.get(1).getChildren()).isEmpty();
+ assertThat(pciDss32Report.get(2).getChildren()).hasSize(4);
+ assertThat(pciDss32Report.get(3).getChildren()).isEmpty();
+ assertThat(pciDss32Report.get(4).getChildren()).isEmpty();
+ assertThat(pciDss32Report.get(5).getChildren()).hasSize(2);
+ assertThat(pciDss32Report.get(6).getChildren()).isEmpty();
+ assertThat(pciDss32Report.get(7).getChildren()).hasSize(1);
+ assertThat(pciDss32Report.get(8).getChildren()).isEmpty();
+ assertThat(pciDss32Report.get(9).getChildren()).hasSize(1);
+ assertThat(pciDss32Report.get(10).getChildren()).isEmpty();
+ assertThat(pciDss32Report.get(11).getChildren()).isEmpty();
+ }
+
+ @Test
public void getOwaspTop10Report_aggregation_with_cwe() {
List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
@@ -286,6 +310,47 @@ public class IssueIndexSecurityReportsTest {
return owaspTop10Report;
}
+ private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertPciDss32Report() {
+ ComponentDto project = newPrivateProjectDto();
+ indexIssues(
+ newDoc("openvul1", project).setPciDss32(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+ .setSeverity(Severity.MAJOR),
+ newDoc("openvul2", project).setPciDss32(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+ .setSeverity(Severity.MINOR),
+ newDoc("openvul3", project).setPciDss32(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+ .setSeverity(Severity.MINOR),
+ newDoc("notpcidssvul", project).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
+ newDoc("toreviewhotspot1", project).setPciDss32(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
+ .setStatus(Issue.STATUS_TO_REVIEW),
+ newDoc("toreviewhotspot2", project).setPciDss32(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
+ newDoc("reviewedHotspot", project).setPciDss32(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
+ .setResolution(Issue.RESOLUTION_FIXED),
+ newDoc("notpcidsshotspot", project).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
+
+ List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(project.uuid(), false, RulesDefinition.PciDssVersion.V3_2).stream()
+ .sorted(comparing(s -> parseInt(s.getCategory())))
+ .collect(toList());
+ assertThat(pciDssReport)
+ .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));
+
+ return pciDssReport;
+ }
+
private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwasp2021Report(boolean includeCwe) {
ComponentDto project = newPrivateProjectDto();
indexIssues(
@@ -396,6 +461,53 @@ public class IssueIndexSecurityReportsTest {
}
@Test
+ public void getPciDssReport_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).setPciDss32(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+ .setSeverity(Severity.MAJOR),
+ newDoc("openvul2", project2).setPciDss32(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+ .setSeverity(Severity.MINOR),
+ newDoc("openvul3", project1).setPciDss32(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+ .setSeverity(Severity.MINOR),
+ newDoc("notpcidssvul", project1).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
+ newDoc("toreviewhotspot1", project2).setPciDss32(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
+ .setStatus(Issue.STATUS_TO_REVIEW),
+ newDoc("toreviewhotspot2", project1).setPciDss32(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
+ newDoc("reviewedHotspot", project2).setPciDss32(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
+ .setResolution(Issue.RESOLUTION_FIXED),
+ newDoc("notpcidsshotspot", project1).setPciDss32(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<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(portfolio1.uuid(), true, RulesDefinition.PciDssVersion.V3_2).stream()
+ .sorted(comparing(s -> parseInt(s.getCategory())))
+ .collect(toList());
+ assertThat(pciDssReport)
+ .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));
+ }
+
+ @Test
public void getCWETop25Report_aggregation() {
ComponentDto project = newPrivateProjectDto();
indexIssues(
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java
index 907e5b34bac..5215a89d36f 100644
--- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java
+++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryTest.java
@@ -50,8 +50,6 @@ public class IssueQueryTest {
.languages(List.of("xoo"))
.tags(List.of("tag1", "tag2"))
.types(List.of("RELIABILITY", "SECURITY"))
- .owaspTop10(List.of("a1", "a2"))
- .owaspTop10For2021(List.of("a3", "a4"))
.sansTop25(List.of("insecure-interaction", "porous-defenses"))
.cwe(List.of("12", "125"))
.branchUuid("my_branch")
@@ -76,8 +74,6 @@ public class IssueQueryTest {
assertThat(query.languages()).containsOnly("xoo");
assertThat(query.tags()).containsOnly("tag1", "tag2");
assertThat(query.types()).containsOnly("RELIABILITY", "SECURITY");
- assertThat(query.owaspTop10()).containsOnly("a1", "a2");
- assertThat(query.owaspTop10For2021()).containsOnly("a3", "a4");
assertThat(query.sansTop25()).containsOnly("insecure-interaction", "porous-defenses");
assertThat(query.cwe()).containsOnly("12", "125");
assertThat(query.branchUuid()).isEqualTo("my_branch");
@@ -95,6 +91,28 @@ public class IssueQueryTest {
}
@Test
+ public void build_pci_dss_query() {
+ IssueQuery query = IssueQuery.builder()
+ .pciDss32(List.of("1.2.3", "3.2.1"))
+ .pciDss40(List.of("3.4.5", "5.6"))
+ .build();
+
+ assertThat(query.pciDss32()).containsOnly("1.2.3", "3.2.1");
+ assertThat(query.pciDss40()).containsOnly("3.4.5", "5.6");
+ }
+
+ @Test
+ public void build_owasp_query() {
+ IssueQuery query = IssueQuery.builder()
+ .owaspTop10(List.of("a1", "a2"))
+ .owaspTop10For2021(List.of("a3", "a4"))
+ .build();
+
+ assertThat(query.owaspTop10()).containsOnly("a1", "a2");
+ assertThat(query.owaspTop10For2021()).containsOnly("a3", "a4");
+ }
+
+ @Test
public void build_query_without_dates() {
IssueQuery query = IssueQuery.builder()
.issueKeys(List.of("ABCDE"))