Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci-scan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ jobs:
iac-scan-path: ./tests/fixtures/iac/
# Note: This test assumes these policies exist in the target Sysdig Secure account.
use-policies: '"All Posture Findings", "MITRE DEFEND"'
stop-on-processing-error: true

- name: Check that the scan has succeeded
run: |
Expand Down
12 changes: 12 additions & 0 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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

Large diffs are not rendered by default.

27 changes: 26 additions & 1 deletion src/infrastructure/sysdig/SysdigCliScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { cliScannerName, cliScannerResult, cliScannerURL, scannerURLForVersion }
import { ScanConfig } from '../../application/ports/ScanConfig';
import { JsonScanResultV1 } from './JsonScanResultV1';
import { ReportParsingError } from '../../application/errors/ReportParsingError';
import { ScanResult } from '../../domain/scanresult';
import { Architecture, EvaluationResult, Family, OperatingSystem, ScanResult, ScanType } from '../../domain/scanresult';
import { JsonScanResultV1ToScanResultAdapter } from './JsonScanResultV1ToScanResultAdapter';
const performance = require('perf_hooks').performance;

Expand Down Expand Up @@ -68,6 +68,12 @@ export class SysdigCliScanner implements IScanner {
let retCode = await exec.exec(command, flags, scanOptions);
core.info("Image analysis took " + Math.round(performance.now() - start) + " milliseconds.");

// IaC mode: No JSON output file - derive result from exit code
if (config.mode === ScanMode.iac) {
return this.createIacResult(retCode);
}

// VM mode: Parse JSON output
if (retCode == 0 || retCode == 1) {
await exec.exec(`cat ./${cliScannerResult}`, undefined, catOptions);
}
Expand All @@ -80,6 +86,25 @@ export class SysdigCliScanner implements IScanner {
}
}

private createIacResult(exitCode: number): ScanResult {
const evaluationResult = exitCode === 0
? EvaluationResult.Passed
: EvaluationResult.Failed;

return new ScanResult(
ScanType.Docker,
'iac-scan',
'iac-scan',
null,
new OperatingSystem(Family.Unknown, 'N/A'),
BigInt(0),
Architecture.Unknown,
{},
new Date(),
evaluationResult
);
}



private composeFlags(config: ScanConfig): ComposeFlags {
Expand Down
82 changes: 82 additions & 0 deletions tests/infrastructure/sysdig/SysdigCliScanner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as exec from '@actions/exec';
import { SysdigCliScanner } from '../../../src/infrastructure/sysdig/SysdigCliScanner';
import { SysdigCliScannerDownloader } from '../../../src/infrastructure/sysdig/SysdigCliScannerDownloader';
import { ScanConfig } from '../../../src/application/ports/ScanConfig';
import { ScanMode } from '../../../src/application/ports/ScannerDTOs';
import { EvaluationResult } from '../../../src/domain/scanresult';

jest.mock('@actions/exec');
jest.mock('@actions/core');

const mockExec = jest.mocked(exec);

describe('SysdigCliScanner', () => {
let scanner: SysdigCliScanner;
let mockDownloader: jest.Mocked<SysdigCliScannerDownloader>;
let iacConfig: ScanConfig;

beforeEach(() => {
jest.resetAllMocks();

mockDownloader = {
download: jest.fn().mockResolvedValue('/path/to/scanner'),
} as unknown as jest.Mocked<SysdigCliScannerDownloader>;

scanner = new SysdigCliScanner(mockDownloader);

iacConfig = {
mode: ScanMode.iac,
iacScanPath: './terraform',
sysdigSecureToken: 'test-token',
sysdigSecureURL: 'https://app.sysdig.com',
cliScannerURL: '',
stopOnFailedPolicyEval: true,
stopOnProcessingError: true,
standalone: false,
skipSummary: false,
groupByPackage: false,
imageTag: '',
overridePullString: '',
registryUser: '',
registryPassword: '',
dbPath: '',
skipUpload: false,
usePolicies: '',
sysdigSkipTLS: false,
extraParameters: '',
recursive: false,
minimumSeverity: '',
};
});

describe('IaC mode', () => {
it('should return ScanResult with Passed evaluation when exit code is 0', async () => {
mockExec.exec.mockResolvedValue(0);

const result = await scanner.executeScan(iacConfig);

expect(result).toBeDefined();
expect(result.getEvaluationResult()).toBe(EvaluationResult.Passed);
});

it('should return ScanResult with Failed evaluation when exit code is 1', async () => {
mockExec.exec.mockResolvedValue(1);

const result = await scanner.executeScan(iacConfig);

expect(result).toBeDefined();
expect(result.getEvaluationResult()).toBe(EvaluationResult.Failed);
});

it('should NOT try to read scan-result.json in IaC mode', async () => {
mockExec.exec.mockResolvedValue(0);

await scanner.executeScan(iacConfig);

const catCalls = mockExec.exec.mock.calls.filter(
call => typeof call[0] === 'string' && call[0].includes('cat')
);
expect(catCalls).toHaveLength(0);
});
});
});
Loading