522 lines
20 KiB
C#
522 lines
20 KiB
C#
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>
|
|
/// Trust and signing owner mutation endpoints backing Administration A6.
|
|
/// </summary>
|
|
public static class AdministrationTrustSigningMutationEndpoints
|
|
{
|
|
private const int DefaultLimit = 50;
|
|
private const int MaxLimit = 200;
|
|
|
|
public static IEndpointRouteBuilder MapAdministrationTrustSigningMutationEndpoints(this IEndpointRouteBuilder app)
|
|
{
|
|
var group = app.MapGroup("/api/v1/administration/trust-signing")
|
|
.WithTags("Administration")
|
|
.RequireAuthorization(PlatformPolicies.TrustRead)
|
|
.RequireTenant();
|
|
|
|
group.MapGet("/keys", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
TimeProvider timeProvider,
|
|
[FromQuery] int? limit,
|
|
[FromQuery] int? offset,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var normalizedLimit = NormalizeLimit(limit);
|
|
var normalizedOffset = NormalizeOffset(offset);
|
|
var items = await store.ListKeysAsync(
|
|
requestContext!.TenantId,
|
|
normalizedLimit,
|
|
normalizedOffset,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(new PlatformListResponse<AdministrationTrustKeySummary>(
|
|
requestContext.TenantId,
|
|
requestContext.ActorId,
|
|
timeProvider.GetUtcNow(),
|
|
Cached: false,
|
|
CacheTtlSeconds: 0,
|
|
items,
|
|
items.Count,
|
|
normalizedLimit,
|
|
normalizedOffset));
|
|
})
|
|
.WithName("ListAdministrationTrustKeys")
|
|
.WithSummary("List trust signing keys")
|
|
.RequireAuthorization(PlatformPolicies.TrustRead);
|
|
|
|
group.MapPost("/keys", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
CreateAdministrationTrustKeyRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var created = await store.CreateKeyAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Created($"/api/v1/administration/trust-signing/keys/{created.KeyId}", created);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId: null, certificateId: null);
|
|
}
|
|
})
|
|
.WithName("CreateAdministrationTrustKey")
|
|
.WithSummary("Create trust signing key")
|
|
.RequireAuthorization(PlatformPolicies.TrustWrite);
|
|
|
|
group.MapPost("/keys/{keyId:guid}/rotate", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
Guid keyId,
|
|
RotateAdministrationTrustKeyRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var updated = await store.RotateKeyAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
keyId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(updated);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId, certificateId: null);
|
|
}
|
|
})
|
|
.WithName("RotateAdministrationTrustKey")
|
|
.WithSummary("Rotate trust signing key")
|
|
.RequireAuthorization(PlatformPolicies.TrustWrite);
|
|
|
|
group.MapPost("/keys/{keyId:guid}/revoke", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
Guid keyId,
|
|
RevokeAdministrationTrustKeyRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var updated = await store.RevokeKeyAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
keyId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(updated);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId, certificateId: null);
|
|
}
|
|
})
|
|
.WithName("RevokeAdministrationTrustKey")
|
|
.WithSummary("Revoke trust signing key")
|
|
.RequireAuthorization(PlatformPolicies.TrustAdmin);
|
|
|
|
group.MapGet("/issuers", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
TimeProvider timeProvider,
|
|
[FromQuery] int? limit,
|
|
[FromQuery] int? offset,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var normalizedLimit = NormalizeLimit(limit);
|
|
var normalizedOffset = NormalizeOffset(offset);
|
|
var items = await store.ListIssuersAsync(
|
|
requestContext!.TenantId,
|
|
normalizedLimit,
|
|
normalizedOffset,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(new PlatformListResponse<AdministrationTrustIssuerSummary>(
|
|
requestContext.TenantId,
|
|
requestContext.ActorId,
|
|
timeProvider.GetUtcNow(),
|
|
Cached: false,
|
|
CacheTtlSeconds: 0,
|
|
items,
|
|
items.Count,
|
|
normalizedLimit,
|
|
normalizedOffset));
|
|
})
|
|
.WithName("ListAdministrationTrustIssuers")
|
|
.WithSummary("List trust issuers")
|
|
.RequireAuthorization(PlatformPolicies.TrustRead);
|
|
|
|
group.MapPost("/issuers", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
RegisterAdministrationTrustIssuerRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var created = await store.RegisterIssuerAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Created($"/api/v1/administration/trust-signing/issuers/{created.IssuerId}", created);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId: null, certificateId: null);
|
|
}
|
|
})
|
|
.WithName("RegisterAdministrationTrustIssuer")
|
|
.WithSummary("Register trust issuer")
|
|
.RequireAuthorization(PlatformPolicies.TrustWrite);
|
|
|
|
group.MapPost("/issuers/{issuerId:guid}/block", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
Guid issuerId,
|
|
BlockAdministrationTrustIssuerRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var updated = await store.BlockIssuerAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
issuerId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(updated);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId: null, certificateId: null);
|
|
}
|
|
})
|
|
.WithName("BlockAdministrationTrustIssuer")
|
|
.WithSummary("Block trust issuer")
|
|
.RequireAuthorization(PlatformPolicies.TrustAdmin);
|
|
|
|
group.MapPost("/issuers/{issuerId:guid}/unblock", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
Guid issuerId,
|
|
UnblockAdministrationTrustIssuerRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var updated = await store.UnblockIssuerAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
issuerId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(updated);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId: null, certificateId: null);
|
|
}
|
|
})
|
|
.WithName("UnblockAdministrationTrustIssuer")
|
|
.WithSummary("Unblock trust issuer")
|
|
.RequireAuthorization(PlatformPolicies.TrustAdmin);
|
|
|
|
group.MapGet("/certificates", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
TimeProvider timeProvider,
|
|
[FromQuery] int? limit,
|
|
[FromQuery] int? offset,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var normalizedLimit = NormalizeLimit(limit);
|
|
var normalizedOffset = NormalizeOffset(offset);
|
|
var items = await store.ListCertificatesAsync(
|
|
requestContext!.TenantId,
|
|
normalizedLimit,
|
|
normalizedOffset,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(new PlatformListResponse<AdministrationTrustCertificateSummary>(
|
|
requestContext.TenantId,
|
|
requestContext.ActorId,
|
|
timeProvider.GetUtcNow(),
|
|
Cached: false,
|
|
CacheTtlSeconds: 0,
|
|
items,
|
|
items.Count,
|
|
normalizedLimit,
|
|
normalizedOffset));
|
|
})
|
|
.WithName("ListAdministrationTrustCertificates")
|
|
.WithSummary("List trust certificates")
|
|
.RequireAuthorization(PlatformPolicies.TrustRead);
|
|
|
|
group.MapPost("/certificates", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
RegisterAdministrationTrustCertificateRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var created = await store.RegisterCertificateAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Created($"/api/v1/administration/trust-signing/certificates/{created.CertificateId}", created);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId: null, certificateId: null);
|
|
}
|
|
})
|
|
.WithName("RegisterAdministrationTrustCertificate")
|
|
.WithSummary("Register trust certificate")
|
|
.RequireAuthorization(PlatformPolicies.TrustWrite);
|
|
|
|
group.MapPost("/certificates/{certificateId:guid}/revoke", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
Guid certificateId,
|
|
RevokeAdministrationTrustCertificateRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var updated = await store.RevokeCertificateAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
certificateId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(updated);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId: null, certificateId);
|
|
}
|
|
})
|
|
.WithName("RevokeAdministrationTrustCertificate")
|
|
.WithSummary("Revoke trust certificate")
|
|
.RequireAuthorization(PlatformPolicies.TrustAdmin);
|
|
|
|
group.MapGet("/transparency-log", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
TimeProvider timeProvider,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
var config = await store.GetTransparencyLogConfigAsync(
|
|
requestContext!.TenantId,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
if (config is null)
|
|
{
|
|
return Results.NotFound(new { error = "transparency_log_not_configured" });
|
|
}
|
|
|
|
return Results.Ok(new PlatformItemResponse<AdministrationTransparencyLogConfig>(
|
|
requestContext.TenantId,
|
|
requestContext.ActorId,
|
|
timeProvider.GetUtcNow(),
|
|
Cached: false,
|
|
CacheTtlSeconds: 0,
|
|
config));
|
|
})
|
|
.WithName("GetAdministrationTrustTransparencyLog")
|
|
.WithSummary("Get trust transparency log configuration")
|
|
.RequireAuthorization(PlatformPolicies.TrustRead);
|
|
|
|
group.MapPut("/transparency-log", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
IAdministrationTrustSigningStore store,
|
|
ConfigureAdministrationTransparencyLogRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
try
|
|
{
|
|
var config = await store.ConfigureTransparencyLogAsync(
|
|
requestContext!.TenantId,
|
|
requestContext.ActorId,
|
|
request,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(config);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return MapStoreError(ex, keyId: null, certificateId: null);
|
|
}
|
|
})
|
|
.WithName("ConfigureAdministrationTrustTransparencyLog")
|
|
.WithSummary("Configure trust transparency log")
|
|
.RequireAuthorization(PlatformPolicies.TrustAdmin);
|
|
|
|
return app;
|
|
}
|
|
|
|
private static IResult MapStoreError(InvalidOperationException exception, Guid? keyId, Guid? certificateId)
|
|
{
|
|
return exception.Message switch
|
|
{
|
|
"request_required" => Results.BadRequest(new { error = "request_required" }),
|
|
"tenant_required" => Results.BadRequest(new { error = "tenant_required" }),
|
|
"tenant_id_invalid" => Results.BadRequest(new { error = "tenant_id_invalid" }),
|
|
"reason_required" => Results.BadRequest(new { error = "reason_required" }),
|
|
"key_alias_required" => Results.BadRequest(new { error = "key_alias_required" }),
|
|
"key_algorithm_required" => Results.BadRequest(new { error = "key_algorithm_required" }),
|
|
"key_alias_exists" => Results.Conflict(new { error = "key_alias_exists" }),
|
|
"key_not_found" => Results.NotFound(new { error = "key_not_found", keyId }),
|
|
"key_revoked" => Results.Conflict(new { error = "key_revoked", keyId }),
|
|
"issuer_name_required" => Results.BadRequest(new { error = "issuer_name_required" }),
|
|
"issuer_uri_required" => Results.BadRequest(new { error = "issuer_uri_required" }),
|
|
"issuer_uri_invalid" => Results.BadRequest(new { error = "issuer_uri_invalid" }),
|
|
"issuer_trust_level_required" => Results.BadRequest(new { error = "issuer_trust_level_required" }),
|
|
"issuer_uri_exists" => Results.Conflict(new { error = "issuer_uri_exists" }),
|
|
"issuer_not_found" => Results.NotFound(new { error = "issuer_not_found" }),
|
|
"certificate_serial_required" => Results.BadRequest(new { error = "certificate_serial_required" }),
|
|
"certificate_validity_invalid" => Results.BadRequest(new { error = "certificate_validity_invalid" }),
|
|
"certificate_serial_exists" => Results.Conflict(new { error = "certificate_serial_exists" }),
|
|
"certificate_not_found" => Results.NotFound(new { error = "certificate_not_found", certificateId }),
|
|
"transparency_log_url_required" => Results.BadRequest(new { error = "transparency_log_url_required" }),
|
|
"transparency_log_url_invalid" => Results.BadRequest(new { error = "transparency_log_url_invalid" }),
|
|
"transparency_witness_url_invalid" => Results.BadRequest(new { error = "transparency_witness_url_invalid" }),
|
|
_ => Results.BadRequest(new { error = exception.Message })
|
|
};
|
|
}
|
|
|
|
private static int NormalizeLimit(int? value)
|
|
{
|
|
return value switch
|
|
{
|
|
null => DefaultLimit,
|
|
< 1 => 1,
|
|
> MaxLimit => MaxLimit,
|
|
_ => value.Value
|
|
};
|
|
}
|
|
|
|
private static int NormalizeOffset(int? value) => value is null or < 0 ? 0 : value.Value;
|
|
|
|
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;
|
|
}
|
|
}
|