compose and authority fixes. finish sprints.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.Policy.RiskProfile.Export;
|
||||
@@ -15,7 +16,7 @@ internal static class ProfileExportEndpoints
|
||||
public static IEndpointRouteBuilder MapProfileExport(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/risk/profiles/export")
|
||||
.RequireAuthorization()
|
||||
.RequireAuthorization(policy => policy.Requirements.Add(new StellaOpsScopeRequirement(new[] { StellaOpsScopes.PolicyRead })))
|
||||
.WithTags("Profile Export/Import");
|
||||
|
||||
group.MapPost("/", ExportProfiles)
|
||||
@@ -30,7 +31,7 @@ internal static class ProfileExportEndpoints
|
||||
.Produces<FileContentHttpResult>(StatusCodes.Status200OK, contentType: "application/json");
|
||||
|
||||
endpoints.MapPost("/api/risk/profiles/import", ImportProfiles)
|
||||
.RequireAuthorization()
|
||||
.RequireAuthorization(policy => policy.Requirements.Add(new StellaOpsScopeRequirement(new[] { StellaOpsScopes.PolicyEdit })))
|
||||
.WithName("ImportProfiles")
|
||||
.WithSummary("Import risk profiles from a signed bundle.")
|
||||
.WithTags("Profile Export/Import")
|
||||
@@ -38,7 +39,7 @@ internal static class ProfileExportEndpoints
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest);
|
||||
|
||||
endpoints.MapPost("/api/risk/profiles/verify", VerifyBundle)
|
||||
.RequireAuthorization()
|
||||
.RequireAuthorization(policy => policy.Requirements.Add(new StellaOpsScopeRequirement(new[] { StellaOpsScopes.PolicyRead })))
|
||||
.WithName("VerifyProfileBundle")
|
||||
.WithSummary("Verify the signature of a profile bundle without importing.")
|
||||
.WithTags("Profile Export/Import")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.Policy.RiskProfile.Lifecycle;
|
||||
using StellaOps.Policy.RiskProfile.Models;
|
||||
@@ -15,7 +16,7 @@ internal static class RiskProfileEndpoints
|
||||
public static IEndpointRouteBuilder MapRiskProfiles(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/risk/profiles")
|
||||
.RequireAuthorization()
|
||||
.RequireAuthorization(policy => policy.Requirements.Add(new StellaOpsScopeRequirement(new[] { StellaOpsScopes.PolicyRead })))
|
||||
.WithTags("Risk Profiles");
|
||||
|
||||
group.MapGet(string.Empty, ListProfiles)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Policy.RiskProfile.Schema;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -19,14 +21,15 @@ internal static class RiskProfileSchemaEndpoints
|
||||
.WithTags("Schema Discovery")
|
||||
.Produces<string>(StatusCodes.Status200OK, contentType: JsonSchemaMediaType)
|
||||
.Produces(StatusCodes.Status304NotModified)
|
||||
.RequireAuthorization();
|
||||
.RequireAuthorization(policy => policy.Requirements.Add(new StellaOpsScopeRequirement(new[] { StellaOpsScopes.PolicyRead })));
|
||||
|
||||
endpoints.MapPost("/api/risk/schema/validate", ValidateProfile)
|
||||
.WithName("ValidateRiskProfile")
|
||||
.WithSummary("Validate a risk profile document against the schema.")
|
||||
.WithTags("Schema Validation")
|
||||
.Produces<RiskProfileValidationResponse>(StatusCodes.Status200OK)
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest);
|
||||
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest)
|
||||
.RequireAuthorization(policy => policy.Requirements.Add(new StellaOpsScopeRequirement(new[] { StellaOpsScopes.PolicyRead })));
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NetEscapades.Configuration.Yaml;
|
||||
using StellaOps.AirGap.Policy;
|
||||
@@ -289,7 +290,29 @@ builder.Services.AddAuthorization();
|
||||
builder.Services.AddStellaOpsScopeHandler();
|
||||
builder.Services.AddStellaOpsResourceServerAuthentication(
|
||||
builder.Configuration,
|
||||
configurationSection: $"{PolicyEngineOptions.SectionName}:ResourceServer");
|
||||
configurationSection: $"{PolicyEngineOptions.SectionName}:ResourceServer",
|
||||
configure: resourceOptions =>
|
||||
{
|
||||
// IConfiguration binder does not always clear default list values.
|
||||
// When local compose sets Audiences to an empty value, explicitly clear
|
||||
// the audience list so no-aud local tokens can be validated.
|
||||
var audiences = builder.Configuration
|
||||
.GetSection($"{PolicyEngineOptions.SectionName}:ResourceServer:Audiences")
|
||||
.Get<string[]>();
|
||||
if (audiences is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
resourceOptions.Audiences.Clear();
|
||||
foreach (var audience in audiences)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(audience))
|
||||
{
|
||||
resourceOptions.Audiences.Add(audience.Trim());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Accept self-signed certificates when HTTPS metadata validation is disabled (dev/Docker)
|
||||
if (!bootstrap.Options.ResourceServer.RequireHttpsMetadata)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -26,6 +27,7 @@ using StellaOps.Policy.Snapshots;
|
||||
using StellaOps.Policy.ToolLattice;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -200,7 +202,29 @@ builder.Services.AddSingleton<IToolAccessEvaluator, ToolAccessEvaluator>();
|
||||
|
||||
builder.Services.AddStellaOpsResourceServerAuthentication(
|
||||
builder.Configuration,
|
||||
configurationSection: $"{PolicyGatewayOptions.SectionName}:ResourceServer");
|
||||
configurationSection: $"{PolicyGatewayOptions.SectionName}:ResourceServer",
|
||||
configure: resourceOptions =>
|
||||
{
|
||||
// IConfiguration binder does not always clear default list values.
|
||||
// When local compose sets Audiences to an empty value, explicitly clear
|
||||
// the audience list so no-aud local tokens can be validated.
|
||||
var audiences = builder.Configuration
|
||||
.GetSection($"{PolicyGatewayOptions.SectionName}:ResourceServer:Audiences")
|
||||
.Get<string[]>();
|
||||
if (audiences is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
resourceOptions.Audiences.Clear();
|
||||
foreach (var audience in audiences)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(audience))
|
||||
{
|
||||
resourceOptions.Audiences.Add(audience.Trim());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Accept self-signed certificates when HTTPS metadata validation is disabled (dev/Docker)
|
||||
if (!bootstrap.Options.ResourceServer.RequireHttpsMetadata)
|
||||
@@ -258,6 +282,11 @@ if (bootstrap.Options.PolicyEngine.ClientCredentials.Enabled)
|
||||
.AddPolicyHandler(static (provider, _) => CreateAuthorityRetryPolicy(provider))
|
||||
.AddHttpMessageHandler<PolicyGatewayDpopHandler>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep DI graph valid when client credentials are disabled.
|
||||
builder.Services.AddSingleton<IStellaOpsTokenClient, DisabledStellaOpsTokenClient>();
|
||||
}
|
||||
|
||||
builder.Services.AddHttpClient<IPolicyEngineClient, PolicyEngineClient>((serviceProvider, client) =>
|
||||
{
|
||||
@@ -295,6 +324,23 @@ app.MapGet("/readyz", () => Results.Ok(new { status = "ready" }))
|
||||
|
||||
app.MapGet("/", () => Results.Redirect("/healthz"));
|
||||
|
||||
app.MapGet("/api/policy/quota", ([FromServices] TimeProvider timeProvider) =>
|
||||
{
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var resetAt = now.Date.AddDays(1).ToString("O", CultureInfo.InvariantCulture);
|
||||
return Results.Ok(new
|
||||
{
|
||||
simulationsPerDay = 1000,
|
||||
simulationsUsed = 0,
|
||||
evaluationsPerDay = 5000,
|
||||
evaluationsUsed = 0,
|
||||
resetAt
|
||||
});
|
||||
})
|
||||
.WithTags("Policy Quota")
|
||||
.WithName("PolicyQuota.Get")
|
||||
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.PolicyRead));
|
||||
|
||||
var policyPacks = app.MapGroup("/api/policy/packs")
|
||||
.WithTags("Policy Packs");
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using StellaOps.Auth.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Policy.Gateway.Services;
|
||||
|
||||
internal sealed class DisabledStellaOpsTokenClient : IStellaOpsTokenClient
|
||||
{
|
||||
private const string DisabledMessage = "Policy Engine client credentials are disabled.";
|
||||
|
||||
public Task<StellaOpsTokenResult> RequestPasswordTokenAsync(
|
||||
string username,
|
||||
string password,
|
||||
string? scope = null,
|
||||
IReadOnlyDictionary<string, string>? additionalParameters = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> Task.FromException<StellaOpsTokenResult>(new InvalidOperationException(DisabledMessage));
|
||||
|
||||
public Task<StellaOpsTokenResult> RequestClientCredentialsTokenAsync(
|
||||
string? scope = null,
|
||||
IReadOnlyDictionary<string, string>? additionalParameters = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> Task.FromException<StellaOpsTokenResult>(new InvalidOperationException(DisabledMessage));
|
||||
|
||||
public Task<JsonWebKeySet> GetJsonWebKeySetAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromException<JsonWebKeySet>(new InvalidOperationException(DisabledMessage));
|
||||
|
||||
public ValueTask<StellaOpsTokenCacheEntry?> GetCachedTokenAsync(string key, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult<StellaOpsTokenCacheEntry?>(null);
|
||||
|
||||
public ValueTask CacheTokenAsync(string key, StellaOpsTokenCacheEntry entry, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.CompletedTask;
|
||||
|
||||
public ValueTask ClearCachedTokenAsync(string key, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
@@ -88,11 +88,22 @@ internal sealed class PolicyEngineTokenProvider
|
||||
}
|
||||
|
||||
var scopeString = BuildScopeClaim(options);
|
||||
var result = await tokenClient.RequestClientCredentialsTokenAsync(scopeString, null, cancellationToken).ConfigureAwait(false);
|
||||
var expiresAt = result.ExpiresAtUtc;
|
||||
cachedToken = new CachedToken(result.AccessToken, string.IsNullOrWhiteSpace(result.TokenType) ? "Bearer" : result.TokenType, expiresAt);
|
||||
logger.LogInformation("Issued Policy Engine client credentials token; expires at {ExpiresAt:o}.", expiresAt);
|
||||
return cachedToken;
|
||||
try
|
||||
{
|
||||
var result = await tokenClient.RequestClientCredentialsTokenAsync(scopeString, null, cancellationToken).ConfigureAwait(false);
|
||||
var expiresAt = result.ExpiresAtUtc;
|
||||
cachedToken = new CachedToken(result.AccessToken, string.IsNullOrWhiteSpace(result.TokenType) ? "Bearer" : result.TokenType, expiresAt);
|
||||
logger.LogInformation("Issued Policy Engine client credentials token; expires at {ExpiresAt:o}.", expiresAt);
|
||||
return cachedToken;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(
|
||||
ex,
|
||||
"Unable to issue Policy Engine client credentials token for scopes '{Scopes}'.",
|
||||
scopeString);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user