save changes

This commit is contained in:
master
2026-02-17 00:51:35 +02:00
parent 70fdbfcf25
commit fb46a927ad
324 changed files with 4976 additions and 1499 deletions

View File

@@ -39,13 +39,20 @@ public sealed class IdentityHeaderPolicyMiddleware
"X-Stella-Project",
"X-Stella-Actor",
"X-Stella-Scopes",
// Headers used by downstream services in header-based auth mode
"X-Scopes",
"X-Tenant-Id",
// Raw claim headers (internal/legacy pass-through)
"sub",
"tid",
"scope",
"scp",
"cnf",
"cnf.jkt"
"cnf.jkt",
// Auth headers consumed by the gateway — strip before proxying
// so backends trust identity headers instead of re-validating JWT.
"Authorization",
"DPoP"
];
public IdentityHeaderPolicyMiddleware(
@@ -91,8 +98,18 @@ public sealed class IdentityHeaderPolicyMiddleware
private void StripReservedHeaders(HttpContext context)
{
var preserveAuthHeaders = _options.JwtPassthroughPrefixes.Count > 0
&& _options.JwtPassthroughPrefixes.Any(prefix =>
context.Request.Path.StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase));
foreach (var header in ReservedHeaders)
{
// Preserve Authorization/DPoP for routes that need JWT pass-through
if (preserveAuthHeaders && (header == "Authorization" || header == "DPoP"))
{
continue;
}
if (context.Request.Headers.ContainsKey(header))
{
_logger.LogDebug(
@@ -114,7 +131,7 @@ public sealed class IdentityHeaderPolicyMiddleware
// In AllowAnonymous mode the Gateway cannot validate identity claims.
// Pass through the client-provided tenant so the upstream service
// can validate it against the JWT's own tenant claim.
var passThruTenant = !string.IsNullOrWhiteSpace(clientTenant) ? clientTenant.Trim() : null;
var passThruTenant = !string.IsNullOrWhiteSpace(clientTenant) ? clientTenant.Trim() : "default";
return new IdentityContext
{
@@ -192,9 +209,37 @@ public sealed class IdentityHeaderPolicyMiddleware
}
}
// Expand coarse OIDC scopes to fine-grained service scopes.
// This bridges the gap between Authority-registered scopes (e.g. "scheduler:read")
// and the fine-grained scopes that downstream services expect (e.g. "scheduler.runs.read").
ExpandCoarseScopes(scopes);
return scopes;
}
/// <summary>
/// Expands coarse OIDC scopes into fine-grained service scopes.
/// Pattern: "{service}:{action}" expands to "{service}.{resource}.{action}" for known resources.
/// </summary>
private static void ExpandCoarseScopes(HashSet<string> scopes)
{
// scheduler:read -> scheduler.schedules.read, scheduler.runs.read
// scheduler:operate -> scheduler.schedules.write, scheduler.runs.write, scheduler.runs.preview, scheduler.runs.manage
if (scopes.Contains("scheduler:read"))
{
scopes.Add("scheduler.schedules.read");
scopes.Add("scheduler.runs.read");
}
if (scopes.Contains("scheduler:operate"))
{
scopes.Add("scheduler.schedules.write");
scopes.Add("scheduler.runs.write");
scopes.Add("scheduler.runs.preview");
scopes.Add("scheduler.runs.manage");
}
}
private void StoreIdentityContext(HttpContext context, IdentityContext identity)
{
context.Items[GatewayContextKeys.IsAnonymous] = identity.IsAnonymous;
@@ -248,6 +293,7 @@ public sealed class IdentityHeaderPolicyMiddleware
if (!string.IsNullOrEmpty(identity.Tenant))
{
headers["X-StellaOps-Tenant"] = identity.Tenant;
headers["X-Tenant-Id"] = identity.Tenant;
if (_options.EnableLegacyHeaders)
{
headers["X-Stella-Tenant"] = identity.Tenant;
@@ -270,6 +316,7 @@ public sealed class IdentityHeaderPolicyMiddleware
var sortedScopes = identity.Scopes.OrderBy(s => s, StringComparer.Ordinal);
var scopesValue = string.Join(" ", sortedScopes);
headers["X-StellaOps-Scopes"] = scopesValue;
headers["X-Scopes"] = scopesValue;
if (_options.EnableLegacyHeaders)
{
headers["X-Stella-Scopes"] = scopesValue;
@@ -279,6 +326,7 @@ public sealed class IdentityHeaderPolicyMiddleware
{
// Explicit empty scopes for anonymous to prevent ambiguity
headers["X-StellaOps-Scopes"] = string.Empty;
headers["X-Scopes"] = string.Empty;
if (_options.EnableLegacyHeaders)
{
headers["X-Stella-Scopes"] = string.Empty;
@@ -347,4 +395,13 @@ public sealed class IdentityHeaderPolicyOptions
/// Default: false (forbidden for security).
/// </summary>
public bool AllowScopeHeaderOverride { get; set; } = false;
/// <summary>
/// Route prefixes where Authorization and DPoP headers should be preserved
/// (passed through to the upstream service) instead of stripped.
/// Use this for upstream services that require JWT validation themselves
/// (e.g., Authority admin API at /console).
/// Default: empty (strip auth headers for all routes).
/// </summary>
public List<string> JwtPassthroughPrefixes { get; set; } = [];
}