mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-31 13:17:09 +00:00
117 lines
3.2 KiB
Go
117 lines
3.2 KiB
Go
package scanner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"atcr.io/pkg/hold"
|
|
"atcr.io/pkg/hold/pds"
|
|
"github.com/distribution/distribution/v3/registry/storage/driver"
|
|
)
|
|
|
|
// Worker performs vulnerability scanning on container images
|
|
type Worker struct {
|
|
config *hold.Config
|
|
driver driver.StorageDriver
|
|
pds *pds.HoldPDS
|
|
queue *Queue
|
|
}
|
|
|
|
// NewWorker creates a new scanner worker
|
|
func NewWorker(config *hold.Config, driver driver.StorageDriver, pds *pds.HoldPDS) *Worker {
|
|
return &Worker{
|
|
config: config,
|
|
driver: driver,
|
|
pds: pds,
|
|
}
|
|
}
|
|
|
|
// Start starts the worker pool and initializes vulnerability database
|
|
func (w *Worker) Start(queue *Queue) {
|
|
w.queue = queue
|
|
|
|
// Initialize vulnerability database on startup if scanning is enabled
|
|
if w.config.Scanner.VulnEnabled {
|
|
go func() {
|
|
ctx := context.Background()
|
|
if err := w.initializeVulnDatabase(ctx); err != nil {
|
|
slog.Error("Failed to initialize vulnerability database", "error", err)
|
|
slog.Warn("Vulnerability scanning will be disabled until database is available")
|
|
}
|
|
}()
|
|
}
|
|
|
|
queue.Start(w.processJob)
|
|
}
|
|
|
|
// processJob processes a single scan job
|
|
func (w *Worker) processJob(ctx context.Context, job *ScanJob) (*ScanResult, error) {
|
|
slog.Info("Processing scan job",
|
|
"repository", job.Repository,
|
|
"tag", job.Tag,
|
|
"digest", job.ManifestDigest,
|
|
"layers", len(job.Layers))
|
|
|
|
startTime := time.Now()
|
|
|
|
// Step 1: Extract image layers from storage
|
|
slog.Info("Extracting image layers", "repository", job.Repository)
|
|
imageDir, cleanup, err := w.extractLayers(ctx, job)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to extract layers: %w", err)
|
|
}
|
|
defer cleanup()
|
|
|
|
// Step 2: Generate SBOM with Syft
|
|
slog.Info("Generating SBOM", "repository", job.Repository)
|
|
sbomResult, _, sbomDigest, err := w.generateSBOM(ctx, imageDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate SBOM: %w", err)
|
|
}
|
|
|
|
// Step 3: Scan SBOM with Grype (if enabled)
|
|
var vulnJSON []byte
|
|
var vulnDigest string
|
|
var summary VulnerabilitySummary
|
|
|
|
if w.config.Scanner.VulnEnabled {
|
|
slog.Info("Scanning for vulnerabilities", "repository", job.Repository)
|
|
vulnJSON, vulnDigest, summary, err = w.scanVulnerabilities(ctx, sbomResult)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan vulnerabilities: %w", err)
|
|
}
|
|
}
|
|
|
|
// Step 4: Upload results to storage and create ORAS manifests
|
|
slog.Info("Storing scan results", "repository", job.Repository)
|
|
err = w.storeResults(ctx, job, sbomDigest, vulnDigest, vulnJSON, summary)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to store results: %w", err)
|
|
}
|
|
|
|
duration := time.Since(startTime)
|
|
slog.Info("Scan job completed",
|
|
"repository", job.Repository,
|
|
"tag", job.Tag,
|
|
"duration", duration,
|
|
"vulnerabilities", summary.Total)
|
|
|
|
return &ScanResult{
|
|
Job: job,
|
|
VulnerabilitiesJSON: vulnJSON,
|
|
Summary: summary,
|
|
SBOMDigest: sbomDigest,
|
|
VulnDigest: vulnDigest,
|
|
ScannedAt: time.Now(),
|
|
ScannerVersion: w.getScannerVersion(),
|
|
}, nil
|
|
}
|
|
|
|
// getScannerVersion returns the version string for the scanner
|
|
func (w *Worker) getScannerVersion() string {
|
|
// TODO: Get actual Syft and Grype versions dynamically
|
|
return "syft-v1.36.0/grype-v0.102.0"
|
|
}
|