UNPKG

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
#!/usr/bin/env node "use strict"; 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); } });