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");
}
}
}