Gaps fill up, fixes, ui restructuring
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Platform.WebService.Constants;
|
||||
using StellaOps.Platform.WebService.Contracts;
|
||||
using StellaOps.Platform.WebService.Services;
|
||||
using StellaOps.Telemetry.Federation.Bundles;
|
||||
using StellaOps.Telemetry.Federation.Consent;
|
||||
using StellaOps.Telemetry.Federation.Intelligence;
|
||||
using StellaOps.Telemetry.Federation.Privacy;
|
||||
|
||||
namespace StellaOps.Platform.WebService.Endpoints;
|
||||
|
||||
public static class FederationTelemetryEndpoints
|
||||
{
|
||||
// In-memory bundle store for MVP; production would use persistent store
|
||||
private static readonly List<FederatedBundle> _bundles = new();
|
||||
private static readonly object _bundleLock = new();
|
||||
|
||||
public static IEndpointRouteBuilder MapFederationTelemetryEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/v1/telemetry/federation")
|
||||
.WithTags("Federated Telemetry");
|
||||
|
||||
// GET /consent — get consent state
|
||||
group.MapGet("/consent", async Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IConsentManager consentManager,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
return failure!;
|
||||
|
||||
var state = await consentManager.GetConsentStateAsync(requestContext!.TenantId, ct).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new FederationConsentStateResponse(
|
||||
state.Granted, state.GrantedBy, state.GrantedAt, state.ExpiresAt, state.DsseDigest));
|
||||
})
|
||||
.WithName("GetFederationConsent")
|
||||
.WithSummary("Get federation consent state for current tenant")
|
||||
.RequireAuthorization(PlatformPolicies.FederationRead);
|
||||
|
||||
// POST /consent/grant — grant consent
|
||||
group.MapPost("/consent/grant", async Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IConsentManager consentManager,
|
||||
FederationGrantConsentRequest request,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
return failure!;
|
||||
|
||||
TimeSpan? ttl = request.TtlHours.HasValue
|
||||
? TimeSpan.FromHours(request.TtlHours.Value)
|
||||
: null;
|
||||
|
||||
var proof = await consentManager.GrantConsentAsync(
|
||||
requestContext!.TenantId, request.GrantedBy, ttl, ct).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new FederationConsentProofResponse(
|
||||
proof.TenantId, proof.GrantedBy, proof.GrantedAt, proof.ExpiresAt, proof.DsseDigest));
|
||||
})
|
||||
.WithName("GrantFederationConsent")
|
||||
.WithSummary("Grant federation telemetry consent")
|
||||
.RequireAuthorization(PlatformPolicies.FederationManage);
|
||||
|
||||
// POST /consent/revoke — revoke consent
|
||||
group.MapPost("/consent/revoke", async Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IConsentManager consentManager,
|
||||
FederationRevokeConsentRequest request,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
return failure!;
|
||||
|
||||
await consentManager.RevokeConsentAsync(requestContext!.TenantId, request.RevokedBy, ct).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new { revoked = true });
|
||||
})
|
||||
.WithName("RevokeFederationConsent")
|
||||
.WithSummary("Revoke federation telemetry consent")
|
||||
.RequireAuthorization(PlatformPolicies.FederationManage);
|
||||
|
||||
// GET /status — federation status
|
||||
group.MapGet("/status", async Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IConsentManager consentManager,
|
||||
IPrivacyBudgetTracker budgetTracker,
|
||||
Microsoft.Extensions.Options.IOptions<Telemetry.Federation.FederatedTelemetryOptions> fedOptions,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
return failure!;
|
||||
|
||||
var consent = await consentManager.GetConsentStateAsync(requestContext!.TenantId, ct).ConfigureAwait(false);
|
||||
var snapshot = budgetTracker.GetSnapshot();
|
||||
|
||||
int bundleCount;
|
||||
lock (_bundleLock) { bundleCount = _bundles.Count; }
|
||||
|
||||
return Results.Ok(new FederationStatusResponse(
|
||||
Enabled: !fedOptions.Value.SealedModeEnabled,
|
||||
SealedMode: fedOptions.Value.SealedModeEnabled,
|
||||
SiteId: fedOptions.Value.SiteId,
|
||||
ConsentGranted: consent.Granted,
|
||||
EpsilonRemaining: snapshot.Remaining,
|
||||
EpsilonTotal: snapshot.Total,
|
||||
BudgetExhausted: snapshot.Exhausted,
|
||||
NextBudgetReset: snapshot.NextReset,
|
||||
BundleCount: bundleCount));
|
||||
})
|
||||
.WithName("GetFederationStatus")
|
||||
.WithSummary("Get federation telemetry status")
|
||||
.RequireAuthorization(PlatformPolicies.FederationRead);
|
||||
|
||||
// GET /bundles — list bundles
|
||||
group.MapGet("/bundles", Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out _, out var failure))
|
||||
return Task.FromResult(failure!);
|
||||
|
||||
List<FederationBundleSummary> summaries;
|
||||
lock (_bundleLock)
|
||||
{
|
||||
summaries = _bundles.Select(b => new FederationBundleSummary(
|
||||
b.Id, b.SourceSiteId,
|
||||
b.Aggregation.Buckets.Count,
|
||||
b.Aggregation.SuppressedBuckets,
|
||||
b.Aggregation.EpsilonSpent,
|
||||
Verified: true,
|
||||
b.CreatedAt)).ToList();
|
||||
}
|
||||
|
||||
return Task.FromResult(Results.Ok(summaries));
|
||||
})
|
||||
.WithName("ListFederationBundles")
|
||||
.WithSummary("List federation telemetry bundles")
|
||||
.RequireAuthorization(PlatformPolicies.FederationRead);
|
||||
|
||||
// GET /bundles/{id} — bundle detail
|
||||
group.MapGet("/bundles/{id:guid}", async Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IFederatedTelemetryBundleBuilder bundleBuilder,
|
||||
Guid id,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out _, out var failure))
|
||||
return failure!;
|
||||
|
||||
FederatedBundle? bundle;
|
||||
lock (_bundleLock) { bundle = _bundles.FirstOrDefault(b => b.Id == id); }
|
||||
|
||||
if (bundle is null)
|
||||
return Results.NotFound(new { error = "bundle_not_found", id });
|
||||
|
||||
var verified = await bundleBuilder.VerifyAsync(bundle, ct).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new FederationBundleDetailResponse(
|
||||
bundle.Id, bundle.SourceSiteId,
|
||||
bundle.Aggregation.TotalFacts,
|
||||
bundle.Aggregation.Buckets.Count,
|
||||
bundle.Aggregation.SuppressedBuckets,
|
||||
bundle.Aggregation.EpsilonSpent,
|
||||
bundle.ConsentDsseDigest,
|
||||
bundle.BundleDsseDigest,
|
||||
verified,
|
||||
bundle.Aggregation.AggregatedAt,
|
||||
bundle.CreatedAt,
|
||||
bundle.Aggregation.Buckets.Select(b => new FederationBucketDetail(
|
||||
b.CveId, b.ObservationCount, b.ArtifactCount, b.NoisyCount, b.Suppressed)).ToList()));
|
||||
})
|
||||
.WithName("GetFederationBundle")
|
||||
.WithSummary("Get federation telemetry bundle detail")
|
||||
.RequireAuthorization(PlatformPolicies.FederationRead);
|
||||
|
||||
// GET /intelligence — exploit corpus
|
||||
group.MapGet("/intelligence", async Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IExploitIntelligenceMerger intelligenceMerger,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out _, out var failure))
|
||||
return failure!;
|
||||
|
||||
var corpus = await intelligenceMerger.GetCorpusAsync(ct).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new FederationIntelligenceResponse(
|
||||
corpus.Entries.Select(e => new FederationIntelligenceEntry(
|
||||
e.CveId, e.SourceSiteId, e.ObservationCount, e.NoisyCount, e.ArtifactCount, e.ObservedAt)).ToList(),
|
||||
corpus.TotalEntries,
|
||||
corpus.UniqueCves,
|
||||
corpus.ContributingSites,
|
||||
corpus.LastUpdated));
|
||||
})
|
||||
.WithName("GetFederationIntelligence")
|
||||
.WithSummary("Get shared exploit intelligence corpus")
|
||||
.RequireAuthorization(PlatformPolicies.FederationRead);
|
||||
|
||||
// GET /privacy-budget — budget snapshot
|
||||
group.MapGet("/privacy-budget", Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IPrivacyBudgetTracker budgetTracker) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out _, out var failure))
|
||||
return Task.FromResult(failure!);
|
||||
|
||||
var snapshot = budgetTracker.GetSnapshot();
|
||||
|
||||
return Task.FromResult(Results.Ok(new FederationPrivacyBudgetResponse(
|
||||
snapshot.Remaining, snapshot.Total, snapshot.Exhausted,
|
||||
snapshot.PeriodStart, snapshot.NextReset,
|
||||
snapshot.QueriesThisPeriod, snapshot.SuppressedThisPeriod)));
|
||||
})
|
||||
.WithName("GetFederationPrivacyBudget")
|
||||
.WithSummary("Get privacy budget snapshot")
|
||||
.RequireAuthorization(PlatformPolicies.FederationRead);
|
||||
|
||||
// POST /trigger — trigger aggregation
|
||||
group.MapPost("/trigger", Task<IResult>(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IPrivacyBudgetTracker budgetTracker) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out _, out var failure))
|
||||
return Task.FromResult(failure!);
|
||||
|
||||
if (budgetTracker.IsBudgetExhausted)
|
||||
{
|
||||
return Task.FromResult(Results.Ok(new FederationTriggerResponse(
|
||||
Triggered: false,
|
||||
Reason: "Privacy budget exhausted")));
|
||||
}
|
||||
|
||||
// Placeholder: actual implementation would trigger sync service
|
||||
return Task.FromResult(Results.Ok(new FederationTriggerResponse(
|
||||
Triggered: true,
|
||||
Reason: null)));
|
||||
})
|
||||
.WithName("TriggerFederationAggregation")
|
||||
.WithSummary("Trigger manual federation aggregation cycle")
|
||||
.RequireAuthorization(PlatformPolicies.FederationManage);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user