Files
n8n-enterprise-unlocked/scripts/scan-n8n-image.mjs

153 lines
4.5 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* This script is used to scan the n8n docker image for vulnerabilities.
* It uses Trivy to scan the image.
*/
import { $, echo, fs, chalk } from 'zx';
import path from 'path';
$.verbose = false;
process.env.FORCE_COLOR = '1';
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
// --- Configuration ---
const config = {
imageBaseName: process.env.IMAGE_BASE_NAME || 'n8n-local',
imageTag: process.env.IMAGE_TAG || 'dev',
trivyImage: process.env.TRIVY_IMAGE || 'aquasec/trivy:latest',
severity: process.env.TRIVY_SEVERITY || 'CRITICAL,HIGH,MEDIUM,LOW',
outputFormat: process.env.TRIVY_FORMAT || 'table',
outputFile: process.env.TRIVY_OUTPUT || null,
scanTimeout: process.env.TRIVY_TIMEOUT || '10m',
ignoreUnfixed: process.env.TRIVY_IGNORE_UNFIXED === 'true',
scanners: process.env.TRIVY_SCANNERS || 'vuln',
quiet: process.env.TRIVY_QUIET === 'true',
rootDir: rootDir,
};
config.fullImageName = `${config.imageBaseName}:${config.imageTag}`;
const printHeader = (title) =>
!config.quiet && echo(`\n${chalk.blue.bold(`===== ${title} =====`)}`);
const printSummary = (status, time, message) => {
if (config.quiet) return;
echo('\n' + chalk.blue.bold('===== Scan Summary ====='));
echo(status === 'success' ? chalk.green.bold(message) : chalk.yellow.bold(message));
echo(chalk[status === 'success' ? 'green' : 'yellow'](` Scan time: ${time}s`));
if (config.outputFile) {
const resolvedPath = path.isAbsolute(config.outputFile)
? config.outputFile
: path.join(config.rootDir, config.outputFile);
echo(chalk[status === 'success' ? 'green' : 'yellow'](` Report saved to: ${resolvedPath}`));
}
echo('\n' + chalk.gray('Scan Configuration:'));
echo(chalk.gray(` • Target Image: ${config.fullImageName}`));
echo(chalk.gray(` • Severity Levels: ${config.severity}`));
echo(chalk.gray(` • Scanners: ${config.scanners}`));
if (config.ignoreUnfixed) echo(chalk.gray(` • Ignored unfixed: yes`));
echo(chalk.blue.bold('========================'));
};
// --- Main Process ---
(async () => {
printHeader('Trivy Security Scan for n8n Image');
try {
await $`command -v docker`;
} catch {
echo(chalk.red('Error: Docker is not installed or not in PATH'));
process.exit(1);
}
try {
await $`docker image inspect ${config.fullImageName} > /dev/null 2>&1`;
} catch {
echo(chalk.red(`Error: Docker image '${config.fullImageName}' not found`));
echo(chalk.yellow('Please run dockerize-n8n.mjs first!'));
process.exit(1);
}
// Pull latest Trivy image silently
try {
await $`docker pull ${config.trivyImage} > /dev/null 2>&1`;
} catch {
// Silent fallback to cached version
}
// Build Trivy command
const trivyArgs = [
'run',
'--rm',
'-v',
'/var/run/docker.sock:/var/run/docker.sock',
config.trivyImage,
'image',
'--severity',
config.severity,
'--format',
config.outputFormat,
'--timeout',
config.scanTimeout,
'--scanners',
config.scanners,
'--no-progress',
];
if (config.ignoreUnfixed) trivyArgs.push('--ignore-unfixed');
if (config.quiet && config.outputFormat === 'table') trivyArgs.push('--quiet');
// Handle output file - resolve relative to root directory
if (config.outputFile) {
const outputPath = path.isAbsolute(config.outputFile)
? config.outputFile
: path.join(config.rootDir, config.outputFile);
await fs.ensureDir(path.dirname(outputPath));
trivyArgs.push('--output', '/tmp/trivy-output', '-v', `${outputPath}:/tmp/trivy-output`);
}
trivyArgs.push(config.fullImageName);
// Run the scan
const startTime = Date.now();
try {
const result = await $`docker ${trivyArgs}`;
// Print Trivy output first
if (!config.outputFile && result.stdout) {
echo(result.stdout);
}
// Then print our summary
const scanTime = Math.floor((Date.now() - startTime) / 1000);
printSummary('success', scanTime, '✅ Security scan completed successfully');
process.exit(0);
} catch (error) {
const scanTime = Math.floor((Date.now() - startTime) / 1000);
// Trivy returns exit code 1 when vulnerabilities are found
if (error.exitCode === 1) {
// Print Trivy output first
if (!config.outputFile && error.stdout) {
echo(error.stdout);
}
// Then print our summary
printSummary('warning', scanTime, '⚠️ Vulnerabilities found!');
process.exit(1);
} else {
echo(chalk.red(`❌ Scan failed: ${error.message}`));
process.exit(error.exitCode || 1);
}
}
})();