Files
michaelschiemer/src/Framework/Console/Security/Commands/DependencyAuditCommand.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

271 lines
9.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Console\Security\Commands;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Console\ExitCode;
use App\Framework\Console\Input\ConsoleInput;
use App\Framework\Console\Layout\ResponsiveOutput;
use App\Framework\Console\Output\ConsoleOutput;
final readonly class DependencyAuditCommand
{
#[ConsoleCommand(
name: 'security:audit',
description: 'Audit dependencies for known security vulnerabilities'
)]
public function audit(ConsoleInput $input, ConsoleOutput $output): ExitCode
{
$format = $input->getOption('format') ?? 'table';
$noDevDeps = $input->hasOption('no-dev');
$failOnVulnerabilities = $input->hasOption('fail-on-vulnerabilities');
$output->writeLine("<yellow>Running Security Audit...</yellow>\n");
// Build composer audit command
$command = 'composer audit --format=' . escapeshellarg($format);
if ($noDevDeps) {
$command .= ' --no-dev';
}
// Execute composer audit
$startTime = microtime(true);
exec($command . ' 2>&1', $outputLines, $returnCode);
$duration = microtime(true) - $startTime;
$outputText = implode("\n", $outputLines);
// Parse JSON output if format is JSON
if ($format === 'json') {
$auditData = json_decode($outputText, true);
if (json_last_error() === JSON_ERROR_NONE) {
$this->displayJsonResults($auditData, $output);
return $this->determineExitCode(
$auditData,
$failOnVulnerabilities
);
}
}
// Display raw output for non-JSON formats
$output->writeLine($outputText);
$output->writeLine("\n<gray>Audit completed in " . number_format($duration, 2) . "s</gray>");
// Return appropriate exit code
if ($returnCode === 0) {
$output->writeLine("\n<green>✓ No security vulnerabilities found!</green>");
return ExitCode::SUCCESS;
}
$output->writeLine("\n<red>✗ Security vulnerabilities detected!</red>");
return $failOnVulnerabilities ? ExitCode::FAILURE : ExitCode::SUCCESS;
}
#[ConsoleCommand(
name: 'security:audit-summary',
description: 'Show summary of dependency security audit'
)]
public function auditSummary(ConsoleInput $input, ConsoleOutput $output): ExitCode
{
$output->writeLine("<yellow>Security Audit Summary</yellow>\n");
// Execute composer audit with JSON format
exec('composer audit --format=json 2>&1', $outputLines, $returnCode);
$auditData = json_decode(implode("\n", $outputLines), true);
if (json_last_error() !== JSON_ERROR_NONE) {
$output->writeLine('<red>Failed to parse audit results</red>');
return ExitCode::FAILURE;
}
$responsiveOutput = ResponsiveOutput::create($output);
// Count vulnerabilities by severity
$severityCounts = [
'critical' => 0,
'high' => 0,
'medium' => 0,
'low' => 0,
];
$totalVulnerabilities = 0;
$affectedPackages = [];
foreach ($auditData['advisories'] ?? [] as $packageName => $advisories) {
$affectedPackages[] = $packageName;
foreach ($advisories as $advisory) {
$severity = strtolower($advisory['severity'] ?? 'unknown');
if (isset($severityCounts[$severity])) {
$severityCounts[$severity]++;
}
$totalVulnerabilities++;
}
}
// Display summary
$summary = [
'Total Vulnerabilities' => $totalVulnerabilities,
'Critical' => "<red>{$severityCounts['critical']}</red>",
'High' => "<yellow>{$severityCounts['high']}</yellow>",
'Medium' => "<cyan>{$severityCounts['medium']}</cyan>",
'Low' => "<gray>{$severityCounts['low']}</gray>",
'Affected Packages' => count($affectedPackages),
'Abandoned Packages' => count($auditData['abandoned'] ?? []),
];
$responsiveOutput->writeKeyValue($summary);
if ($totalVulnerabilities > 0) {
$output->writeLine("\n<red>⚠ Security vulnerabilities detected!</red>");
$output->writeLine("Run <cyan>php console.php security:audit</cyan> for details");
} else {
$output->writeLine("\n<green>✓ No security vulnerabilities found!</green>");
}
if (!empty($auditData['abandoned'])) {
$output->writeLine("\n<yellow>Abandoned Packages:</yellow>");
foreach ($auditData['abandoned'] as $package => $replacement) {
$replacementText = $replacement ? " (use {$replacement} instead)" : '';
$output->writeLine("{$package}{$replacementText}");
}
}
return $totalVulnerabilities > 0 ? ExitCode::FAILURE : ExitCode::SUCCESS;
}
#[ConsoleCommand(
name: 'security:audit-details',
description: 'Show detailed information about a specific package vulnerability'
)]
public function auditDetails(ConsoleInput $input, ConsoleOutput $output): ExitCode
{
$packageName = $input->getArgument('package');
if (!$packageName) {
$output->writeLine('<red>Package name is required</red>');
$output->writeLine('Usage: php console.php security:audit-details <package>');
return ExitCode::INVALID_ARGUMENTS;
}
// Execute composer audit with JSON format
exec('composer audit --format=json 2>&1', $outputLines);
$auditData = json_decode(implode("\n", $outputLines), true);
if (json_last_error() !== JSON_ERROR_NONE) {
$output->writeLine('<red>Failed to parse audit results</red>');
return ExitCode::FAILURE;
}
// Find package advisories
$advisories = $auditData['advisories'][$packageName] ?? null;
if (!$advisories) {
$output->writeLine("<yellow>No vulnerabilities found for package: {$packageName}</yellow>");
return ExitCode::SUCCESS;
}
$output->writeLine("<yellow>Vulnerabilities for {$packageName}</yellow>\n");
foreach ($advisories as $advisory) {
$this->displayAdvisoryDetails($advisory, $output);
$output->writeLine(str_repeat('─', 80));
}
return ExitCode::SUCCESS;
}
private function displayJsonResults(array $auditData, ConsoleOutput $output): void
{
$advisories = $auditData['advisories'] ?? [];
$abandoned = $auditData['abandoned'] ?? [];
if (empty($advisories) && empty($abandoned)) {
$output->writeLine('<green>✓ No security issues found!</green>');
return;
}
if (!empty($advisories)) {
$output->writeLine("<red>Security Vulnerabilities:</red>\n");
foreach ($advisories as $packageName => $packageAdvisories) {
$output->writeLine("<yellow>{$packageName}</yellow>");
foreach ($packageAdvisories as $advisory) {
$severity = $advisory['severity'] ?? 'unknown';
$severityColor = $this->getSeverityColor($severity);
$cve = $advisory['cve'] ?? 'N/A';
$title = $advisory['title'] ?? 'No title';
$output->writeLine(" • [{$severityColor}{$severity}</color>] {$cve}: {$title}");
if (!empty($advisory['link'])) {
$output->writeLine(" Link: <cyan>{$advisory['link']}</cyan>");
}
}
$output->writeLine('');
}
}
if (!empty($abandoned)) {
$output->writeLine("<yellow>Abandoned Packages:</yellow>\n");
foreach ($abandoned as $package => $replacement) {
$replacementText = $replacement ? " (use {$replacement})" : '';
$output->writeLine("{$package}{$replacementText}");
}
}
}
private function displayAdvisoryDetails(array $advisory, ConsoleOutput $output): void
{
$responsiveOutput = ResponsiveOutput::create($output);
$details = [
'Title' => $advisory['title'] ?? 'Unknown',
'CVE' => $advisory['cve'] ?? 'N/A',
'Severity' => $this->formatSeverity($advisory['severity'] ?? 'unknown'),
'Affected Versions' => $advisory['affectedVersions'] ?? 'N/A',
'Source' => $advisory['sources'][0]['remoteId'] ?? 'N/A',
];
$responsiveOutput->writeKeyValue($details);
if (!empty($advisory['link'])) {
$output->writeLine("\n<cyan>More Info:</cyan> {$advisory['link']}");
}
$output->writeLine('');
}
private function getSeverityColor(string $severity): string
{
return match (strtolower($severity)) {
'critical' => '<red>',
'high' => '<yellow>',
'medium' => '<cyan>',
'low' => '<gray>',
default => '<white>',
};
}
private function formatSeverity(string $severity): string
{
$color = $this->getSeverityColor($severity);
return "{$color}" . strtoupper($severity) . '</color>';
}
private function determineExitCode(array $auditData, bool $failOnVulnerabilities): ExitCode
{
$hasVulnerabilities = !empty($auditData['advisories']);
if ($hasVulnerabilities && $failOnVulnerabilities) {
return ExitCode::FAILURE;
}
return ExitCode::SUCCESS;
}
}