diff --git a/src/JobEngine/StellaOps.Scheduler.WebService/GraphJobs/GraphJobEndpointExtensions.cs b/src/JobEngine/StellaOps.Scheduler.WebService/GraphJobs/GraphJobEndpointExtensions.cs index 971704e60..c6c8ffd99 100644 --- a/src/JobEngine/StellaOps.Scheduler.WebService/GraphJobs/GraphJobEndpointExtensions.cs +++ b/src/JobEngine/StellaOps.Scheduler.WebService/GraphJobs/GraphJobEndpointExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Scheduler.Models; @@ -19,10 +20,12 @@ public static class GraphJobEndpointExtensions group.MapPost("/build", CreateGraphBuildJob) .WithName("CreateGraphBuildJob") - .WithDescription("Enqueues a graph build job to construct the reachability graph for the specified tenant scope. Returns 201 Created with the new job ID. Requires graph.write scope."); + .WithDescription("Enqueues a graph build job to construct the reachability graph for the specified tenant scope. Returns 201 Created with the new job ID. Requires graph.write scope.") + .Audited("scheduler", "create_graph_build", "graph_job"); group.MapPost("/overlays", CreateGraphOverlayJob) .WithName("CreateGraphOverlayJob") - .WithDescription("Enqueues a graph overlay job to apply incremental VEX or policy updates onto an existing reachability graph. Returns 201 Created with the new job ID. Requires graph.write scope."); + .WithDescription("Enqueues a graph overlay job to apply incremental VEX or policy updates onto an existing reachability graph. Returns 201 Created with the new job ID. Requires graph.write scope.") + .Audited("scheduler", "create_graph_overlay", "graph_job"); group.MapGet("/jobs", GetGraphJobs) .WithName("GetGraphJobs") .WithDescription("Lists graph jobs for the tenant with optional filters by status and job type. Returns a paginated collection ordered by creation time. Requires graph.read scope."); diff --git a/src/JobEngine/StellaOps.Scheduler.WebService/PolicyRuns/PolicyRunEndpointExtensions.cs b/src/JobEngine/StellaOps.Scheduler.WebService/PolicyRuns/PolicyRunEndpointExtensions.cs index 9b4f8f3a6..bf64383d0 100644 --- a/src/JobEngine/StellaOps.Scheduler.WebService/PolicyRuns/PolicyRunEndpointExtensions.cs +++ b/src/JobEngine/StellaOps.Scheduler.WebService/PolicyRuns/PolicyRunEndpointExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Scheduler.Models; @@ -31,7 +32,8 @@ internal static class PolicyRunEndpointExtensions group.MapPost("/", CreatePolicyRunAsync) .WithName("CreatePolicyRun") .WithDescription("Enqueues a new policy evaluation run for the specified policy ID and version. Returns 201 Created with the run ID and initial queued status. Requires policy.run scope.") - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "create_policy_run", "policy_run"); } internal static async Task ListPolicyRunsAsync( diff --git a/src/JobEngine/StellaOps.Scheduler.WebService/Program.cs b/src/JobEngine/StellaOps.Scheduler.WebService/Program.cs index af1f9c4d2..22b741971 100644 --- a/src/JobEngine/StellaOps.Scheduler.WebService/Program.cs +++ b/src/JobEngine/StellaOps.Scheduler.WebService/Program.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using StellaOps.Audit.Emission; using StellaOps.Auth.Abstractions; using StellaOps.Localization; using StellaOps.Auth.ServerIntegration; @@ -332,6 +333,7 @@ else builder.Services.AddStellaOpsTenantServices(); builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddAuditEmission(builder.Configuration); builder.Services.AddStellaOpsLocalization(builder.Configuration); builder.Services.AddTranslationBundle(System.Reflection.Assembly.GetExecutingAssembly()); diff --git a/src/JobEngine/StellaOps.Scheduler.WebService/Runs/RunEndpoints.cs b/src/JobEngine/StellaOps.Scheduler.WebService/Runs/RunEndpoints.cs index 3d81c2380..98fd6563e 100644 --- a/src/JobEngine/StellaOps.Scheduler.WebService/Runs/RunEndpoints.cs +++ b/src/JobEngine/StellaOps.Scheduler.WebService/Runs/RunEndpoints.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; using static StellaOps.Localization.T; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Scheduler.ImpactIndex; using StellaOps.Scheduler.Models; @@ -52,15 +53,18 @@ internal static class RunEndpoints group.MapPost("/", CreateRunAsync) .WithName("CreateSchedulerRun") .WithDescription(_t("scheduler.run.create_description")) - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "create_run", "run"); group.MapPost("/{runId}/cancel", CancelRunAsync) .WithName("CancelSchedulerRun") .WithDescription(_t("scheduler.run.cancel_description")) - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "cancel_run", "run"); group.MapPost("/{runId}/retry", RetryRunAsync) .WithName("RetrySchedulerRun") .WithDescription(_t("scheduler.run.retry_description")) - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "retry_run", "run"); group.MapPost("/preview", PreviewImpactAsync) .WithName("PreviewRunImpact") .WithDescription(_t("scheduler.run.preview_description")) diff --git a/src/JobEngine/StellaOps.Scheduler.WebService/Schedules/ScheduleEndpoints.cs b/src/JobEngine/StellaOps.Scheduler.WebService/Schedules/ScheduleEndpoints.cs index b4f0467c4..23606b5b1 100644 --- a/src/JobEngine/StellaOps.Scheduler.WebService/Schedules/ScheduleEndpoints.cs +++ b/src/JobEngine/StellaOps.Scheduler.WebService/Schedules/ScheduleEndpoints.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using static StellaOps.Localization.T; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Scheduler.Models; using StellaOps.Scheduler.Persistence.Postgres.Repositories; @@ -36,23 +37,28 @@ internal static class ScheduleEndpoints group.MapPost("/", CreateScheduleAsync) .WithName("CreateSchedule") .WithDescription(_t("scheduler.schedule.create_description")) - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "create_schedule", "schedule"); group.MapPatch("/{scheduleId}", UpdateScheduleAsync) .WithName("UpdateSchedule") .WithDescription(_t("scheduler.schedule.update_description")) - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "update_schedule", "schedule"); group.MapDelete("/{scheduleId}", DeleteScheduleAsync) .WithName("DeleteSchedule") .WithDescription("Soft-deletes a schedule. System-managed schedules cannot be deleted.") - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "delete_schedule", "schedule"); group.MapPost("/{scheduleId}/pause", PauseScheduleAsync) .WithName("PauseSchedule") .WithDescription(_t("scheduler.schedule.pause_description")) - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "pause_schedule", "schedule"); group.MapPost("/{scheduleId}/resume", ResumeScheduleAsync) .WithName("ResumeSchedule") .WithDescription(_t("scheduler.schedule.resume_description")) - .RequireAuthorization(SchedulerPolicies.Operate); + .RequireAuthorization(SchedulerPolicies.Operate) + .Audited("scheduler", "resume_schedule", "schedule"); return routes; } diff --git a/src/JobEngine/StellaOps.Scheduler.WebService/StellaOps.Scheduler.WebService.csproj b/src/JobEngine/StellaOps.Scheduler.WebService/StellaOps.Scheduler.WebService.csproj index 4d6560e86..44512782c 100644 --- a/src/JobEngine/StellaOps.Scheduler.WebService/StellaOps.Scheduler.WebService.csproj +++ b/src/JobEngine/StellaOps.Scheduler.WebService/StellaOps.Scheduler.WebService.csproj @@ -26,6 +26,7 @@ + diff --git a/src/JobEngine/StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Plugin.Abstractions/ScanJobPlugin.cs b/src/JobEngine/StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Plugin.Abstractions/ScanJobPlugin.cs index 3f1b9e213..c87ddb4af 100644 --- a/src/JobEngine/StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Plugin.Abstractions/ScanJobPlugin.cs +++ b/src/JobEngine/StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Plugin.Abstractions/ScanJobPlugin.cs @@ -60,7 +60,7 @@ public sealed class ScanJobPlugin : ISchedulerJobPlugin { // Scan jobs use the standard Mode/Selector fields, not PluginConfig. // Any PluginConfig on a scan schedule is ignored but valid. - return Task.FromResult(JobConfigValidationResult.Success); + return Task.FromResult(JobConfigValidationResult.Valid); } /// diff --git a/src/Notify/StellaOps.Notify.WebService/Endpoints/EscalationEndpoints.cs b/src/Notify/StellaOps.Notify.WebService/Endpoints/EscalationEndpoints.cs index 67b522a80..8bd1bbcd4 100644 --- a/src/Notify/StellaOps.Notify.WebService/Endpoints/EscalationEndpoints.cs +++ b/src/Notify/StellaOps.Notify.WebService/Endpoints/EscalationEndpoints.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Notify.WebService.Constants; using StellaOps.Notify.WebService.Extensions; @@ -39,19 +40,22 @@ public static class EscalationEndpoints .WithName("CreateEscalationPolicy") .WithSummary("Create an escalation policy") .WithDescription(_t("notifier.escalation_policy.create_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "create_escalation_policy", "escalation_policy"); policies.MapPut("/{policyId}", UpdatePolicyAsync) .WithName("UpdateEscalationPolicy") .WithSummary("Update an escalation policy") .WithDescription(_t("notifier.escalation_policy.update_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "update_escalation_policy", "escalation_policy"); policies.MapDelete("/{policyId}", DeletePolicyAsync) .WithName("DeleteEscalationPolicy") .WithSummary("Delete an escalation policy") .WithDescription(_t("notifier.escalation_policy.delete_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "delete_escalation_policy", "escalation_policy"); // On-Call Schedules var schedules = app.MapGroup("/api/v2/oncall-schedules") @@ -74,19 +78,22 @@ public static class EscalationEndpoints .WithName("CreateOnCallSchedule") .WithSummary("Create an on-call schedule") .WithDescription(_t("notifier.oncall_schedule.create_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "create_oncall_schedule", "oncall_schedule"); schedules.MapPut("/{scheduleId}", UpdateScheduleAsync) .WithName("UpdateOnCallSchedule") .WithSummary("Update an on-call schedule") .WithDescription(_t("notifier.oncall_schedule.update_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "update_oncall_schedule", "oncall_schedule"); schedules.MapDelete("/{scheduleId}", DeleteScheduleAsync) .WithName("DeleteOnCallSchedule") .WithSummary("Delete an on-call schedule") .WithDescription(_t("notifier.oncall_schedule.delete_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "delete_oncall_schedule", "oncall_schedule"); schedules.MapGet("/{scheduleId}/oncall", GetCurrentOnCallAsync) .WithName("GetCurrentOnCall") @@ -97,13 +104,15 @@ public static class EscalationEndpoints .WithName("CreateOnCallOverride") .WithSummary("Create an on-call override") .WithDescription(_t("notifier.oncall_schedule.create_override_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "create_oncall_override", "oncall_override"); schedules.MapDelete("/{scheduleId}/overrides/{overrideId}", DeleteOverrideAsync) .WithName("DeleteOnCallOverride") .WithSummary("Delete an on-call override") .WithDescription(_t("notifier.oncall_schedule.delete_override_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "delete_oncall_override", "oncall_override"); // Active Escalations var escalations = app.MapGroup("/api/v2/escalations") @@ -126,19 +135,22 @@ public static class EscalationEndpoints .WithName("StartEscalation") .WithSummary("Start escalation for an incident") .WithDescription(_t("notifier.escalation.start_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "start_escalation", "escalation"); escalations.MapPost("/{incidentId}/escalate", ManualEscalateAsync) .WithName("ManualEscalate") .WithSummary("Manually escalate to next level") .WithDescription(_t("notifier.escalation.manual_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "manual_escalate", "escalation"); escalations.MapPost("/{incidentId}/stop", StopEscalationAsync) .WithName("StopEscalation") .WithSummary("Stop escalation") .WithDescription(_t("notifier.escalation.stop_description")) - .RequireAuthorization(NotifierPolicies.NotifyEscalate); + .RequireAuthorization(NotifierPolicies.NotifyEscalate) + .Audited("notify", "stop_escalation", "escalation"); // Ack Bridge var ack = app.MapGroup("/api/v2/ack") diff --git a/src/Notify/StellaOps.Notify.WebService/Endpoints/NotifyApiEndpoints.cs b/src/Notify/StellaOps.Notify.WebService/Endpoints/NotifyApiEndpoints.cs index afacdcb33..77a5a5f90 100644 --- a/src/Notify/StellaOps.Notify.WebService/Endpoints/NotifyApiEndpoints.cs +++ b/src/Notify/StellaOps.Notify.WebService/Endpoints/NotifyApiEndpoints.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Notify.WebService.Constants; using StellaOps.Notify.WebService.Contracts; using StellaOps.Notify.WebService.Extensions; @@ -117,7 +118,8 @@ public static class NotifyApiEndpoints return Results.Created($"/api/v2/notify/rules/{rule.RuleId}", MapRuleToResponse(rule)); }) .WithDescription(_t("notifier.rule.create_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "create_rule", "rule"); group.MapPut("/rules/{ruleId}", async ( HttpContext context, @@ -156,7 +158,8 @@ public static class NotifyApiEndpoints return Results.Ok(MapRuleToResponse(updated)); }) .WithDescription(_t("notifier.rule.update_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "update_rule", "rule"); group.MapDelete("/rules/{ruleId}", async ( HttpContext context, @@ -189,7 +192,8 @@ public static class NotifyApiEndpoints return Results.NoContent(); }) .WithDescription(_t("notifier.rule.delete_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "delete_rule", "rule"); } private static void MapTemplatesEndpoints(RouteGroupBuilder group) @@ -311,7 +315,8 @@ public static class NotifyApiEndpoints : Results.Ok(MapTemplateToResponse(created!)); }) .WithDescription(_t("notifier.template.upsert_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "create_template", "template"); group.MapDelete("/templates/{templateId}", async ( HttpContext context, @@ -336,7 +341,8 @@ public static class NotifyApiEndpoints return Results.NoContent(); }) .WithDescription(_t("notifier.template.delete_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "delete_template", "template"); group.MapPost("/templates/preview", async ( HttpContext context, @@ -514,7 +520,8 @@ public static class NotifyApiEndpoints return Results.NoContent(); }) .WithDescription(_t("notifier.incident.ack_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "acknowledge_incident", "incident"); group.MapPost("/incidents/{incidentId}/resolve", async ( HttpContext context, @@ -541,7 +548,8 @@ public static class NotifyApiEndpoints return Results.NoContent(); }) .WithDescription(_t("notifier.incident.resolve_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "resolve_incident", "incident"); } #region Helpers diff --git a/src/Notify/StellaOps.Notify.WebService/Endpoints/OperatorOverrideEndpoints.cs b/src/Notify/StellaOps.Notify.WebService/Endpoints/OperatorOverrideEndpoints.cs index f81979a9b..c144102b4 100644 --- a/src/Notify/StellaOps.Notify.WebService/Endpoints/OperatorOverrideEndpoints.cs +++ b/src/Notify/StellaOps.Notify.WebService/Endpoints/OperatorOverrideEndpoints.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Notify.WebService.Constants; using StellaOps.Notify.WebService.Extensions; @@ -38,13 +39,15 @@ public static class OperatorOverrideEndpoints .WithName("CreateOperatorOverride") .WithSummary("Create an operator override") .WithDescription(_t("notifier.override.create_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "create_override", "operator_override"); group.MapPost("/{overrideId}/revoke", RevokeOverrideAsync) .WithName("RevokeOperatorOverride") .WithSummary("Revoke an operator override") .WithDescription(_t("notifier.override.revoke_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "revoke_override", "operator_override"); group.MapPost("/check", CheckOverrideAsync) .WithName("CheckOperatorOverride") diff --git a/src/Notify/StellaOps.Notify.WebService/Endpoints/QuietHoursEndpoints.cs b/src/Notify/StellaOps.Notify.WebService/Endpoints/QuietHoursEndpoints.cs index da28d38d1..da1b7cefe 100644 --- a/src/Notify/StellaOps.Notify.WebService/Endpoints/QuietHoursEndpoints.cs +++ b/src/Notify/StellaOps.Notify.WebService/Endpoints/QuietHoursEndpoints.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Notify.WebService.Constants; using StellaOps.Notify.WebService.Extensions; @@ -38,19 +39,22 @@ public static class QuietHoursEndpoints .WithName("CreateQuietHoursCalendar") .WithSummary("Create a quiet hours calendar") .WithDescription(_t("notifier.quiet_hours.create_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "create_quiet_hours", "quiet_hours"); group.MapPut("/calendars/{calendarId}", UpdateCalendarAsync) .WithName("UpdateQuietHoursCalendar") .WithSummary("Update a quiet hours calendar") .WithDescription(_t("notifier.quiet_hours.update_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "update_quiet_hours", "quiet_hours"); group.MapDelete("/calendars/{calendarId}", DeleteCalendarAsync) .WithName("DeleteQuietHoursCalendar") .WithSummary("Delete a quiet hours calendar") .WithDescription(_t("notifier.quiet_hours.delete_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "delete_quiet_hours", "quiet_hours"); group.MapPost("/evaluate", EvaluateAsync) .WithName("EvaluateQuietHours") diff --git a/src/Notify/StellaOps.Notify.WebService/Endpoints/RuleEndpoints.cs b/src/Notify/StellaOps.Notify.WebService/Endpoints/RuleEndpoints.cs index 2caca21f4..6d59194d5 100644 --- a/src/Notify/StellaOps.Notify.WebService/Endpoints/RuleEndpoints.cs +++ b/src/Notify/StellaOps.Notify.WebService/Endpoints/RuleEndpoints.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Notify.WebService.Constants; using StellaOps.Notify.WebService.Contracts; @@ -40,19 +41,22 @@ public static class RuleEndpoints .WithName("CreateRule") .WithSummary("Creates a new rule") .WithDescription(_t("notifier.rule.create2_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "create_rule", "rule"); group.MapPut("/{ruleId}", UpdateRuleAsync) .WithName("UpdateRule") .WithSummary("Updates an existing rule") .WithDescription(_t("notifier.rule.update2_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "update_rule", "rule"); group.MapDelete("/{ruleId}", DeleteRuleAsync) .WithName("DeleteRule") .WithSummary("Deletes a rule") .WithDescription(_t("notifier.rule.delete2_description")) - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "delete_rule", "rule"); return app; } diff --git a/src/Notify/StellaOps.Notify.WebService/Endpoints/TemplateEndpoints.cs b/src/Notify/StellaOps.Notify.WebService/Endpoints/TemplateEndpoints.cs index c2feb4148..840a778a6 100644 --- a/src/Notify/StellaOps.Notify.WebService/Endpoints/TemplateEndpoints.cs +++ b/src/Notify/StellaOps.Notify.WebService/Endpoints/TemplateEndpoints.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Notify.WebService.Constants; using StellaOps.Notify.WebService.Contracts; @@ -40,19 +41,22 @@ public static class TemplateEndpoints .WithName("CreateTemplate") .WithSummary("Creates a new template") .WithDescription("Creates a new notification template. Template body syntax is validated before persisting. Returns conflict if a template with the same ID already exists.") - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "create_template", "template"); group.MapPut("/{templateId}", UpdateTemplateAsync) .WithName("UpdateTemplate") .WithSummary("Updates an existing template") .WithDescription("Updates an existing notification template. Template body syntax is validated before persisting. An audit entry is written on update.") - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "update_template", "template"); group.MapDelete("/{templateId}", DeleteTemplateAsync) .WithName("DeleteTemplate") .WithSummary("Deletes a template") .WithDescription("Permanently removes a notification template. Rules referencing this template will fall back to channel defaults on the next delivery. An audit entry is written on deletion.") - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "delete_template", "template"); group.MapPost("/preview", PreviewTemplateAsync) .WithName("PreviewTemplate") diff --git a/src/Notify/StellaOps.Notify.WebService/Endpoints/ThrottleEndpoints.cs b/src/Notify/StellaOps.Notify.WebService/Endpoints/ThrottleEndpoints.cs index 35f417013..2c5eaf99d 100644 --- a/src/Notify/StellaOps.Notify.WebService/Endpoints/ThrottleEndpoints.cs +++ b/src/Notify/StellaOps.Notify.WebService/Endpoints/ThrottleEndpoints.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Notify.WebService.Constants; using StellaOps.Notify.WebService.Extensions; @@ -32,13 +33,15 @@ public static class ThrottleEndpoints .WithName("UpdateThrottleConfiguration") .WithSummary("Update throttle configuration") .WithDescription("Creates or replaces the throttle configuration for the tenant. The default duration and optional per-event-kind overrides control how long duplicate notifications are suppressed.") - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "update_throttle_config", "throttle"); group.MapDelete("/config", DeleteConfigurationAsync) .WithName("DeleteThrottleConfiguration") .WithSummary("Delete throttle configuration") .WithDescription("Removes the tenant-specific throttle configuration, reverting all throttle windows to the platform defaults.") - .RequireAuthorization(NotifierPolicies.NotifyOperator); + .RequireAuthorization(NotifierPolicies.NotifyOperator) + .Audited("notify", "delete_throttle_config", "throttle"); group.MapPost("/evaluate", EvaluateAsync) .WithName("EvaluateThrottle") diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/AdministrationTrustSigningMutationEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/AdministrationTrustSigningMutationEndpoints.cs index 57724698a..a4d8de666 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/AdministrationTrustSigningMutationEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/AdministrationTrustSigningMutationEndpoints.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; @@ -90,7 +91,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("CreateAdministrationTrustKey") .WithSummary("Create trust signing key") - .RequireAuthorization(PlatformPolicies.TrustWrite); + .RequireAuthorization(PlatformPolicies.TrustWrite) + .Audited("platform", "create_trust_key", "trust_key"); group.MapPost("/keys/{keyId:guid}/rotate", async Task( HttpContext context, @@ -123,7 +125,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("RotateAdministrationTrustKey") .WithSummary("Rotate trust signing key") - .RequireAuthorization(PlatformPolicies.TrustWrite); + .RequireAuthorization(PlatformPolicies.TrustWrite) + .Audited("platform", "rotate_trust_key", "trust_key"); group.MapPost("/keys/{keyId:guid}/revoke", async Task( HttpContext context, @@ -156,7 +159,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("RevokeAdministrationTrustKey") .WithSummary("Revoke trust signing key") - .RequireAuthorization(PlatformPolicies.TrustAdmin); + .RequireAuthorization(PlatformPolicies.TrustAdmin) + .Audited("platform", "revoke_trust_key", "trust_key"); group.MapGet("/issuers", async Task( HttpContext context, @@ -224,7 +228,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("RegisterAdministrationTrustIssuer") .WithSummary("Register trust issuer") - .RequireAuthorization(PlatformPolicies.TrustWrite); + .RequireAuthorization(PlatformPolicies.TrustWrite) + .Audited("platform", "register_trust_issuer", "trust_issuer"); group.MapPost("/issuers/{issuerId:guid}/block", async Task( HttpContext context, @@ -257,7 +262,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("BlockAdministrationTrustIssuer") .WithSummary("Block trust issuer") - .RequireAuthorization(PlatformPolicies.TrustAdmin); + .RequireAuthorization(PlatformPolicies.TrustAdmin) + .Audited("platform", "block_trust_issuer", "trust_issuer"); group.MapPost("/issuers/{issuerId:guid}/unblock", async Task( HttpContext context, @@ -290,7 +296,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("UnblockAdministrationTrustIssuer") .WithSummary("Unblock trust issuer") - .RequireAuthorization(PlatformPolicies.TrustAdmin); + .RequireAuthorization(PlatformPolicies.TrustAdmin) + .Audited("platform", "unblock_trust_issuer", "trust_issuer"); group.MapGet("/certificates", async Task( HttpContext context, @@ -358,7 +365,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("RegisterAdministrationTrustCertificate") .WithSummary("Register trust certificate") - .RequireAuthorization(PlatformPolicies.TrustWrite); + .RequireAuthorization(PlatformPolicies.TrustWrite) + .Audited("platform", "register_trust_certificate", "trust_certificate"); group.MapPost("/certificates/{certificateId:guid}/revoke", async Task( HttpContext context, @@ -391,7 +399,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("RevokeAdministrationTrustCertificate") .WithSummary("Revoke trust certificate") - .RequireAuthorization(PlatformPolicies.TrustAdmin); + .RequireAuthorization(PlatformPolicies.TrustAdmin) + .Audited("platform", "revoke_trust_certificate", "trust_certificate"); group.MapGet("/transparency-log", async Task( HttpContext context, @@ -455,7 +464,8 @@ public static class AdministrationTrustSigningMutationEndpoints }) .WithName("ConfigureAdministrationTrustTransparencyLog") .WithSummary("Configure trust transparency log") - .RequireAuthorization(PlatformPolicies.TrustAdmin); + .RequireAuthorization(PlatformPolicies.TrustAdmin) + .Audited("platform", "configure_transparency_log", "transparency_log"); return app; } diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/CryptoProviderAdminEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/CryptoProviderAdminEndpoints.cs index 7624efced..1e26d00e3 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/CryptoProviderAdminEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/CryptoProviderAdminEndpoints.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; @@ -101,7 +102,8 @@ public static class CryptoProviderAdminEndpoints .WithName("UpsertCryptoProviderPreference") .WithSummary("Create or update a crypto provider preference") .WithDescription( - "Upserts a tenant crypto provider preference. The unique key is (tenantId, providerId, algorithmScope)."); + "Upserts a tenant crypto provider preference. The unique key is (tenantId, providerId, algorithmScope).") + .Audited("platform", "update_crypto_preference", "crypto_preference"); group.MapDelete("/preferences/{id:guid}", async Task( HttpContext context, @@ -128,7 +130,8 @@ public static class CryptoProviderAdminEndpoints }) .WithName("DeleteCryptoProviderPreference") .WithSummary("Delete a crypto provider preference") - .WithDescription("Removes a single crypto provider preference by ID. Tenant-isolated."); + .WithDescription("Removes a single crypto provider preference by ID. Tenant-isolated.") + .Audited("platform", "delete_crypto_preference", "crypto_preference"); return app; } diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/EnvironmentSettingsAdminEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/EnvironmentSettingsAdminEndpoints.cs index d8f9a7db3..d5402f795 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/EnvironmentSettingsAdminEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/EnvironmentSettingsAdminEndpoints.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Services; @@ -48,7 +49,8 @@ public static class EnvironmentSettingsAdminEndpoints .WithSummary("Create or update a DB-layer environment setting") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(PlatformPolicies.SetupAdmin); + .RequireAuthorization(PlatformPolicies.SetupAdmin) + .Audited("platform", "update_environment_setting", "environment_setting"); group.MapDelete("/{key}", async (string key, IEnvironmentSettingsStore store, CancellationToken ct) => { @@ -62,7 +64,8 @@ public static class EnvironmentSettingsAdminEndpoints .WithSummary("Delete a DB-layer environment setting") .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status400BadRequest) - .RequireAuthorization(PlatformPolicies.SetupAdmin); + .RequireAuthorization(PlatformPolicies.SetupAdmin) + .Audited("platform", "delete_environment_setting", "environment_setting"); return app; } diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/IdentityProviderEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/IdentityProviderEndpoints.cs index d475c6a84..2100c1f78 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/IdentityProviderEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/IdentityProviderEndpoints.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; @@ -73,7 +74,8 @@ public static class IdentityProviderEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }); + }) + .Audited("platform", "create_identity_provider", "identity_provider"); group.MapPut("/{id:guid}", async Task ( HttpContext context, @@ -101,7 +103,8 @@ public static class IdentityProviderEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }); + }) + .Audited("platform", "update_identity_provider", "identity_provider"); group.MapDelete("/{id:guid}", async Task ( HttpContext context, @@ -115,7 +118,8 @@ public static class IdentityProviderEndpoints var deleted = await service.DeleteAsync(requestContext!.TenantId, id, cancellationToken).ConfigureAwait(false); return deleted ? Results.NoContent() : Results.NotFound(); - }); + }) + .Audited("platform", "delete_identity_provider", "identity_provider"); group.MapPost("/{id:guid}/enable", async Task ( HttpContext context, @@ -135,7 +139,8 @@ public static class IdentityProviderEndpoints cancellationToken).ConfigureAwait(false); return result is null ? Results.NotFound() : Results.Ok(result); - }); + }) + .Audited("platform", "enable_identity_provider", "identity_provider"); group.MapPost("/{id:guid}/disable", async Task ( HttpContext context, @@ -155,7 +160,8 @@ public static class IdentityProviderEndpoints cancellationToken).ConfigureAwait(false); return result is null ? Results.NotFound() : Results.Ok(result); - }); + }) + .Audited("platform", "disable_identity_provider", "identity_provider"); group.MapPost("/test-connection", async Task ( HttpContext context, @@ -176,7 +182,8 @@ public static class IdentityProviderEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }); + }) + .Audited("platform", "test_identity_provider", "identity_provider"); group.MapGet("/{id:guid}/health", async Task ( HttpContext context, @@ -241,7 +248,8 @@ public static class IdentityProviderEndpoints return Results.Ok(new { applied = false, providerId = id, providerName = item.Name, error = "Authority unreachable; config saved but not applied." }); } - }); + }) + .Audited("platform", "apply_identity_provider", "identity_provider"); group.MapGet("/types", async Task ( HttpContext context, diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/PlatformEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/PlatformEndpoints.cs index fac898430..3a0125b5f 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/PlatformEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/PlatformEndpoints.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; using StellaOps.Platform.WebService.Services; @@ -223,7 +224,8 @@ public static class PlatformEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }).RequireAuthorization(PlatformPolicies.QuotaAdmin); + }).RequireAuthorization(PlatformPolicies.QuotaAdmin) + .Audited("platform", "create_quota_alert", "quota_alert"); } private static void MapOnboardingEndpoints(IEndpointRouteBuilder platform) @@ -266,7 +268,8 @@ public static class PlatformEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }).RequireAuthorization(PlatformPolicies.OnboardingWrite); + }).RequireAuthorization(PlatformPolicies.OnboardingWrite) + .Audited("platform", "complete_onboarding_step", "onboarding"); onboarding.MapPost("/skip", async Task ( HttpContext context, @@ -282,7 +285,8 @@ public static class PlatformEndpoints var state = await service.SkipAsync(requestContext!, request?.Reason, cancellationToken).ConfigureAwait(false); return Results.Ok(state); - }).RequireAuthorization(PlatformPolicies.OnboardingWrite); + }).RequireAuthorization(PlatformPolicies.OnboardingWrite) + .Audited("platform", "skip_onboarding", "onboarding"); platform.MapGet("/tenants/{tenantId}/setup-status", async Task ( HttpContext context, @@ -346,7 +350,8 @@ public static class PlatformEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }).RequireAuthorization(PlatformPolicies.PreferencesWrite); + }).RequireAuthorization(PlatformPolicies.PreferencesWrite) + .Audited("platform", "update_dashboard_preferences", "preferences"); preferences.MapGet("/language", async Task ( HttpContext context, @@ -384,7 +389,8 @@ public static class PlatformEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }).RequireAuthorization(PlatformPolicies.PreferencesWrite); + }).RequireAuthorization(PlatformPolicies.PreferencesWrite) + .Audited("platform", "update_language_preference", "preferences"); preferences.MapGet("/email", async Task ( HttpContext context, @@ -422,7 +428,8 @@ public static class PlatformEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }).RequireAuthorization(PlatformPolicies.PreferencesWrite); + }).RequireAuthorization(PlatformPolicies.PreferencesWrite) + .Audited("platform", "update_email_preference", "preferences"); var profiles = platform.MapGroup("/dashboard/profiles").WithTags("Platform Preferences"); @@ -478,7 +485,8 @@ public static class PlatformEndpoints { return Results.BadRequest(new { error = ex.Message }); } - }).RequireAuthorization(PlatformPolicies.PreferencesWrite); + }).RequireAuthorization(PlatformPolicies.PreferencesWrite) + .Audited("platform", "create_dashboard_profile", "dashboard_profile"); } private static void MapSearchEndpoints(IEndpointRouteBuilder app, IEndpointRouteBuilder platform) diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/ReleaseOrchestratorEnvironmentEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/ReleaseOrchestratorEnvironmentEndpoints.cs index cb0136b04..fe1f76211 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/ReleaseOrchestratorEnvironmentEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/ReleaseOrchestratorEnvironmentEndpoints.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.ReleaseOrchestrator.Environment.FreezeWindow; @@ -30,22 +31,26 @@ public static class ReleaseOrchestratorEnvironmentEndpoints environments.MapPost(string.Empty, CreateEnvironment) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("CreateReleaseOrchestratorEnvironment") - .WithSummary("Create a release orchestrator environment"); + .WithSummary("Create a release orchestrator environment") + .Audited("platform", "create_environment", "environment"); environments.MapPut("/{id:guid}", UpdateEnvironment) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("UpdateReleaseOrchestratorEnvironment") - .WithSummary("Update a release orchestrator environment"); + .WithSummary("Update a release orchestrator environment") + .Audited("platform", "update_environment", "environment"); environments.MapDelete("/{id:guid}", DeleteEnvironment) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("DeleteReleaseOrchestratorEnvironment") - .WithSummary("Delete a release orchestrator environment"); + .WithSummary("Delete a release orchestrator environment") + .Audited("platform", "delete_environment", "environment"); environments.MapPut("/{id:guid}/settings", UpdateEnvironmentSettings) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("UpdateReleaseOrchestratorEnvironmentSettings") - .WithSummary("Update environment release settings"); + .WithSummary("Update environment release settings") + .Audited("platform", "update_environment_settings", "environment"); environments.MapGet("/{id:guid}/targets", ListTargets) .WithName("ListReleaseOrchestratorEnvironmentTargets") @@ -54,17 +59,20 @@ public static class ReleaseOrchestratorEnvironmentEndpoints environments.MapPost("/{id:guid}/targets", CreateTarget) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("CreateReleaseOrchestratorEnvironmentTarget") - .WithSummary("Create an environment target"); + .WithSummary("Create an environment target") + .Audited("platform", "create_target", "environment_target"); environments.MapPut("/{id:guid}/targets/{targetId:guid}", UpdateTarget) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("UpdateReleaseOrchestratorEnvironmentTarget") - .WithSummary("Update an environment target"); + .WithSummary("Update an environment target") + .Audited("platform", "update_target", "environment_target"); environments.MapDelete("/{id:guid}/targets/{targetId:guid}", DeleteTarget) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("DeleteReleaseOrchestratorEnvironmentTarget") - .WithSummary("Delete an environment target"); + .WithSummary("Delete an environment target") + .Audited("platform", "delete_target", "environment_target"); environments.MapPost("/{id:guid}/targets/{targetId:guid}/health-check", CheckTargetHealth) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) @@ -78,17 +86,20 @@ public static class ReleaseOrchestratorEnvironmentEndpoints environments.MapPost("/{id:guid}/freeze-windows", CreateFreezeWindow) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("CreateReleaseOrchestratorFreezeWindow") - .WithSummary("Create an environment freeze window"); + .WithSummary("Create an environment freeze window") + .Audited("platform", "create_freeze_window", "freeze_window"); environments.MapPut("/{id:guid}/freeze-windows/{freezeWindowId:guid}", UpdateFreezeWindow) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("UpdateReleaseOrchestratorFreezeWindow") - .WithSummary("Update an environment freeze window"); + .WithSummary("Update an environment freeze window") + .Audited("platform", "update_freeze_window", "freeze_window"); environments.MapDelete("/{id:guid}/freeze-windows/{freezeWindowId:guid}", DeleteFreezeWindow) .RequireAuthorization(PlatformPolicies.ReleaseControlOperate) .WithName("DeleteReleaseOrchestratorFreezeWindow") - .WithSummary("Delete an environment freeze window"); + .WithSummary("Delete an environment freeze window") + .Audited("platform", "delete_freeze_window", "freeze_window"); return app; } diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/ScoreEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/ScoreEndpoints.cs index fd39e894e..fa7253c19 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/ScoreEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/ScoreEndpoints.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; @@ -124,7 +125,8 @@ public static class ScoreEndpoints .WithName("EvaluateScore") .WithSummary("Compute unified score") .WithDescription("Evaluates a unified trust score combining EWS computation with Determinization entropy.") - .RequireAuthorization(PlatformPolicies.ScoreEvaluate); + .RequireAuthorization(PlatformPolicies.ScoreEvaluate) + .Audited("platform", "evaluate_score", "score"); // GET /api/v1/score/{scoreId} - Get score by ID score.MapGet("/{scoreId}", async Task ( @@ -408,7 +410,8 @@ public static class ScoreEndpoints .WithName("VerifyScoreReplay") .WithSummary("Verify score replay") .WithDescription("Verifies a signed replay log by re-executing the score computation and comparing results.") - .RequireAuthorization(PlatformPolicies.ScoreRead); + .RequireAuthorization(PlatformPolicies.ScoreRead) + .Audited("platform", "verify_score", "score"); } private static bool TryResolveContext( diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/ScriptEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/ScriptEndpoints.cs index c25ec4021..52253dd58 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/ScriptEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/ScriptEndpoints.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; @@ -110,7 +111,8 @@ public static class ScriptEndpoints .WithName("CreateScript") .WithSummary("Create new script") .WithDescription("Creates a new script in the registry with initial content and metadata.") - .RequireAuthorization(PlatformPolicies.ScriptWrite); + .RequireAuthorization(PlatformPolicies.ScriptWrite) + .Audited("platform", "create_script", "script"); // GET /api/v2/scripts/{id} - Get script with content scripts.MapGet("/{id}", async Task( @@ -189,7 +191,8 @@ public static class ScriptEndpoints .WithName("UpdateScript") .WithSummary("Update script") .WithDescription("Updates an existing script. Content changes create a new version automatically.") - .RequireAuthorization(PlatformPolicies.ScriptWrite); + .RequireAuthorization(PlatformPolicies.ScriptWrite) + .Audited("platform", "update_script", "script"); // DELETE /api/v2/scripts/{id} - Delete script scripts.MapDelete("/{id}", async Task( @@ -219,7 +222,8 @@ public static class ScriptEndpoints .WithName("DeleteScript") .WithSummary("Delete script") .WithDescription("Deletes a script and all its version history.") - .RequireAuthorization(PlatformPolicies.ScriptWrite); + .RequireAuthorization(PlatformPolicies.ScriptWrite) + .Audited("platform", "delete_script", "script"); // GET /api/v2/scripts/{id}/versions - Version history scripts.MapGet("/{id}/versions", async Task( diff --git a/src/Platform/StellaOps.Platform.WebService/Endpoints/SetupEndpoints.cs b/src/Platform/StellaOps.Platform.WebService/Endpoints/SetupEndpoints.cs index 63698b5aa..9510d2744 100644 --- a/src/Platform/StellaOps.Platform.WebService/Endpoints/SetupEndpoints.cs +++ b/src/Platform/StellaOps.Platform.WebService/Endpoints/SetupEndpoints.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using StellaOps.Audit.Emission; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; using StellaOps.Platform.WebService.Options; @@ -178,7 +179,8 @@ public static class SetupEndpoints }).AllowAnonymous() .WithName("CreateSetupSession") .Produces(StatusCodes.Status201Created) - .Produces(StatusCodes.Status400BadRequest); + .Produces(StatusCodes.Status400BadRequest) + .Audited("platform", "create_setup_session", "setup"); // POST /api/v1/setup/sessions/resume - Resume or create session sessions.MapPost("/resume", async Task ( @@ -247,7 +249,8 @@ public static class SetupEndpoints return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest)); } }).AllowAnonymous() - .WithName("ExecuteSetupStepByPath"); + .WithName("ExecuteSetupStepByPath") + .Audited("platform", "execute_setup_step", "setup"); // POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/skip - Skip step (frontend path) sessions.MapPost("/{sessionId}/steps/{stepId}/skip", async Task ( @@ -290,7 +293,8 @@ public static class SetupEndpoints }).AllowAnonymous() .WithName("SkipSetupStepByPath") .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest); + .Produces(StatusCodes.Status400BadRequest) + .Audited("platform", "skip_setup_step", "setup"); // POST /api/v1/setup/sessions/{sessionId}/steps/{stepId}/checks/run - Run checks (frontend path) sessions.MapPost("/{sessionId}/steps/{stepId}/checks/run", async Task ( @@ -448,7 +452,8 @@ public static class SetupEndpoints }).AllowAnonymous() .WithName("FinalizeSetupSessionByPath") .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest); + .Produces(StatusCodes.Status400BadRequest) + .Audited("platform", "finalize_setup", "setup"); // POST /api/v1/setup/sessions/finalize - Finalize session sessions.MapPost("/finalize", async Task ( @@ -480,7 +485,8 @@ public static class SetupEndpoints }).AllowAnonymous() .WithName("FinalizeSetupSession") .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest); + .Produces(StatusCodes.Status400BadRequest) + .Audited("platform", "finalize_setup", "setup"); } private static void MapStepEndpoints(IEndpointRouteBuilder setup) @@ -522,7 +528,8 @@ public static class SetupEndpoints }).AllowAnonymous() .WithName("ExecuteSetupStep") .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest); + .Produces(StatusCodes.Status400BadRequest) + .Audited("platform", "execute_setup_step", "setup"); // POST /api/v1/setup/steps/skip - Skip a step steps.MapPost("/skip", async Task ( @@ -559,7 +566,8 @@ public static class SetupEndpoints }).AllowAnonymous() .WithName("SkipSetupStep") .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest); + .Produces(StatusCodes.Status400BadRequest) + .Audited("platform", "skip_setup_step", "setup"); // POST /api/v1/setup/steps/{stepId}/test-connection - Test connectivity for a step steps.MapPost("/{stepId}/test-connection", async Task ( diff --git a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ApprovalEndpoints.cs b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ApprovalEndpoints.cs index 74a0182c6..c08186b5f 100644 --- a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ApprovalEndpoints.cs +++ b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ApprovalEndpoints.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.ReleaseOrchestrator.WebApi.Contracts; using StellaOps.ReleaseOrchestrator.WebApi.Services; @@ -46,7 +47,8 @@ public static class ApprovalEndpoints var approve = group.MapPost("/{id}/approve", Approve) .WithDescription(_t("orchestrator.approval.approve_description")) - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "approve_release", "approval"); if (includeRouteNames) { approve.WithName("Approval_Approve"); @@ -54,7 +56,8 @@ public static class ApprovalEndpoints var reject = group.MapPost("/{id}/reject", Reject) .WithDescription(_t("orchestrator.approval.reject_description")) - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "reject_release", "approval"); if (includeRouteNames) { reject.WithName("Approval_Reject"); @@ -62,7 +65,8 @@ public static class ApprovalEndpoints var batchApprove = group.MapPost("/batch-approve", BatchApprove) .WithDescription(_t("orchestrator.approval.create_description")) - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "batch_approve", "approval"); if (includeRouteNames) { batchApprove.WithName("Approval_BatchApprove"); @@ -70,7 +74,8 @@ public static class ApprovalEndpoints var batchReject = group.MapPost("/batch-reject", BatchReject) .WithDescription(_t("orchestrator.approval.cancel_description")) - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "batch_reject", "approval"); if (includeRouteNames) { batchReject.WithName("Approval_BatchReject"); diff --git a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/DeploymentEndpoints.cs b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/DeploymentEndpoints.cs index f3295f048..35da6037b 100644 --- a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/DeploymentEndpoints.cs +++ b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/DeploymentEndpoints.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.ReleaseOrchestrator.WebApi.Services; using System.Security.Claims; @@ -21,18 +22,18 @@ public static class DeploymentEndpoints .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseRead) .RequireTenant(); - var create = group.MapPost("", CreateAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + var create = group.MapPost("", CreateAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite).Audited("release", "create_deployment", "deployment"); var list = group.MapGet("", ListAsync); var detail = group.MapGet("/{id}", GetAsync); var logs = group.MapGet("/{id}/logs", GetLogsAsync); var targetLogs = group.MapGet("/{id}/targets/{targetId}/logs", GetTargetLogsAsync); var events = group.MapGet("/{id}/events", GetEventsAsync); var metrics = group.MapGet("/{id}/metrics", GetMetricsAsync); - var pause = group.MapPost("/{id}/pause", PauseAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); - var resume = group.MapPost("/{id}/resume", ResumeAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); - var cancel = group.MapPost("/{id}/cancel", CancelAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); - var rollback = group.MapPost("/{id}/rollback", RollbackAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); - var retry = group.MapPost("/{id}/targets/{targetId}/retry", RetryTargetAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + var pause = group.MapPost("/{id}/pause", PauseAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite).Audited("release", "pause_deployment", "deployment"); + var resume = group.MapPost("/{id}/resume", ResumeAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite).Audited("release", "resume_deployment", "deployment"); + var cancel = group.MapPost("/{id}/cancel", CancelAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite).Audited("release", "cancel_deployment", "deployment"); + var rollback = group.MapPost("/{id}/rollback", RollbackAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove).Audited("release", "rollback_deployment", "deployment"); + var retry = group.MapPost("/{id}/targets/{targetId}/retry", RetryTargetAsync).RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite).Audited("release", "retry_target", "deployment"); if (!named) { diff --git a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/EvidenceEndpoints.cs b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/EvidenceEndpoints.cs index 7ec369b95..7f8480620 100644 --- a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/EvidenceEndpoints.cs +++ b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/EvidenceEndpoints.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using System.Security.Cryptography; using System.Text; using System.Text.Json; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; namespace StellaOps.ReleaseOrchestrator.WebApi.Endpoints; @@ -47,7 +48,8 @@ public static class EvidenceEndpoints } var verify = group.MapPost("/{id}/verify", VerifyEvidence) - .WithDescription("Verify the integrity of the specified evidence packet by recomputing and comparing its content hash. Returns the verification result including the computed hash, algorithm used, and whether the content matches the stored digest."); + .WithDescription("Verify the integrity of the specified evidence packet by recomputing and comparing its content hash. Returns the verification result including the computed hash, algorithm used, and whether the content matches the stored digest.") + .Audited("release", "verify_evidence", "evidence"); if (includeRouteNames) { verify.WithName("Evidence_Verify"); diff --git a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseControlV2Endpoints.cs b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseControlV2Endpoints.cs index 430b94d5d..d96c36ef8 100644 --- a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseControlV2Endpoints.cs +++ b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseControlV2Endpoints.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.ReleaseOrchestrator.WebApi.Contracts; using StellaOps.ReleaseOrchestrator.WebApi.Services; @@ -52,7 +53,8 @@ public static class ReleaseControlV2Endpoints approvals.MapPost("/{id}/decision", PostApprovalDecision) .WithName("ApprovalsV2_Decision") .WithDescription("Apply a structured decision action (approve, reject, defer, escalate) to the specified v2 approval, attributing the decision to the calling principal with an optional comment. Returns 409 if the approval is not in a state that accepts decisions.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "approval_decision", "approval"); } private static void MapRunsV2(IEndpointRouteBuilder app) @@ -70,7 +72,8 @@ public static class ReleaseControlV2Endpoints runs.MapPost("/{id}/rollback", TriggerRollback) .WithDescription("Initiate a rollback of the specified promotion run, computing a guard-state projection that identifies any post-deployment state that must be unwound before the rollback can proceed. Returns the rollback plan with an estimated blast radius assessment.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "rollback_run", "run"); } var apiRuns = app.MapGroup("/api/v1/runs") diff --git a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseDashboardEndpoints.cs b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseDashboardEndpoints.cs index 8431d4c9e..12e27441c 100644 --- a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseDashboardEndpoints.cs +++ b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseDashboardEndpoints.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.ReleaseOrchestrator.WebApi.Services; @@ -32,7 +33,8 @@ public static class ReleaseDashboardEndpoints var approve = group.MapPost("/promotions/{id}/approve", ApprovePromotion) .WithDescription("Record an approval decision on the specified pending promotion request, allowing the associated release to advance to the next environment. The calling principal must hold the release approval scope. Returns 404 when the promotion ID does not exist.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "approve_promotion", "promotion"); if (includeRouteNames) { approve.WithName("ReleaseDashboard_ApprovePromotion"); @@ -40,7 +42,8 @@ public static class ReleaseDashboardEndpoints var reject = group.MapPost("/promotions/{id}/reject", RejectPromotion) .WithDescription("Record a rejection decision on the specified pending promotion request with an optional rejection reason, blocking the release from advancing. The calling principal must hold the release approval scope. Returns 404 when the promotion ID does not exist.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "reject_promotion", "promotion"); if (includeRouteNames) { reject.WithName("ReleaseDashboard_RejectPromotion"); diff --git a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseEndpoints.cs b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseEndpoints.cs index ee33a3fe7..d83a1d83b 100644 --- a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseEndpoints.cs +++ b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ReleaseEndpoints.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.ReleaseOrchestrator.WebApi.Services; @@ -47,7 +48,8 @@ public static class ReleaseEndpoints var create = group.MapPost(string.Empty, CreateRelease) .WithDescription("Create a new release record in Draft state. The release captures an intent to promote a versioned set of components through defined environments. Returns 409 if a release with the same name and version already exists.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite) + .Audited("release", "create_release", "release"); if (includeRouteNames) { create.WithName("Release_Create"); @@ -55,7 +57,8 @@ public static class ReleaseEndpoints var update = group.MapPatch("/{id}", UpdateRelease) .WithDescription("Update mutable metadata on the specified release including description, target environment, and custom labels. Status transitions must be performed through the dedicated lifecycle endpoints. Returns 404 when the release does not exist.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite) + .Audited("release", "update_release", "release"); if (includeRouteNames) { update.WithName("Release_Update"); @@ -63,7 +66,8 @@ public static class ReleaseEndpoints var remove = group.MapDelete("/{id}", DeleteRelease) .WithDescription("Permanently remove the specified release record. Only releases in Draft or Failed status can be deleted; returns 409 for releases in other states. All associated components and events are removed with the release record.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite) + .Audited("release", "delete_release", "release"); if (includeRouteNames) { remove.WithName("Release_Delete"); @@ -71,7 +75,8 @@ public static class ReleaseEndpoints var ready = group.MapPost("/{id}/ready", MarkReady) .WithDescription("Transition the specified release from Draft to Ready state, signalling that all components are assembled and the release is eligible for promotion gate evaluation. Returns 409 if the release is not in Draft state or required components are missing.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite) + .Audited("release", "mark_ready", "release"); if (includeRouteNames) { ready.WithName("Release_MarkReady"); @@ -79,7 +84,8 @@ public static class ReleaseEndpoints var promote = group.MapPost("/{id}/promote", RequestPromotion) .WithDescription("Initiate the promotion workflow to advance the specified release to its next target environment, triggering policy gate evaluation. The promotion runs asynchronously; poll the release record or subscribe to events for outcome updates.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "promote_release", "release"); if (includeRouteNames) { promote.WithName("Release_Promote"); @@ -87,7 +93,8 @@ public static class ReleaseEndpoints var deploy = group.MapPost("/{id}/deploy", Deploy) .WithDescription("Trigger deployment of the specified release to its current target environment. Deployment is orchestrated by the platform and may include pre-deployment checks, artifact staging, and post-deployment validation. Returns 409 if gates have not been satisfied.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "deploy_release", "release"); if (includeRouteNames) { deploy.WithName("Release_Deploy"); @@ -95,7 +102,8 @@ public static class ReleaseEndpoints var rollback = group.MapPost("/{id}/rollback", Rollback) .WithDescription("Initiate a rollback of the specified deployed release to the previous stable version in the current environment. The rollback is audited and creates a new release event. Returns 409 if the release is not in Deployed state or no prior stable version exists.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseApprove) + .Audited("release", "rollback_release", "release"); if (includeRouteNames) { rollback.WithName("Release_Rollback"); @@ -103,7 +111,8 @@ public static class ReleaseEndpoints var clone = group.MapPost("/{id}/clone", CloneRelease) .WithDescription("Create a new release by copying the components, labels, and target environment from the specified source release, applying a new name and version. The cloned release starts in Draft state and is independent of the source.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite) + .Audited("release", "clone_release", "release"); if (includeRouteNames) { clone.WithName("Release_Clone"); @@ -118,7 +127,8 @@ public static class ReleaseEndpoints var addComponent = group.MapPost("/{releaseId}/components", AddComponent) .WithDescription("Register a new component in the specified release, supplying the artifact reference and content digest. Components must be added before the release is marked Ready. Returns 409 if a component with the same name is already registered.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite) + .Audited("release", "add_component", "release_component"); if (includeRouteNames) { addComponent.WithName("Release_AddComponent"); @@ -126,7 +136,8 @@ public static class ReleaseEndpoints var updateComponent = group.MapPatch("/{releaseId}/components/{componentId}", UpdateComponent) .WithDescription("Update the artifact reference, version, or content digest of the specified release component. Returns 404 when the component does not exist within the release or the release itself does not exist in the tenant.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite) + .Audited("release", "update_component", "release_component"); if (includeRouteNames) { updateComponent.WithName("Release_UpdateComponent"); @@ -134,7 +145,8 @@ public static class ReleaseEndpoints var removeComponent = group.MapDelete("/{releaseId}/components/{componentId}", RemoveComponent) .WithDescription("Remove the specified component from the release. Only permitted when the release is in Draft state; returns 409 for releases that are Ready or beyond. Returns 404 when the component or release does not exist in the tenant.") - .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite); + .RequireAuthorization(ReleaseOrchestratorPolicies.ReleaseWrite) + .Audited("release", "remove_component", "release_component"); if (includeRouteNames) { removeComponent.WithName("Release_RemoveComponent"); diff --git a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ScriptsEndpoints.cs b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ScriptsEndpoints.cs index 3acb78a84..4caf8ec7a 100644 --- a/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ScriptsEndpoints.cs +++ b/src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/ScriptsEndpoints.cs @@ -4,6 +4,7 @@ using System.Text.Json.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; +using StellaOps.Audit.Emission; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.ReleaseOrchestrator.Scripts; @@ -39,17 +40,20 @@ internal static class ScriptsEndpoints group.MapPost("/", CreateScriptAsync) .WithName("CreateScript") .WithDescription("Create a new script.") - .RequireAuthorization(ReleaseOrchestratorPolicies.Operate); + .RequireAuthorization(ReleaseOrchestratorPolicies.Operate) + .Audited("release", "create_script", "script"); group.MapPatch("/{scriptId}", UpdateScriptAsync) .WithName("UpdateScript") .WithDescription("Update an existing script.") - .RequireAuthorization(ReleaseOrchestratorPolicies.Operate); + .RequireAuthorization(ReleaseOrchestratorPolicies.Operate) + .Audited("release", "update_script", "script"); group.MapDelete("/{scriptId}", DeleteScriptAsync) .WithName("DeleteScript") .WithDescription("Delete a script.") - .RequireAuthorization(ReleaseOrchestratorPolicies.Operate); + .RequireAuthorization(ReleaseOrchestratorPolicies.Operate) + .Audited("release", "delete_script", "script"); group.MapPost("/validate", ValidateScriptAsync) .WithName("ValidateScript")