Skip to content

Commit 9d197a3

Browse files
authored
feat(COD-6201): Docker approach of running the codesec GHA - unified scanning of both SCA and IaC (#244)
1 parent 84852b2 commit 9d197a3

3 files changed

Lines changed: 341 additions & 85 deletions

File tree

action.yaml

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,8 @@ outputs:
4646
runs:
4747
using: 'composite'
4848
steps:
49-
- if: runner.os == 'Linux'
50-
shell: bash
49+
- shell: bash
5150
run: echo "LACEWORK_START_TIME=$(date --rfc-3339=seconds)" >> $GITHUB_ENV
52-
- if: runner.os == 'macOS'
53-
shell: bash
54-
run: |
55-
brew install coreutils
56-
echo "LACEWORK_START_TIME=$(gdate --rfc-3339=seconds)" >> $GITHUB_ENV
5751
- id: init
5852
shell: bash
5953
env:
@@ -63,7 +57,6 @@ runs:
6357
echo "Lacework context ID: $LACEWORK_CONTEXT_ID"
6458
echo "LACEWORK_CONTEXT_ID=$(echo $LACEWORK_CONTEXT_ID)" >> $GITHUB_ENV
6559
echo "LACEWORK_ACTION_REF=$(echo $LACEWORK_ACTION_REF)" >> $GITHUB_ENV
66-
curl https://raw.githubusercontent.com/lacework/go-sdk/main/cli/install.sh | bash
6760
- name: Sets LW_LOG var for debug
6861
shell: bash
6962
if: ${{ inputs.debug == 'true' }}
@@ -75,13 +68,6 @@ runs:
7568
if [ -n "$LW_ACCOUNT_NAME" ]; then
7669
echo "LW_ACCOUNT=$LW_ACCOUNT_NAME" >> $GITHUB_ENV
7770
fi
78-
- name: Install Lacework CLI component
79-
shell: bash
80-
run: |
81-
lacework --noninteractive component install sca
82-
lacework --noninteractive version
83-
env:
84-
CDK_DOWNLOAD_TIMEOUT_MINUTES: 2
8571
- uses: actions/setup-node@v4
8672
with:
8773
node-version: 18

src/index.ts

Lines changed: 121 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import { error, getInput, info, setOutput } from '@actions/core'
2-
import { existsSync, readFileSync } from 'fs'
2+
import { copyFileSync, existsSync, mkdirSync } from 'fs'
3+
import * as path from 'path'
34
import {
45
downloadArtifact,
56
postCommentIfInPr,
67
resolveExistingCommentIfFound,
78
uploadArtifact,
89
} from './actions'
9-
import { callLaceworkCli, debug, generateUILink, getOptionalEnvVariable } from './util'
10+
import { callCommand, runCodesec, getOptionalEnvVariable, readMarkdownFile } from './util'
1011

11-
import path from 'path'
12-
13-
const artifactPrefix = getInput('artifact-prefix')
14-
const sarifReportPath = getInput('code-scanning-path')
15-
const comparisonMarkdownPath = 'comparison.md'
12+
// Global scanner toggles - set to false to disable a scanner globally
13+
const enableScaRunning = true
14+
const enableIacRunning = false // TODO: change to true when ready
1615

1716
async function runAnalysis() {
1817
const target = getInput('target')
@@ -30,85 +29,142 @@ async function runAnalysis() {
3029
info('Analyzing ' + target)
3130
const toUpload: string[] = []
3231

33-
// command to print both sarif and lwjson formats
34-
var args = ['scan', '.', '--formats', 'sarif', '--output', sarifReportPath, '--deployment', 'ci']
35-
if (target === 'push') {
36-
args.push('--save-results')
32+
// Run codesec Docker scanner
33+
// targetScan: 'new'/'old' for PR mode, 'scan' for push mode (should upload results to db)
34+
var targetScan = target
35+
if (target == 'push') {
36+
targetScan = 'scan'
37+
}
38+
const resultsPath = await runCodesec('scan', enableIacRunning, enableScaRunning, targetScan)
39+
40+
// Upload SCA SARIF from the returned results path
41+
if (enableScaRunning) {
42+
const scaSarifFile = path.join(resultsPath, 'sca', `sca-${targetScan}.sarif`)
43+
if (existsSync(scaSarifFile)) {
44+
info(`Found SCA SARIF file to upload: ${scaSarifFile}`)
45+
toUpload.push(scaSarifFile)
46+
47+
// Copy SARIF to code-scanning-path for backward compatibility
48+
const codeScanningPath = getInput('code-scanning-path')
49+
if (codeScanningPath) {
50+
info(`Copying SARIF to code-scanning-path: ${codeScanningPath}`)
51+
copyFileSync(scaSarifFile, codeScanningPath)
52+
}
53+
} else {
54+
info(`SCA SARIF file not found at: ${scaSarifFile}`)
55+
}
3756
}
38-
if (debug()) {
39-
args.push('--debug')
57+
58+
// Upload IAC JSON from the returned results path
59+
if (enableIacRunning) {
60+
const iacJsonFile = path.join(resultsPath, 'iac', `iac-${targetScan}.json`)
61+
if (existsSync(iacJsonFile)) {
62+
info(`Found IAC JSON file to upload: ${iacJsonFile}`)
63+
toUpload.push(iacJsonFile)
64+
} else {
65+
info(`IAC JSON file not found at: ${iacJsonFile}`)
66+
}
4067
}
41-
await callLaceworkCli(...args)
42-
toUpload.push(sarifReportPath)
4368

44-
await uploadArtifact(getArtifactName(target), ...toUpload)
69+
const artifactPrefix = getInput('artifact-prefix')
70+
const artifactName =
71+
artifactPrefix !== '' ? artifactPrefix + '-results-' + target : 'results-' + target
72+
info(`Uploading artifact '${artifactName}' with ${toUpload.length} file(s)`)
73+
await uploadArtifact(artifactName, ...toUpload)
4574
setOutput(`${target}-completed`, true)
4675
}
4776

48-
export async function compareResults(oldReport: string, newReport: string): Promise<string> {
49-
const args = [
50-
'compare',
51-
'--old',
52-
oldReport,
53-
'--new',
54-
newReport,
55-
'--output',
56-
sarifReportPath,
57-
'--markdown',
58-
comparisonMarkdownPath,
59-
'--markdown-variant',
60-
'GitHub',
61-
'--deployment',
62-
'ci',
63-
]
64-
const uiLink = generateUILink()
65-
if (uiLink) args.push(...['--ui-link', uiLink])
66-
if (debug()) args.push('--debug')
77+
async function displayResults() {
78+
info('Displaying results')
6779

68-
await callLaceworkCli(...args)
69-
await uploadArtifact(getArtifactName('compare'), sarifReportPath, comparisonMarkdownPath)
80+
// Download artifacts from previous jobs
81+
const artifactOld = await downloadArtifact('results-old')
82+
const artifactNew = await downloadArtifact('results-new')
7083

71-
return existsSync(comparisonMarkdownPath) ? readFileSync(comparisonMarkdownPath, 'utf8') : ''
72-
}
84+
// Create local scan-results directory for compare
85+
if (enableScaRunning) {
86+
mkdirSync('scan-results/sca', { recursive: true })
87+
}
88+
if (enableIacRunning) {
89+
mkdirSync('scan-results/iac', { recursive: true })
90+
}
7391

74-
async function displayResults() {
75-
info('Displaying results')
76-
const downloadStart = Date.now()
77-
const artifactOld = await downloadArtifact(getArtifactName('old'))
78-
const artifactNew = await downloadArtifact(getArtifactName('new'))
79-
const sarifFileOld = path.join(artifactOld, sarifReportPath)
80-
const sarifFileNew = path.join(artifactNew, sarifReportPath)
81-
82-
var compareMessage: string
83-
if (existsSync(sarifFileOld) && existsSync(sarifFileNew)) {
84-
compareMessage = await compareResults(sarifFileOld, sarifFileNew)
85-
} else {
86-
throw new Error('SARIF file not found')
92+
// Check and copy files for each scanner type
93+
const scaAvailable =
94+
enableScaRunning && (await prepareScannerFiles('sca', artifactOld, artifactNew))
95+
const iacAvailable =
96+
enableIacRunning && (await prepareScannerFiles('iac', artifactOld, artifactNew))
97+
98+
// Need at least one scanner to compare
99+
if (!scaAvailable && !iacAvailable) {
100+
info('No scanner files available for comparison. Nothing to compare.')
101+
setOutput('display-completed', true)
102+
return
87103
}
88104

89-
const commentStart = Date.now()
90-
if (compareMessage.length > 0 && getInput('token').length > 0) {
91-
info('Posting comment to GitHub PR as there were new issues introduced:')
92-
if (getInput('footer') !== '') {
93-
compareMessage += '\n\n' + getInput('footer')
105+
// Run codesec compare mode with available scanners
106+
await runCodesec('compare', enableIacRunning && iacAvailable, enableScaRunning && scaAvailable)
107+
108+
// Read comparison output - check all possible outputs
109+
const outputs = [
110+
'scan-results/compare/merged-compare.md',
111+
'scan-results/compare/sca-compare.md',
112+
'scan-results/compare/iac-compare.md',
113+
]
114+
115+
let message: string | null = null
116+
for (const output of outputs) {
117+
if (existsSync(output)) {
118+
info(`Using comparison output: ${output}`)
119+
message = readMarkdownFile(output)
120+
break
94121
}
95-
info(compareMessage)
96-
const commentUrl = await postCommentIfInPr(compareMessage)
122+
}
123+
124+
if (!message) {
125+
info('No comparison output produced. No changes detected.')
126+
setOutput('display-completed', true)
127+
return
128+
}
129+
130+
// Check if there are new violations (non-zero count in "Found N new potential violations")
131+
const hasViolations = /Found\s+[1-9]\d*\s+/.test(message)
132+
133+
if (hasViolations && getInput('token').length > 0) {
134+
info('Posting comment to GitHub PR as there were new issues introduced')
135+
const commentUrl = await postCommentIfInPr(message)
97136
if (commentUrl !== undefined) {
98137
setOutput('posted-comment', commentUrl)
99138
}
100139
} else {
140+
// No new violations or no token - resolve existing comment if found
101141
await resolveExistingCommentIfFound()
102142
}
103-
setOutput(`display-completed`, true)
143+
144+
setOutput('display-completed', true)
104145
}
105146

106-
function getArtifactName(target: string): string {
107-
var artifactName = 'results-'
108-
if (artifactPrefix !== '') {
109-
artifactName = artifactPrefix + '-' + artifactName
147+
async function prepareScannerFiles(
148+
scanner: 'sca' | 'iac',
149+
artifactOld: string,
150+
artifactNew: string
151+
): Promise<boolean> {
152+
const ext = scanner === 'sca' ? 'sarif' : 'json'
153+
const oldPath = path.join(artifactOld, 'scan-results', scanner, `${scanner}-old.${ext}`)
154+
const newPath = path.join(artifactNew, 'scan-results', scanner, `${scanner}-new.${ext}`)
155+
156+
const oldExists = existsSync(oldPath)
157+
const newExists = existsSync(newPath)
158+
159+
if (!oldExists || !newExists) {
160+
info(`${scanner.toUpperCase()} files not found for compare. old=${oldExists}, new=${newExists}`)
161+
return false
110162
}
111-
return artifactName + target
163+
164+
info(`Copying ${scanner.toUpperCase()} files for compare`)
165+
await callCommand('cp', oldPath, path.join('scan-results', scanner, `${scanner}-old.${ext}`))
166+
await callCommand('cp', newPath, path.join('scan-results', scanner, `${scanner}-new.${ext}`))
167+
return true
112168
}
113169

114170
async function main() {

0 commit comments

Comments
 (0)