Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 5 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {
} from './actions'
import {
callCommand,
runCodesec,
runCodesecScan,
runCodesecCompare,
getModifiedFiles,
getOptionalEnvVariable,
readMarkdownFile,
shouldRunIaCScanner,
} from './util'
import { simpleGit } from 'simple-git'
Expand Down Expand Up @@ -79,8 +79,7 @@ async function runAnalysis() {
}

if (!cacheHit) {
let success = await runCodesec(
'scan',
let success = await runCodesecScan(
enableIacRunning,
enableScaRunning,
resultsPath,
Expand Down Expand Up @@ -185,30 +184,11 @@ async function displayResults() {
}

// Run codesec compare mode with available scanners
const resultsPath = path.join(process.cwd(), 'scan-results')
await runCodesec(
'compare',
const message = await runCodesecCompare(
enableIacRunning && iacAvailable,
enableScaRunning && scaAvailable,
resultsPath
enableScaRunning && scaAvailable
)

// Read comparison output - check all possible outputs
const outputs = [
'scan-results/compare/merged-compare.md',
'scan-results/compare/sca-compare.md',
'scan-results/compare/iac-compare.md',
]

let message: string | null = null
for (const output of outputs) {
if (existsSync(output)) {
info(`Using comparison output: ${output}`)
message = readMarkdownFile(output)
break
}
}

if (!message) {
info('No comparison output produced. No changes detected.')
setOutput('display-completed', true)
Expand Down
249 changes: 117 additions & 132 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,18 +157,7 @@ export function shouldRunIaCScanner(modifiedFiles: string): boolean {
})
}

// runCodesec - Docker-based scanner using codesec:latest image
//
// Modes:
// 1. action='scan', scanTarget='new'/'old' -> produces analysis for PR comment
// 2. action='scan', scanTarget='scan' -> full scan for scheduled events (uploads to Lacework)
// 3. action='compare' -> compares new/old results, generates diff markdown for PR comment
//
// Parameters:
// - runIac/runSca: which scanners to enable (default false - enable when ready to test)
// - scanTarget: 'new', 'old', or 'scan' depending on mode
export async function runCodesec(
action: string,
export async function runCodesecScan(
runIac: boolean = false,
runSca: boolean = false,
reportsDir: string,
Expand All @@ -179,137 +168,133 @@ export async function runCodesec(
const lwApiKey = getRequiredEnvVariable('LW_API_KEY')
const lwApiSecret = getRequiredEnvVariable('LW_API_SECRET')

if (action === 'scan') {
const containerName = `codesec-scan-${scanTarget || 'default'}`

info(`Running codesec scan (target: ${scanTarget || 'scan'})`)

// Create env file with GitHub CI vars for the lacework iac binary
const envFile = createEnvFile()

// Run the scanner
const dockerArgs = [
'run',
'--name',
containerName,
'-v',
`${process.cwd()}:/app/src`,
'--env-file',
envFile,
'-e',
`WORKSPACE=src`,
'-e',
`LW_ACCOUNT=${lwAccount}`,
'-e',
`LW_API_KEY=${lwApiKey}`,
'-e',
`LW_API_SECRET=${lwApiSecret}`,
'-e',
`RUN_SCA=${runSca}`,
'-e',
`RUN_IAC=${runIac}`,
'-e',
`SCAN_TARGET=${scanTarget || 'scan'}`,
...(modifiedFiles ? ['-e', `MODIFIED_FILES=${modifiedFiles}`] : []),
'lacework/codesec:latest',
'scan',
]

await callCommand('docker', ...dockerArgs)

// Copy results out of container to temp dir
if (runSca) {
const scaDir = path.join(reportsDir, 'sca')
mkdirSync(scaDir, { recursive: true })
await callCommand(
'docker',
'container',
'cp',
`${containerName}:/tmp/scan-results/sca/sca-${scanTarget || 'scan'}.sarif`,
path.join(scaDir, `sca-${scanTarget || 'scan'}.sarif`)
)
}
const containerName = `codesec-scan-${scanTarget || 'default'}`

info(`Running codesec scan (target: ${scanTarget || 'scan'})`)

const envFile = createEnvFile()

const dockerArgs = [
'run',
'--name',
containerName,
'-v',
`${process.cwd()}:/app/src`,
'--env-file',
envFile,
'-e',
`WORKSPACE=src`,
'-e',
`LW_ACCOUNT=${lwAccount}`,
'-e',
`LW_API_KEY=${lwApiKey}`,
'-e',
`LW_API_SECRET=${lwApiSecret}`,
'-e',
`RUN_SCA=${runSca}`,
'-e',
`RUN_IAC=${runIac}`,
'-e',
`SCAN_TARGET=${scanTarget || 'scan'}`,
...(modifiedFiles ? ['-e', `MODIFIED_FILES=${modifiedFiles}`] : []),
'lacework/codesec:latest',
'scan',
]

if (runIac) {
const iacDir = path.join(reportsDir, 'iac')
mkdirSync(iacDir, { recursive: true })
await callCommand(
'docker',
'container',
'cp',
`${containerName}:/tmp/scan-results/iac/iac-${scanTarget || 'scan'}.json`,
path.join(iacDir, `iac-${scanTarget || 'scan'}.json`)
)
}
await callCommand('docker', ...dockerArgs)

// Cleanup container
await callCommand('docker', 'rm', containerName)
} else if (action === 'compare') {
const containerName = 'codesec-compare'

info('Running codesec compare')

// Create env file with GitHub CI vars for the lacework iac binary
const envFile = createEnvFile()

// Append LW_UI_LINK so the image can pass it to `lacework sca compare --ui-link`
const uiLink = generateUILink()
writeFileSync(envFile, `\nLW_UI_LINK=${uiLink}`, { flag: 'a' })

// Mounts both the repo and the scan-results directory separately
const dockerArgs = [
'run',
'--name',
containerName,
'-v',
`${process.cwd()}:/app/src`,
'-v',
`${path.join(process.cwd(), 'scan-results')}:/app/scan-results`,
'--env-file',
envFile,
'-e',
`WORKSPACE=src`,
'-e',
`LW_ACCOUNT=${lwAccount}`,
'-e',
`LW_API_KEY=${lwApiKey}`,
'-e',
`LW_API_SECRET=${lwApiSecret}`,
'-e',
`RUN_SCA=${runSca}`,
'-e',
`RUN_IAC=${runIac}`,
'lacework/codesec:latest',
'compare',
]

await callCommand('docker', ...dockerArgs)

// Copy comparison results out
const compareDir = path.join(reportsDir, 'compare')
mkdirSync(compareDir, { recursive: true })

// Copy the entire compare directory out
if (runSca) {
const scaDir = path.join(reportsDir, 'sca')
mkdirSync(scaDir, { recursive: true })
await callCommand(
'docker',
'container',
'cp',
`${containerName}:/tmp/scan-results/compare/.`,
compareDir
`${containerName}:/tmp/scan-results/sca/sca-${scanTarget || 'scan'}.sarif`,
path.join(scaDir, `sca-${scanTarget || 'scan'}.sarif`)
)
}

// Verify at least one output was produced
const compareFiles = ['merged-compare.md', 'sca-compare.md', 'iac-compare.md']
const copied = compareFiles.filter((f) => existsSync(path.join(compareDir, f)))
if (runIac) {
const iacDir = path.join(reportsDir, 'iac')
mkdirSync(iacDir, { recursive: true })
await callCommand(
'docker',
'container',
'cp',
`${containerName}:/tmp/scan-results/iac/iac-${scanTarget || 'scan'}.json`,
path.join(iacDir, `iac-${scanTarget || 'scan'}.json`)
)
}

if (copied.length === 0) {
throw new Error('No comparison outputs found in container')
}
await callCommand('docker', 'rm', containerName)
return true
}

// Cleanup container
await callCommand('docker', 'rm', containerName)
export async function runCodesecCompare(
runIac: boolean = false,
runSca: boolean = false
): Promise<string | null> {
const lwAccount = getRequiredEnvVariable('LW_ACCOUNT')
const lwApiKey = getRequiredEnvVariable('LW_API_KEY')
const lwApiSecret = getRequiredEnvVariable('LW_API_SECRET')

const containerName = 'codesec-compare'

info('Running codesec compare')

const envFile = createEnvFile()

const uiLink = generateUILink()
writeFileSync(envFile, `\nLW_UI_LINK=${uiLink}`, { flag: 'a' })

const dockerArgs = [
'run',
'--name',
containerName,
'-v',
`${process.cwd()}:/app/src`,
'-v',
`${path.join(process.cwd(), 'scan-results')}:/app/scan-results`,
'--env-file',
envFile,
'-e',
`WORKSPACE=src`,
'-e',
`LW_ACCOUNT=${lwAccount}`,
'-e',
`LW_API_KEY=${lwApiKey}`,
'-e',
`LW_API_SECRET=${lwApiSecret}`,
'-e',
`RUN_SCA=${runSca}`,
'-e',
`RUN_IAC=${runIac}`,
'lacework/codesec:latest',
'compare',
]

await callCommand('docker', ...dockerArgs)

const compareDir = path.join(os.tmpdir(), `codesec-compare-${Date.now()}`)
mkdirSync(compareDir, { recursive: true })

await callCommand(
'docker',
'container',
'cp',
`${containerName}:/tmp/scan-results/compare/.`,
compareDir
)

await callCommand('docker', 'rm', containerName)

const mergedPath = path.join(compareDir, 'merged-compare.md')
if (existsSync(mergedPath)) {
return readFileSync(mergedPath, 'utf-8')
}
return true

return null
}

export function readMarkdownFile(filePath: string): string {
Expand Down
Loading