aws-container-image-scanner
Version:
AWS Container Image Scanner - Enterprise tool for scanning EKS clusters, analyzing Bitnami container dependencies, and generating migration guidance for AWS ECR alternatives with security best practices.
335 lines (334 loc) ⢠15.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.program = void 0;
const tslib_1 = require("tslib");
const commander_1 = require("commander");
const scanner_1 = require("./scanner");
const migrate_command_simple_1 = require("./migrate-command-simple");
const query_command_1 = require("./query-command");
const ui_server_1 = require("./ui-server");
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const program = new commander_1.Command();
exports.program = program;
process.on('uncaughtException', (error) => {
console.error(chalk_1.default.red('\nš„ Fatal Error:'), error.message);
if (process.env.NODE_ENV === 'development') {
console.error(error.stack);
}
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
console.error(chalk_1.default.red('\nš„ Unhandled Promise Rejection:'), reason?.message || reason);
if (process.env.NODE_ENV === 'development') {
console.error(reason?.stack || reason);
}
process.exit(1);
});
process.on('SIGINT', () => {
console.log(chalk_1.default.yellow('\n\nš Scan interrupted by user'));
process.exit(0);
});
process.on('SIGTERM', () => {
console.log(chalk_1.default.yellow('\n\nš Scan terminated'));
process.exit(0);
});
program
.name('cis')
.description('Container Image Scanner - Enterprise Dependency Analysis Platform with AWS Security Best Practices')
.version('2.5.2');
program
.command('analyze')
.description('š Complete container dependency analysis across infrastructure')
.option('-a, --accounts <accounts>', 'Comma-separated AWS account IDs')
.option('-r, --regions <regions>', 'AWS regions to scan', 'us-east-1,us-west-2')
.option('--org-scan', 'Scan entire AWS Organization')
.option('--role-arn <arn>', 'Cross-account role ARN pattern')
.option('-o, --output <file>', 'Output JSON file path')
.option('--critical-only', 'Show only critical and high risk images')
.option('--verbose', 'Enable verbose logging')
.option('--interactive', 'Start interactive query mode after scan')
.option('--search <text>', 'Search for specific images after scan')
.option('--filter <filter>', 'Filter results (e.g., "riskLevel=CRITICAL")')
.action(async (options) => {
const scanner = new scanner_1.ContainerImageScanner();
console.log(chalk_1.default.bgBlue.white.bold(' š CONTAINER IMAGE SCANNER v2.4.1 '));
console.log(chalk_1.default.blue('Enterprise dependency analysis and migration planning\n'));
try {
await validateScanOptions(options);
}
catch (error) {
console.error(chalk_1.default.red(`ā Configuration error: ${error.message}`));
process.exit(1);
}
try {
await scanner.performScan(options);
if (options.interactive) {
await scanner.startInteractiveMode();
}
else if (options.search) {
console.log(chalk_1.default.blue(`\nš Search results for "${options.search}":`));
const results = scanner.searchImages(options.search);
console.log(chalk_1.default.yellow(`Found ${results.length} matching images`));
scanner.displayResults(results.slice(0, 20));
}
else if (options.filter) {
const [field, value] = options.filter.split('=');
const results = scanner.filterImages([{ field, operator: 'contains', value }]);
console.log(chalk_1.default.blue(`\nš Filter results: ${results.length} images`));
scanner.displayResults(results.slice(0, 20));
}
}
catch (error) {
console.error(chalk_1.default.red(`\nā Scan failed: ${error.message}`));
process.exit(1);
}
});
program
.command('migrate')
.description('š Generate migration plan and guidance')
.requiredOption('-i, --input <file>', 'Input JSON file from analyze command')
.option('-o, --output <dir>', 'Output directory for migration plan', './migration-plan')
.option('--script-type <type>', 'Migration script type (bash|powershell)', 'bash')
.option('--update-manifests', 'Generate updated Kubernetes manifests')
.option('--helm-values', 'Generate updated Helm values')
.action(async (options) => {
const planner = new migrate_command_simple_1.MigrationPlanner();
try {
await planner.generateMigrationPlanAsync(options);
}
catch (error) {
console.error(chalk_1.default.red(`ā Migration planning failed: ${error.message}`));
process.exit(1);
}
});
program
.command('query')
.description('š Interactive querying and analysis of scan results')
.requiredOption('-i, --input <file>', 'Input JSON file from analyze command')
.option('--interactive', 'Start interactive query session')
.option('--search <text>', 'Search images by name, container, or namespace')
.option('--filter <filter>', 'Filter results (e.g., "riskLevel equals CRITICAL")')
.option('-o, --output <file>', 'Save results to file')
.option('--format <format>', 'Output format (table|json|csv)', 'table')
.option('--limit <number>', 'Limit number of results', '50')
.action(async (options) => {
try {
options.limit = parseInt(options.limit);
await query_command_1.QueryCommand.execute(options);
}
catch (error) {
console.error(chalk_1.default.red(`ā Query failed: ${error.message}`));
process.exit(1);
}
});
program
.command('explore')
.description('š Explore scan results interactively (Powerpipe-style)')
.requiredOption('-i, --input <file>', 'Input JSON file from analyze command')
.option('--search <text>', 'Search images by text')
.option('--cluster <name>', 'Filter by cluster name')
.option('--namespace <name>', 'Filter by namespace')
.option('--risk <level>', 'Filter by risk level (CRITICAL, HIGH, MEDIUM, LOW)')
.option('--interactive', 'Start interactive exploration mode')
.action(async (options) => {
try {
const fs = require('fs/promises');
const data = await fs.readFile(options.input, 'utf-8');
const scanResults = JSON.parse(data);
const scanner = new scanner_1.ContainerImageScanner();
scanner.results = scanResults;
console.log(chalk_1.default.blue.bold('\nš Container Image Explorer'));
console.log(chalk_1.default.gray(`Loaded ${scanResults.images?.length || 0} images from ${scanResults.clusters?.length || 0} clusters\n`));
if (options.interactive) {
await scanner.startInteractiveMode();
}
else {
let results = scanResults.images || [];
if (options.search) {
results = scanner.searchImages(options.search);
console.log(chalk_1.default.blue(`š Search: "${options.search}" (${results.length} found)`));
}
if (options.cluster || options.namespace || options.risk) {
const filters = [];
if (options.cluster)
filters.push({ field: 'cluster', operator: 'contains', value: options.cluster });
if (options.namespace)
filters.push({ field: 'namespace', operator: 'contains', value: options.namespace });
if (options.risk)
filters.push({ field: 'riskLevel', operator: 'equals', value: options.risk });
results = scanner.filterImages(filters);
console.log(chalk_1.default.blue(`š Filtered results: ${results.length} images`));
}
if (results.length === 0) {
console.log(chalk_1.default.yellow('No images found matching your criteria.'));
}
else {
scanner.displayResults(results.slice(0, 20));
if (results.length > 20) {
console.log(chalk_1.default.gray(`\nShowing first 20 of ${results.length} results. Use --interactive for more.`));
}
}
console.log(chalk_1.default.blue('\nšļø Cluster Summary:'));
scanner.displayClusterSummary();
}
}
catch (error) {
console.error(chalk_1.default.red(`ā Exploration failed: ${error.message}`));
process.exit(1);
}
});
program
.command('ui')
.description('š„ļø Start local web UI for interactive scanning')
.option('-p, --port <port>', 'Port for web server', '3000')
.option('--host <host>', 'Host to bind server', 'localhost')
.action(async (options) => {
const port = parseInt(options.port);
try {
await (0, ui_server_1.startUIServer)(port, {
host: options.host,
verbose: program.opts().verbose
});
}
catch (error) {
console.error(chalk_1.default.red(`ā UI server failed: ${error.message}`));
process.exit(1);
}
});
program
.command('doctor')
.description('𩺠Diagnose system configuration and dependencies')
.action(async () => {
console.log(chalk_1.default.bgYellow.black.bold(' 𩺠SYSTEM DIAGNOSTICS '));
console.log(chalk_1.default.yellow('Checking system configuration and dependencies\n'));
const checks = [
{ name: 'AWS CLI', command: 'aws --version' },
{ name: 'kubectl', command: 'kubectl version --client' },
{ name: 'Docker', command: 'docker --version' }
];
for (const check of checks) {
try {
console.log(chalk_1.default.green(`ā
${check.name}: Available`));
}
catch {
console.log(chalk_1.default.red(`ā ${check.name}: Not found`));
}
}
console.log(chalk_1.default.cyan('\nš Migration Prerequisites:'));
console.log(chalk_1.default.cyan('⢠AWS CLI configured with appropriate permissions'));
console.log(chalk_1.default.cyan('⢠Docker installed for image migration'));
console.log(chalk_1.default.cyan('⢠kubectl configured for manifest updates'));
console.log(chalk_1.default.cyan('⢠Helm installed for chart migrations'));
});
program
.command('powerpipe')
.description('š Start Powerpipe dashboard for advanced analytics')
.requiredOption('-i, --input <file>', 'Input JSON file from analyze command')
.option('-w, --workspace <dir>', 'Powerpipe workspace directory', './powerpipe-workspace')
.option('-p, --port <port>', 'Port for Powerpipe server', '9033')
.option('--host <host>', 'Host to bind server', 'localhost')
.option('--export <format>', 'Export data format (csv|json|sql)')
.option('--dashboard <name>', 'Generate custom dashboard')
.option('--install', 'Install Powerpipe if not present')
.action(async (options) => {
const { PowerpipeIntegration, checkPowerpipeInstallation, installPowerpipe } = await Promise.resolve().then(() => tslib_1.__importStar(require('./powerpipe-integration')));
try {
const isInstalled = await checkPowerpipeInstallation();
if (!isInstalled) {
if (options.install) {
await installPowerpipe();
}
else {
console.log(chalk_1.default.red('ā Powerpipe is not installed'));
console.log(chalk_1.default.cyan('Install with: --install flag or manually:'));
console.log(chalk_1.default.cyan(' macOS: brew install turbot/tap/powerpipe'));
console.log(chalk_1.default.cyan(' Linux: See https://powerpipe.io/downloads'));
process.exit(1);
}
}
const powerpipe = new PowerpipeIntegration({
workspaceDir: options.workspace,
port: parseInt(options.port),
host: options.host
});
console.log(chalk_1.default.bgBlue.white.bold(' š POWERPIPE INTEGRATION '));
console.log(chalk_1.default.blue('Advanced container analytics and dashboards\n'));
await powerpipe.initialize();
await powerpipe.loadScanResults(options.input);
if (options.export) {
await powerpipe.exportForPowerpipe(options.export);
}
if (options.dashboard) {
await powerpipe.generateCustomDashboard(options.dashboard);
}
const serverProcess = await powerpipe.startServer();
console.log(chalk_1.default.green('\nā
Powerpipe is running!'));
console.log(chalk_1.default.cyan('Press Ctrl+C to stop the server\n'));
process.on('SIGINT', () => {
console.log(chalk_1.default.yellow('\nš Stopping Powerpipe server...'));
serverProcess.kill();
process.exit(0);
});
await new Promise(() => { });
}
catch (error) {
console.error(chalk_1.default.red(`ā Powerpipe integration failed: ${error.message}`));
process.exit(1);
}
});
program
.command('setup-roles')
.description('š§ Generate IAM role setup for cross-account scanning')
.option('--accounts <accounts>', 'Comma-separated target account IDs')
.option('--management-account <account>', 'Management account ID')
.action(async (options) => {
const scanner = new scanner_1.ContainerImageScanner();
await scanner.generateRoleSetupInstructions(options);
});
async function validateScanOptions(options) {
if (options.regions) {
const regions = options.regions.split(',').map((r) => r.trim());
const validRegions = [
'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
'eu-west-1', 'eu-west-2', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2'
];
for (const region of regions) {
if (!validRegions.includes(region)) {
throw new Error(`Invalid region: ${region}`);
}
}
}
if (options.accounts) {
const accounts = options.accounts.split(',').map((a) => a.trim());
for (const account of accounts) {
if (!/^\d{12}$/.test(account)) {
throw new Error(`Invalid AWS account ID: ${account}`);
}
}
}
}
if (require.main === module) {
program.parse();
}
program
.command('alternatives')
.description('Analyze technical alternatives for Bitnami images')
.option('--input <file>', 'Input scan results file')
.option('--images <images>', 'Comma-separated list of images to analyze')
.option('--output <format>', 'Output format (table|json|csv)', 'table')
.action(async (options) => {
const { execSync } = require('child_process');
if (options.images) {
const images = options.images.split(',');
const imageList = images.map((img) => `"${img.trim()}"`).join(' ');
execSync(`node scripts/technical-alternatives.js ${imageList}`, { stdio: 'inherit' });
}
else if (options.input) {
execSync(`node scripts/technical-alternatives.js --input "${options.input}"`, { stdio: 'inherit' });
}
else {
console.log('Please provide either --images or --input parameter');
process.exit(1);
}
});
;