Some checks failed
api-governance / spectral-lint (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
109 lines
3.9 KiB
C#
109 lines
3.9 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using StellaOps.AirGap.Controller.Endpoints.Contracts;
|
|
using StellaOps.AirGap.Controller.Services;
|
|
using StellaOps.AirGap.Time.Models;
|
|
using StellaOps.AirGap.Time.Services;
|
|
|
|
namespace StellaOps.AirGap.Controller.Endpoints;
|
|
|
|
internal static class AirGapEndpoints
|
|
{
|
|
private const string StatusScope = "airgap:status:read";
|
|
private const string SealScope = "airgap:seal";
|
|
|
|
public static RouteGroupBuilder MapAirGapEndpoints(this IEndpointRouteBuilder app)
|
|
{
|
|
var group = app.MapGroup("/system/airgap")
|
|
.RequireAuthorization();
|
|
|
|
group.MapGet("/status", HandleStatus)
|
|
.RequireScope(StatusScope)
|
|
.WithName("AirGapStatus");
|
|
|
|
group.MapPost("/seal", HandleSeal)
|
|
.RequireScope(SealScope)
|
|
.WithName("AirGapSeal");
|
|
|
|
group.MapPost("/unseal", HandleUnseal)
|
|
.RequireScope(SealScope)
|
|
.WithName("AirGapUnseal");
|
|
|
|
return group;
|
|
}
|
|
|
|
private static async Task<IResult> HandleStatus(
|
|
ClaimsPrincipal user,
|
|
AirGapStateService service,
|
|
TimeProvider timeProvider,
|
|
HttpContext httpContext,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var tenantId = ResolveTenant(httpContext);
|
|
var status = await service.GetStatusAsync(tenantId, timeProvider.GetUtcNow(), cancellationToken);
|
|
return Results.Ok(AirGapStatusResponse.FromStatus(status));
|
|
}
|
|
|
|
private static async Task<IResult> HandleSeal(
|
|
SealRequest request,
|
|
ClaimsPrincipal user,
|
|
AirGapStateService service,
|
|
StalenessCalculator stalenessCalculator,
|
|
TimeProvider timeProvider,
|
|
HttpContext httpContext,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request.PolicyHash))
|
|
{
|
|
return Results.BadRequest(new { error = "policy_hash_required" });
|
|
}
|
|
|
|
var tenantId = ResolveTenant(httpContext);
|
|
var anchor = request.TimeAnchor ?? TimeAnchor.Unknown;
|
|
var budget = request.StalenessBudget ?? StalenessBudget.Default;
|
|
|
|
var now = timeProvider.GetUtcNow();
|
|
var state = await service.SealAsync(tenantId, request.PolicyHash!, anchor, budget, now, cancellationToken);
|
|
var status = new AirGapStatus(state, stalenessCalculator.Evaluate(anchor, budget, now), now);
|
|
return Results.Ok(AirGapStatusResponse.FromStatus(status));
|
|
}
|
|
|
|
private static async Task<IResult> HandleUnseal(
|
|
ClaimsPrincipal user,
|
|
AirGapStateService service,
|
|
TimeProvider timeProvider,
|
|
HttpContext httpContext,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var tenantId = ResolveTenant(httpContext);
|
|
var state = await service.UnsealAsync(tenantId, timeProvider.GetUtcNow(), cancellationToken);
|
|
var status = new AirGapStatus(state, StalenessEvaluation.Unknown, timeProvider.GetUtcNow());
|
|
return Results.Ok(AirGapStatusResponse.FromStatus(status));
|
|
}
|
|
|
|
private static string ResolveTenant(HttpContext httpContext)
|
|
{
|
|
if (httpContext.Request.Headers.TryGetValue("x-tenant-id", out var tenantHeader) && !string.IsNullOrWhiteSpace(tenantHeader))
|
|
{
|
|
return tenantHeader.ToString();
|
|
}
|
|
return "default";
|
|
}
|
|
}
|
|
|
|
internal static class AuthorizationExtensions
|
|
{
|
|
public static RouteHandlerBuilder RequireScope(this RouteHandlerBuilder builder, string requiredScope)
|
|
{
|
|
return builder.RequireAuthorization(policy =>
|
|
{
|
|
policy.RequireAssertion(ctx =>
|
|
{
|
|
var scopes = ctx.User.FindFirstValue("scope") ?? ctx.User.FindFirstValue("scp") ?? string.Empty;
|
|
return scopes.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
.Contains(requiredScope, StringComparer.OrdinalIgnoreCase);
|
|
});
|
|
});
|
|
}
|
|
}
|