mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-20 15:22:59 +00:00
feat(images): Trivy-powered vulnerability scanning (#635)
* feat(images): Trivy-powered vulnerability scanning Scan container images for known CVEs via Trivy. On-demand scanning and severity badges are available on every tier; scheduled scans, scan policies, SBOM generation, and scan history are gated to Skipper+. - New TrivyService (binary detection, per-image scan, SBOM, digest cache) - Three new tables: vulnerability_scans, vulnerability_details, scan_policies - 12 routes under /api/security (scan, results, summaries, SBOM, policies, compare) - Post-deploy async scans wired into all five deploy paths, with a per-deploy opt-out toggle in the App Store deploy sheet - "scan" action type added to SchedulerService for fleet-wide recurring scans - Frontend: severity badges in Resources Hub with animated cursor detail, scan results drawer with vulnerability table and filters, and a new Security section in Settings for scan policy CRUD - Policy threshold violations dispatch a warning or critical alert based on the policy's block_on_deploy flag; deploys themselves are never blocked * fix(security): compute scan age in useEffect to satisfy react-hooks/purity
This commit is contained in:
@@ -12,6 +12,7 @@ import { getErrorMessage } from '../utils/errors';
|
||||
import { captureLocalNodeFiles, captureRemoteNodeFiles } from '../utils/snapshot-capture';
|
||||
import { NodeRegistry } from './NodeRegistry';
|
||||
import { NotificationService } from './NotificationService';
|
||||
import TrivyService from './TrivyService';
|
||||
|
||||
export class SchedulerService {
|
||||
private static instance: SchedulerService;
|
||||
@@ -82,9 +83,10 @@ export class SchedulerService {
|
||||
|
||||
// Clean up old runs periodically (piggyback on tick)
|
||||
db.cleanupOldTaskRuns(30);
|
||||
db.deleteOldScans(90 * 24 * 60 * 60 * 1000);
|
||||
|
||||
for (const task of dueTasks) {
|
||||
if (!isAdmiral && task.action !== 'update') {
|
||||
if (!isAdmiral && task.action !== 'update' && task.action !== 'scan') {
|
||||
if (isDebugEnabled()) console.log(`[SchedulerService] Task ${task.id} skipped: action "${task.action}" requires Admiral tier`);
|
||||
continue;
|
||||
}
|
||||
@@ -159,6 +161,9 @@ export class SchedulerService {
|
||||
case 'update':
|
||||
output = await this.executeUpdate(task);
|
||||
break;
|
||||
case 'scan':
|
||||
output = await this.executeScan(task);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isDebugEnabled()) console.log(`[SchedulerService:debug] Task ${task.id} action completed in ${Date.now() - actionStart}ms`);
|
||||
@@ -498,4 +503,23 @@ export class SchedulerService {
|
||||
|
||||
return `Stack "${stackName}": updated (${updatedImages.join(', ')}).`;
|
||||
}
|
||||
|
||||
private async executeScan(task: ScheduledTask): Promise<string> {
|
||||
const trivy = TrivyService.getInstance();
|
||||
if (!trivy.isTrivyAvailable()) {
|
||||
throw new Error('Trivy binary is not available on this node');
|
||||
}
|
||||
|
||||
const nodeId = task.node_id ?? NodeRegistry.getInstance().getDefaultNodeId();
|
||||
if (task.node_id == null && isDebugEnabled()) {
|
||||
console.log(`[SchedulerService:debug] Scan task ${task.id}: no node_id specified, using default node ${nodeId}`);
|
||||
}
|
||||
|
||||
const summary = await trivy.scanAllNodeImages(nodeId, 'scheduled');
|
||||
|
||||
const parts: string[] = [`Scanned ${summary.scanned} image(s)`];
|
||||
if (summary.skipped > 0) parts.push(`${summary.skipped} skipped (cached)`);
|
||||
if (summary.failed > 0) parts.push(`${summary.failed} failed`);
|
||||
return parts.join('; ');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user