From 7c7525f353f301bd58e111d3f22f66b1fb902c89 Mon Sep 17 00:00:00 2001 From: master <> Date: Thu, 9 Apr 2026 11:08:00 +0300 Subject: [PATCH] feat(audit): annotate endpoints in EvidenceLocker + Integrations + Scanner (Batch 1) - Add AuditedRouteGroupExtensions with WithAuditFilter() and Audited() helpers - EvidenceLocker: 7 endpoints (store, snapshot, verify, hold, store_verdict, verify_verdict, export) - Integrations: 6 endpoints (create, update, delete, test, discover, run_code_guard) - Scanner: ~55 annotations across 25 endpoint files covering sources CRUD, scan submission, scan policies, approvals, triage, webhooks, reports, reachability, secret detection, offline kit, runtime, and more - Skipped read-only POSTs per convention (delta compare, counterfactual, EPSS batch, slice query, policy diagnostics/preview/runtime/overlay) - All 3 services build clean with 0 errors/warnings - Sprint 005: FILTER-001, FILTER-002, FILTER-003 marked DONE Co-Authored-By: Claude Opus 4.6 (1M context) --- ..._005_Audit_endpoint_filters_deprecation.md | 23 ++++----- .../Api/ExportEndpoints.cs | 4 +- .../Api/VerdictEndpoints.cs | 7 ++- .../Program.cs | 12 +++-- .../IntegrationEndpoints.cs | 19 +++++--- .../Endpoints/ApprovalEndpoints.cs | 7 ++- .../Endpoints/CallGraphEndpoints.cs | 4 +- .../Endpoints/FidelityEndpoints.cs | 7 ++- .../Endpoints/GitHubCodeScanningEndpoints.cs | 4 +- .../Endpoints/LayerSbomEndpoints.cs | 4 +- .../Endpoints/OfflineKitEndpoints.cs | 7 ++- .../Endpoints/ReachabilityEndpoints.cs | 4 +- .../ReachabilityEvidenceEndpoints.cs | 7 ++- .../Endpoints/ReplayEndpoints.cs | 4 +- .../Endpoints/ReportEndpoints.cs | 4 +- .../Endpoints/RuntimeEndpoints.cs | 7 ++- .../Endpoints/SbomEndpoints.cs | 4 +- .../Endpoints/SbomUploadEndpoints.cs | 4 +- .../Endpoints/ScanEndpoints.cs | 7 ++- .../Endpoints/ScanPolicyEndpoints.cs | 10 ++-- .../Endpoints/ScoreReplayEndpoints.cs | 13 +++-- .../SecretDetectionSettingsEndpoints.cs | 16 +++++-- .../Endpoints/SmartDiffEndpoints.cs | 7 ++- .../Endpoints/SourcesEndpoints.cs | 28 +++++++---- .../Endpoints/Triage/BatchTriageEndpoints.cs | 4 +- .../Endpoints/Triage/ProofBundleEndpoints.cs | 4 +- .../Endpoints/Triage/TriageStatusEndpoints.cs | 10 ++-- .../Endpoints/ValidationEndpoints.cs | 4 +- .../Endpoints/WebhookEndpoints.cs | 16 +++++-- .../Endpoints/WitnessEndpoints.cs | 4 +- .../AuditedRouteGroupExtensions.cs | 48 +++++++++++++++++++ 31 files changed, 224 insertions(+), 79 deletions(-) create mode 100644 src/__Libraries/StellaOps.Audit.Emission/AuditedRouteGroupExtensions.cs diff --git a/docs/implplan/SPRINT_20260408_005_Audit_endpoint_filters_deprecation.md b/docs/implplan/SPRINT_20260408_005_Audit_endpoint_filters_deprecation.md index 60d9835e8..7ff3b7e63 100644 --- a/docs/implplan/SPRINT_20260408_005_Audit_endpoint_filters_deprecation.md +++ b/docs/implplan/SPRINT_20260408_005_Audit_endpoint_filters_deprecation.md @@ -364,7 +364,7 @@ This minimizes the per-endpoint boilerplate (no `.AddEndpointFilter(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(StellaOpsResourceServerPolicies.ExportOperator); + .RequireAuthorization(StellaOpsResourceServerPolicies.ExportOperator) + .Audited("evidence", "export"); // GET /api/v1/bundles/{bundleId}/export/{exportId} group.MapGet("/{bundleId}/export/{exportId}", GetExportStatusAsync) diff --git a/src/EvidenceLocker/StellaOps.EvidenceLocker/Api/VerdictEndpoints.cs b/src/EvidenceLocker/StellaOps.EvidenceLocker/Api/VerdictEndpoints.cs index 668f7830b..a07f28f09 100644 --- a/src/EvidenceLocker/StellaOps.EvidenceLocker/Api/VerdictEndpoints.cs +++ b/src/EvidenceLocker/StellaOps.EvidenceLocker/Api/VerdictEndpoints.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.EvidenceLocker.Storage; @@ -35,7 +36,8 @@ public static class VerdictEndpoints .RequireAuthorization(StellaOpsResourceServerPolicies.EvidenceCreate) .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status500InternalServerError); + .Produces(StatusCodes.Status500InternalServerError) + .Audited("evidence", "store_verdict"); // GET /api/v1/verdicts/{verdictId} group.MapGet("/{verdictId}", GetVerdictAsync) @@ -67,7 +69,8 @@ public static class VerdictEndpoints .RequireAuthorization(StellaOpsResourceServerPolicies.EvidenceRead) .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status500InternalServerError); + .Produces(StatusCodes.Status500InternalServerError) + .Audited("evidence", "verify_verdict"); // GET /api/v1/verdicts/{verdictId}/envelope - SPRINT_4000_0100_0001 group.MapGet("/{verdictId}/envelope", DownloadEnvelopeAsync) diff --git a/src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.WebService/Program.cs b/src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.WebService/Program.cs index 0229172be..1e599f08e 100644 --- a/src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.WebService/Program.cs +++ b/src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.WebService/Program.cs @@ -110,7 +110,8 @@ app.MapPost("/evidence", .Produces(StatusCodes.Status403Forbidden) .WithName("StoreEvidenceGateArtifact") .WithTags("Evidence") - .WithSummary("Ingest producer gate artifact evidence and compute deterministic evidence score."); + .WithSummary("Ingest producer gate artifact evidence and compute deterministic evidence score.") + .Audited("evidence", "store"); app.MapGet("/evidence/score", async (HttpContext context, ClaimsPrincipal user, IStellaOpsTenantAccessor tenantAccessor, string artifact_id, EvidenceGateArtifactService service, ILoggerFactory loggerFactory, CancellationToken cancellationToken) => @@ -179,7 +180,8 @@ app.MapPost("/evidence/snapshot", .Produces(StatusCodes.Status403Forbidden) .WithName("CreateEvidenceSnapshot") .WithTags("Evidence") - .WithSummary("Create a new evidence snapshot for the tenant."); + .WithSummary("Create a new evidence snapshot for the tenant.") + .Audited("evidence", "snapshot"); app.MapGet("/evidence/{bundleId:guid}", async (HttpContext context, ClaimsPrincipal user, IStellaOpsTenantAccessor tenantAccessor, Guid bundleId, EvidenceSnapshotService service, ILoggerFactory loggerFactory, CancellationToken cancellationToken) => @@ -355,7 +357,8 @@ app.MapPost("/evidence/verify", .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status403Forbidden) .WithName("VerifyEvidenceBundle") - .WithTags("Evidence"); + .WithTags("Evidence") + .Audited("evidence", "verify"); app.MapPost("/evidence/hold/{caseId}", async (HttpContext context, ClaimsPrincipal user, IStellaOpsTenantAccessor tenantAccessor, string caseId, EvidenceHoldRequestDto request, EvidenceSnapshotService service, ILoggerFactory loggerFactory, CancellationToken cancellationToken) => @@ -416,7 +419,8 @@ app.MapPost("/evidence/hold/{caseId}", .Produces(StatusCodes.Status403Forbidden) .WithName("CreateEvidenceHold") .WithTags("Evidence") - .WithSummary("Create a legal hold for the specified case identifier."); + .WithSummary("Create a legal hold for the specified case identifier.") + .Audited("evidence", "hold"); // Export endpoints app.MapExportEndpoints(); diff --git a/src/Integrations/StellaOps.Integrations.WebService/IntegrationEndpoints.cs b/src/Integrations/StellaOps.Integrations.WebService/IntegrationEndpoints.cs index 7a092b9c6..6c43d39d5 100644 --- a/src/Integrations/StellaOps.Integrations.WebService/IntegrationEndpoints.cs +++ b/src/Integrations/StellaOps.Integrations.WebService/IntegrationEndpoints.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using System.Security.Claims; using static StellaOps.Localization.T; +using StellaOps.Audit.Emission; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Integrations.Contracts; @@ -34,7 +35,8 @@ public static class IntegrationEndpoints }) .RequireAuthorization(IntegrationPolicies.Operate) .WithName("RunAiCodeGuard") - .WithDescription(_t("integrations.ai_code_guard.run_description")); + .WithDescription(_t("integrations.ai_code_guard.run_description")) + .Audited("integrations", "run_code_guard"); // List integrations group.MapGet("/", async ( @@ -86,7 +88,8 @@ public static class IntegrationEndpoints }) .RequireAuthorization(IntegrationPolicies.Write) .WithName("CreateIntegration") - .WithDescription(_t("integrations.integration.create_description")); + .WithDescription(_t("integrations.integration.create_description")) + .Audited("integrations", "create"); // Update integration group.MapPut("/{id:guid}", async ( @@ -103,7 +106,8 @@ public static class IntegrationEndpoints }) .RequireAuthorization(IntegrationPolicies.Write) .WithName("UpdateIntegration") - .WithDescription(_t("integrations.integration.update_description")); + .WithDescription(_t("integrations.integration.update_description")) + .Audited("integrations", "update"); // Delete integration group.MapDelete("/{id:guid}", async ( @@ -119,7 +123,8 @@ public static class IntegrationEndpoints }) .RequireAuthorization(IntegrationPolicies.Write) .WithName("DeleteIntegration") - .WithDescription(_t("integrations.integration.delete_description")); + .WithDescription(_t("integrations.integration.delete_description")) + .Audited("integrations", "delete"); // Test connection group.MapPost("/{id:guid}/test", async ( @@ -135,7 +140,8 @@ public static class IntegrationEndpoints }) .RequireAuthorization(IntegrationPolicies.Operate) .WithName("TestIntegrationConnection") - .WithDescription(_t("integrations.integration.test_description")); + .WithDescription(_t("integrations.integration.test_description")) + .Audited("integrations", "test"); // Discover resources group.MapPost("/{id:guid}/discover", async ( @@ -163,7 +169,8 @@ public static class IntegrationEndpoints }) .RequireAuthorization(IntegrationPolicies.Operate) .WithName("DiscoverIntegrationResources") - .WithDescription("Discover resources exposed by the integration provider."); + .WithDescription("Discover resources exposed by the integration provider.") + .Audited("integrations", "discover"); // Health check group.MapGet("/{id:guid}/health", async ( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ApprovalEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ApprovalEndpoints.cs index 7baece86d..e84755af0 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ApprovalEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ApprovalEndpoints.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Domain; @@ -48,7 +49,8 @@ internal static class ApprovalEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status403Forbidden) - .RequireAuthorization(ScannerPolicies.ScansApprove); + .RequireAuthorization(ScannerPolicies.ScansApprove) + .Audited("scanner", "create", "approval"); // GET /scans/{scanId}/approvals scansGroup.MapGet("/{scanId}/approvals", HandleListApprovalsAsync) @@ -78,7 +80,8 @@ internal static class ApprovalEndpoints .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansApprove); + .RequireAuthorization(ScannerPolicies.ScansApprove) + .Audited("scanner", "revoke", "approval"); } private static async Task HandleCreateApprovalAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/CallGraphEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/CallGraphEndpoints.cs index 66bbf558f..50bc299cd 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/CallGraphEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/CallGraphEndpoints.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Domain; @@ -39,7 +40,8 @@ internal static class CallGraphEndpoints .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status409Conflict) .Produces(StatusCodes.Status413PayloadTooLarge) - .RequireAuthorization(ScannerPolicies.CallGraphIngest); + .RequireAuthorization(ScannerPolicies.CallGraphIngest) + .Audited("scanner", "submit", "callgraph"); } private static async Task HandleSubmitCallGraphAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/FidelityEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/FidelityEndpoints.cs index 41ca6fc4a..24590d77d 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/FidelityEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/FidelityEndpoints.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Scanner.Orchestration.Fidelity; using StellaOps.Scanner.WebService.Security; using static StellaOps.Localization.T; @@ -25,7 +26,8 @@ public static class FidelityEndpoints }) .WithName("AnalyzeWithFidelity") .WithDescription(_t("scanner.fidelity.analyze_description")) - .Produces(200); + .Produces(200) + .Audited("scanner", "analyze", "fidelity"); // POST /api/v1/scan/findings/{findingId}/upgrade group.MapPost("/findings/{findingId:guid}/upgrade", async ( @@ -42,6 +44,7 @@ public static class FidelityEndpoints .WithName("UpgradeFidelity") .WithDescription(_t("scanner.fidelity.upgrade_description")) .Produces(200) - .Produces(400); + .Produces(400) + .Audited("scanner", "upgrade", "fidelity"); } } diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/GitHubCodeScanningEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/GitHubCodeScanningEndpoints.cs index b08bf75ca..c3b622c9c 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/GitHubCodeScanningEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/GitHubCodeScanningEndpoints.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Domain; using StellaOps.Scanner.WebService.Infrastructure; @@ -33,7 +34,8 @@ internal static class GitHubCodeScanningEndpoints .Produces(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "upload_sarif", "github"); // GET /scans/{scanId}/github/upload-status/{sarifId} // Check the processing status of a SARIF upload diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/LayerSbomEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/LayerSbomEndpoints.cs index 728230bee..1f0a2a3d3 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/LayerSbomEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/LayerSbomEndpoints.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Domain; @@ -60,7 +61,8 @@ internal static class LayerSbomEndpoints .WithTags("Scans", "SBOM") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansRead); + .RequireAuthorization(ScannerPolicies.ScansRead) + .Audited("scanner", "verify", "composition_recipe"); } private static async Task HandleListLayersAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/OfflineKitEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/OfflineKitEndpoints.cs index 924571883..1aba96ca1 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/OfflineKitEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/OfflineKitEndpoints.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; using StellaOps.Scanner.Core.Configuration; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Infrastructure; using StellaOps.Scanner.WebService.Security; @@ -48,7 +49,8 @@ internal static class OfflineKitEndpoints .Produces(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status422UnprocessableEntity); + .Produces(StatusCodes.Status422UnprocessableEntity) + .Audited("scanner", "import", "offline_kit"); group.MapGet("/status", HandleStatusAsync) .WithName($"scanner.offline-kit.status{suffix}") @@ -70,7 +72,8 @@ internal static class OfflineKitEndpoints .WithName($"scanner.offline-kit.validate{suffix}") .RequireAuthorization(ScannerPolicies.OfflineKitValidate) .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest); + .Produces(StatusCodes.Status400BadRequest) + .Audited("scanner", "validate", "offline_kit"); } private static async Task HandleImportAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityEndpoints.cs index 58bfa020c..9837fa93e 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityEndpoints.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Domain; @@ -35,7 +36,8 @@ internal static class ReachabilityEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status409Conflict) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "compute", "reachability"); // GET /scans/{scanId}/reachability/components scansGroup.MapGet("/{scanId}/reachability/components", HandleGetComponentsAsync) diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityEvidenceEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityEvidenceEndpoints.cs index f0e7b1df7..524c770b8 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityEvidenceEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReachabilityEvidenceEndpoints.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.Reachability.Jobs; using StellaOps.Scanner.Reachability.Services; using StellaOps.Scanner.Reachability.Vex; @@ -36,7 +37,8 @@ public static class ReachabilityEvidenceEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "analyze", "reachability"); // Get job result group.MapGet("/result/{jobId}", GetResultAsync) @@ -58,7 +60,8 @@ public static class ReachabilityEvidenceEndpoints .WithSummary("Generate VEX statement from reachability analysis") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "generate_vex", "reachability"); return routes; } diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReplayEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReplayEndpoints.cs index 6d822afe0..044ce932c 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReplayEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReplayEndpoints.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Domain; using StellaOps.Scanner.WebService.Security; @@ -18,7 +19,8 @@ internal static class ReplayEndpoints .WithName("scanner.replay.attach") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status400BadRequest); + .Produces(StatusCodes.Status400BadRequest) + .Audited("scanner", "attach_replay"); } private static async Task HandleAttachAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReportEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReportEndpoints.cs index 1e4d324b9..df8723399 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReportEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ReportEndpoints.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using StellaOps.Policy; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Infrastructure; @@ -50,7 +51,8 @@ internal static class ReportEndpoints operation.Summary = "Assemble a signed scan report."; operation.Description = "Aggregates latest findings with the active policy snapshot, returning verdicts plus an optional DSSE envelope."; return operation; - }); + }) + .Audited("scanner", "create", "report"); } private static async Task HandleCreateReportAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/RuntimeEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/RuntimeEndpoints.cs index 76a179866..e99fdce29 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/RuntimeEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/RuntimeEndpoints.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Infrastructure; @@ -39,7 +40,8 @@ internal static class RuntimeEndpoints .Produces(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status429TooManyRequests) - .RequireAuthorization(ScannerPolicies.RuntimeIngest); + .RequireAuthorization(ScannerPolicies.RuntimeIngest) + .Audited("scanner", "ingest", "runtime_event"); runtime.MapPost("/reconcile", HandleRuntimeReconcileAsync) .WithName("scanner.runtime.reconcile") @@ -48,7 +50,8 @@ internal static class RuntimeEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.RuntimeIngest); + .RequireAuthorization(ScannerPolicies.RuntimeIngest) + .Audited("scanner", "reconcile", "runtime"); } private static async Task HandleRuntimeEventsAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomEndpoints.cs index 5a2b36a29..6c5265f8d 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomEndpoints.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Domain; @@ -38,7 +39,8 @@ internal static class SbomEndpoints .Produces(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "submit_sbom"); } private static async Task HandleSubmitSbomAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomUploadEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomUploadEndpoints.cs index 67be66a27..2a5a800aa 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomUploadEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomUploadEndpoints.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Infrastructure; @@ -21,7 +22,8 @@ internal static class SbomUploadEndpoints .WithTags("SBOM") .Produces(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "upload", "sbom"); sbomGroup.MapGet("/uploads/{sbomId}", HandleGetUploadAsync) .WithName("scanner.sbom.uploads.get") diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScanEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScanEndpoints.cs index c6b8302c3..26170b28b 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScanEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScanEndpoints.cs @@ -13,6 +13,7 @@ using StellaOps.Scanner.WebService.Infrastructure; using StellaOps.Scanner.WebService.Options; using StellaOps.Scanner.WebService.Security; using StellaOps.Scanner.WebService.Services; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Tenancy; using System.Collections.Generic; using System.IO.Pipelines; @@ -43,7 +44,8 @@ internal static class ScanEndpoints .Produces(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status409Conflict) - .RequireAuthorization(ScannerPolicies.ScansEnqueue); + .RequireAuthorization(ScannerPolicies.ScansEnqueue) + .Audited("scanner", "submit"); scans.MapGet("/{scanId}", HandleStatusAsync) .WithName("scanner.scans.status") @@ -56,7 +58,8 @@ internal static class ScanEndpoints .Produces(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "attach_entropy"); scans.MapGet("/{scanId}/events", HandleProgressStreamAsync) .WithName("scanner.scans.events") diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScanPolicyEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScanPolicyEndpoints.cs index b1eb47eac..553e18e11 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScanPolicyEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScanPolicyEndpoints.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Security; using System.Collections.Concurrent; @@ -42,7 +43,8 @@ internal static class ScanPolicyEndpoints .WithDescription("Create a new scan policy.") .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "create", "scan_policy"); // PUT /v1/scan-policies/{id} - Update an existing scan policy group.MapPut("/{id:guid}", HandleUpdatePolicy) @@ -51,7 +53,8 @@ internal static class ScanPolicyEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "update", "scan_policy"); // DELETE /v1/scan-policies/{id} - Delete a scan policy group.MapDelete("/{id:guid}", HandleDeletePolicy) @@ -59,7 +62,8 @@ internal static class ScanPolicyEndpoints .WithDescription("Delete a scan policy.") .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "delete", "scan_policy"); } // ======================================================================== diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScoreReplayEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScoreReplayEndpoints.cs index 60f3234e9..083183f1d 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScoreReplayEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ScoreReplayEndpoints.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.Core; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Security; @@ -32,7 +33,8 @@ internal static class ScoreReplayEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status422UnprocessableEntity) .WithDescription(_t("scanner.score_replay.replay_description")) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "replay", "score"); scans.MapGet("/bundle", HandleGetBundleAsync) .WithName("scanner.scans.score.bundle") @@ -46,7 +48,8 @@ internal static class ScoreReplayEndpoints .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status422UnprocessableEntity) .WithDescription(_t("scanner.score_replay.verify_description")) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "verify", "score"); scans.MapGet("/history", HandleGetHistoryAsync) .WithName("scanner.scans.score.history") @@ -62,7 +65,8 @@ internal static class ScoreReplayEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status422UnprocessableEntity) .WithDescription(_t("scanner.score_replay.replay_description")) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "replay", "score"); legacy.MapGet("/{scanId}/bundle", HandleGetBundleAsync) .WithName("scanner.score.bundle") @@ -76,7 +80,8 @@ internal static class ScoreReplayEndpoints .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status422UnprocessableEntity) .WithDescription(_t("scanner.score_replay.verify_description")) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "verify", "score"); legacy.MapGet("/{scanId}/history", HandleGetHistoryAsync) .WithName("scanner.score.history") diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SecretDetectionSettingsEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SecretDetectionSettingsEndpoints.cs index 7fd40e21f..0ffeb9bf0 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SecretDetectionSettingsEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SecretDetectionSettingsEndpoints.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Security; using StellaOps.Scanner.WebService.Services; @@ -53,7 +54,8 @@ internal static class SecretDetectionSettingsEndpoints .WithDescription("Create default secret detection settings for a tenant.") .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status409Conflict) - .RequireAuthorization(ScannerPolicies.SecretSettingsWrite); + .RequireAuthorization(ScannerPolicies.SecretSettingsWrite) + .Audited("scanner", "create", "secret_settings"); // PUT /v1/secrets/config/settings/{tenantId} - Update settings settings.MapPut("/{tenantId:guid}", HandleUpdateSettingsAsync) @@ -63,7 +65,8 @@ internal static class SecretDetectionSettingsEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status409Conflict) - .RequireAuthorization(ScannerPolicies.SecretSettingsWrite); + .RequireAuthorization(ScannerPolicies.SecretSettingsWrite) + .Audited("scanner", "update", "secret_settings"); // ==================================================================== // Exception Pattern Endpoints @@ -90,7 +93,8 @@ internal static class SecretDetectionSettingsEndpoints .WithDescription("Create a new secret exception pattern.") .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(ScannerPolicies.SecretExceptionsWrite); + .RequireAuthorization(ScannerPolicies.SecretExceptionsWrite) + .Audited("scanner", "create", "secret_exception"); // PUT /v1/secrets/config/exceptions/{tenantId}/{exceptionId} - Update exception pattern exceptions.MapPut("/{tenantId:guid}/{exceptionId:guid}", HandleUpdateExceptionAsync) @@ -99,7 +103,8 @@ internal static class SecretDetectionSettingsEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SecretExceptionsWrite); + .RequireAuthorization(ScannerPolicies.SecretExceptionsWrite) + .Audited("scanner", "update", "secret_exception"); // DELETE /v1/secrets/config/exceptions/{tenantId}/{exceptionId} - Delete exception pattern exceptions.MapDelete("/{tenantId:guid}/{exceptionId:guid}", HandleDeleteExceptionAsync) @@ -107,7 +112,8 @@ internal static class SecretDetectionSettingsEndpoints .WithDescription("Delete a secret exception pattern.") .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SecretExceptionsWrite); + .RequireAuthorization(ScannerPolicies.SecretExceptionsWrite) + .Audited("scanner", "delete", "secret_exception"); // ==================================================================== // Rule Catalog Endpoints diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SmartDiffEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SmartDiffEndpoints.cs index f602b7d4b..45c346959 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SmartDiffEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SmartDiffEndpoints.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.SmartDiff.Detection; using StellaOps.Scanner.SmartDiff.Output; using StellaOps.Scanner.WebService.Security; @@ -54,7 +55,8 @@ internal static class SmartDiffEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "review", "vex_candidate"); group.MapGet("/images/{digest}/candidates", HandleGetCandidatesAsync) .WithName("scanner.smartdiff.candidates") @@ -76,7 +78,8 @@ internal static class SmartDiffEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansWrite); + .RequireAuthorization(ScannerPolicies.ScansWrite) + .Audited("scanner", "review", "vex_candidate"); } /// diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SourcesEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SourcesEndpoints.cs index 42bc1f9eb..bf95fc8a7 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SourcesEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/SourcesEndpoints.cs @@ -5,6 +5,7 @@ using StellaOps.Scanner.Sources.Configuration; using StellaOps.Scanner.Sources.Contracts; using StellaOps.Scanner.Sources.Domain; using StellaOps.Scanner.Sources.Services; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Constants; using StellaOps.Scanner.WebService.Infrastructure; using StellaOps.Scanner.WebService.Security; @@ -58,7 +59,8 @@ internal static class SourcesEndpoints .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status409Conflict) - .RequireAuthorization(ScannerPolicies.SourcesWrite); + .RequireAuthorization(ScannerPolicies.SourcesWrite) + .Audited("scanner", "create", "source"); // Update source sources.MapPut("/{sourceId:guid}", HandleUpdateAsync) @@ -66,49 +68,56 @@ internal static class SourcesEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SourcesWrite); + .RequireAuthorization(ScannerPolicies.SourcesWrite) + .Audited("scanner", "update", "source"); // Delete source sources.MapDelete("/{sourceId:guid}", HandleDeleteAsync) .WithName("scanner.sources.delete") .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SourcesAdmin); + .RequireAuthorization(ScannerPolicies.SourcesAdmin) + .Audited("scanner", "delete", "source"); // Test connection (existing source) sources.MapPost("/{sourceId:guid}/test", HandleTestConnectionAsync) .WithName("scanner.sources.test") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SourcesRead); + .RequireAuthorization(ScannerPolicies.SourcesRead) + .Audited("scanner", "test", "source"); // Test connection (new configuration) sources.MapPost("/test", HandleTestNewConnectionAsync) .WithName("scanner.sources.testNew") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(ScannerPolicies.SourcesWrite); + .RequireAuthorization(ScannerPolicies.SourcesWrite) + .Audited("scanner", "test", "source"); // Pause source sources.MapPost("/{sourceId:guid}/pause", HandlePauseAsync) .WithName("scanner.sources.pause") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SourcesWrite); + .RequireAuthorization(ScannerPolicies.SourcesWrite) + .Audited("scanner", "pause", "source"); // Resume source sources.MapPost("/{sourceId:guid}/resume", HandleResumeAsync) .WithName("scanner.sources.resume") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SourcesWrite); + .RequireAuthorization(ScannerPolicies.SourcesWrite) + .Audited("scanner", "resume", "source"); // Activate source (Draft -> Active) sources.MapPost("/{sourceId:guid}/activate", HandleActivateAsync) .WithName("scanner.sources.activate") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SourcesWrite); + .RequireAuthorization(ScannerPolicies.SourcesWrite) + .Audited("scanner", "activate", "source"); // Trigger scan sources.MapPost("/{sourceId:guid}/scan", HandleTriggerScanAsync) @@ -116,7 +125,8 @@ internal static class SourcesEndpoints .Produces(StatusCodes.Status202Accepted) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.SourcesWrite); + .RequireAuthorization(ScannerPolicies.SourcesWrite) + .Audited("scanner", "trigger_scan", "source"); // List runs for a source sources.MapGet("/{sourceId:guid}/runs", HandleListRunsAsync) diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/BatchTriageEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/BatchTriageEndpoints.cs index 28b51c1b0..26cd3838e 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/BatchTriageEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/BatchTriageEndpoints.cs @@ -4,6 +4,7 @@ using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.Triage.Models; using StellaOps.Scanner.Triage.Services; using StellaOps.Scanner.WebService.Contracts; @@ -38,7 +39,8 @@ internal static class BatchTriageEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.TriageWrite); + .RequireAuthorization(ScannerPolicies.TriageWrite) + .Audited("scanner", "batch_action", "triage"); } private static async Task HandleGetClusterStatsAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/ProofBundleEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/ProofBundleEndpoints.cs index ed4b5c4ff..b5c2ad4f6 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/ProofBundleEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/ProofBundleEndpoints.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.Triage.Models; using StellaOps.Scanner.WebService.Security; using System.Text.Json; @@ -37,7 +38,8 @@ internal static class ProofBundleEndpoints .WithDescription(_t("scanner.triage.proof_bundle_description")) .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(ScannerPolicies.TriageWrite); + .RequireAuthorization(ScannerPolicies.TriageWrite) + .Audited("scanner", "generate_proof", "triage"); } private static async Task HandleGenerateProofBundleAsync( diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/TriageStatusEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/TriageStatusEndpoints.cs index 88c965acd..0208e182a 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/TriageStatusEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/Triage/TriageStatusEndpoints.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.WebService.Contracts; using StellaOps.Scanner.WebService.Security; using StellaOps.Scanner.WebService.Services; @@ -54,7 +55,8 @@ internal static class TriageStatusEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.TriageWrite); + .RequireAuthorization(ScannerPolicies.TriageWrite) + .Audited("scanner", "update_status", "triage"); // POST /v1/triage/findings/{findingId}/vex - Submit VEX statement triageGroup.MapPost("/findings/{findingId}/vex", HandleSubmitVexAsync) @@ -63,7 +65,8 @@ internal static class TriageStatusEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.TriageWrite); + .RequireAuthorization(ScannerPolicies.TriageWrite) + .Audited("scanner", "submit_vex", "triage"); // POST /v1/triage/query - Bulk query findings triageGroup.MapPost("/query", HandleBulkQueryAsync) @@ -71,7 +74,8 @@ internal static class TriageStatusEndpoints .WithDescription(_t("scanner.triage.query_description")) .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(ScannerPolicies.TriageRead); + .RequireAuthorization(ScannerPolicies.TriageRead) + .Audited("scanner", "bulk_query", "triage"); // GET /v1/triage/summary - Get triage summary for an artifact triageGroup.MapGet("/summary", HandleGetSummaryAsync) diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ValidationEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ValidationEndpoints.cs index d4ad4a035..233c8af63 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ValidationEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/ValidationEndpoints.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; +using StellaOps.Audit.Emission; using StellaOps.Scanner.Validation; using StellaOps.Scanner.WebService.Security; using System.Text.Json; @@ -45,7 +46,8 @@ internal static class ValidationEndpoints .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status503ServiceUnavailable) - .RequireAuthorization(ScannerPolicies.ScansRead); + .RequireAuthorization(ScannerPolicies.ScansRead) + .Audited("scanner", "validate", "sbom"); // GET /api/v1/sbom/validators group.MapGet("/validators", GetValidatorsAsync) diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/WebhookEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/WebhookEndpoints.cs index f184e712a..0c1c5ebb5 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/WebhookEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/WebhookEndpoints.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using StellaOps.Scanner.Sources.Domain; +using StellaOps.Audit.Emission; using StellaOps.Scanner.Sources.Handlers; using StellaOps.Scanner.Sources.Persistence; using StellaOps.Scanner.Sources.Services; @@ -50,7 +51,8 @@ internal static class WebhookEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) - .AllowAnonymous(); + .AllowAnonymous() + .Audited("scanner", "receive_webhook", "webhook"); // Docker Hub webhook (uses source name for friendlier URLs) webhooks.MapPost("/docker/{sourceName}", HandleDockerHubWebhookAsync) @@ -60,7 +62,8 @@ internal static class WebhookEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) - .AllowAnonymous(); + .AllowAnonymous() + .Audited("scanner", "receive_webhook", "webhook"); // GitHub webhook webhooks.MapPost("/github/{sourceName}", HandleGitHubWebhookAsync) @@ -70,7 +73,8 @@ internal static class WebhookEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) - .AllowAnonymous(); + .AllowAnonymous() + .Audited("scanner", "receive_webhook", "webhook"); // GitLab webhook webhooks.MapPost("/gitlab/{sourceName}", HandleGitLabWebhookAsync) @@ -80,7 +84,8 @@ internal static class WebhookEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) - .AllowAnonymous(); + .AllowAnonymous() + .Audited("scanner", "receive_webhook", "webhook"); // Harbor webhook webhooks.MapPost("/harbor/{sourceName}", HandleHarborWebhookAsync) @@ -90,7 +95,8 @@ internal static class WebhookEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound) - .AllowAnonymous(); + .AllowAnonymous() + .Audited("scanner", "receive_webhook", "webhook"); } /// diff --git a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/WitnessEndpoints.cs b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/WitnessEndpoints.cs index be769042b..96bd14ef1 100644 --- a/src/Scanner/StellaOps.Scanner.WebService/Endpoints/WitnessEndpoints.cs +++ b/src/Scanner/StellaOps.Scanner.WebService/Endpoints/WitnessEndpoints.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Scanner.Storage.Repositories; using StellaOps.Scanner.WebService.Security; using System.Text.Json; @@ -43,7 +44,8 @@ internal static class WitnessEndpoints .WithName("scanner.witnesses.verify") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) - .RequireAuthorization(ScannerPolicies.ScansRead); + .RequireAuthorization(ScannerPolicies.ScansRead) + .Audited("scanner", "verify", "witness"); } private static async Task HandleGetWitnessByIdAsync( diff --git a/src/__Libraries/StellaOps.Audit.Emission/AuditedRouteGroupExtensions.cs b/src/__Libraries/StellaOps.Audit.Emission/AuditedRouteGroupExtensions.cs new file mode 100644 index 000000000..29fcdaa0a --- /dev/null +++ b/src/__Libraries/StellaOps.Audit.Emission/AuditedRouteGroupExtensions.cs @@ -0,0 +1,48 @@ +// Copyright (c) StellaOps. Licensed under the BUSL-1.1. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace StellaOps.Audit.Emission; + +/// +/// Convenience extensions for applying the to route groups +/// and individual endpoints. +/// +public static class AuditedRouteGroupExtensions +{ + /// + /// Registers at the route-group level so that all + /// endpoints in the group are evaluated. Only endpoints that carry an + /// via .WithMetadata() will actually emit + /// audit events; the rest pass through silently. + /// + public static RouteGroupBuilder WithAuditFilter(this RouteGroupBuilder group) + { + group.AddEndpointFilter(); + return group; + } + + /// + /// Shorthand: adds the and the + /// metadata in a single call. + /// Use this on individual endpoints when group-level registration is not feasible. + /// + /// The route handler builder. + /// The owning module name (e.g., "scanner", "integrations"). + /// The action name (e.g., "create", "delete"). + /// + /// Optional resource type override. When null, the filter infers the type from the route. + /// + public static RouteHandlerBuilder Audited( + this RouteHandlerBuilder builder, + string module, + string action, + string? resourceType = null) + { + return builder + .AddEndpointFilter() + .WithMetadata(new AuditActionAttribute(module, action) { ResourceType = resourceType }); + } +}