Merge branch 'main' of https://git.stella-ops.org/stella-ops.org/git.stella-ops.org
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-27 21:45:32 +02:00
510 changed files with 138401 additions and 51276 deletions

View File

@@ -1,227 +1,362 @@
using System.Collections.ObjectModel;
using StellaOps.Auth.Abstractions;
namespace StellaOps.Policy.Engine.Options;
/// <summary>
/// Root configuration for the Policy Engine host.
/// </summary>
public sealed class PolicyEngineOptions
{
public const string SectionName = "PolicyEngine";
public PolicyEngineAuthorityOptions Authority { get; } = new();
public PolicyEngineStorageOptions Storage { get; } = new();
public PolicyEngineWorkerOptions Workers { get; } = new();
public PolicyEngineResourceServerOptions ResourceServer { get; } = new();
public PolicyEngineCompilationOptions Compilation { get; } = new();
public PolicyEngineActivationOptions Activation { get; } = new();
public void Validate()
{
Authority.Validate();
Storage.Validate();
Workers.Validate();
ResourceServer.Validate();
Compilation.Validate();
Activation.Validate();
}
}
public sealed class PolicyEngineAuthorityOptions
{
public bool Enabled { get; set; } = true;
public string Issuer { get; set; } = "https://authority.stella-ops.local";
public string ClientId { get; set; } = "policy-engine";
public string? ClientSecret { get; set; }
public IList<string> Scopes { get; } = new List<string>
{
StellaOpsScopes.PolicyRun,
StellaOpsScopes.FindingsRead,
StellaOpsScopes.EffectiveWrite
};
public int BackchannelTimeoutSeconds { get; set; } = 30;
public void Validate()
{
if (!Enabled)
{
return;
}
if (string.IsNullOrWhiteSpace(Issuer))
{
throw new InvalidOperationException("Policy Engine authority configuration requires an issuer.");
}
if (!Uri.TryCreate(Issuer, UriKind.Absolute, out var issuerUri) || !issuerUri.IsAbsoluteUri)
{
throw new InvalidOperationException("Policy Engine authority issuer must be an absolute URI.");
}
if (issuerUri.Scheme != Uri.UriSchemeHttps && !issuerUri.IsLoopback)
{
throw new InvalidOperationException("Policy Engine authority issuer must use HTTPS unless targeting loopback.");
}
if (string.IsNullOrWhiteSpace(ClientId))
{
throw new InvalidOperationException("Policy Engine authority configuration requires a clientId.");
}
if (Scopes.Count == 0)
{
throw new InvalidOperationException("Policy Engine authority configuration requires at least one scope.");
}
if (BackchannelTimeoutSeconds <= 0)
{
throw new InvalidOperationException("Policy Engine authority backchannel timeout must be greater than zero.");
}
}
}
public sealed class PolicyEngineStorageOptions
{
public string ConnectionString { get; set; } = "mongodb://localhost:27017/policy-engine";
public string DatabaseName { get; set; } = "policy_engine";
public int CommandTimeoutSeconds { get; set; } = 30;
public void Validate()
{
if (string.IsNullOrWhiteSpace(ConnectionString))
{
throw new InvalidOperationException("Policy Engine storage configuration requires a MongoDB connection string.");
}
if (string.IsNullOrWhiteSpace(DatabaseName))
{
throw new InvalidOperationException("Policy Engine storage configuration requires a database name.");
}
if (CommandTimeoutSeconds <= 0)
{
throw new InvalidOperationException("Policy Engine storage command timeout must be greater than zero.");
}
}
public TimeSpan CommandTimeout => TimeSpan.FromSeconds(CommandTimeoutSeconds);
}
public sealed class PolicyEngineWorkerOptions
{
public int SchedulerIntervalSeconds { get; set; } = 15;
public int MaxConcurrentEvaluations { get; set; } = 4;
public void Validate()
{
if (SchedulerIntervalSeconds <= 0)
{
throw new InvalidOperationException("Policy Engine worker interval must be greater than zero.");
}
if (MaxConcurrentEvaluations <= 0)
{
throw new InvalidOperationException("Policy Engine worker concurrency must be greater than zero.");
}
}
}
public sealed class PolicyEngineResourceServerOptions
{
public string Authority { get; set; } = "https://authority.stella-ops.local";
public IList<string> Audiences { get; } = new List<string> { "api://policy-engine" };
public IList<string> RequiredScopes { get; } = new List<string> { StellaOpsScopes.PolicyRun };
public IList<string> RequiredTenants { get; } = new List<string>();
public IList<string> BypassNetworks { get; } = new List<string> { "127.0.0.1/32", "::1/128" };
public bool RequireHttpsMetadata { get; set; } = true;
public void Validate()
{
if (string.IsNullOrWhiteSpace(Authority))
{
throw new InvalidOperationException("Resource server configuration requires an Authority URL.");
}
if (!Uri.TryCreate(Authority.Trim(), UriKind.Absolute, out var uri))
{
throw new InvalidOperationException("Resource server Authority URL must be absolute.");
}
if (RequireHttpsMetadata && !uri.IsLoopback && !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Resource server Authority URL must use HTTPS when HTTPS metadata is required.");
}
}
}
public sealed class PolicyEngineCompilationOptions
{
/// <summary>
/// Maximum allowed complexity score for compiled policies. Set to <c>&lt;= 0</c> to disable.
/// </summary>
public double MaxComplexityScore { get; set; } = 750d;
/// <summary>
/// Maximum allowed compilation wall-clock duration in milliseconds. Set to <c>&lt;= 0</c> to disable.
/// </summary>
public int MaxDurationMilliseconds { get; set; } = 1500;
public bool EnforceComplexity => MaxComplexityScore > 0;
public bool EnforceDuration => MaxDurationMilliseconds > 0;
public void Validate()
{
if (MaxComplexityScore < 0)
{
throw new InvalidOperationException("Compilation.maxComplexityScore must be greater than or equal to zero.");
}
if (MaxDurationMilliseconds < 0)
{
throw new InvalidOperationException("Compilation.maxDurationMilliseconds must be greater than or equal to zero.");
}
}
}
public sealed class PolicyEngineActivationOptions
{
/// <summary>
/// Forces two distinct approvals for every activation regardless of the request payload.
/// </summary>
public bool ForceTwoPersonApproval { get; set; } = false;
/// <summary>
/// Default value applied when callers omit <c>requiresTwoPersonApproval</c>.
/// </summary>
public bool DefaultRequiresTwoPersonApproval { get; set; } = false;
/// <summary>
/// Emits structured audit logs for every activation attempt.
/// </summary>
public bool EmitAuditLogs { get; set; } = true;
public void Validate()
{
}
}
using System.Collections.ObjectModel;
using StellaOps.Auth.Abstractions;
using StellaOps.Policy.Engine.Telemetry;
namespace StellaOps.Policy.Engine.Options;
/// <summary>
/// Root configuration for the Policy Engine host.
/// </summary>
public sealed class PolicyEngineOptions
{
public const string SectionName = "PolicyEngine";
public PolicyEngineAuthorityOptions Authority { get; } = new();
public PolicyEngineStorageOptions Storage { get; } = new();
public PolicyEngineWorkerOptions Workers { get; } = new();
public PolicyEngineResourceServerOptions ResourceServer { get; } = new();
public PolicyEngineCompilationOptions Compilation { get; } = new();
public PolicyEngineActivationOptions Activation { get; } = new();
public PolicyEngineTelemetryOptions Telemetry { get; } = new();
public PolicyEngineRiskProfileOptions RiskProfile { get; } = new();
public void Validate()
{
Authority.Validate();
Storage.Validate();
Workers.Validate();
ResourceServer.Validate();
Compilation.Validate();
Activation.Validate();
Telemetry.Validate();
RiskProfile.Validate();
}
}
public sealed class PolicyEngineAuthorityOptions
{
public bool Enabled { get; set; } = true;
public string Issuer { get; set; } = "https://authority.stella-ops.local";
public string ClientId { get; set; } = "policy-engine";
public string? ClientSecret { get; set; }
public IList<string> Scopes { get; } = new List<string>
{
StellaOpsScopes.PolicyRun,
StellaOpsScopes.FindingsRead,
StellaOpsScopes.EffectiveWrite
};
public int BackchannelTimeoutSeconds { get; set; } = 30;
public void Validate()
{
if (!Enabled)
{
return;
}
if (string.IsNullOrWhiteSpace(Issuer))
{
throw new InvalidOperationException("Policy Engine authority configuration requires an issuer.");
}
if (!Uri.TryCreate(Issuer, UriKind.Absolute, out var issuerUri) || !issuerUri.IsAbsoluteUri)
{
throw new InvalidOperationException("Policy Engine authority issuer must be an absolute URI.");
}
if (issuerUri.Scheme != Uri.UriSchemeHttps && !issuerUri.IsLoopback)
{
throw new InvalidOperationException("Policy Engine authority issuer must use HTTPS unless targeting loopback.");
}
if (string.IsNullOrWhiteSpace(ClientId))
{
throw new InvalidOperationException("Policy Engine authority configuration requires a clientId.");
}
if (Scopes.Count == 0)
{
throw new InvalidOperationException("Policy Engine authority configuration requires at least one scope.");
}
if (BackchannelTimeoutSeconds <= 0)
{
throw new InvalidOperationException("Policy Engine authority backchannel timeout must be greater than zero.");
}
}
}
public sealed class PolicyEngineStorageOptions
{
public string ConnectionString { get; set; } = "mongodb://localhost:27017/policy-engine";
public string DatabaseName { get; set; } = "policy_engine";
public int CommandTimeoutSeconds { get; set; } = 30;
public void Validate()
{
if (string.IsNullOrWhiteSpace(ConnectionString))
{
throw new InvalidOperationException("Policy Engine storage configuration requires a MongoDB connection string.");
}
if (string.IsNullOrWhiteSpace(DatabaseName))
{
throw new InvalidOperationException("Policy Engine storage configuration requires a database name.");
}
if (CommandTimeoutSeconds <= 0)
{
throw new InvalidOperationException("Policy Engine storage command timeout must be greater than zero.");
}
}
public TimeSpan CommandTimeout => TimeSpan.FromSeconds(CommandTimeoutSeconds);
}
public sealed class PolicyEngineWorkerOptions
{
public int SchedulerIntervalSeconds { get; set; } = 15;
public int MaxConcurrentEvaluations { get; set; } = 4;
public void Validate()
{
if (SchedulerIntervalSeconds <= 0)
{
throw new InvalidOperationException("Policy Engine worker interval must be greater than zero.");
}
if (MaxConcurrentEvaluations <= 0)
{
throw new InvalidOperationException("Policy Engine worker concurrency must be greater than zero.");
}
}
}
public sealed class PolicyEngineResourceServerOptions
{
public string Authority { get; set; } = "https://authority.stella-ops.local";
public IList<string> Audiences { get; } = new List<string> { "api://policy-engine" };
public IList<string> RequiredScopes { get; } = new List<string> { StellaOpsScopes.PolicyRun };
public IList<string> RequiredTenants { get; } = new List<string>();
public IList<string> BypassNetworks { get; } = new List<string> { "127.0.0.1/32", "::1/128" };
public bool RequireHttpsMetadata { get; set; } = true;
public void Validate()
{
if (string.IsNullOrWhiteSpace(Authority))
{
throw new InvalidOperationException("Resource server configuration requires an Authority URL.");
}
if (!Uri.TryCreate(Authority.Trim(), UriKind.Absolute, out var uri))
{
throw new InvalidOperationException("Resource server Authority URL must be absolute.");
}
if (RequireHttpsMetadata && !uri.IsLoopback && !string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Resource server Authority URL must use HTTPS when HTTPS metadata is required.");
}
}
}
public sealed class PolicyEngineCompilationOptions
{
/// <summary>
/// Maximum allowed complexity score for compiled policies. Set to <c>&lt;= 0</c> to disable.
/// </summary>
public double MaxComplexityScore { get; set; } = 750d;
/// <summary>
/// Maximum allowed compilation wall-clock duration in milliseconds. Set to <c>&lt;= 0</c> to disable.
/// </summary>
public int MaxDurationMilliseconds { get; set; } = 1500;
public bool EnforceComplexity => MaxComplexityScore > 0;
public bool EnforceDuration => MaxDurationMilliseconds > 0;
public void Validate()
{
if (MaxComplexityScore < 0)
{
throw new InvalidOperationException("Compilation.maxComplexityScore must be greater than or equal to zero.");
}
if (MaxDurationMilliseconds < 0)
{
throw new InvalidOperationException("Compilation.maxDurationMilliseconds must be greater than or equal to zero.");
}
}
}
public sealed class PolicyEngineActivationOptions
{
/// <summary>
/// Forces two distinct approvals for every activation regardless of the request payload.
/// </summary>
public bool ForceTwoPersonApproval { get; set; } = false;
/// <summary>
/// Default value applied when callers omit <c>requiresTwoPersonApproval</c>.
/// </summary>
public bool DefaultRequiresTwoPersonApproval { get; set; } = false;
/// <summary>
/// Emits structured audit logs for every activation attempt.
/// </summary>
public bool EmitAuditLogs { get; set; } = true;
public void Validate()
{
}
}
public sealed class PolicyEngineRiskProfileOptions
{
/// <summary>
/// Enables risk profile integration for policy evaluation.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Default profile ID to use when no profile is specified.
/// </summary>
public string DefaultProfileId { get; set; } = "default";
/// <summary>
/// Directory containing risk profile JSON files.
/// </summary>
public string? ProfileDirectory { get; set; }
/// <summary>
/// Maximum inheritance depth for profile resolution.
/// </summary>
public int MaxInheritanceDepth { get; set; } = 10;
/// <summary>
/// Whether to validate profiles against the JSON schema on load.
/// </summary>
public bool ValidateOnLoad { get; set; } = true;
/// <summary>
/// Whether to cache resolved profiles in memory.
/// </summary>
public bool CacheResolvedProfiles { get; set; } = true;
/// <summary>
/// Inline profile definitions (for config-based profiles).
/// </summary>
public List<RiskProfileDefinition> Profiles { get; } = new();
public void Validate()
{
if (MaxInheritanceDepth <= 0)
{
throw new InvalidOperationException("RiskProfile.MaxInheritanceDepth must be greater than zero.");
}
if (string.IsNullOrWhiteSpace(DefaultProfileId))
{
throw new InvalidOperationException("RiskProfile.DefaultProfileId is required.");
}
}
}
/// <summary>
/// Inline risk profile definition in configuration.
/// </summary>
public sealed class RiskProfileDefinition
{
/// <summary>
/// Profile identifier.
/// </summary>
public required string Id { get; set; }
/// <summary>
/// Profile version (SemVer).
/// </summary>
public required string Version { get; set; }
/// <summary>
/// Human-readable description.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Parent profile ID for inheritance.
/// </summary>
public string? Extends { get; set; }
/// <summary>
/// Signal definitions for risk scoring.
/// </summary>
public List<RiskProfileSignalDefinition> Signals { get; } = new();
/// <summary>
/// Weight per signal name.
/// </summary>
public Dictionary<string, double> Weights { get; } = new();
/// <summary>
/// Optional metadata.
/// </summary>
public Dictionary<string, object?>? Metadata { get; set; }
}
/// <summary>
/// Inline signal definition in configuration.
/// </summary>
public sealed class RiskProfileSignalDefinition
{
/// <summary>
/// Signal name.
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Signal source.
/// </summary>
public required string Source { get; set; }
/// <summary>
/// Signal type (boolean, numeric, categorical).
/// </summary>
public required string Type { get; set; }
/// <summary>
/// JSON Pointer path in evidence.
/// </summary>
public string? Path { get; set; }
/// <summary>
/// Optional transform expression.
/// </summary>
public string? Transform { get; set; }
/// <summary>
/// Optional unit for numeric signals.
/// </summary>
public string? Unit { get; set; }
}