View Javadoc
1   /*
2    * Cloudformation Plugin for SonarQube
3    * Copyright (C) 2019 James Pether Sörling
4    * james@hack23.com
5    *
6    * This program is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 3 of the License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with this program; if not, write to the Free Software Foundation,
18   * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19   */
20  package com.hack23.sonar.cloudformation.reports.process;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.nio.file.Files;
25  import java.util.List;
26  import java.util.Optional;
27  
28  import org.apache.commons.lang3.StringUtils;
29  import org.sonar.api.batch.fs.FileSystem;
30  import org.sonar.api.batch.fs.InputFile;
31  import org.sonar.api.batch.rule.ActiveRules;
32  import org.sonar.api.batch.sensor.SensorContext;
33  import org.sonar.api.batch.sensor.issue.NewIssue;
34  import org.sonar.api.batch.sensor.issue.NewIssueLocation;
35  import org.sonar.api.rule.RuleKey;
36  import org.sonar.api.scan.filesystem.PathResolver;
37  import org.sonar.api.utils.log.Logger;
38  import org.sonar.api.utils.log.Loggers;
39  
40  import com.hack23.sonar.cloudformation.reports.cfnnag.CfnNagScanReport;
41  import com.hack23.sonar.cloudformation.reports.cfnnag.CfnNagViolation;
42  
43  /**
44   * The Class CfnNagProcessReports.
45   */
46  public final class CfnNagProcessReports extends AbstractProcessReports {
47  
48  	/** The Constant UNDEFINED_FAILURE. */
49  	public static final String UNDEFINED_FAILURE = "FUNDEFINED";
50  
51  	/** The Constant UNDEFINED_WARNING. */
52  	public static final String UNDEFINED_WARNING = "WUNDEFINED";
53  
54  	/** The Constant LOGGER. */
55  	private static final Logger LOGGER = Loggers.get(CfnNagProcessReports.class);
56  
57  	/** The cfn nag scan report reader. */
58  	private final CfnNagScanReportReader cfnNagScanReportReader;
59  
60  	/** The path resolver. */
61  	private final PathResolver pathResolver;
62  
63  	/** The file system. */
64  	private final FileSystem fileSystem;
65  
66  
67  	/**
68  	 * Instantiates a new cfn nag process reports.
69  	 *
70  	 * @param fileSystem the file system
71  	 * @param pathResolver the path resolver
72  	 */
73  	public CfnNagProcessReports(final FileSystem fileSystem, final PathResolver pathResolver) {
74  		super();
75  		this.cfnNagScanReportReader = new CfnNagScanReportReader();
76  		this.fileSystem = fileSystem;
77  		this.pathResolver = pathResolver;
78  	}
79  
80  	/**
81  	 * Process cfn nag report.
82  	 *
83  	 * @param context the context
84  	 * @param reportFilesProperty the report files property
85  	 * @throws IOException Signals that an I/O exception has occurred.
86  	 */
87  	public void processCfnNagReport(final SensorContext context, final Optional<String> reportFilesProperty)
88  			throws IOException {
89  		if (reportFilesProperty.isPresent()) {
90  
91  			final String reports = reportFilesProperty.get();
92  			final String[] reportFiles = StringUtils.split(reports, ",");
93  
94  			for (final String report : reportFiles) {
95  				LOGGER.info("Processing cfn-nag :" + report);
96  				if (pathResolver.relativeFile(fileSystem.baseDir(), report).exists()) {
97  
98  					handleCfnNagScanReports(context, report);
99  				} else {
100 					LOGGER.warn("Processing cfn-nag:" + report + " missing");
101 				}
102 			}
103 		} else {
104 			LOGGER.warn("Missing property:{}", CloudformationConstants.CFN_NAG_REPORT_FILES_PROPERTY);
105 		}
106 	}
107 
108 	/**
109 	 * Handle cfn nag scan reports.
110 	 *
111 	 * @param context the context
112 	 * @param report the report
113 	 * @throws IOException Signals that an I/O exception has occurred.
114 	 */
115 	private void handleCfnNagScanReports(final SensorContext context, final String report)
116 			throws IOException {
117 		LOGGER.info("Reading cfn-nag reports:{}", report);
118 		final List<CfnNagScanReport> cfnNagscanReports = cfnNagScanReportReader
119 				.readReport(Files.newInputStream(pathResolver.relativeFile(fileSystem.baseDir(), report).toPath()));
120 
121 		for (final CfnNagScanReport nagScanReport : cfnNagscanReports) {
122 
123 			final String filename = nagScanReport.getFilename();
124 			LOGGER.info("Cfn-nag scanned file :{}", filename);
125 
126 			final InputFile templateInputFile = findTemplate(fileSystem,
127 					filename.substring(filename.lastIndexOf(File.separator) + 1), filename);
128 
129 			final List<CfnNagViolation> violations = nagScanReport.getFileResults().getViolations();
130 			for (final CfnNagViolation cfnNagViolation : violations) {
131 				addIssue(context, cfnNagViolation, templateInputFile);
132 			}
133 		}
134 	}
135 
136 	/**
137 	 * Adds the issue.
138 	 *
139 	 * @param context the context
140 	 * @param violation the violation
141 	 * @param templateInputFile the template input file
142 	 */
143 	private static void addIssue(final SensorContext context, final CfnNagViolation violation,
144 			final InputFile templateInputFile) {
145 		final ActiveRules activeRules = context.activeRules();
146 
147 		if (templateInputFile != null) {
148 
149 			if (violation.getLineNumbers().isEmpty()) {
150 				final NewIssue newIssue = context.newIssue().forRule(RuleKey.of("cloudformation-plugin-cfn", findRuleId(activeRules, violation)));
151 				final NewIssueLocation location = newIssue.newLocation()
152 			            .on(templateInputFile)
153 			            .message(violation.getMessage());
154 			    newIssue.at(location).save();
155 
156 			} else {
157 				final List<Integer> lineNumbers = violation.getLineNumbers();
158 				for (final Integer line : lineNumbers) {
159 					if (line != null && line >= 0) {
160 						final NewIssue newIssue = context.newIssue().forRule(RuleKey.of("cloudformation-plugin-cfn", findRuleId(activeRules, violation)));
161 						final NewIssueLocation location = newIssue.newLocation()
162 					            .on(templateInputFile).at(templateInputFile.selectLine(line))
163 					            .message(violation.getMessage());
164 					    newIssue.at(location).save();
165 
166 					} else {
167 						final NewIssue newIssue = context.newIssue().forRule(RuleKey.of("cloudformation-plugin-cfn", findRuleId(activeRules, violation)));
168 						final NewIssueLocation location = newIssue.newLocation()
169 					            .on(templateInputFile)
170 					            .message(violation.getMessage());
171 					    newIssue.at(location).save();
172 					}
173 				}
174 			}
175 		} else {
176 			final NewIssue newIssue = context.newIssue().forRule(RuleKey.of("cloudformation-plugin-cfn", findRuleId(activeRules, violation)));
177 			final NewIssueLocation location = newIssue.newLocation()
178 		            .on(context.project())
179 		            .message(violation.getMessage());
180 		    newIssue.at(location).save();
181 		}
182 	}
183 
184 	/**
185 	 * Find rule id.
186 	 *
187 	 * @param activeRules the active rules
188 	 * @param violation the violation
189 	 * @return the string
190 	 */
191 	private static String findRuleId(final ActiveRules activeRules, final CfnNagViolation violation) {
192 		final RuleKey ruleKey = RuleKey.of("cloudformation-plugin-cfn", violation.getId());
193 
194 		if (activeRules.find(ruleKey) != null) {
195 			return violation.getId();
196 		} else {
197 			if (violation.getId().startsWith("W")) {
198 				return UNDEFINED_WARNING;
199 			} else {
200 				return UNDEFINED_FAILURE;
201 			}
202 		}
203 	}
204 
205 }