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:
master
2026-02-23 15:30:50 +02:00
parent bd8fee6ed8
commit e746577380
1424 changed files with 81225 additions and 25251 deletions

View File

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

View File

@@ -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);

View File

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