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
24 changes: 6 additions & 18 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,17 @@ on:
workflow_dispatch:

env:
LW_ACCOUNT_NAME: ${{ secrets.LW_ACCOUNT_CAT }}
LW_API_KEY: ${{ secrets.LW_API_KEY_CAT }}
LW_API_SECRET: ${{ secrets.LW_API_SECRET_CAT }}
LW_ACCOUNT_NAME: ${{ secrets.LW_ACCOUNT_UEDEMO }}
LW_API_KEY: ${{ secrets.LW_API_KEY_UEDEMO }}
LW_API_SECRET: ${{ secrets.LW_API_SECRET_UEDEMO }}
DEBUG: 'true'

jobs:
build:
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Move action
run: |
mkdir ../action
Expand All @@ -44,7 +33,6 @@ jobs:
target: push
sources: ${{ github.workspace }}
debug: true
artifact-prefix: ${{ matrix.os }}
- name: Check run succeeded
env:
RUN_OUTPUT: ${{ steps.run-action.outputs.push-completed }}
Expand All @@ -58,12 +46,12 @@ jobs:
- name: Download results
uses: actions/download-artifact@v4
with:
name: ${{ matrix.os }}-results-push
name: results-push
path: artifact
- name: Check results
working-directory: artifact
run: |
export SCA_RESULTS=`jq '.runs | map (.results | length) | add' sca.sarif`
export SCA_RESULTS=`jq '.runs | map (.results | length) | add' scan-results/sca/sca-scan.sarif`
echo "Got $SCA_RESULTS from SCA"
if [ "$SCA_RESULTS" -eq 0 ]; then
echo "::error::Expected to have $expectedScaResults SCA results!"
Expand Down
13 changes: 0 additions & 13 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ runs:
- if: runner.os == 'Linux'
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,19 +58,11 @@ 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' }}
run: |
echo "LW_LOG=debug" >> $GITHUB_ENV
- name: Install Lacework CLI component
shell: bash
run: |
lacework --noninteractive -a "${LW_ACCOUNT_NAME}" -k "${LW_API_KEY}" -s "${LW_API_SECRET}" component install sca
lacework --noninteractive -a "${LW_ACCOUNT_NAME}" -k "${LW_API_KEY}" -s "${LW_API_SECRET}" version
env:
CDK_DOWNLOAD_TIMEOUT_MINUTES: 2
- uses: actions/setup-node@v4
with:
node-version: 18
Expand Down
167 changes: 100 additions & 67 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { error, getInput, info, setOutput } from '@actions/core'
import { existsSync, readFileSync } from 'fs'
import { existsSync, mkdirSync } from 'fs'
import * as path from 'path'
import {
downloadArtifact,
postCommentIfInPr,
resolveExistingCommentIfFound,
uploadArtifact,
} from './actions'
import { callLaceworkCli, debug, generateUILink, getOptionalEnvVariable } from './util'

import path from 'path'

const artifactPrefix = getInput('artifact-prefix')
const sarifReportPath = getInput('code-scanning-path')
const comparisonMarkdownPath = 'comparison.md'
import { callCommand, codesecRun, getOptionalEnvVariable, readMarkdownFile } from './util'

async function runAnalysis() {
const target = getInput('target')
Expand All @@ -30,87 +25,125 @@ 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'
}
if (debug()) {
args.push('--debug')
const resultsPath = await codesecRun('scan', true, true, targetScan)

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

const uploadStart = Date.now()
// Upload IAC JSON from the returned results path
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 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
mkdirSync('scan-results/sca', { recursive: true })
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 = await prepareScannerFiles('sca', artifactOld, artifactNew)
const iacAvailable = 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', iacAvailable, 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