UNPKG

container-image-scanner

Version:

Enterprise Container Image Scanner with AWS Security Best Practices. Scan EKS clusters for Bitnami container image dependencies and generate migration guidance for AWS ECR alternatives.

291 lines (246 loc) • 9.99 kB
#!/usr/bin/env node const { execSync } = require('child_process'); const chalk = require('chalk'); class EnhancedAnalyzer { constructor() { this.kubectlAvailable = this.checkKubectl(); this.helmAvailable = this.checkHelm(); } checkKubectl() { try { execSync('kubectl version --client', { stdio: 'ignore' }); return true; } catch { return false; } } checkHelm() { try { execSync('helm version', { stdio: 'ignore' }); return true; } catch { return false; } } analyzeImageVersioning(imageName) { const [name, tag] = imageName.split(':'); const tagValue = tag || 'latest'; return { imageName: name, tag: tagValue, isLatest: tagValue === 'latest', isPinned: tagValue !== 'latest' && /^\d+\.\d+/.test(tagValue), isSemver: /^\d+\.\d+\.\d+/.test(tagValue), updateFrequency: tagValue === 'latest' ? 'frequent' : 'regular' }; } detectHelmDeployments() { if (this.helmAvailable) { try { const output = execSync('helm list --all-namespaces', { encoding: 'utf8' }); return { isHelmManaged: output.trim().length > 0, releases: output.split('\n').length - 1 }; } catch { return { isHelmManaged: false, releases: 0 }; } } if (this.kubectlAvailable) { try { const output = execSync('kubectl get all --all-namespaces -o json', { encoding: 'utf8' }); const hasHelm = output.includes('helm.sh') || output.includes('Helm'); return { isHelmManaged: hasHelm, releases: hasHelm ? 1 : 0 }; } catch { return { isHelmManaged: false, releases: 0 }; } } return { isHelmManaged: false, releases: 0 }; } analyzeContainerSecurity(imageName) { const security = { hasSecurityContext: false, runAsNonRoot: false, readOnlyRootFilesystem: false, hasResourceLimits: false }; if (!this.kubectlAvailable) return security; try { // Check for security contexts in deployments using this image const output = execSync('kubectl get deployments --all-namespaces -o json', { encoding: 'utf8' }); const deployments = JSON.parse(output); deployments.items.forEach(deployment => { const containers = deployment.spec?.template?.spec?.containers || []; containers.forEach(container => { if (container.image && container.image.includes(imageName.split(':')[0])) { if (container.securityContext) { security.hasSecurityContext = true; if (container.securityContext.runAsNonRoot) security.runAsNonRoot = true; if (container.securityContext.readOnlyRootFilesystem) security.readOnlyRootFilesystem = true; } if (container.resources?.limits) security.hasResourceLimits = true; } }); }); } catch { // Unable to analyze security } return security; } generateReport(imageName) { const versionAnalysis = this.analyzeImageVersioning(imageName); const helmDeployment = this.detectHelmDeployments(); const containerSecurity = this.analyzeContainerSecurity(imageName); const recommendations = []; if (versionAnalysis.isLatest) { recommendations.push('āœ… GOOD: Using "latest" tag - no breaking changes expected from Broadcom transition'); } else if (versionAnalysis.isPinned) { recommendations.push('🚨 CRITICAL: Pinned version may break - Broadcom removing free Bitnami images'); recommendations.push('šŸ“‹ MIGRATION REQUIRED: Switch to AWS managed service or upstream alternative'); } else { recommendations.push('āš ļø HIGH: Semver tags at risk - may break when Broadcom removes free access'); recommendations.push('šŸ“‹ MIGRATION RECOMMENDED: Plan transition to avoid service disruption'); } if (helmDeployment.isHelmManaged) { recommendations.push('āœ… Good: Helm-managed deployment - easier migration to alternatives'); } else { recommendations.push('šŸ“‹ Consider migration complexity for direct manifest deployments'); } if (!containerSecurity.hasSecurityContext) { recommendations.push('šŸ”’ Add security contexts for production readiness'); } return { imageName, timestamp: new Date().toISOString(), versionAnalysis, helmDeployment, containerSecurity, recommendations, toolsAvailable: { kubectl: this.kubectlAvailable, helm: this.helmAvailable } }; } displayReport(report) { console.log(chalk.bold.blue(`\nšŸ“¦ Enhanced Analysis: ${report.imageName}`)); console.log(chalk.gray(`Analyzed: ${new Date(report.timestamp).toLocaleString()}`)); // Version Analysis console.log(chalk.bold('\nšŸ·ļø Version Analysis:')); console.log(` Tag: ${report.versionAnalysis.tag}`); console.log(` Latest: ${report.versionAnalysis.isLatest ? chalk.red('Yes') : chalk.green('No')}`); console.log(` Pinned: ${report.versionAnalysis.isPinned ? chalk.green('Yes') : chalk.yellow('No')}`); console.log(` Semver: ${report.versionAnalysis.isSemver ? chalk.green('Yes') : chalk.gray('No')}`); console.log(` Update Frequency: ${chalk.blue(report.versionAnalysis.updateFrequency)}`); // Helm Information console.log(chalk.bold('\nāš™ļø Helm Deployment:')); if (report.helmDeployment.isHelmManaged) { console.log(chalk.green(` āœ… Helm-managed (${report.helmDeployment.releases} releases)`)); } else { console.log(chalk.yellow(' āš ļø Not Helm-managed (direct manifest)')); } // Container Security console.log(chalk.bold('\nšŸ”’ Container Security:')); console.log(` Security Context: ${report.containerSecurity.hasSecurityContext ? chalk.green('Yes') : chalk.yellow('No')}`); console.log(` Non-Root User: ${report.containerSecurity.runAsNonRoot ? chalk.green('Yes') : chalk.yellow('No')}`); console.log(` Read-Only Root FS: ${report.containerSecurity.readOnlyRootFilesystem ? chalk.green('Yes') : chalk.yellow('No')}`); console.log(` Resource Limits: ${report.containerSecurity.hasResourceLimits ? chalk.green('Yes') : chalk.yellow('No')}`); // Tools Available console.log(chalk.bold('\nšŸ”§ Tools Available:')); console.log(` kubectl: ${report.toolsAvailable.kubectl ? chalk.green('Yes') : chalk.red('No')}`); console.log(` helm: ${report.toolsAvailable.helm ? chalk.green('Yes') : chalk.red('No')}`); // Recommendations if (report.recommendations.length > 0) { console.log(chalk.bold('\nšŸ’” Recommendations:')); report.recommendations.forEach(rec => { console.log(` ${rec}`); }); } console.log(chalk.gray('\n' + '─'.repeat(60))); } } // Main execution if (require.main === module) { const analyzer = new EnhancedAnalyzer(); const testImages = [ 'bitnami/mysql:latest', 'bitnami/mysql:8.0.35', 'bitnami/redis:7.2.4', 'bitnami/postgresql:15.4.0' ]; console.log(chalk.bold.blue('šŸ” Enhanced Container Analysis Demo')); console.log(chalk.gray('Testing container image security and deployment analysis\n')); testImages.forEach(image => { const report = analyzer.generateReport(image); analyzer.displayReport(report); }); console.log(chalk.bold.green('\nāœ… Enhanced analysis completed!')); console.log(chalk.gray('This demonstrates:')); console.log(chalk.gray('• Image versioning analysis (latest vs pinned)')); console.log(chalk.gray('• Helm deployment detection')); console.log(chalk.gray('• Container security assessment')); console.log(chalk.gray('• Tool availability checking')); console.log(chalk.gray('• Intelligent recommendations')); } module.exports = { EnhancedAnalyzer }; // Add Bitnami Premium migration analysis class BitnamiPremiumAnalyzer { analyzeMigrationComplexity(imageName) { const [name, tag] = imageName.split(':'); const tagValue = tag || 'latest'; if (!name.includes('bitnami')) { return { applicable: false }; } const isLatest = tagValue === 'latest'; const isPinned = tagValue !== 'latest' && /^\d+\.\d+/.test(tagValue); return { applicable: true, currentImage: imageName, premiumImage: imageName.replace('bitnami/', 'bitnamiprem/'), migrationComplexity: isLatest ? 'LOW' : 'HIGH', reason: isLatest ? 'Latest tag - automatic transition to Premium' : 'Pinned version - requires manual registry migration', authRequired: true, registryChanges: !isLatest, helmChartImpact: true }; } generateMigrationScript(analysis) { if (!analysis.applicable) return ''; if (analysis.migrationComplexity === 'LOW') { return ` # EASY MIGRATION (Latest Tag) # 1. Setup Docker Hub Premium access # 2. Configure pull secrets # 3. Images will automatically pull from Premium registry kubectl create secret docker-registry regcred \\ --docker-server=https://index.docker.io \\ --docker-username=<your-username> \\ --docker-password=<your-pat> \\ --docker-email=<your-email> `; } else { return ` # COMPLEX MIGRATION (Pinned Version) # 1. Update image references # 2. Setup authentication # 3. Update CI/CD pipelines # Old image: ${analysis.currentImage} # New image: ${analysis.premiumImage} # Update deployments: kubectl patch deployment <deployment-name> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container-name>","image":"${analysis.premiumImage}"}]}}}}' # Create pull secret: kubectl create secret docker-registry regcred \\ --docker-server=https://index.docker.io \\ --docker-username=<your-username> \\ --docker-password=<your-pat> \\ --docker-email=<your-email> `; } } } // Export for use if (typeof module !== 'undefined') { module.exports = { EnhancedAnalyzer, BitnamiPremiumAnalyzer }; }