Skip to content

Commit fdcf190

Browse files
authored
Merge pull request #5 from nwestfall/github_action
GitHub action
2 parents 41bf60d + a994dc7 commit fdcf190

File tree

7 files changed

+337
-517
lines changed

7 files changed

+337
-517
lines changed

action.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const Netsparker = require('./netsparker');
2+
const core = require('@actions/core')
3+
const githubEvent = require(process.env.GITHUB_EVENT_PATH)
4+
5+
async function exec () {
6+
try
7+
{
8+
var config = parseConfig();
9+
netsparker = new Netsparker(config.userid, config.apitoken, config.profilename, config.targetsite);
10+
const scanId = await netsparker.scan();
11+
if(config.report
12+
|| (config.criticalthreshold || config.highthreshold || config.mediumthreshold)) {
13+
await netsparker.waitForScanToComplete(scanId);
14+
const scanResults = await netsparker.scanResults(scanId);
15+
core.setOutput('scanresults', scanResults);
16+
const scanReport = await netsparker.scanReport(scanId, 'Vulnerabilities', 'Json');
17+
core.setOutput('scanreport', scanReport);
18+
if(config.report) {
19+
if(config.junit) {
20+
await this.netsparker.createJunitTestReport(scanResults, config.junit);
21+
} else {
22+
console.table(scanResults);
23+
}
24+
}
25+
if(config.criticalthreshold || config.highthreshold || config.mediumthreshold) {
26+
var criticalCount = 0;
27+
var highCount = 0;
28+
var mediumCount = 0;
29+
for(var i = 0; i < scanReport.Vulnerabilities.length; i++) {
30+
var v = scanReport.Vulnerabilities[i];
31+
switch(v.Severity) {
32+
case "Critical":
33+
criticalCount++;
34+
break;
35+
case "High":
36+
highCount++;
37+
break;
38+
case "Medium":
39+
mediumCount++;
40+
break;
41+
}
42+
}
43+
44+
var thresholdReached = false;
45+
if(config.criticalthreshold) {
46+
if(criticalCount > parseInt(config.criticalthreshold)) {
47+
thresholdReached = true;
48+
console.error(`Critical count exceeds threshold (${criticalCount}).`);
49+
}
50+
}
51+
if(config.highthreshold) {
52+
if(highCount > parseInt(config.highthreshold)) {
53+
thresholdReached = true;
54+
console.error(`High count exceeds threshold (${highCount}).`);
55+
}
56+
}
57+
if(config.mediumthreshold) {
58+
if(mediumCount > parseInt(config.mediumthreshold)) {
59+
thresholdReached = true;
60+
console.error(`Medium count exceeds threshold (${mediumCount}).`)
61+
}
62+
}
63+
64+
if(thresholdReached) {
65+
throw new Error("One or more thresholds where reached. Please see report in Netsparker");
66+
}
67+
}
68+
}
69+
} catch (error) {
70+
console.error(error)
71+
process.exit(1)
72+
}
73+
}
74+
75+
function parseConfig () {
76+
return {
77+
userid: core.getInput('userid'),
78+
apitoken: core.getInput('apitoken'),
79+
profilename: core.getInput('profilename'),
80+
targetsite: core.getInput('targetsite'),
81+
report: core.getInput('report'),
82+
junit: core.getInput('junit'),
83+
criticalthreshold: core.getInput('criticalthreshold'),
84+
highthreshold: core.getInput('highthreshold'),
85+
mediumthreshold: core.getInput('mediumthreshold')
86+
}
87+
}
88+
89+
exec()

action.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: 'Netsparker Scan Runner'
2+
description: 'Run Netsparker Scans and get back test results'
3+
author: 'nwestfall'
4+
inputs:
5+
userid:
6+
description: 'The user id from your Netsparker Account'
7+
required: true
8+
apitoken:
9+
description: 'The api token from your Netsparker Account'
10+
required: true
11+
profilename:
12+
description: 'The profile name saved in your Netsparker Account'
13+
required: true
14+
targetsite:
15+
description: 'The target url you want to run against'
16+
required: true
17+
report:
18+
description: 'If you want to wait around for the report (true) or to fire and forget (false)'
19+
required: false
20+
default: 'true'
21+
junit:
22+
description: 'If you want to generate a junit report, enter the file name and location here'
23+
required: false
24+
criticalthreshold:
25+
description: 'Critical Severity Threshold'
26+
required: false
27+
default: '0'
28+
highthreshold:
29+
description: 'High Severity Threshold'
30+
required: false
31+
default: '0'
32+
mediumthreshold:
33+
description: 'Medium Severity Threshold'
34+
required: false
35+
default: '0'
36+
outputs:
37+
scanresults:
38+
description: 'Scan results from Netsparker (blank if `report` is false)'
39+
scanreport:
40+
description: 'Scan report from Netsparker (blank if `report` is false)'
41+
runs:
42+
using: 'node12'
43+
main: 'dist/index.js'
44+
branding:
45+
icon: 'shield'
46+
color: 'orange'

dist/index.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netsparker.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
const jUnitBuilder = require('junit-report-builder');
2+
const fetch = require('node-fetch');
3+
const header = require('basic-auth-header');
4+
const sleep = require('sleep-promise');
5+
6+
class Netsparker {
7+
constructor(userid, apitoken, profilename, targetsite) {
8+
this.userid = userid;
9+
this.apitoken = apitoken;
10+
this.profilename = profilename;
11+
this.targetsite = targetsite;
12+
}
13+
14+
async scan() {
15+
const response = await fetch('https://www.netsparkercloud.com/api/1.0/scans/newwithprofile', {
16+
method: 'POST',
17+
body: `{ "ProfileName": "${this.profilename}", "TargetUri": "${this.targetsite}" }`,
18+
headers: {
19+
'Content-Type': 'application/json',
20+
'Accept': 'application/json',
21+
'Authorization': header(this.userid, this.apitoken)
22+
}
23+
});
24+
const body = await response.text();
25+
if(!response.ok) {
26+
throw new Error(`${response.statusText} - ${body}`);
27+
}
28+
const scanId = JSON.parse(body).Id;
29+
return scanId;
30+
}
31+
32+
async scanStatus(scanId) {
33+
const response = await fetch(`https://www.netsparkercloud.com/api/1.0/scans/status/${scanId}`, {
34+
method: 'GET',
35+
headers: {
36+
'Accept': 'application/json',
37+
'Authorization': header(this.userid, this.apitoken)
38+
}
39+
});
40+
41+
if(!response.ok) {
42+
throw new Error(response.statusText);
43+
}
44+
45+
const body = await response.text();
46+
const result = JSON.parse(body);
47+
return result;
48+
}
49+
50+
async waitForScanToComplete(scanId) {
51+
var complete = false;
52+
do
53+
{
54+
const scanStatusResult = await this.scanStatus(scanId);
55+
if(scanStatusResult.State == "Complete")
56+
complete = true;
57+
else {
58+
if(scanStatusResult.EstimatedLaunchTime == null)
59+
console.log(`Scan running - ${scanStatusResult.CompletedSteps}/${scanStatusResult.EstimatedSteps} complete`);
60+
else
61+
console.log(`Scan estimated start time - ${scanStatusResult.EstimatedLaunchTime}`);
62+
await sleep(5000);
63+
}
64+
} while(!complete);
65+
}
66+
67+
async scanResults(scanId) {
68+
const response = await fetch(`https://www.netsparkercloud.com/api/1.0/scans/result/${scanId}`, {
69+
method: 'GET',
70+
headers: {
71+
'Accept': 'application/json',
72+
'Authorization': header(this.userid, this.apitoken)
73+
}
74+
});
75+
76+
if(!response.ok) {
77+
throw new Error(response.statusText);
78+
}
79+
80+
const body = await response.text();
81+
const results = JSON.parse(body);
82+
return results;
83+
}
84+
85+
async scanReport(scanId, type, format) {
86+
const response = await fetch(`https://www.netsparkercloud.com/api/1.0/scans/report/?excludeResponseData=true&format=${format}&id=${scanId}&type=${type}`, {
87+
method: 'GET',
88+
headers: {
89+
'Authorization': header(this.userid, this.apitoken)
90+
}
91+
});
92+
93+
if(!response.ok) {
94+
throw new Error(response.statusText);
95+
}
96+
97+
const body = await response.text();
98+
const results = JSON.parse(body);
99+
return results;
100+
}
101+
102+
createJunitTestReport(scanResults, junitFile) {
103+
const suite = jUnitBuilder.testSuite().name('NetsparkerSuite');
104+
for(var i = 0; i < scanResults.length; i++) {
105+
const result = scanResults[i];
106+
suite.testCase()
107+
.className(result.Type)
108+
.name(result.Title)
109+
.standardOutput(result.IssueUrl)
110+
.failure();
111+
}
112+
jUnitBuilder.writeTo(junitFile);
113+
}
114+
}
115+
116+
module.exports = Netsparker

0 commit comments

Comments
 (0)