mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-19 08:44:14 +00:00
124 lines
4.2 KiB
Go
124 lines
4.2 KiB
Go
package scanner
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"atcr.io/pkg/atproto"
|
|
)
|
|
|
|
// storeResults uploads scan results and creates ORAS manifest records in the hold's PDS
|
|
func (w *Worker) storeResults(ctx context.Context, job *ScanJob, sbomDigest, vulnDigest string, vulnJSON []byte, summary VulnerabilitySummary) error {
|
|
if !w.config.Scanner.VulnEnabled {
|
|
slog.Info("Vulnerability scanning disabled, skipping result storage")
|
|
return nil
|
|
}
|
|
|
|
slog.Info("Storing scan results as ORAS artifact",
|
|
"repository", job.Repository,
|
|
"subjectDigest", job.ManifestDigest,
|
|
"vulnDigest", vulnDigest)
|
|
|
|
// Create ORAS manifest for vulnerability report
|
|
orasManifest := map[string]interface{}{
|
|
"schemaVersion": 2,
|
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
|
"artifactType": "application/vnd.atcr.vulnerabilities+json",
|
|
"config": map[string]interface{}{
|
|
"mediaType": "application/vnd.oci.empty.v1+json",
|
|
"digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", // Empty JSON object
|
|
"size": 2,
|
|
},
|
|
"subject": map[string]interface{}{
|
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
|
"digest": job.ManifestDigest,
|
|
"size": 0, // We don't have the size, but it's optional
|
|
},
|
|
"layers": []map[string]interface{}{
|
|
{
|
|
"mediaType": "application/json",
|
|
"digest": vulnDigest,
|
|
"size": len(vulnJSON),
|
|
"annotations": map[string]string{
|
|
"org.opencontainers.image.title": "vulnerability-report.json",
|
|
},
|
|
},
|
|
},
|
|
"annotations": map[string]string{
|
|
"io.atcr.vuln.critical": fmt.Sprintf("%d", summary.Critical),
|
|
"io.atcr.vuln.high": fmt.Sprintf("%d", summary.High),
|
|
"io.atcr.vuln.medium": fmt.Sprintf("%d", summary.Medium),
|
|
"io.atcr.vuln.low": fmt.Sprintf("%d", summary.Low),
|
|
"io.atcr.vuln.total": fmt.Sprintf("%d", summary.Total),
|
|
"io.atcr.vuln.scannedAt": time.Now().Format(time.RFC3339),
|
|
"io.atcr.vuln.scannerVersion": w.getScannerVersion(),
|
|
},
|
|
}
|
|
|
|
// Encode ORAS manifest to JSON
|
|
orasManifestJSON, err := json.Marshal(orasManifest)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encode ORAS manifest: %w", err)
|
|
}
|
|
|
|
// Calculate ORAS manifest digest
|
|
orasDigest := fmt.Sprintf("sha256:%x", sha256Bytes(orasManifestJSON))
|
|
|
|
// Upload ORAS manifest blob to storage
|
|
if err := w.uploadBlob(ctx, orasDigest, orasManifestJSON); err != nil {
|
|
return fmt.Errorf("failed to upload ORAS manifest blob: %w", err)
|
|
}
|
|
|
|
// Create manifest record in hold's PDS
|
|
if err := w.createManifestRecord(ctx, job, orasDigest, orasManifestJSON, summary); err != nil {
|
|
return fmt.Errorf("failed to create manifest record: %w", err)
|
|
}
|
|
|
|
slog.Info("Successfully stored scan results", "orasDigest", orasDigest)
|
|
return nil
|
|
}
|
|
|
|
// createManifestRecord creates an ORAS manifest record in the hold's PDS
|
|
func (w *Worker) createManifestRecord(ctx context.Context, job *ScanJob, orasDigest string, orasManifestJSON []byte, summary VulnerabilitySummary) error {
|
|
// Create ManifestRecord from ORAS manifest
|
|
record, err := atproto.NewManifestRecord(job.Repository, orasDigest, orasManifestJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create manifest record: %w", err)
|
|
}
|
|
|
|
// Set SBOM/vulnerability specific fields
|
|
record.OwnerDID = job.UserDID
|
|
record.ScannedAt = time.Now().Format(time.RFC3339)
|
|
record.ScannerVersion = w.getScannerVersion()
|
|
|
|
// Add hold DID (this ORAS artifact is stored in the hold's PDS)
|
|
record.HoldDID = w.pds.DID()
|
|
|
|
// Convert digest to record key (remove "sha256:" prefix)
|
|
rkey := orasDigest[len("sha256:"):]
|
|
|
|
// Store record in hold's PDS
|
|
slog.Info("Creating manifest record in hold's PDS",
|
|
"collection", atproto.ManifestCollection,
|
|
"rkey", rkey,
|
|
"ownerDid", job.UserDID)
|
|
|
|
_, _, err = w.pds.CreateManifestRecord(ctx, record, rkey)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to put record in PDS: %w", err)
|
|
}
|
|
|
|
slog.Info("Manifest record created successfully", "uri", fmt.Sprintf("at://%s/%s/%s", w.pds.DID(), atproto.ManifestCollection, rkey))
|
|
return nil
|
|
}
|
|
|
|
// sha256Bytes calculates SHA256 hash of byte slice
|
|
func sha256Bytes(data []byte) []byte {
|
|
hash := sha256.Sum256(data)
|
|
return hash[:]
|
|
}
|