diff options
author | Léo Geoffroy <leo.geoffroy@sonarsource.com> | 2024-07-18 15:33:01 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-07-23 20:02:45 +0000 |
commit | 6cef4199aaf67b3757947c379a76e575aff8578d (patch) | |
tree | d0aa45f6cc1aee73239841cfdee08a6196e4bcb5 /plugins/sonar-xoo-plugin | |
parent | dcdb0f14b776a8e65ea20dab77ba2486d932b30f (diff) | |
download | sonarqube-6cef4199aaf67b3757947c379a76e575aff8578d.tar.gz sonarqube-6cef4199aaf67b3757947c379a76e575aff8578d.zip |
SONAR-22542 Update XOO plugin to be able to manage security standards
Diffstat (limited to 'plugins/sonar-xoo-plugin')
6 files changed, 266 insertions, 7 deletions
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java index 277a14bea00..35914a7a95a 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java @@ -69,6 +69,7 @@ import org.sonar.xoo.rule.OnePredefinedAndAdHocRuleExternalIssuePerLineSensor; import org.sonar.xoo.rule.OnePredefinedRuleExternalIssuePerLineSensor; import org.sonar.xoo.rule.OneQuickFixPerLineSensor; import org.sonar.xoo.rule.OneVulnerabilityIssuePerProjectSensor; +import org.sonar.xoo.rule.OneVulnerabilityPerSecurityStandardSensor; import org.sonar.xoo.rule.RandomAccessSensor; import org.sonar.xoo.rule.SaveDataTwiceSensor; import org.sonar.xoo.rule.Xoo2BasicProfile; @@ -181,6 +182,7 @@ public class XooPlugin implements Plugin { OneBugIssuePerLineSensor.class, OneCodeSmellIssuePerLineSensor.class, OneVulnerabilityIssuePerProjectSensor.class, + OneVulnerabilityPerSecurityStandardSensor.class, DeprecatedGlobalSensor.class, GlobalProjectSensor.class, diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneVulnerabilityPerSecurityStandardSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneVulnerabilityPerSecurityStandardSensor.java new file mode 100644 index 00000000000..f20ad1fefea --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneVulnerabilityPerSecurityStandardSensor.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.xoo.rule; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Map; +import java.util.stream.Collectors; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.rule.ActiveRule; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.issue.NewIssue; +import org.sonar.api.rule.RuleKey; +import org.sonar.xoo.Xoo; + +/** + * Raise a vulnerability issue per standard that are defined on the line. + * Eg. if line contains: owaspAsvs-4.0:1.4.2 will raise an issue with the corresponding standard. + */ +public class OneVulnerabilityPerSecurityStandardSensor implements Sensor { + + public static final String RULE_KEY_PREFIX = "OneVulnerabilityPerLine_"; + + private final FileSystem fs; + private final ActiveRules activeRules; + + public OneVulnerabilityPerSecurityStandardSensor(FileSystem fs, ActiveRules activeRules) { + this.fs = fs; + this.activeRules = activeRules; + } + + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .onlyOnLanguage(Xoo.KEY) + .createIssuesForRuleRepository(XooRulesDefinition.XOO_REPOSITORY); + } + + @Override + public void execute(SensorContext context) { + doAnalyse(context, Xoo.KEY); + } + + private void doAnalyse(SensorContext context, String languageKey) { + String rulePrefix = languageKey + ":" + RULE_KEY_PREFIX; + Map<String, RuleKey> rulesBySecurityStandard = activeRules.findAll().stream() + .filter(r -> r.ruleKey().toString().startsWith(rulePrefix)) + .collect(Collectors.toMap(c -> c.ruleKey().toString().substring(rulePrefix.length()), ActiveRule::ruleKey)); + + for (InputFile inputFile : fs.inputFiles(fs.predicates().hasLanguage(languageKey))) { + for (String securityStandard : rulesBySecurityStandard.keySet()) { + processFile(inputFile, context, rulesBySecurityStandard.get(securityStandard), securityStandard); + } + } + } + + + protected void processFile(InputFile inputFile, SensorContext context, RuleKey ruleKey, String securityStandard) { + try { + int[] lineCounter = {1}; + try (InputStreamReader isr = new InputStreamReader(inputFile.inputStream(), inputFile.charset()); + BufferedReader reader = new BufferedReader(isr)) { + reader.lines().forEachOrdered(lineStr -> { + + if (lineStr.contains(securityStandard)) { + NewIssue newIssue = context.newIssue(); + newIssue + .forRule(ruleKey) + .at(newIssue.newLocation() + .on(inputFile) + .at(inputFile.newRange(lineCounter[0], lineStr.indexOf(securityStandard), lineCounter[0], lineStr.indexOf(securityStandard) + securityStandard.length()))) + .save(); + } + lineCounter[0]++; + }); + } + } catch (IOException e) { + throw new IllegalStateException("Fail to process " + inputFile, e); + } + } + +} diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java index 374ba116047..c02e8f42e99 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java @@ -21,6 +21,7 @@ package org.sonar.xoo.rule; import javax.annotation.Nullable; import org.sonar.api.SonarRuntime; +import org.sonar.api.config.Configuration; import org.sonar.api.issue.impact.Severity; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleScope; @@ -34,10 +35,10 @@ import org.sonar.api.utils.Version; import org.sonar.xoo.Xoo; import org.sonar.xoo.Xoo2; import org.sonar.xoo.checks.Check; -import org.sonar.xoo.rule.variant.HotspotWithCodeVariantsSensor; import org.sonar.xoo.rule.hotspot.HotspotWithContextsSensor; import org.sonar.xoo.rule.hotspot.HotspotWithSingleContextSensor; import org.sonar.xoo.rule.hotspot.HotspotWithoutContextSensor; +import org.sonar.xoo.rule.variant.HotspotWithCodeVariantsSensor; import org.sonar.xoo.rule.variant.IssueWithCodeVariantsSensor; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; @@ -63,12 +64,16 @@ public class XooRulesDefinition implements RulesDefinition { @Nullable private final Version version; + @Nullable + private final Configuration configuration; + public XooRulesDefinition() { - this(null); + this(null, null); } - public XooRulesDefinition(@Nullable SonarRuntime sonarRuntime) { + public XooRulesDefinition(@Nullable SonarRuntime sonarRuntime, @Nullable Configuration configuration) { this.version = sonarRuntime != null ? sonarRuntime.getApiVersion() : null; + this.configuration = configuration; } @Override @@ -215,7 +220,6 @@ public class XooRulesDefinition implements RulesDefinition { .setType(RuleType.SECURITY_HOTSPOT); addAllDescriptionSections(hotspotWithRangeAndMultipleLocations, "Hotspot with range and multiple locations"); - NewRule issueOnEachFileWithExtUnknown = repo.createRule(OneIssuePerUnknownFileSensor.RULE_KEY).setName("Creates issues on each file with extension 'unknown'"); addAllDescriptionSections(issueOnEachFileWithExtUnknown, "This issue is generated on each file with extenstion 'unknown'"); @@ -241,6 +245,19 @@ public class XooRulesDefinition implements RulesDefinition { .setType(RuleType.VULNERABILITY); addAllDescriptionSections(oneVulnerabilityIssuePerProject, "Generate an issue on each project"); + if (configuration != null && configuration.get("sonar.xoo.OneVulnerabilityPerLine.securityStandards").isPresent()) { + String[] standards = configuration.get("sonar.xoo.OneVulnerabilityPerLine.securityStandards").get().split(","); + for (String standard : standards) { + NewRule oneVulnerabilityIssuePerSecurityStandard = repo.createRule(OneVulnerabilityPerSecurityStandardSensor.RULE_KEY_PREFIX + standard) + .setName("One Vulnerability per line containing '%s'".formatted(standard)) + .addDefaultImpact(SoftwareQuality.SECURITY, Severity.MEDIUM) + .setCleanCodeAttribute(CleanCodeAttribute.TRUSTWORTHY) + .setType(RuleType.VULNERABILITY); + addSecurityStandard(oneVulnerabilityIssuePerSecurityStandard, standard); + addAllDescriptionSections(oneVulnerabilityIssuePerSecurityStandard, "Generate an issue on each line containing '" + standard + "'."); + } + } + oneVulnerabilityIssuePerProject .setDebtRemediationFunction(oneVulnerabilityIssuePerProject.debtRemediationFunctions().linearWithOffset("25min", "1h")) .setGapDescription("A certified architect will need roughly half an hour to start working on removal of project, " + @@ -297,6 +314,13 @@ public class XooRulesDefinition implements RulesDefinition { .addOwaspAsvs(OwaspAsvsVersion.V4_0, "11.1.2", "14.5.1", "14.5.4"); } + if (version != null && version.isGreaterThanOrEqual(Version.create(10, 7))) { + hotspot + .addStig(StigVersion.ASD_V5R3, "V-222643", "V-222564", "V-222655"); + oneVulnerabilityIssuePerProject + .addStig(StigVersion.ASD_V5R3, "V-222480", "V-222473", "V-222524"); + } + NewRule hotspotWithContexts = repo.createRule(HotspotWithContextsSensor.RULE_KEY) .setName("Find security hotspots with contexts") .setType(RuleType.SECURITY_HOTSPOT) @@ -320,6 +344,33 @@ public class XooRulesDefinition implements RulesDefinition { repo.done(); } + private void addSecurityStandard(NewRule rule, String standard) { + String[] splitStandard = standard.split(":"); + switch (splitStandard[0]) { + case "cwe": + rule.addCwe(Integer.parseInt(splitStandard[1])); + break; + case "owaspTop10": + rule.addOwaspTop10(Y2017, OwaspTop10.valueOf(splitStandard[1])); + break; + case "owaspTop10-2021": + rule.addOwaspTop10(Y2021, OwaspTop10.valueOf(splitStandard[1])); + break; + case "stig-ASD_V5R3": + if (version != null && version.isGreaterThanOrEqual(Version.create(10, 7))) { + rule.addStig(StigVersion.ASD_V5R3, splitStandard[1]); + } + break; + case "pciDss-3.2": + rule.addPciDss(PciDssVersion.V3_2, splitStandard[1]); + break; + case "pciDss-4.0": + rule.addPciDss(PciDssVersion.V4_0, splitStandard[1]); + default: + throw new IllegalArgumentException("Unknown standard: " + standard); + } + } + private static void defineRulesXooExternalWithCct(Context context) { NewRepository repo = context.createExternalRepository(OneExternalIssueCctPerLineSensor.ENGINE_ID, Xoo.KEY).setName(OneExternalIssueCctPerLineSensor.ENGINE_ID); diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneVulnerabilityPerSecurityStandardSensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneVulnerabilityPerSecurityStandardSensorTest.java new file mode 100644 index 00000000000..dd9026cee43 --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneVulnerabilityPerSecurityStandardSensorTest.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.xoo.rule; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.batch.rule.ActiveRule; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.internal.apachecommons.io.IOUtils; +import org.sonar.api.rule.RuleKey; +import org.sonar.xoo.Xoo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class OneVulnerabilityPerSecurityStandardSensorTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private final ActiveRules activeRules = mock(ActiveRules.class); + + @Before + public void before() { + List<ActiveRule> rules = List.of(mockActiveRule("OneVulnerabilityPerLine_stig-ASD_V5R3:V12345"), + mockActiveRule("OneVulnerabilityPerLine_stig-ASD_V5R3:V12346")); + when(activeRules.findAll()).thenReturn(rules); + } + + private ActiveRule mockActiveRule(String ruleKey) { + ActiveRule activeRuleMock = mock(ActiveRule.class); + when(activeRuleMock.ruleKey()).thenReturn(RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, ruleKey)); + return activeRuleMock; + } + + @Test + public void execute_dataAndExecutionFlowsAreDetectedAndMessageIsFormatted() throws IOException { + DefaultInputFile inputFile = newTestFile(IOUtils.toString(getClass().getResource("vulnerabilities.xoo"), StandardCharsets.UTF_8)); + + DefaultFileSystem fs = new DefaultFileSystem(temp.newFolder()); + fs.add(inputFile); + + OneVulnerabilityPerSecurityStandardSensor sensor = new OneVulnerabilityPerSecurityStandardSensor(fs, activeRules); + + SensorContextTester sensorContextTester = SensorContextTester.create(fs.baseDir()); + sensorContextTester.setFileSystem(fs); + sensor.execute(sensorContextTester); + + assertThat(sensorContextTester.allIssues()).hasSize(2); + } + + private DefaultInputFile newTestFile(String content) { + return new TestInputFileBuilder("foo", "src/vulnerabilities.xoo") + .setLanguage(Xoo.KEY) + .setType(InputFile.Type.MAIN) + .setCharset(Charset.defaultCharset()) + .setContents(content) + .build(); + } +} diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java index 82253f62328..9ca1e62e02c 100644 --- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java +++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java @@ -23,6 +23,7 @@ import org.junit.Before; import org.junit.Test; import org.sonar.api.SonarEdition; import org.sonar.api.SonarQubeSide; +import org.sonar.api.config.Configuration; import org.sonar.api.impl.server.RulesDefinitionContext; import org.sonar.api.internal.SonarRuntimeImpl; import org.sonar.api.server.debt.DebtRemediationFunction; @@ -32,11 +33,12 @@ import org.sonar.xoo.rule.hotspot.HotspotWithContextsSensor; import org.sonar.xoo.rule.hotspot.HotspotWithoutContextSensor; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; public class XooRulesDefinitionTest { - private XooRulesDefinition def = new XooRulesDefinition(SonarRuntimeImpl.forSonarQube(Version.create(9, 3), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY)); + private XooRulesDefinition def = new XooRulesDefinition(SonarRuntimeImpl.forSonarQube(Version.create(10, 7), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY), mock(Configuration.class)); private RulesDefinition.Context context = new RulesDefinitionContext(); @@ -68,7 +70,10 @@ public class XooRulesDefinitionTest { assertThat(rule.securityStandards()) .isNotEmpty() .containsExactlyInAnyOrder("cwe:1", "cwe:89", "cwe:123", "cwe:863", "owaspTop10:a1", "owaspTop10:a3", - "owaspTop10-2021:a3", "owaspTop10-2021:a2"); + "owaspTop10-2021:a3", "owaspTop10-2021:a2", "owaspAsvs-4.0:2.8.7", "owaspAsvs-4.0:3.1.1", + "owaspAsvs-4.0:4.2.2", "pciDss-3.2:4.2", "pciDss-3.2:4.2b", "pciDss-3.2:6.5.1", + "pciDss-3.2:6.5a.1b", "pciDss-4.0:4.1", "pciDss-4.0:4.2c", "pciDss-4.0:6.5.1", "pciDss-4.0:6.5a.1", + "stig-ASD_V5R3:V-222564", "stig-ASD_V5R3:V-222643", "stig-ASD_V5R3:V-222655"); } @Test @@ -93,7 +98,10 @@ public class XooRulesDefinitionTest { assertThat(rule.securityStandards()) .isNotEmpty() .containsExactlyInAnyOrder("cwe:250", "cwe:546", "cwe:564", "cwe:943", "owaspTop10-2021:a6", "owaspTop10-2021:a9", - "owaspTop10:a10", "owaspTop10:a9"); + "owaspTop10:a10", "owaspTop10:a9", + "owaspAsvs-4.0:11.1.2", "owaspAsvs-4.0:14.5.1", "owaspAsvs-4.0:14.5.4", + "pciDss-3.2:10.1a.2c", "pciDss-3.2:10.2", "pciDss-4.0:10.1", "pciDss-4.0:10.1a.2b", + "stig-ASD_V5R3:V-222473", "stig-ASD_V5R3:V-222480", "stig-ASD_V5R3:V-222524"); } @Test diff --git a/plugins/sonar-xoo-plugin/src/test/resources/org/sonar/xoo/rule/vulnerabilities.xoo b/plugins/sonar-xoo-plugin/src/test/resources/org/sonar/xoo/rule/vulnerabilities.xoo new file mode 100644 index 00000000000..64a049aee12 --- /dev/null +++ b/plugins/sonar-xoo-plugin/src/test/resources/org/sonar/xoo/rule/vulnerabilities.xoo @@ -0,0 +1,5 @@ +line1 +line2 +stig-ASD_V5R3:V12345 +line4 +stig-ASD_V5R3:V12346 |