Skip to content

Commit

Permalink
Merge pull request #46 from Enterprise-CMCS/zarate-scan-type
Browse files Browse the repository at this point in the history
Self-detecting Snyk Scan Type
  • Loading branch information
maira-samtek authored Sep 18, 2024
2 parents b5b5e23 + 110c06a commit ba67c24
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 43 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ jira-labels: Labels to be applied to the created Jira tickets (c
jira-custom-field-key-value: A JSON string containing key-value pairs of custom fields and their values in Jira.
assign-jira-ticket-to: The accountID of a user to assigne a ticket to.
scan-output-path: The path to the scan output file.
snyk-test-type: The type of Snyk scan being run. Accepts 'open-source', 'iac', or 'container'. Defaults to 'open-source'.
min-severity: The minimum severity level a vulnerability must have for a Jira ticket to be created. Accepts 'low', 'medium', 'high', 'critical'.
```
# Usage
Expand Down Expand Up @@ -57,4 +56,4 @@ jobs:

Ensure that you have the required secrets (JIRA_HOST, JIRA_USERNAME, and JIRA_TOKEN) configured in your repository's settings so that they can be accessed by the Action script.

The workflow configuration assumes that you are running the security scan command and saving the output to a file named scan-output.json. Adjust the command and file name according to your specific scan tool and configuration. For more information on how to implement a Snyk scan and use it in conjuction with this action, please view [`SNYK.md`](./SNYK.md).
The workflow configuration assumes that the security scan command is executed, and the output is saved to a file named scan-output.json. The scan test type is automatically detected based on the structure of the scan output for snyk. Currently, supported snyk test types include IaC, open-source, and container scans. You can adjust the command and file name according to your specific scan tool and configuration. For detailed guidance on implementing a Snyk scan and integrating it with this action, please refer to [`SNYK.md`](./SNYK.md).
5 changes: 1 addition & 4 deletions SNYK.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,11 @@ First the `snyk` CLI will need to be installed with `npm`. It is then used to ru
# assign-jira-ticket-to: ''
scan-output-path: 'snyk_output.txt'
scan-type: 'snyk'
snyk-test-type: 'iac'
min-severity: 'critical'
major-version-only: 'true'
```

Note that the `snyk-test-type` input has been added. Because the output format of each `snyk` command is different, we must specifiy what kind of Snyk scan is being run to successfully parse the output file and create Jira tickets (if no input is provided for `snyk-test-type`, it defaults to `'open-source'`).
Note that the scan type is automatically detected based on the output structure of the snyk command and the supported scan test types are "iac", "open-source" and "container".

**Also note:** `snyk iac test` will most likely detect a lot of low and medium severity level vulnerabilities. To keep the Jira ticket creation at a manageable amount, it is advisiable to not set `min-severity` any lower than `'critical'` or `'high'`.

Expand Down Expand Up @@ -162,7 +161,6 @@ The following example demonstrates how to use `snyk container test` in conjuncti
is_jira_enterprise: true
scan-output-path: 'snyk_output.txt'
scan-type: 'snyk'
snyk-test-type: 'container'
min-severity: 'critical'
major-version-only: 'true'
```
Expand Down Expand Up @@ -230,7 +228,6 @@ This example demonstrates how to scan an image that is stored in an ECR reposito
jira-title-prefix: '[CMCSMACD] - Snyk :'
is_jira_enterprise: true
scan-output-path: 'snyk_output.txt'
snyk-test-type: 'container'
```

## Exit Codes
Expand Down
5 changes: 1 addition & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ inputs:
min-severity:
description: 'minimum severity level that Jira tickets should be created for (acceptable values are "low", "medium", "high", and "critical")'
required: false
default: 'low'
# Scan
zap-risk-code:
description: 'riskcode type to report'
Expand All @@ -45,10 +46,6 @@ inputs:
description: 'can type to perform "snyk" or "zap"'
required: false
default: 'snyk'
snyk-test-type:
description: 'type of snyk test being performed; can be "open-source", "iac", "container", or "code"'
required: false
default: 'open-source'
major-version-only:
description: 'ticket in jira will be created only for the major version of vulnerability package; can be "true" or "false"'
required: false
Expand Down
60 changes: 44 additions & 16 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

60 changes: 44 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ try {
}
})();
} else if (scanType === 'snyk') {
let snykTestType = ''
const isMajorVersion = (v1, v2) => {
if (!v1 || !v2 || typeof v1 !== 'string' || typeof v2 !== 'string' ||
!/\d+\.\d+\.\d+/.test(v1) || !/\d+\.\d+\.\d+/.test(v2)) {
Expand All @@ -211,10 +212,10 @@ try {

// severity level enum
const Severities = {
low: 0,
medium: 1,
high: 2,
critical: 3
low: 1,
medium: 2,
high: 3,
critical: 4
};

let vulnerabilities = [];
Expand All @@ -223,11 +224,26 @@ try {
console.error("invalid input for min-severity; must be set to 'low', 'medium', 'high', or 'critical'");
process.exit(2);
}

if (inputData) {
if (core.getInput('snyk-test-type') === 'open-source') {
const data = JSON.parse(inputData);
if (Array.isArray(data)) {
if (data.some(d => d.infrastructureAsCodeIssues)) {
snykTestType = 'iac';
} else if (data.some(d => d.vulnerabilities)) {
snykTestType = 'open-source';
}
} else if (data && data.vulnerabilities) {
snykTestType = 'container';
}

if (!snykTestType) {
console.error('Error: Unable to determine Snyk scan type. Invalid or unknown data structure.');
process.exit(1);
}

if (snykTestType === 'open-source') {
try {
const data = JSON.parse(inputData);

if (Array.isArray(data)) {
for (const project of data) {
if (project && project.vulnerabilities && Array.isArray(project.vulnerabilities)) {
Expand All @@ -237,18 +253,21 @@ try {
vulnerabilities.push(v);
}
});
} else {
console.error(`Error: Invalid project structure in open-source data. Project data: ${JSON.stringify(project)}`);
}
}
} else {
console.error('Error: Open-source scan expected an array, but received invalid data.');
}
} catch (error) {
console.error('Error parsing Snyk output:', error);
process.exit(2);
// vulnerabilities = parseNonJsonData(inputData);
}
}
else if (core.getInput('snyk-test-type') === 'container') {
else if (snykTestType === 'container') {
try {
const data = JSON.parse(inputData);
if (data && data.vulnerabilities && Array.isArray(data.vulnerabilities)) {
data.vulnerabilities.forEach(v => {
if (minSeverity && Severities[v.severity] >= Severities[minSeverity]) {
Expand All @@ -265,19 +284,22 @@ try {
vulnerabilities.push(v);
}
})
} else {
console.error(`Error: Invalid application structure in container scan. Application data: ${JSON.stringify(app)}`);
}
});
}
} else {
console.error('Error: Container scan expected "vulnerabilities" to be an array, but received invalid data.');
}
} catch (error) {
console.error('Error parsing Snyk output:', error);
process.exit(2);
// vulnerabilities = parseNonJsonData(inputData);
}
}
else if (core.getInput('snyk-test-type') === 'iac') {
else if (snykTestType === 'iac') {
try {
const data = JSON.parse(inputData);
if (data && Array.isArray(data)) {
data.forEach(d => {
if (Array.isArray(d.infrastructureAsCodeIssues) && d.infrastructureAsCodeIssues.length > 0) {
Expand All @@ -288,23 +310,29 @@ try {
vulnerabilities.push(iacIssue);
}
});
} else {
console.error(`Error: Invalid IAC data structure. InfrastructureAsCodeIssues not found in data: ${JSON.stringify(d)}`);
}
});
} else {
console.error('Error: IAC scan expected an array, but received invalid data.');
}
} catch (error) {
console.error('Error parsing Snyk output:', error);
process.exit(2);
}
} else {
console.error(`Error: Unknown Snyk scan type "${snykTestType}".`);
}

if (vulnerabilities.length === 0) {
console.error('No Vulnerabilities Detetcted or Invalid JSON data format.');
console.error('No Vulnerabilities Detected');
}

return vulnerabilities;

} else {
console.error('No Vulnerabilities Detetcted or Invalid JSON data format.');
console.error('No input-data/vulnerabilities Detected');
// vulnerabilities = parseNonJsonData(inputData);
}
}
Expand All @@ -321,7 +349,7 @@ try {
return descriptionStr;
}

async function createSnykJiraTicket(vulnerability, comment='') {
async function createSnykJiraTicket(vulnerability, comment='', snykTestType = 'container') {
try {

const title = vulnerability.title.replaceAll("\"", "\\\"");
Expand All @@ -345,7 +373,7 @@ try {
"key": `${core.getInput('jira-project-key')}`
},
"summary": `${core.getInput('jira-title-prefix')} ${vulnerability.title}`,
"description": `${comment}${ core.getInput('snyk-test-type') === 'iac' ? iacDescriptionStr(vulnerability) : vulnerability.description}`,
"description": `${comment}${ snykTestType === 'iac' ? iacDescriptionStr(vulnerability) : vulnerability.description}`,
"issuetype": {
"name": `${core.getInput('jira-issue-type')}`
},
Expand Down Expand Up @@ -413,7 +441,7 @@ try {
console.log(
`Creating Jira ticket for vulnerability: ${vulnerability.title}`
);
const resp = await createSnykJiraTicket(vulnerability, comment);
const resp = await createSnykJiraTicket(vulnerability, comment, snykTestType);
console.log(resp)
} else {
console.log('skipping because not major update')
Expand Down

0 comments on commit ba67c24

Please sign in to comment.