Skip to content

Commit

Permalink
SIGINT-1189, SIGINT-1493: GitHub Advance security SARIF (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
spurohitsynopsys authored Feb 19, 2024
1 parent c9932a0 commit aa9e6b4
Show file tree
Hide file tree
Showing 18 changed files with 834 additions and 22 deletions.
15 changes: 15 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ inputs:
blackduck_prComment_enabled:
description: 'Flag to enable pull request comments for new issues found in the Black Duck scan'
required: false
blackduck_reports_sarif_create:
description: 'Flag to enable/disable Black Duck SARIF report generation'
required: false
blackduck_reports_sarif_file_path:
description: 'File path including file name where Black Duck SARIF report should be created'
required: false
blackduck_reports_sarif_severities:
description: 'Indicates what SAST/SCA issues severity categories to include in Black Duck SARIF file report'
required: false
blackduck_reports_sarif_groupSCAIssues:
description: 'Flag to enable/disable Component-Version grouping for SCA Issues in Black Duck SARIF report rules section'
required: false
blackduck_upload_sarif_report:
description: 'Flag to enable/disable uploading of Black Duck SARIF report to GitHub Advanced Security'
required: false
coverity_prComment_enabled:
description: 'Flag to enable pull request comments for new issues found in the Coverity scan'
required: false
Expand Down
254 changes: 242 additions & 12 deletions dist/index.js

Large diffs are not rendered by default.

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

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "synopsys-action",
"version": "1.7.0",
"version": "1.8.0",
"private": true,
"description": "Perform security scan using Synopsys Tools",
"main": "lib/main.js",
Expand Down
20 changes: 18 additions & 2 deletions src/application-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export const BLACKDUCK_FIXPR_USE_UPGRADE_GUIDANCE_KEY = 'blackduck_fixpr_useUpgr
*/
export const BLACKDUCK_AUTOMATION_PRCOMMENT_KEY = 'blackduck_automation_prcomment'
export const BLACKDUCK_PRCOMMENT_ENABLED_KEY = 'blackduck_prComment_enabled'
export const BLACKDUCK_REPORTS_SARIF_CREATE_KEY = 'blackduck_reports_sarif_create'
export const BLACKDUCK_REPORTS_SARIF_FILE_PATH_KEY = 'blackduck_reports_sarif_file_path'
export const BLACKDUCK_REPORTS_SARIF_SEVERITIES_KEY = 'blackduck_reports_sarif_severities'
export const BLACKDUCK_REPORTS_SARIF_GROUP_SCA_ISSUES_KEY = 'blackduck_reports_sarif_groupSCAIssues'
export const BLACKDUCK_UPLOAD_SARIF_REPORT_KEY = 'blackduck_upload_sarif_report'

export const GITHUB_HOST_URL_KEY = 'github_host_url'
export const GITHUB_TOKEN_KEY = 'github_token'
Expand All @@ -89,7 +94,7 @@ export const NETWORK_AIRGAP_KEY = 'network_airgap'
export const DIAGNOSTICS_RETENTION_DAYS_KEY = 'diagnostics_retention_days'

// Bridge Exit Codes
export let EXIT_CODE_MAP = new Map<string, string>([
export const EXIT_CODE_MAP = new Map<string, string>([
['0', 'Bridge execution successfully completed'],
['1', 'Undefined error, check error logs'],
['2', 'Error from adapter end'],
Expand All @@ -102,6 +107,16 @@ export const RETRY_DELAY_IN_MILLISECONDS = 15000
export const RETRY_COUNT = 3
export const NON_RETRY_HTTP_CODES = new Set([200, 201, 401, 403, 416])
export const GITHUB_CLOUD_URL = 'https://github.com'
export const GITHUB_CLOUD_API_URL = 'https://api.github.com'
export const BRIDGE_LOCAL_DIRECTORY = '.bridge'
export const BLACKDUCK_SARIF_GENERATOR_DIRECTORY = 'Blackduck SARIF Generator'
export const BLACKDUCK_SARIF_ARTIFACT_NAME = 'blackduck_sarif_report'
export const SARIF_DEFAULT_FILE_NAME = 'report.sarif.json'
export const X_RATE_LIMIT_RESET = 'x-ratelimit-reset'
export const X_RATE_LIMIT_REMAINING = 'x-ratelimit-remaining'
export const SECONDARY_RATE_LIMIT = 'secondary rate limit'
export const HTTP_STATUS_ACCEPTED = 202
export const HTTP_STATUS_FORBIDDEN = 403

export const GITHUB_ENVIRONMENT_VARIABLES = {
GITHUB_TOKEN: 'GITHUB_TOKEN',
Expand All @@ -112,5 +127,6 @@ export const GITHUB_ENVIRONMENT_VARIABLES = {
GITHUB_REPOSITORY_OWNER: 'GITHUB_REPOSITORY_OWNER',
GITHUB_BASE_REF: 'GITHUB_BASE_REF',
GITHUB_EVENT_NAME: 'GITHUB_EVENT_NAME',
GITHUB_SERVER_URL: 'GITHUB_SERVER_URL'
GITHUB_SERVER_URL: 'GITHUB_SERVER_URL',
GITHUB_SHA: 'GITHUB_SHA'
}
14 changes: 12 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {info, setFailed} from '@actions/core'
import {cleanupTempDir, createTempDir} from './synopsys-action/utility'
import {cleanupTempDir, createTempDir, parseToBoolean} from './synopsys-action/utility'
import {SynopsysBridge} from './synopsys-action/synopsys-bridge'
import {getWorkSpaceDirectory} from '@actions/artifact/lib/internal/config-variables'
import * as constants from './application-constants'
import * as inputs from './synopsys-action/inputs'
import {uploadDiagnostics} from './synopsys-action/diagnostics'
import {uploadDiagnostics, uploadSarifReportAsArtifact} from './synopsys-action/artifacts'
import {GithubClientService} from './synopsys-action/github-client-service'

export async function run() {
info('Synopsys Action started...')
Expand Down Expand Up @@ -34,6 +35,15 @@ export async function run() {
if (inputs.INCLUDE_DIAGNOSTICS) {
await uploadDiagnostics()
}
// Upload Black Duck sarif file as GitHub artifact
if (parseToBoolean(inputs.BLACKDUCK_REPORTS_SARIF_CREATE)) {
await uploadSarifReportAsArtifact(constants.BLACKDUCK_SARIF_GENERATOR_DIRECTORY, inputs.BLACKDUCK_REPORTS_SARIF_FILE_PATH, constants.BLACKDUCK_SARIF_ARTIFACT_NAME)
}
// Upload Black Duck SARIF Report to code scanning tab
if (parseToBoolean(inputs.BLACKDUCK_UPLOAD_SARIF_REPORT)) {
const gitHubClientService = new GithubClientService()
await gitHubClientService.uploadSarifReport(constants.BLACKDUCK_SARIF_GENERATOR_DIRECTORY, inputs.BLACKDUCK_REPORTS_SARIF_FILE_PATH)
}
await cleanupTempDir(tempDir)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import {UploadResponse} from '@actions/artifact/lib/internal/upload-response'
import {getWorkSpaceDirectory} from '@actions/artifact/lib/internal/config-variables'
import * as fs from 'fs'
import * as inputs from './inputs'
import {getDefaultSarifReportPath} from './utility'
import {UploadOptions} from '@actions/artifact/lib/internal/upload-options'
import {warning} from '@actions/core'
import path from 'path'

export async function uploadDiagnostics(): Promise<UploadResponse | void> {
const artifactClient = artifact.create()
Expand Down Expand Up @@ -48,3 +50,12 @@ export function getFiles(dir: string, allFiles: string[]): string[] {
}
return allFiles
}

export async function uploadSarifReportAsArtifact(defaultSarifReportDirectory: string, userSarifFilePath: string, artifactName: string): Promise<UploadResponse> {
const artifactClient = artifact.create()
const sarifFilePath = userSarifFilePath ? userSarifFilePath : getDefaultSarifReportPath(defaultSarifReportDirectory, true)
const rootDir = userSarifFilePath ? path.dirname(userSarifFilePath) : getDefaultSarifReportPath(defaultSarifReportDirectory, false)
const options: UploadOptions = {}
options.continueOnError = false
return await artifactClient.uploadArtifact(artifactName, [sarifFilePath], rootDir, options)
}
110 changes: 110 additions & 0 deletions src/synopsys-action/github-client-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {HttpClient} from 'typed-rest-client/HttpClient'
import * as inputs from './inputs'
import * as fs from 'fs'
import * as zlib from 'zlib'
import {checkIfPathExists, getDefaultSarifReportPath, sleep} from './utility'
import {debug, info} from '@actions/core'
import {isNullOrEmptyValue} from './validators'
import * as constants from '../application-constants'

export class GithubClientService {
gitHubCodeScanningUrl: string
githubToken: string
githubRepo: string
repoName: string
repoOwner: string
githubServerUrl: string
githubApiURL: string
commit_sha: string
githubRef: string

constructor() {
this.gitHubCodeScanningUrl = '/repos/{0}/{1}/code-scanning/sarifs'
this.githubToken = inputs.GITHUB_TOKEN
this.githubRepo = process.env[constants.GITHUB_ENVIRONMENT_VARIABLES.GITHUB_REPOSITORY] || ''
this.repoName = this.githubRepo !== '' ? this.githubRepo.substring(this.githubRepo.indexOf('/') + 1, this.githubRepo.length).trim() : 'node-goat-1'
this.repoOwner = process.env[constants.GITHUB_ENVIRONMENT_VARIABLES.GITHUB_REPOSITORY_OWNER] || 'spurohitsynopsys'
this.githubServerUrl = process.env[constants.GITHUB_ENVIRONMENT_VARIABLES.GITHUB_SERVER_URL] || 'https://api.github.com'
this.githubApiURL = this.githubServerUrl === constants.GITHUB_CLOUD_URL ? constants.GITHUB_CLOUD_API_URL : this.githubServerUrl
this.commit_sha = process.env[constants.GITHUB_ENVIRONMENT_VARIABLES.GITHUB_SHA] || 'e434b96cc4d26a0396ede1e76c4946afbcaf6ec6'
this.githubRef = process.env[constants.GITHUB_ENVIRONMENT_VARIABLES.GITHUB_REF] || 'ssss'
}

async uploadSarifReport(defaultSarifReportDirectory: string, userSarifFilePath: string): Promise<void> {
info('Uploading SARIF results to GitHub')
if (isNullOrEmptyValue(inputs.GITHUB_TOKEN)) {
throw new Error('Missing required GitHub token for uploading SARIF report to GitHub Advanced Security')
}
let retryCountLocal = constants.RETRY_COUNT
let retryDelay = constants.RETRY_DELAY_IN_MILLISECONDS
const stringFormat = (url: string, ...args: string[]): string => {
return url.replace(/{(\d+)}/g, (match, index) => args[index] || '')
}
const endpoint = stringFormat(this.githubApiURL.concat(this.gitHubCodeScanningUrl), this.repoOwner, this.repoName)
const sarifFilePath = userSarifFilePath ? userSarifFilePath : getDefaultSarifReportPath(defaultSarifReportDirectory, true)

if (checkIfPathExists(sarifFilePath)) {
try {
const sarifContent = fs.readFileSync(sarifFilePath, 'utf8')
const compressedSarif = zlib.gzipSync(sarifContent)
const base64Sarif = compressedSarif.toString('base64')
const data = {
commit_sha: this.commit_sha,
ref: this.githubRef,
sarif: base64Sarif,
validate: true
}
do {
const httpClient = new HttpClient('GithubClientService')
const httpResponse = await httpClient.post(endpoint, JSON.stringify(data), {
Authorization: `Bearer ${this.githubToken}`,
Accept: 'application/vnd.github+json'
})
debug(`HTTP Status Code: ${httpResponse.message.statusCode}`)
debug(`HTTP Response Headers: ${JSON.stringify(httpResponse.message.headers)}`)
const responseBody = await httpResponse.readBody()
const rateLimitRemaining = httpResponse.message?.headers[constants.X_RATE_LIMIT_REMAINING] || ''
if (httpResponse.message.statusCode === constants.HTTP_STATUS_ACCEPTED) {
info('SARIF result uploaded successfully to GitHub Advance Security')
retryCountLocal = 0
} else if (httpResponse.message.statusCode === constants.HTTP_STATUS_FORBIDDEN && (rateLimitRemaining === '0' || responseBody.includes(constants.SECONDARY_RATE_LIMIT))) {
const rateLimitResetHeader = httpResponse.message?.headers[constants.X_RATE_LIMIT_RESET] || ''
const rateLimitReset = Array.isArray(rateLimitResetHeader) ? rateLimitResetHeader[0] : rateLimitResetHeader
const currentTimeInSeconds = Math.floor(Date.now() / 1000)
const resetTimeInSeconds = parseInt(rateLimitReset, 10)
const secondsUntilReset = resetTimeInSeconds - currentTimeInSeconds
// Retry only if rate limit reset time is less than or equals to sum of time of 3 retry attempts in seconds: 15+30+60=105
if (secondsUntilReset <= 105) {
retryDelay = await this.retrySleepHelper('Uploading SARIF report to GitHub Advanced Security has been failed due to rate limit, Retries left: ', retryCountLocal, retryDelay)
} else {
const minutesUntilreset = Math.ceil(secondsUntilReset / 60)
throw new Error(`GitHub API rate limit has been exceeded, retry after ${minutesUntilreset} minutes.`)
}
retryCountLocal--
} else {
retryCountLocal = 0
throw new Error(responseBody)
}
} while (retryCountLocal > 0)
} catch (error) {
throw new Error(`Uploading SARIF report to GitHub Advanced Security failed: ${error}`)
}
} else {
throw new Error('No SARIF file found to upload')
}
}

private async retrySleepHelper(message: string, retryCountLocal: number, retryDelay: number): Promise<number> {
info(
message
.concat(String(retryCountLocal))
.concat(', Waiting: ')
.concat(String(retryDelay / 1000))
.concat(' Seconds')
)
await sleep(retryDelay)
// Delayed exponentially starting from 15 seconds
retryDelay = retryDelay * 2
return retryDelay
}
}
3 changes: 3 additions & 0 deletions src/synopsys-action/input-data/blackduck.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {Reports} from './reports'

export enum BLACKDUCK_SCAN_FAILURE_SEVERITIES {
ALL = 'ALL',
NONE = 'NONE',
Expand All @@ -23,6 +25,7 @@ export interface BlackduckData {
scan?: {full?: boolean; failure?: {severities: BLACKDUCK_SCAN_FAILURE_SEVERITIES[]}}
automation: AutomationData
fixpr?: BlackDuckFixPrData
reports?: Reports
}

export interface Branch {
Expand Down
19 changes: 19 additions & 0 deletions src/synopsys-action/input-data/reports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface Reports {
sarif?: Sarif
}

export interface Sarif {
create?: boolean
file?: File
issue?: Issue
severities?: string[]
groupSCAIssues?: boolean
}

export interface File {
path?: string
}

export interface Issue {
types?: string[]
}
5 changes: 5 additions & 0 deletions src/synopsys-action/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export const BLACKDUCK_FIXPR_MAXCOUNT = getInput(constants.BLACKDUCK_FIXPR_MAXCO
export const BLACKDUCK_FIXPR_CREATE_SINGLE_PR = getInput(constants.BLACKDUCK_FIXPR_CREATE_SINGLE_PR_KEY)?.trim() || ''
export const BLACKDUCK_FIXPR_FILTER_SEVERITIES = getInput(constants.BLACKDUCK_FIXPR_FILTER_SEVERITIES_KEY)?.trim() || ''
export const BLACKDUCK_FIXPR_LONG_TERM_GUIDANCE = getInput(constants.BLACKDUCK_FIXPR_USE_UPGRADE_GUIDANCE_KEY)?.trim() || ''
export const BLACKDUCK_REPORTS_SARIF_CREATE = getInput(constants.BLACKDUCK_REPORTS_SARIF_CREATE_KEY)?.trim() || ''
export const BLACKDUCK_REPORTS_SARIF_FILE_PATH = getInput(constants.BLACKDUCK_REPORTS_SARIF_FILE_PATH_KEY)?.trim() || ''
export const BLACKDUCK_REPORTS_SARIF_SEVERITIES = getInput(constants.BLACKDUCK_REPORTS_SARIF_SEVERITIES_KEY)?.trim() || ''
export const BLACKDUCK_REPORTS_SARIF_GROUP_SCA_ISSUES = getInput(constants.BLACKDUCK_REPORTS_SARIF_GROUP_SCA_ISSUES_KEY)?.trim() || ''
export const BLACKDUCK_UPLOAD_SARIF_REPORT = getInput(constants.BLACKDUCK_UPLOAD_SARIF_REPORT_KEY)?.trim() || ''
export const GITHUB_TOKEN = getInput(constants.GITHUB_TOKEN_KEY)?.trim() || ''
export const INCLUDE_DIAGNOSTICS = getInput(constants.INCLUDE_DIAGNOSTICS_KEY)?.trim() || ''
export const DIAGNOSTICS_RETENTION_DAYS = getInput(constants.DIAGNOSTICS_RETENTION_DAYS_KEY)?.trim() || ''
24 changes: 23 additions & 1 deletion src/synopsys-action/tools-parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {InputData} from './input-data/input-data'
import {Coverity} from './input-data/coverity'
import {Blackduck, BLACKDUCK_SCAN_FAILURE_SEVERITIES, GithubData, BlackDuckFixPrData} from './input-data/blackduck'
import * as constants from '../application-constants'
import {parseToBoolean} from './utility'
import {isBoolean, parseToBoolean} from './utility'
import {GITHUB_ENVIRONMENT_VARIABLES} from '../application-constants'

export class SynopsysToolsParameter {
Expand Down Expand Up @@ -270,6 +270,28 @@ export class SynopsysToolsParameter {
blackduckData.data.blackduck.automation.prcomment = false
}

if (parseToBoolean(inputs.BLACKDUCK_REPORTS_SARIF_CREATE)) {
const sarifReportFilterSeverities: string[] = []
if (inputs.BLACKDUCK_REPORTS_SARIF_SEVERITIES) {
const filterSeverities = inputs.BLACKDUCK_REPORTS_SARIF_SEVERITIES.split(',')
for (const fixPrSeverity of filterSeverities) {
if (fixPrSeverity != null && fixPrSeverity !== '') {
sarifReportFilterSeverities.push(fixPrSeverity.trim())
}
}
}
blackduckData.data.blackduck.reports = {
sarif: {
create: true,
severities: sarifReportFilterSeverities,
file: {
path: inputs.BLACKDUCK_REPORTS_SARIF_FILE_PATH.trim()
},
groupSCAIssues: isBoolean(inputs.BLACKDUCK_REPORTS_SARIF_GROUP_SCA_ISSUES) ? JSON.parse(inputs.BLACKDUCK_REPORTS_SARIF_GROUP_SCA_ISSUES) : true
}
}
}

const inputJson = JSON.stringify(blackduckData)

const stateFilePath = path.join(this.tempDir, SynopsysToolsParameter.BD_STATE_FILE_NAME)
Expand Down
14 changes: 14 additions & 0 deletions src/synopsys-action/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as os from 'os'
import path from 'path'
import {APPLICATION_NAME} from '../application-constants'
import {rmRF} from '@actions/io'
import {getWorkSpaceDirectory} from '@actions/artifact/lib/internal/config-variables'
import * as constants from '../application-constants'

export function cleanUrl(url: string): string {
if (url && url.endsWith('/')) {
Expand Down Expand Up @@ -35,6 +37,13 @@ export function parseToBoolean(value: string | boolean): boolean {
return false
}

export function isBoolean(value: string | boolean): boolean {
if (value !== null && value !== '' && (value.toString().toLowerCase() === 'true' || value === true || value.toString().toLowerCase() === 'false' || value === false)) {
return true
}
return false
}

export function checkIfPathExists(fileOrDirectoryPath: string): boolean {
if (fileOrDirectoryPath && fs.existsSync(fileOrDirectoryPath.trim())) {
return true
Expand All @@ -47,3 +56,8 @@ export async function sleep(duration: number): Promise<void> {
setTimeout(resolve, duration)
})
}

export function getDefaultSarifReportPath(sarifReportDirectory: string, appendFilePath: boolean): string {
const pwd = getWorkSpaceDirectory()
return !appendFilePath ? path.join(pwd, constants.BRIDGE_LOCAL_DIRECTORY, sarifReportDirectory) : path.join(pwd, constants.BRIDGE_LOCAL_DIRECTORY, sarifReportDirectory, constants.SARIF_DEFAULT_FILE_NAME)
}
Loading

0 comments on commit aa9e6b4

Please sign in to comment.