248 lines
8.9 KiB
C#
248 lines
8.9 KiB
C#
// SPDX-License-Identifier: BUSL-1.1
|
|
// Copyright (c) 2025 StellaOps
|
|
// Sprint: SPRINT_20260122_041_Policy_interop_import_export_rego
|
|
// Task: TASK-07 - Platform API Endpoints
|
|
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using StellaOps.Auth.ServerIntegration.Tenancy;
|
|
using StellaOps.Platform.WebService.Constants;
|
|
using StellaOps.Platform.WebService.Contracts;
|
|
using StellaOps.Platform.WebService.Services;
|
|
|
|
namespace StellaOps.Platform.WebService.Endpoints;
|
|
|
|
/// <summary>
|
|
/// Policy import/export interop API endpoints.
|
|
/// Provides bidirectional policy exchange between JSON (PolicyPack v2) and OPA/Rego formats.
|
|
/// </summary>
|
|
public static class PolicyInteropEndpoints
|
|
{
|
|
/// <summary>
|
|
/// Maps policy interop endpoints under /api/v1/policy/interop.
|
|
/// </summary>
|
|
public static IEndpointRouteBuilder MapPolicyInteropEndpoints(this IEndpointRouteBuilder app)
|
|
{
|
|
var interop = app.MapGroup("/api/v1/policy/interop")
|
|
.WithTags("PolicyInterop")
|
|
.RequireAuthorization(PlatformPolicies.PolicyRead)
|
|
.RequireTenant();
|
|
|
|
MapExportEndpoint(interop);
|
|
MapImportEndpoint(interop);
|
|
MapValidateEndpoint(interop);
|
|
MapEvaluateEndpoint(interop);
|
|
MapFormatsEndpoint(interop);
|
|
|
|
return app;
|
|
}
|
|
|
|
private static void MapExportEndpoint(IEndpointRouteBuilder group)
|
|
{
|
|
// POST /api/v1/policy/interop/export
|
|
group.MapPost("/export", async Task<IResult> (
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IPolicyInteropService service,
|
|
[FromBody] PolicyExportApiRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var result = await service.ExportAsync(
|
|
requestContext!,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
if (!result.Success)
|
|
{
|
|
return Results.BadRequest(new { error = "export_failed", diagnostics = result.Diagnostics });
|
|
}
|
|
|
|
return Results.Ok(new PlatformItemResponse<PolicyExportApiResponse>(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
DateTimeOffset.UtcNow,
|
|
false,
|
|
0,
|
|
result));
|
|
})
|
|
.WithName("ExportPolicy")
|
|
.WithSummary("Export policy to format")
|
|
.WithDescription("Exports a PolicyPack v2 document to JSON or OPA/Rego format with optional environment-specific thresholds and remediation hints.")
|
|
.RequireAuthorization(PlatformPolicies.PolicyRead);
|
|
|
|
// POST /api/v1/policy/interop/import
|
|
}
|
|
|
|
private static void MapImportEndpoint(IEndpointRouteBuilder group)
|
|
{
|
|
group.MapPost("/import", async Task<IResult> (
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IPolicyInteropService service,
|
|
[FromBody] PolicyImportApiRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var result = await service.ImportAsync(
|
|
requestContext!,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
if (!result.Success)
|
|
{
|
|
return Results.BadRequest(new { error = "import_failed", diagnostics = result.Diagnostics });
|
|
}
|
|
|
|
return Results.Ok(new PlatformItemResponse<PolicyImportApiResponse>(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
DateTimeOffset.UtcNow,
|
|
false,
|
|
0,
|
|
result));
|
|
})
|
|
.WithName("ImportPolicy")
|
|
.WithSummary("Import policy from format")
|
|
.WithDescription("Imports a policy from JSON or OPA/Rego format into the native PolicyPack v2 model. Unknown Rego patterns are preserved for OPA evaluation.")
|
|
.RequireAuthorization(PlatformPolicies.PolicyWrite);
|
|
}
|
|
|
|
private static void MapValidateEndpoint(IEndpointRouteBuilder group)
|
|
{
|
|
// POST /api/v1/policy/interop/validate
|
|
group.MapPost("/validate", async Task<IResult> (
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IPolicyInteropService service,
|
|
[FromBody] PolicyValidateApiRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var result = await service.ValidateAsync(
|
|
requestContext!,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(new PlatformItemResponse<PolicyValidateApiResponse>(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
DateTimeOffset.UtcNow,
|
|
false,
|
|
0,
|
|
result));
|
|
})
|
|
.WithName("ValidatePolicy")
|
|
.WithSummary("Validate policy document")
|
|
.WithDescription("Validates a policy document against the PolicyPack v2 schema or checks Rego syntax via embedded OPA.")
|
|
.RequireAuthorization(PlatformPolicies.PolicyRead);
|
|
}
|
|
|
|
private static void MapEvaluateEndpoint(IEndpointRouteBuilder group)
|
|
{
|
|
// POST /api/v1/policy/interop/evaluate
|
|
group.MapPost("/evaluate", async Task<IResult> (
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IPolicyInteropService service,
|
|
[FromBody] PolicyEvaluateApiRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var result = await service.EvaluateAsync(
|
|
requestContext!,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
var statusCode = result.Decision switch
|
|
{
|
|
"allow" => StatusCodes.Status200OK,
|
|
"warn" => StatusCodes.Status200OK,
|
|
"block" => StatusCodes.Status200OK,
|
|
_ => StatusCodes.Status200OK
|
|
};
|
|
|
|
return Results.Ok(new PlatformItemResponse<PolicyEvaluateApiResponse>(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
DateTimeOffset.UtcNow,
|
|
false,
|
|
0,
|
|
result));
|
|
})
|
|
.WithName("EvaluatePolicy")
|
|
.WithSummary("Evaluate policy against input")
|
|
.WithDescription("Evaluates a policy (JSON or Rego) against evidence input and returns allow/warn/block decision with remediation hints.")
|
|
.RequireAuthorization(PlatformPolicies.PolicyEvaluate);
|
|
}
|
|
|
|
private static void MapFormatsEndpoint(IEndpointRouteBuilder group)
|
|
{
|
|
// GET /api/v1/policy/interop/formats
|
|
group.MapGet("/formats", (
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var formats = new PolicyFormatsApiResponse
|
|
{
|
|
Formats =
|
|
[
|
|
new PolicyFormatInfo("json", "PolicyPack v2 (JSON)", "policy.stellaops.io/v2", true, true),
|
|
new PolicyFormatInfo("rego", "OPA/Rego", "package stella.release", true, true)
|
|
]
|
|
};
|
|
|
|
return Results.Ok(new PlatformItemResponse<PolicyFormatsApiResponse>(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
DateTimeOffset.UtcNow,
|
|
true,
|
|
3600,
|
|
formats));
|
|
})
|
|
.WithName("ListPolicyFormats")
|
|
.WithSummary("List supported policy formats")
|
|
.WithDescription("Returns the list of supported policy import/export formats.")
|
|
.RequireAuthorization(PlatformPolicies.PolicyRead);
|
|
}
|
|
|
|
private static bool TryResolveContext(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
out PlatformRequestContext? requestContext,
|
|
out IResult? failure)
|
|
{
|
|
if (resolver.TryResolve(context, out requestContext, out var error))
|
|
{
|
|
failure = null;
|
|
return true;
|
|
}
|
|
|
|
failure = Results.BadRequest(new { error = error ?? "tenant_missing" });
|
|
return false;
|
|
}
|
|
}
|