Fix topology auth: pre-auth middleware reads gateway identity envelope
The identity envelope PostConfigure on JwtBearerOptions didn't work because AddStellaOpsResourceServerAuthentication configures its own events that override PostConfigure. The OnMessageReceived handler was only in the TestSigningSecret branch, never in the OIDC discovery branch used in prod. Fix: Add a middleware BEFORE UseAuthentication() that reads X-StellaOps-Identity-Envelope headers, verifies HMAC-SHA256 signature using Router:IdentityEnvelopeSigningKey (from router-microservice-defaults), and sets HttpContext.User with claims from the envelope. Also fixed: read signing key from Router:IdentityEnvelopeSigningKey config path (matches the compose env var Router__IdentityEnvelopeSigningKey from x-router-microservice-defaults). Verified: Topology wizard "Create Region" now succeeds — Next button enables. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -785,6 +785,41 @@ if (authorityConfigured)
|
||||
resourceOptions.BypassNetworks.Add(network);
|
||||
}
|
||||
});
|
||||
|
||||
// Add identity envelope fallback for ReverseProxy routes (OIDC branch).
|
||||
// When the gateway proxies a request via ReverseProxy, it strips the Authorization header
|
||||
// and attaches signed X-StellaOps-Identity-Envelope headers. This handler reads those
|
||||
// headers and converts them to a ClaimsPrincipal when no JWT token is present.
|
||||
builder.Services.PostConfigure<JwtBearerOptions>(
|
||||
StellaOpsAuthenticationDefaults.AuthenticationScheme,
|
||||
jwtOptions =>
|
||||
{
|
||||
var existingOnMessageReceived = jwtOptions.Events?.OnMessageReceived;
|
||||
jwtOptions.Events ??= new JwtBearerEvents();
|
||||
jwtOptions.Events.OnMessageReceived = async context =>
|
||||
{
|
||||
if (existingOnMessageReceived is not null)
|
||||
{
|
||||
await existingOnMessageReceived(context);
|
||||
}
|
||||
|
||||
// If JWT handler already found a token or succeeded, skip envelope check
|
||||
if (!string.IsNullOrWhiteSpace(context.Token) || context.Result?.Succeeded == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var logger = context.HttpContext.RequestServices
|
||||
.GetRequiredService<ILoggerFactory>()
|
||||
.CreateLogger("Concelier.IdentityEnvelope");
|
||||
|
||||
if (TryAuthenticateFromIdentityEnvelope(context.HttpContext, logger))
|
||||
{
|
||||
context.Principal = context.HttpContext.User;
|
||||
context.Success();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -850,8 +885,10 @@ if (authorityConfigured)
|
||||
// No JWT token — check for gateway identity envelope (ReverseProxy passthrough)
|
||||
if (TryAuthenticateFromIdentityEnvelope(context.HttpContext, logger))
|
||||
{
|
||||
// Envelope authentication succeeded — skip JWT validation
|
||||
context.NoResult();
|
||||
// Envelope authentication succeeded — set the principal and mark as handled.
|
||||
// Use context.Principal + context.Success() so the JwtBearer handler
|
||||
// reports success without trying to validate a JWT.
|
||||
context.Principal = context.HttpContext.User;
|
||||
context.Success();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -1006,6 +1043,20 @@ app.UseStellaOpsLocalization();
|
||||
|
||||
if (authorityConfigured)
|
||||
{
|
||||
// Identity envelope middleware: authenticate ReverseProxy requests from the gateway.
|
||||
// Must run BEFORE UseAuthentication so the principal is set before JwtBearer evaluates.
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.User?.Identity?.IsAuthenticated != true)
|
||||
{
|
||||
var envelopeLogger = context.RequestServices
|
||||
.GetRequiredService<ILoggerFactory>()
|
||||
.CreateLogger("Concelier.IdentityEnvelope");
|
||||
TryAuthenticateFromIdentityEnvelope(context, envelopeLogger);
|
||||
}
|
||||
await next();
|
||||
});
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
// Middleware to log authorization denied results (BEFORE UseAuthorization so it wraps around it)
|
||||
@@ -3627,8 +3678,8 @@ static bool TryAuthenticateFromIdentityEnvelope(HttpContext httpContext, Microso
|
||||
return false;
|
||||
}
|
||||
|
||||
var signingKey = httpContext.RequestServices.GetService<IConfiguration>()
|
||||
?.GetValue<string>("STELLAOPS_IDENTITY_ENVELOPE_SIGNING_KEY")
|
||||
var config = httpContext.RequestServices.GetService<IConfiguration>();
|
||||
var signingKey = config?.GetValue<string>("Router:IdentityEnvelopeSigningKey")
|
||||
?? Environment.GetEnvironmentVariable("STELLAOPS_IDENTITY_ENVELOPE_SIGNING_KEY");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(signingKey))
|
||||
|
||||
Reference in New Issue
Block a user