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
16 changes: 1 addition & 15 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,8 @@ outputs:
runs:
using: 'composite'
steps:
- if: runner.os == 'Linux'
shell: bash
- shell: bash
run: echo "LACEWORK_START_TIME=$(date --rfc-3339=seconds)" >> $GITHUB_ENV
- if: runner.os == 'macOS'
shell: bash
run: |
brew install coreutils
echo "LACEWORK_START_TIME=$(gdate --rfc-3339=seconds)" >> $GITHUB_ENV
- id: init
shell: bash
env:
Expand All @@ -63,7 +57,6 @@ runs:
echo "Lacework context ID: $LACEWORK_CONTEXT_ID"
echo "LACEWORK_CONTEXT_ID=$(echo $LACEWORK_CONTEXT_ID)" >> $GITHUB_ENV
echo "LACEWORK_ACTION_REF=$(echo $LACEWORK_ACTION_REF)" >> $GITHUB_ENV
curl https://raw.githubusercontent.com/lacework/go-sdk/main/cli/install.sh | bash
- name: Sets LW_LOG var for debug
shell: bash
if: ${{ inputs.debug == 'true' }}
Expand All @@ -75,13 +68,6 @@ runs:
if [ -n "$LW_ACCOUNT_NAME" ]; then
echo "LW_ACCOUNT=$LW_ACCOUNT_NAME" >> $GITHUB_ENV
fi
- name: Install Lacework CLI component
shell: bash
run: |
lacework --noninteractive component install sca
lacework --noninteractive version
env:
CDK_DOWNLOAD_TIMEOUT_MINUTES: 2
- uses: actions/setup-node@v4
with:
node-version: 18
Expand Down
186 changes: 121 additions & 65 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { error, getInput, info, setOutput } from '@actions/core'
import { existsSync, readFileSync } from 'fs'
import { copyFileSync, existsSync, mkdirSync } from 'fs'
import * as path from 'path'
import {
downloadArtifact,
postCommentIfInPr,
resolveExistingCommentIfFound,
uploadArtifact,
} from './actions'
import { callLaceworkCli, debug, generateUILink, getOptionalEnvVariable } from './util'
import { callCommand, codesecRun, getOptionalEnvVariable, readMarkdownFile } from './util'

import path from 'path'

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

async function runAnalysis() {
const target = getInput('target')
Expand All @@ -30,85 +29,142 @@ async function runAnalysis() {
info('Analyzing ' + target)
const toUpload: string[] = []

// command to print both sarif and lwjson formats
var args = ['scan', '.', '--formats', 'sarif', '--output', sarifReportPath, '--deployment', 'ci']
if (target === 'push') {
args.push('--save-results')
// Run codesec Docker scanner
// targetScan: 'new'/'old' for PR mode, 'scan' for push mode (should upload results to db)
var targetScan = target
if (target == 'push') {
targetScan = 'scan'
}
const resultsPath = await codesecRun('scan', enableIacRunning, enableScaRunning, targetScan)

// Upload SCA SARIF from the returned results path
if (enableScaRunning) {
const scaSarifFile = path.join(resultsPath, 'sca', `sca-${targetScan}.sarif`)
if (existsSync(scaSarifFile)) {
info(`Found SCA SARIF file to upload: ${scaSarifFile}`)
toUpload.push(scaSarifFile)

// Copy SARIF to code-scanning-path for backward compatibility
const codeScanningPath = getInput('code-scanning-path')
if (codeScanningPath) {
info(`Copying SARIF to code-scanning-path: ${codeScanningPath}`)
copyFileSync(scaSarifFile, codeScanningPath)
}
} else {
info(`SCA SARIF file not found at: ${scaSarifFile}`)
}
}
if (debug()) {
args.push('--debug')

// Upload IAC JSON from the returned results path
if (enableIacRunning) {
const iacJsonFile = path.join(resultsPath, 'iac', `iac-${targetScan}.json`)
if (existsSync(iacJsonFile)) {
info(`Found IAC JSON file to upload: ${iacJsonFile}`)
toUpload.push(iacJsonFile)
} else {
info(`IAC JSON file not found at: ${iacJsonFile}`)
}
}
await callLaceworkCli(...args)
toUpload.push(sarifReportPath)

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

export async function compareResults(oldReport: string, newReport: string): Promise<string> {
const args = [
'compare',
'--old',
oldReport,
'--new',
newReport,
'--output',
sarifReportPath,
'--markdown',
comparisonMarkdownPath,
'--markdown-variant',
'GitHub',
'--deployment',
'ci',
]
const uiLink = generateUILink()
if (uiLink) args.push(...['--ui-link', uiLink])
if (debug()) args.push('--debug')
async function displayResults() {
info('Displaying results')

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

return existsSync(comparisonMarkdownPath) ? readFileSync(comparisonMarkdownPath, 'utf8') : ''
}
// Create local scan-results directory for compare
if (enableScaRunning) {
mkdirSync('scan-results/sca', { recursive: true })
}
if (enableIacRunning) {
mkdirSync('scan-results/iac', { recursive: true })
}

async function displayResults() {
info('Displaying results')
const downloadStart = Date.now()
const artifactOld = await downloadArtifact(getArtifactName('old'))
const artifactNew = await downloadArtifact(getArtifactName('new'))
const sarifFileOld = path.join(artifactOld, sarifReportPath)
const sarifFileNew = path.join(artifactNew, sarifReportPath)

var compareMessage: string
if (existsSync(sarifFileOld) && existsSync(sarifFileNew)) {
compareMessage = await compareResults(sarifFileOld, sarifFileNew)
} else {
throw new Error('SARIF file not found')
// Check and copy files for each scanner type
const scaAvailable =
enableScaRunning && (await prepareScannerFiles('sca', artifactOld, artifactNew))
const iacAvailable =
enableIacRunning && (await prepareScannerFiles('iac', artifactOld, artifactNew))

// Need at least one scanner to compare
if (!scaAvailable && !iacAvailable) {
info('No scanner files available for comparison. Nothing to compare.')
setOutput('display-completed', true)
return
}

const commentStart = Date.now()
if (compareMessage.length > 0 && getInput('token').length > 0) {
info('Posting comment to GitHub PR as there were new issues introduced:')
if (getInput('footer') !== '') {
compareMessage += '\n\n' + getInput('footer')
// Run codesec compare mode with available scanners
await codesecRun('compare', enableIacRunning && iacAvailable, 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
}
info(compareMessage)
const commentUrl = await postCommentIfInPr(compareMessage)
}

if (!message) {
info('No comparison output produced. No changes detected.')
setOutput('display-completed', true)
return
}

// Check if there are new violations (non-zero count in "Found N new potential violations")
const hasViolations = /Found\s+[1-9]\d*\s+/.test(message)

if (hasViolations && getInput('token').length > 0) {
info('Posting comment to GitHub PR as there were new issues introduced')
const commentUrl = await postCommentIfInPr(message)
if (commentUrl !== undefined) {
setOutput('posted-comment', commentUrl)
}
} else {
// No new violations or no token - resolve existing comment if found
await resolveExistingCommentIfFound()
}
setOutput(`display-completed`, true)

setOutput('display-completed', true)
}

function getArtifactName(target: string): string {
var artifactName = 'results-'
if (artifactPrefix !== '') {
artifactName = artifactPrefix + '-' + artifactName
async function prepareScannerFiles(
scanner: 'sca' | 'iac',
artifactOld: string,
artifactNew: string
): Promise<boolean> {
const ext = scanner === 'sca' ? 'sarif' : 'json'
const oldPath = path.join(artifactOld, 'scan-results', scanner, `${scanner}-old.${ext}`)
const newPath = path.join(artifactNew, 'scan-results', scanner, `${scanner}-new.${ext}`)

const oldExists = existsSync(oldPath)
const newExists = existsSync(newPath)

if (!oldExists || !newExists) {
info(`${scanner.toUpperCase()} files not found for compare. old=${oldExists}, new=${newExists}`)
return false
}
return artifactName + target

info(`Copying ${scanner.toUpperCase()} files for compare`)
await callCommand('cp', oldPath, path.join('scan-results', scanner, `${scanner}-old.${ext}`))
await callCommand('cp', newPath, path.join('scan-results', scanner, `${scanner}-new.${ext}`))
return true
}

async function main() {
Expand Down
Loading
Loading