Skip to content

Commit 8512761

Browse files
Add a parser for AWS CodeGuru Security JSON output (#1515)
1 parent 28a880b commit 8512761

7 files changed

Lines changed: 747 additions & 1 deletion

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package edu.hm.hafner.analysis.parser;
2+
3+
import org.json.JSONArray;
4+
import org.json.JSONObject;
5+
6+
import edu.hm.hafner.analysis.Issue;
7+
import edu.hm.hafner.analysis.IssueBuilder;
8+
import edu.hm.hafner.analysis.Report;
9+
import edu.umd.cs.findbugs.annotations.CheckForNull;
10+
11+
import java.io.Serial;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
/**
16+
* A parser for AWS CodeGuru Security JSON output.
17+
*
18+
* @author Akash Manna
19+
* @see <a href="https://docs.aws.amazon.com/cli/latest/reference/codeguru-security/get-findings.html">AWS CodeGuru
20+
* Security get-findings</a>
21+
*/
22+
public class CodeGuruSecurityParser extends JsonIssueParser {
23+
@Serial
24+
private static final long serialVersionUID = 1187547319271125446L;
25+
26+
private static final String FINDINGS = "findings";
27+
private static final String TITLE = "title";
28+
private static final String DESCRIPTION = "description";
29+
private static final String SEVERITY = "severity";
30+
private static final String TYPE = "type";
31+
private static final String RULE_ID = "ruleId";
32+
private static final String DETECTOR_ID = "detectorId";
33+
private static final String DETECTOR_NAME = "detectorName";
34+
private static final String VULNERABILITY = "vulnerability";
35+
private static final String FILE_PATH = "filePath";
36+
private static final String PATH = "path";
37+
private static final String NAME = "name";
38+
private static final String START_LINE = "startLine";
39+
private static final String END_LINE = "endLine";
40+
private static final String RECOMMENDATION = "recommendation";
41+
private static final String TEXT = "text";
42+
private static final String URL = "url";
43+
private static final String REFERENCE_URLS = "referenceUrls";
44+
private static final String SUGGESTED_FIXES = "suggestedFixes";
45+
private static final String FIX_TITLE = "title";
46+
private static final String FIX_DESCRIPTION = "description";
47+
private static final String CODE_SNIPPET = "codeSnippet";
48+
private static final String CONTENT = "content";
49+
private static final String NUMBER = "number";
50+
51+
@Override
52+
protected void parseJsonObject(final Report report, final JSONObject jsonReport, final IssueBuilder issueBuilder) {
53+
var findings = jsonReport.optJSONArray(FINDINGS);
54+
if (findings == null) {
55+
return;
56+
}
57+
58+
for (int i = 0; i < findings.length(); i++) {
59+
var finding = findings.optJSONObject(i);
60+
if (finding != null) {
61+
report.add(convertToIssue(finding, issueBuilder));
62+
}
63+
}
64+
}
65+
66+
private Issue convertToIssue(final JSONObject finding, final IssueBuilder issueBuilder) {
67+
var vulnerability = finding.optJSONObject(VULNERABILITY);
68+
69+
issueBuilder
70+
.setType(firstNonBlank(finding, RULE_ID, DETECTOR_ID, DETECTOR_NAME, TYPE))
71+
.setMessage(finding.optString(TITLE, ""))
72+
.guessSeverity(finding.optString(SEVERITY, "Info"))
73+
.setDescription(buildDescription(finding, vulnerability));
74+
75+
if (vulnerability != null) {
76+
var filePath = vulnerability.optJSONObject(FILE_PATH);
77+
var fileName = firstNonBlank(filePath, PATH, NAME);
78+
issueBuilder.setFileName(fileName);
79+
if (!fileName.isEmpty()) {
80+
issueBuilder.setLineStart(filePath.optInt(START_LINE, 0))
81+
.setLineEnd(filePath.optInt(END_LINE, 0));
82+
}
83+
}
84+
85+
return issueBuilder.buildAndClean();
86+
}
87+
88+
private String buildDescription(final JSONObject finding, @CheckForNull final JSONObject vulnerability) {
89+
var sections = new ArrayList<String>();
90+
91+
appendIfNotBlank(sections, finding.optString(DESCRIPTION, ""));
92+
93+
var recommendation = finding.optJSONObject(RECOMMENDATION);
94+
if (recommendation != null) {
95+
appendIfNotBlank(sections, recommendation.optString(TEXT, ""));
96+
appendIfNotBlank(sections, recommendation.optString(URL, ""));
97+
}
98+
99+
appendReferenceUrls(sections, vulnerability);
100+
appendSuggestedFixes(sections, finding.optJSONArray(SUGGESTED_FIXES));
101+
appendCodeSnippets(sections, vulnerability == null ? null : vulnerability.optJSONArray(CODE_SNIPPET));
102+
103+
return String.join("\n\n", sections);
104+
}
105+
106+
private void appendReferenceUrls(final List<String> sections, @CheckForNull final JSONObject vulnerability) {
107+
if (vulnerability == null) {
108+
return;
109+
}
110+
111+
var referenceUrls = vulnerability.optJSONArray(REFERENCE_URLS);
112+
if (referenceUrls == null || referenceUrls.isEmpty()) {
113+
return;
114+
}
115+
116+
var urls = new ArrayList<String>();
117+
for (int i = 0; i < referenceUrls.length(); i++) {
118+
var url = referenceUrls.optString(i, "").trim();
119+
if (!url.isBlank()) {
120+
urls.add(url);
121+
}
122+
}
123+
124+
if (!urls.isEmpty()) {
125+
sections.add("References: " + String.join(", ", urls));
126+
}
127+
}
128+
129+
private void appendSuggestedFixes(final List<String> sections, @CheckForNull final JSONArray suggestedFixes) {
130+
if (suggestedFixes == null || suggestedFixes.isEmpty()) {
131+
return;
132+
}
133+
134+
var fixes = new ArrayList<String>();
135+
for (int i = 0; i < suggestedFixes.length(); i++) {
136+
var fix = formatSuggestedFix(suggestedFixes.optJSONObject(i));
137+
if (fix != null) {
138+
fixes.add(fix);
139+
}
140+
}
141+
142+
if (!fixes.isEmpty()) {
143+
sections.add("Suggested fixes: " + String.join(" | ", fixes));
144+
}
145+
}
146+
147+
@CheckForNull
148+
private String formatSuggestedFix(@CheckForNull final JSONObject suggestedFix) {
149+
if (suggestedFix == null) {
150+
return null;
151+
}
152+
153+
var title = suggestedFix.optString(FIX_TITLE, "").trim();
154+
var description = suggestedFix.optString(FIX_DESCRIPTION, "").trim();
155+
if (title.isBlank()) {
156+
return description.isBlank() ? null : description;
157+
}
158+
if (description.isBlank()) {
159+
return title;
160+
}
161+
162+
return title + ": " + description;
163+
}
164+
165+
private void appendCodeSnippets(final List<String> sections, @CheckForNull final JSONArray codeSnippet) {
166+
if (codeSnippet == null || codeSnippet.isEmpty()) {
167+
return;
168+
}
169+
170+
var lines = new ArrayList<String>();
171+
for (int i = 0; i < codeSnippet.length(); i++) {
172+
var codeLine = codeSnippet.optJSONObject(i);
173+
if (codeLine == null) {
174+
continue;
175+
}
176+
177+
var number = codeLine.optInt(NUMBER, 0);
178+
var content = codeLine.optString(CONTENT, "").trim();
179+
if (!content.isBlank()) {
180+
lines.add(number > 0 ? number + ": " + content : content);
181+
}
182+
}
183+
184+
if (!lines.isEmpty()) {
185+
sections.add("Code snippet:\n" + String.join("\n", lines));
186+
}
187+
}
188+
189+
private void appendIfNotBlank(final List<String> sections, final String value) {
190+
var trimmed = value.trim();
191+
if (!trimmed.isBlank()) {
192+
sections.add(trimmed);
193+
}
194+
}
195+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package edu.hm.hafner.analysis.registry;
2+
3+
import edu.hm.hafner.analysis.IssueParser;
4+
import edu.hm.hafner.analysis.Report.IssueType;
5+
import edu.hm.hafner.analysis.parser.CodeGuruSecurityParser;
6+
7+
/**
8+
* A descriptor for AWS CodeGuru Security.
9+
*
10+
* @author Akash Manna
11+
*/
12+
class CodeGuruSecurityDescriptor extends ParserDescriptor {
13+
private static final String ID = "codeguru-security";
14+
private static final String NAME = "AWS CodeGuru Security";
15+
16+
CodeGuruSecurityDescriptor() {
17+
super(ID, NAME);
18+
}
19+
20+
@Override
21+
public IssueType getType() {
22+
return IssueType.VULNERABILITY;
23+
}
24+
25+
@Override
26+
public IssueParser create(final Option... options) {
27+
return new CodeGuruSecurityParser();
28+
}
29+
30+
@Override
31+
public String getPattern() {
32+
return "**/codeguru-security-report.json";
33+
}
34+
35+
@Override
36+
public String getHelp() {
37+
return "Use commandline <code>aws codeguru-security get-findings --scan-name &lt;scan-name&gt; --output json "
38+
+ "> codeguru-security-report.json</code> to generate JSON output.<br/>"
39+
+ "See <a href='https://docs.aws.amazon.com/cli/latest/reference/codeguru-security/get-findings.html'>"
40+
+ "AWS CodeGuru Security get-findings</a> for usage details.";
41+
}
42+
43+
@Override
44+
public String getUrl() {
45+
return "https://docs.aws.amazon.com/cli/latest/reference/codeguru-security/get-findings.html";
46+
}
47+
}

src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public class ParserRegistry {
6363
new CmakeDescriptor(),
6464
new CodeAnalysisDescriptor(),
6565
new CodeCheckerDescriptor(),
66+
new CodeGuruSecurityDescriptor(),
6667
new CodeClimateDescriptor(),
6768
new CodeGeneratorDescriptor(),
6869
new CodeNarcDescriptor(),

0 commit comments

Comments
 (0)