]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22542 Update XOO plugin to be able to manage security standards
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Thu, 18 Jul 2024 13:33:01 +0000 (15:33 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 23 Jul 2024 20:02:45 +0000 (20:02 +0000)
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneVulnerabilityPerSecurityStandardSensor.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/OneVulnerabilityPerSecurityStandardSensorTest.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java
plugins/sonar-xoo-plugin/src/test/resources/org/sonar/xoo/rule/vulnerabilities.xoo [new file with mode: 0644]

index 277a14bea0031b602d1d92dd6942b343382eeaa6..35914a7a95a6926cad5f87eb919455ad54b6d286 100644 (file)
@@ -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 (file)
index 0000000..f20ad1f
--- /dev/null
@@ -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);
+    }
+  }
+
+}
index 374ba1160476ea1a04143d2f5aa49866c5a2ad20..c02e8f42e99aec9d77bb663921cb019757f25895 100644 (file)
@@ -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 (file)
index 0000000..dd9026c
--- /dev/null
@@ -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();
+  }
+}
index 82253f62328be05acb39666d7369262cade81587..9ca1e62e02c4e21c0011cd42fb3e1398db5e5990 100644 (file)
@@ -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 (file)
index 0000000..64a049a
--- /dev/null
@@ -0,0 +1,5 @@
+line1
+line2
+stig-ASD_V5R3:V12345
+line4
+stig-ASD_V5R3:V12346