up
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
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
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using StellaOps.AirGap.Controller.Domain;
|
||||
using StellaOps.AirGap.Controller.Services;
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
|
||||
namespace StellaOps.AirGap.Controller.Endpoints.Contracts;
|
||||
|
||||
public sealed record AirGapStatusResponse(
|
||||
string TenantId,
|
||||
bool Sealed,
|
||||
string? PolicyHash,
|
||||
TimeAnchor TimeAnchor,
|
||||
StalenessEvaluation Staleness,
|
||||
DateTimeOffset LastTransitionAt,
|
||||
DateTimeOffset EvaluatedAt)
|
||||
{
|
||||
public static AirGapStatusResponse FromStatus(AirGapStatus status) =>
|
||||
new(
|
||||
status.State.TenantId,
|
||||
status.State.Sealed,
|
||||
status.State.PolicyHash,
|
||||
status.State.TimeAnchor,
|
||||
status.Staleness,
|
||||
status.State.LastTransitionAt,
|
||||
status.EvaluatedAt);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
|
||||
namespace StellaOps.AirGap.Controller.Endpoints.Contracts;
|
||||
|
||||
public sealed class SealRequest
|
||||
{
|
||||
[Required]
|
||||
public string? PolicyHash { get; set; }
|
||||
|
||||
public TimeAnchor? TimeAnchor { get; set; }
|
||||
|
||||
public StalenessBudget? StalenessBudget { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user