using Microsoft.AspNetCore.Mvc; using StellaOps.Policy.Engine.AirGap; namespace StellaOps.Policy.Engine.Endpoints; /// /// Endpoints for sealed-mode operations per CONTRACT-SEALED-MODE-004. /// public static class SealedModeEndpoints { public static IEndpointRouteBuilder MapSealedMode(this IEndpointRouteBuilder routes) { var group = routes.MapGroup("/system/airgap"); group.MapPost("/seal", SealAsync) .WithName("AirGap.Seal") .WithDescription("Seal the environment") .RequireAuthorization(policy => policy.RequireClaim("scope", "airgap:seal")) .ProducesProblem(StatusCodes.Status400BadRequest) .ProducesProblem(StatusCodes.Status500InternalServerError); group.MapPost("/unseal", UnsealAsync) .WithName("AirGap.Unseal") .WithDescription("Unseal the environment") .RequireAuthorization(policy => policy.RequireClaim("scope", "airgap:seal")) .ProducesProblem(StatusCodes.Status500InternalServerError); group.MapGet("/status", GetStatusAsync) .WithName("AirGap.GetStatus") .WithDescription("Get sealed-mode status") .RequireAuthorization(policy => policy.RequireClaim("scope", "airgap:status:read")); group.MapPost("/verify", VerifyBundleAsync) .WithName("AirGap.VerifyBundle") .WithDescription("Verify a bundle against trust roots") .RequireAuthorization(policy => policy.RequireClaim("scope", "airgap:verify")) .ProducesProblem(StatusCodes.Status400BadRequest) .ProducesProblem(StatusCodes.Status422UnprocessableEntity); return routes; } private static async Task SealAsync( [FromHeader(Name = "X-Tenant-Id")] string? tenantId, [FromBody] SealRequest request, ISealedModeService service, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(tenantId)) { tenantId = "default"; } try { var response = await service.SealAsync(tenantId, request, cancellationToken).ConfigureAwait(false); return Results.Ok(response); } catch (SealedModeException ex) { return SealedModeResultHelper.ToProblem(ex); } catch (ArgumentException ex) { return SealedModeResultHelper.ToProblem( SealedModeErrorCodes.SealFailed, ex.Message, "Ensure all required parameters are provided"); } catch (Exception ex) { return SealedModeResultHelper.ToProblem( SealedModeErrorCodes.SealFailed, $"Seal operation failed: {ex.Message}"); } } private static async Task UnsealAsync( [FromHeader(Name = "X-Tenant-Id")] string? tenantId, ISealedModeService service, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(tenantId)) { tenantId = "default"; } try { var response = await service.UnsealAsync(tenantId, cancellationToken).ConfigureAwait(false); return Results.Ok(response); } catch (SealedModeException ex) { return SealedModeResultHelper.ToProblem(ex); } catch (Exception ex) { return SealedModeResultHelper.ToProblem( SealedModeErrorCodes.UnsealFailed, $"Unseal operation failed: {ex.Message}"); } } private static async Task GetStatusAsync( [FromHeader(Name = "X-Tenant-Id")] string? tenantId, ISealedModeService service, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(tenantId)) { tenantId = "default"; } var status = await service.GetStatusAsync(tenantId, cancellationToken).ConfigureAwait(false); return Results.Ok(status); } private static async Task VerifyBundleAsync( [FromBody] BundleVerifyRequest request, ISealedModeService service, CancellationToken cancellationToken) { try { var response = await service.VerifyBundleAsync(request, cancellationToken).ConfigureAwait(false); // Return problem details if verification failed if (!response.Valid && response.VerificationResult.Error is not null) { return SealedModeResultHelper.ToProblem( SealedModeErrorCodes.SignatureInvalid, response.VerificationResult.Error, "Verify bundle integrity and trust root configuration", 422); } return Results.Ok(response); } catch (SealedModeException ex) { return SealedModeResultHelper.ToProblem(ex); } catch (ArgumentException ex) { return SealedModeResultHelper.ToProblem( SealedModeErrorCodes.BundleInvalid, ex.Message, "Ensure bundle path is valid and accessible"); } catch (FileNotFoundException ex) { return SealedModeResultHelper.ToProblem( SealedModeErrorCodes.BundleInvalid, $"Bundle file not found: {ex.FileName ?? ex.Message}", "Verify the bundle path is correct"); } } }