import * as vscode from 'vscode'; import * as child from 'child_process'; import { v4 as uuid } from 'uuid'; import * as path from 'path'; import { unlinkSync, readdirSync } from 'fs'; export class TrivyWrapper { private workingPath: string[] = []; constructor( private outputChannel: vscode.OutputChannel, private readonly resultsStoragePath: string) { if (!vscode.workspace || !vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length <= 0) { return; } const folders = vscode.workspace.workspaceFolders; for (let i = 0; i < folders.length; i++) { if (folders[i]) { const workspaceFolder = folders[i]; if (!workspaceFolder) { continue; } this.workingPath.push(workspaceFolder.uri.fsPath); } } } run() { let outputChannel = this.outputChannel; this.outputChannel.appendLine(""); this.outputChannel.appendLine("Running Trivy to update results"); if (!this.checkTrivyInstalled()) { return; } var files = readdirSync(this.resultsStoragePath).filter(fn => fn.endsWith('_results.json') || fn.endsWith('_results.json.json')); files.forEach(file => { let deletePath = path.join(this.resultsStoragePath, file); unlinkSync(deletePath); }); const binary = this.getBinaryPath(); this.workingPath.forEach(workingPath => { let command = this.buildCommand(workingPath); this.outputChannel.appendLine(`command: ${command}`); var execution = child.spawn(binary, command); execution.stdout.on('data', function (data) { outputChannel.appendLine(data.toString()); }); execution.stderr.on('data', function (data) { outputChannel.appendLine(data.toString()); }); execution.on('exit', function (code) { if (code !== 0) { vscode.window.showErrorMessage("Trivy failed to run"); return; }; vscode.window.showInformationMessage('Trivy ran successfully, updating results'); outputChannel.appendLine('Reloading the Findings Explorer content'); setTimeout(() => { vscode.commands.executeCommand("trivy-vulnerability-scanner.refresh"); }, 250); }); }); } showCurrentTrivyVersion() { const currentVersion = this.getInstalledTrivyVersion(); if (currentVersion) { vscode.window.showInformationMessage(`Current Trivy version is ${currentVersion}`); } } private getBinaryPath() { const config = vscode.workspace.getConfiguration('trivy'); var binary = config.get('binaryPath', 'trivy'); if (binary === "") { binary = "trivy"; } return binary; }; private checkTrivyInstalled(): boolean { const binaryPath = this.getBinaryPath(); var command = []; command.push(binaryPath); command.push('--help'); try { child.execSync(command.join(' ')); } catch (err) { this.outputChannel.show(); this.outputChannel.appendLine(`Trivy not found. Check the Trivy extension settings to ensure the path is correct. [${binaryPath}]`); return false; } return true; }; private getInstalledTrivyVersion(): string { if (!this.checkTrivyInstalled) { vscode.window.showErrorMessage("Trivy could not be found, check Output window"); return ""; } let binary = this.getBinaryPath(); var command = []; command.push(binary); command.push('--version'); const getVersion = child.execSync(command.join(' ')); return getVersion.toString(); }; private buildCommand(workingPath: string): string[] { const config = vscode.workspace.getConfiguration('trivy'); var command = []; if (config.get<boolean>('debug')) { command.push('--debug'); } let requireChecks = "config,vuln"; if (config.get<boolean>("secretScanning")) { requireChecks = `${requireChecks},secret`; } command.push("fs"); command.push(`--security-checks=${requireChecks}`); command.push(this.getRequiredSeverities(config)); if (config.get<boolean>("offlineScan")) { command.push('--offline-scan'); } if (config.get<boolean>("fixedOnly")) { command.push('--ignore-unfixed'); } command.push('--format=json'); const resultsPath = path.join(this.resultsStoragePath, `${uuid()}_results.json`); command.push(`--output=${resultsPath}`); command.push(workingPath); return command; } private getRequiredSeverities(config: vscode.WorkspaceConfiguration): string { let requiredSeverities: string[] = []; const minRequired = config.get<string>("minimumReportedSeverity"); const severities: string[] = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN"]; for (let i = 0; i < severities.length; i++) { const s = severities[i]; if (!s) { continue; } requiredSeverities.push(s); if (s === minRequired) { break; } } return `--severity=${requiredSeverities.join(",")}`; } }