wip: doctor/cli/docs/api to vector db consolidation; api hardening for descriptions, tenant, and scopes; migrations and conversions of all DALs to EF v10
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using StellaOps.AirGap.Controller.Endpoints.Contracts;
|
||||
using StellaOps.AirGap.Controller.Security;
|
||||
using StellaOps.AirGap.Controller.Services;
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
@@ -11,30 +11,30 @@ namespace StellaOps.AirGap.Controller.Endpoints;
|
||||
|
||||
internal static class AirGapEndpoints
|
||||
{
|
||||
private const string StatusScope = "airgap:status:read";
|
||||
private const string SealScope = "airgap:seal";
|
||||
private const string VerifyScope = "airgap:verify";
|
||||
|
||||
public static RouteGroupBuilder MapAirGapEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/system/airgap")
|
||||
.RequireAuthorization();
|
||||
.RequireAuthorization(AirGapPolicies.StatusRead);
|
||||
|
||||
group.MapGet("/status", HandleStatus)
|
||||
.RequireScope(StatusScope)
|
||||
.WithName("AirGapStatus");
|
||||
.RequireAuthorization(AirGapPolicies.StatusRead)
|
||||
.WithName("AirGapStatus")
|
||||
.WithDescription("Returns the current air-gap seal status for the tenant including seal state, staleness evaluation, and content budget freshness. Requires airgap:status:read scope.");
|
||||
|
||||
group.MapPost("/seal", HandleSeal)
|
||||
.RequireScope(SealScope)
|
||||
.WithName("AirGapSeal");
|
||||
.RequireAuthorization(AirGapPolicies.Seal)
|
||||
.WithName("AirGapSeal")
|
||||
.WithDescription("Seals the air-gap environment for the tenant by recording a policy hash, time anchor, and staleness budget. Returns the updated seal status including staleness evaluation. Requires airgap:seal scope.");
|
||||
|
||||
group.MapPost("/unseal", HandleUnseal)
|
||||
.RequireScope(SealScope)
|
||||
.WithName("AirGapUnseal");
|
||||
.RequireAuthorization(AirGapPolicies.Seal)
|
||||
.WithName("AirGapUnseal")
|
||||
.WithDescription("Unseals the air-gap environment for the tenant, allowing normal connectivity. Returns the updated unsealed status. Requires airgap:seal scope.");
|
||||
|
||||
group.MapPost("/verify", HandleVerify)
|
||||
.RequireScope(VerifyScope)
|
||||
.WithName("AirGapVerify");
|
||||
.RequireAuthorization(AirGapPolicies.Verify)
|
||||
.WithName("AirGapVerify")
|
||||
.WithDescription("Verifies the current air-gap state against a provided policy hash and deterministic replay evidence. Returns a verification result indicating whether the seal state matches the expected evidence. Requires airgap:verify scope.");
|
||||
|
||||
return group;
|
||||
}
|
||||
@@ -235,34 +235,3 @@ internal static class AirGapEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
internal static class AuthorizationExtensions
|
||||
{
|
||||
public static RouteHandlerBuilder RequireScope(this RouteHandlerBuilder builder, string requiredScope)
|
||||
{
|
||||
return builder.RequireAuthorization(policy =>
|
||||
{
|
||||
policy.RequireAssertion(ctx =>
|
||||
{
|
||||
if (ctx.User.HasClaim(c => c.Type == StellaOpsClaimTypes.ScopeItem))
|
||||
{
|
||||
return ctx.User.FindAll(StellaOpsClaimTypes.ScopeItem)
|
||||
.Select(c => c.Value)
|
||||
.Contains(requiredScope, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var scopes = ctx.User.FindAll(StellaOpsClaimTypes.Scope)
|
||||
.SelectMany(c => c.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
.ToArray();
|
||||
|
||||
if (scopes.Length == 0)
|
||||
{
|
||||
scopes = ctx.User.FindAll("scp")
|
||||
.SelectMany(c => c.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
return scopes.Contains(requiredScope, StringComparer.OrdinalIgnoreCase);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.AirGap.Controller.Auth;
|
||||
using StellaOps.AirGap.Controller.DependencyInjection;
|
||||
using StellaOps.AirGap.Controller.Endpoints;
|
||||
using StellaOps.AirGap.Controller.Security;
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
|
||||
@@ -12,7 +14,17 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddAuthentication(HeaderScopeAuthenticationHandler.SchemeName)
|
||||
.AddScheme<AuthenticationSchemeOptions, HeaderScopeAuthenticationHandler>(HeaderScopeAuthenticationHandler.SchemeName, _ => { });
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(AirGapPolicies.StatusRead, policy =>
|
||||
policy.RequireAssertion(ctx => AirGapScopeAssertion.HasScope(ctx, StellaOpsScopes.AirgapStatusRead)));
|
||||
options.AddPolicy(AirGapPolicies.Seal, policy =>
|
||||
policy.RequireAssertion(ctx => AirGapScopeAssertion.HasScope(ctx, StellaOpsScopes.AirgapSeal)));
|
||||
options.AddPolicy(AirGapPolicies.Import, policy =>
|
||||
policy.RequireAssertion(ctx => AirGapScopeAssertion.HasScope(ctx, StellaOpsScopes.AirgapImport)));
|
||||
options.AddPolicy(AirGapPolicies.Verify, policy =>
|
||||
policy.RequireAssertion(ctx => AirGapScopeAssertion.HasScope(ctx, "airgap:verify")));
|
||||
});
|
||||
builder.Services.AddSingleton<TimeProvider>(TimeProvider.System);
|
||||
|
||||
builder.Services.AddAirGapController(builder.Configuration);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.AirGap.Controller.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Named authorization policy constants for the AirGap Controller service.
|
||||
/// Policies are registered via assertion-based policies in Program.cs using
|
||||
/// <see cref="AirGapScopeAssertion"/> to evaluate claims from the HeaderScope
|
||||
/// authentication handler.
|
||||
/// </summary>
|
||||
internal static class AirGapPolicies
|
||||
{
|
||||
/// <summary>Policy for reading air-gap status and staleness information. Requires airgap:status:read scope.</summary>
|
||||
public const string StatusRead = "AirGap.StatusRead";
|
||||
|
||||
/// <summary>Policy for sealing and unsealing the air-gap environment. Requires airgap:seal scope.</summary>
|
||||
public const string Seal = "AirGap.Seal";
|
||||
|
||||
/// <summary>Policy for importing offline bundles while in air-gapped mode. Requires airgap:import scope.</summary>
|
||||
public const string Import = "AirGap.Import";
|
||||
|
||||
/// <summary>Policy for verifying air-gap state against policy hash and replay evidence. Requires airgap:verify scope.</summary>
|
||||
public const string Verify = "AirGap.Verify";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scope assertion helper for AirGap policies. Evaluates scope claims populated by
|
||||
/// the HeaderScope authentication handler against a required scope string.
|
||||
/// </summary>
|
||||
internal static class AirGapScopeAssertion
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> when the authenticated principal carries the required scope
|
||||
/// in either <see cref="StellaOpsClaimTypes.ScopeItem"/> or space-delimited
|
||||
/// <see cref="StellaOpsClaimTypes.Scope"/> / <c>scp</c> claims.
|
||||
/// </summary>
|
||||
public static bool HasScope(AuthorizationHandlerContext context, string requiredScope)
|
||||
{
|
||||
var user = context.User;
|
||||
|
||||
if (user.HasClaim(c => c.Type == StellaOpsClaimTypes.ScopeItem))
|
||||
{
|
||||
return user.FindAll(StellaOpsClaimTypes.ScopeItem)
|
||||
.Select(c => c.Value)
|
||||
.Contains(requiredScope, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var scopes = user.FindAll(StellaOpsClaimTypes.Scope)
|
||||
.SelectMany(c => c.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
.ToArray();
|
||||
|
||||
if (scopes.Length == 0)
|
||||
{
|
||||
scopes = user.FindAll("scp")
|
||||
.SelectMany(c => c.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
return scopes.Contains(requiredScope, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user