CheckovProcessReports.java
/*
* Cloudformation Plugin for SonarQube
* Copyright (C) 2019 James Pether Sörling
* james@hack23.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 com.hack23.sonar.cloudformation.reports.process;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.TextPointer;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.scan.filesystem.PathResolver;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import com.hack23.sonar.cloudformation.reports.checkov.CheckovPassedCheck;
import com.hack23.sonar.cloudformation.reports.checkov.CheckovReport;
/**
* The Class CheckovProcessReports.
*/
public final class CheckovProcessReports extends AbstractProcessReports {
/** The Constant SENSOR_NAME. */
public static final String SENSOR_NAME = "Cloudformation Check";
/** The Constant LOGGER. */
private static final Logger LOGGER = Loggers.get(CheckovProcessReports.class);
/** The checkov report reader. */
private final CheckovReportReader checkovReportReader;
/** The path resolver. */
private final PathResolver pathResolver;
/** The file system. */
private final FileSystem fileSystem;
/**
* Instantiates a new checkov process reports.
*
* @param fileSystem the file system
* @param pathResolver the path resolver
*/
public CheckovProcessReports(final FileSystem fileSystem,
final PathResolver pathResolver) {
super();
this.checkovReportReader = new CheckovReportReader();
this.fileSystem = fileSystem;
this.pathResolver = pathResolver;
}
/**
* Process checkov report.
*
* @param context the context
* @param reportFilesProperty the report files property
* @throws IOException Signals that an I/O exception has occurred.
*/
public void processCheckovReport(final SensorContext context,final Optional<String> reportFilesProperty) throws IOException {
if (reportFilesProperty.isPresent()) {
final String reports = reportFilesProperty.get();
LOGGER.info(CloudformationConstants.CHECKOV_REPORT_FILES_PROPERTY + "=" + reports);
final String[] reportFiles = StringUtils.split(reports, ",");
for (final String report : reportFiles) {
LOGGER.info("Processing checkov :" + report);
if (pathResolver.relativeFile(fileSystem.baseDir(), report).exists()) {
handleCheckovReports(context, report);
} else {
LOGGER.warn("Processing checkov:" + report + " missing");
}
}
} else {
LOGGER.warn("Missing property:{}", CloudformationConstants.CHECKOV_REPORT_FILES_PROPERTY);
}
}
/**
* Handle checkov reports.
*
* @param context the context
* @param report the report
* @throws IOException Signals that an I/O exception has occurred.
*/
private void handleCheckovReports(final SensorContext context, final String report) throws IOException {
LOGGER.info("Reading checkov reports:{}", report);
final List<CheckovReport> checkovReportList = checkovReportReader
.readReport(Files.newInputStream(pathResolver.relativeFile(fileSystem.baseDir(), report).toPath()));
for (final CheckovReport checkovReport : checkovReportList) {
final ActiveRules activeRules = context.activeRules();
for (final CheckovPassedCheck failedChecks : checkovReport.getResults().getFailedChecks()) {
final String filename = failedChecks.getFilePath();
LOGGER.info("Checkov scanned file :{}", filename);
final InputFile templateInputFile = findTemplate(fileSystem,
filename.substring(filename.lastIndexOf(File.separator) + 1), filename);
addCheckovIssue(context, activeRules, checkovReport, failedChecks, templateInputFile);
}
}
}
/**
* Adds the checkov issue.
*
* @param context the context
* @param activeRules the active rules
* @param checkovReport the checkov report
* @param failedChecks the failed checks
* @param templateInputFile the template input file
*/
private static void addCheckovIssue(final SensorContext context, final ActiveRules activeRules, final CheckovReport checkovReport,
final CheckovPassedCheck failedChecks, final InputFile templateInputFile) {
String repoName = "cloudformation-plugin-cfn";
if (templateInputFile != null && "terraform".equalsIgnoreCase(templateInputFile.language())) {
repoName = "cloudformation-plugin-terraform";
}
final RuleKey ruleKey = RuleKey.of(repoName,checkovReport.getCheckType() + "-" + failedChecks.getCheckId());
if (activeRules.find(ruleKey) == null) {
LOGGER.warn("No active checkov rule detected for:'{}' with key {} detected in {}",failedChecks.getCheckName(), ruleKey,failedChecks.getFilePath());
return;
}
if (templateInputFile == null) {
LOGGER.warn("File not found {} for rule {} issue not created", failedChecks.getFilePath(), ruleKey);
return;
}
final List<Integer> lineNumbers = failedChecks.getFileLineRange();
if(!lineNumbers.isEmpty()) {
final TextPointer startLine = templateInputFile.selectLine(lineNumbers.get(0)).start();
final TextPointer endLine = templateInputFile.selectLine(lineNumbers.get(lineNumbers.size()-1)).end();
final NewIssue newIssue = context.newIssue().forRule(ruleKey);
final NewIssueLocation location = newIssue.newLocation()
.on(templateInputFile).at(templateInputFile.newRange(startLine, endLine))
.message(failedChecks.getCheckName());
newIssue.at(location).save();
}
}
}