Skip to content

Commit 0ffc106

Browse files
authored
Merge pull request #10 from villamorvinzie/feature/batch_scan_template
feat: add support for batch scan of templates
2 parents 5369a50 + dd7edb9 commit 0ffc106

File tree

2 files changed

+61
-28
lines changed

2 files changed

+61
-28
lines changed

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ inputs:
2525
templatePath:
2626
description: "Location of the file to be scanned, eg templates/template.yml."
2727
required: true
28+
templatesDirPath:
29+
description: "(Optional) Location of the directory of templates to be scanned, (e.g., templates). This ignores the value of 'templatePath' if supplied."
30+
required: false
2831
branding:
2932
icon: "check"
3033
color: "red"

scan.js

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const fs = require('fs');
55
const { promisify } = require('util');
66
const readFile = promisify(fs.readFile);
77
const CloudConformity = require("cloud-conformity");
8+
const readDir = promisify(fs.readdir);
9+
const readOptions = { encoding: "utf8" }
810

911
const computeFailures = (result, messages) => {
1012
console.log(JSON.stringify(result, null, 2));
@@ -31,26 +33,40 @@ const computeFailures = (result, messages) => {
3133
});
3234
}
3335

34-
const scan = async (templatePath, ccEndpoint, ccApiKey, profileId, accountId) => {
36+
const scan = async (templatePath, ccEndpoint, ccApiKey, profileId, accountId, templatesDirPath) => {
3537
const cc = new CloudConformity.CloudConformity(ccEndpoint, ccApiKey);
36-
const template = await readFile(templatePath, 'utf8');
38+
if (templatesDirPath) {
39+
return batchScanTemplates(cc, templatesDirPath, profileId, accountId)
40+
}
41+
return scanTemplate(cc, templatePath, profileId, accountId)
42+
}
43+
44+
const failOnFailure = (failures, acceptedQty) => {
45+
return ((failures.extreme > acceptedQty.extreme) || (failures.veryHigh > acceptedQty.veryHigh) || (failures.high > acceptedQty.high) || (failures.medium > acceptedQty.medium) || (failures.low > acceptedQty.low))
46+
};
47+
48+
const batchScanTemplates = async (cc, templatesDirPath, profileId, accountId) => {
49+
const dir = await readDir(templatesDirPath, readOptions)
50+
return Promise.all(dir.map(async (template) => {
51+
const fullPath = templatesDirPath + "/" + template
52+
return scanTemplate(cc, fullPath, profileId, accountId)
53+
}))
54+
}
55+
56+
const scanTemplate = async (cc, templatePath, profileId, accountId) => {
57+
const template = await readFile(templatePath, readOptions);
3758
// Scans the template using Conformity module.
59+
console.log("Scan template: (%s)", templatePath)
3860
const result = await cc.scanACloudFormationTemplateAndReturAsArrays(template, profileId, accountId);
3961
const messages = [];
4062
const results = computeFailures(result, messages);
4163
return {
42-
detections: result.failure,
43-
results: results,
44-
messages: messages
64+
template: templatePath,
65+
detections: result.failure,
66+
results: results,
67+
messages: messages
4568
};
46-
};
47-
48-
const failOnFailure = (failures, acceptedQty) => {
49-
if ((failures.extreme > acceptedQty.extreme) || (failures.veryHigh > acceptedQty.veryHigh) || (failures.high > acceptedQty.high) || (failures.medium > acceptedQty.medium) || (failures.low > acceptedQty.low)) {
50-
return true;
51-
}
52-
return false;
53-
};
69+
}
5470

5571
const region = process.env.cc_region;
5672
const apikey = process.env.cc_apikey;
@@ -65,26 +81,40 @@ const acceptedResults = {
6581
const outputResults = process.env.cc_output_results? true : false;
6682
const profileId = process.env.profileId;
6783
const accountId = process.env.accountId;
84+
const templatesDirPath = process.env.templatesDirPath;
6885

69-
scan(templatePath, region, apikey, profileId, accountId)
70-
.then(res => {
71-
console.log(`Failures found: ${JSON.stringify(res.results, null, 2)}`);
72-
console.log('\n');
73-
console.log(`Quantity of failures allowed: ${JSON.stringify(acceptedResults, null, 2)}`);
74-
if (outputResults && res.messages){
75-
console.log('\n');
76-
console.log('Results:\n');
77-
console.log(res.messages.join('\n'));
86+
scan(templatePath, region, apikey, profileId, accountId, templatesDirPath)
87+
.then(value => {
88+
const results = Array.isArray(value) ? value : [value]
89+
const COMPLIANT_MESSASGE = "Template passes configured checks."
90+
const NON_COMPLIANT_MESSAGE = "Security and/or misconfiguration issue(s) found in template(s): "
91+
const nonCompliantTemplates = [];
92+
let isCompliant = true;
93+
for (const result of results) {
94+
console.log(`\nFailures found: ${JSON.stringify(result.results, null, 2)}`);
95+
console.log('\n');
96+
console.log(`Quantity of failures allowed: ${JSON.stringify(acceptedResults, null, 2)}`);
97+
if (outputResults && result.messages) {
98+
console.log('\n');
99+
console.log('Results:\n');
100+
console.log(result.messages.join('\n'));
101+
}
102+
console.log('\n');
103+
if (failOnFailure(result.results, acceptedResults)) {
104+
isCompliant = false;
105+
nonCompliantTemplates.push(result.template)
106+
}
78107
}
79-
console.log('\n');
80-
return failOnFailure(res.results, acceptedResults);
108+
return {
109+
status: isCompliant,
110+
message: isCompliant ? COMPLIANT_MESSASGE : NON_COMPLIANT_MESSAGE + " [" + nonCompliantTemplates + "]"
111+
};
81112
})
82113
.then(res => {
83-
if (res){
84-
console.log("Security and/or misconfiguration issue(s) found in template.");
85-
process.exit(1);
114+
console.log(res.message)
115+
if (!res.status) {
116+
process.exit(1);
86117
}
87-
console.log("Template passes configured checks.");
88118
process.exit(0);
89119
})
90120
.catch(err => {

0 commit comments

Comments
 (0)