stela ops usage fixes roles propagation and timoeut, one account to support multi tenants, migrations consolidation, search to support documentation, doctor and open api vector db search
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
@@ -12,8 +13,6 @@ using StellaOps.Gateway.WebService.Middleware;
|
||||
using StellaOps.Gateway.WebService.Routing;
|
||||
using StellaOps.Gateway.WebService.Security;
|
||||
using StellaOps.Gateway.WebService.Services;
|
||||
using StellaOps.Messaging.DependencyInjection;
|
||||
using StellaOps.Messaging.Transport.Valkey;
|
||||
using StellaOps.Router.AspNet;
|
||||
using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Common.Models;
|
||||
@@ -25,11 +24,6 @@ using StellaOps.Router.Gateway.Middleware;
|
||||
using StellaOps.Router.Gateway.OpenApi;
|
||||
using StellaOps.Router.Gateway.RateLimit;
|
||||
using StellaOps.Router.Gateway.Routing;
|
||||
using StellaOps.Router.Transport.Messaging;
|
||||
using StellaOps.Router.Transport.Messaging.Options;
|
||||
using StellaOps.Router.Transport.Tcp;
|
||||
using StellaOps.Router.Transport.Tls;
|
||||
using System.Net;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -55,6 +49,25 @@ builder.Services.AddRouterGatewayCore();
|
||||
builder.Services.AddRouterRateLimiting(builder.Configuration);
|
||||
|
||||
builder.Services.AddSingleton<IEffectiveClaimsStore, EffectiveClaimsStore>();
|
||||
builder.Services.AddSingleton<StellaOps.Router.Gateway.Authorization.IEffectiveClaimsStore>(sp =>
|
||||
sp.GetRequiredService<IEffectiveClaimsStore>());
|
||||
|
||||
var authorityClaimsUrl = ResolveAuthorityClaimsUrl(bootstrapOptions.Auth.Authority);
|
||||
StellaOps.Router.Gateway.Authorization.AuthorizationServiceCollectionExtensions.AddAuthorityIntegration(
|
||||
builder.Services,
|
||||
options =>
|
||||
{
|
||||
options.Enabled = !string.IsNullOrWhiteSpace(authorityClaimsUrl);
|
||||
options.AuthorityUrl = authorityClaimsUrl ?? string.Empty;
|
||||
options.RefreshInterval = TimeSpan.FromSeconds(30);
|
||||
options.WaitForAuthorityOnStartup = false;
|
||||
options.StartupTimeout = TimeSpan.FromSeconds(10);
|
||||
options.UseAuthorityPushNotifications = false;
|
||||
});
|
||||
|
||||
builder.Services.Replace(ServiceDescriptor.Singleton<StellaOps.Router.Gateway.Authorization.IEffectiveClaimsStore>(
|
||||
sp => sp.GetRequiredService<IEffectiveClaimsStore>()));
|
||||
|
||||
builder.Services.AddSingleton<GatewayServiceStatus>();
|
||||
builder.Services.AddSingleton<GatewayMetrics>();
|
||||
|
||||
@@ -62,55 +75,35 @@ builder.Services.AddSingleton<GatewayMetrics>();
|
||||
var transportPluginLoader = new RouterTransportPluginLoader(
|
||||
NullLoggerFactory.Instance.CreateLogger<RouterTransportPluginLoader>());
|
||||
|
||||
// Try to load from plugins directory, fallback to direct registration if not found
|
||||
var pluginsPath = Path.Combine(AppContext.BaseDirectory, "plugins", "router", "transports");
|
||||
if (Directory.Exists(pluginsPath))
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(assembly =>
|
||||
assembly.GetName().Name?.StartsWith("StellaOps.Router.Transport.", StringComparison.OrdinalIgnoreCase) == true))
|
||||
{
|
||||
transportPluginLoader.LoadFromDirectory(pluginsPath);
|
||||
transportPluginLoader.LoadFromAssembly(assembly);
|
||||
}
|
||||
|
||||
// Register TCP and TLS transports (from plugins or fallback to compile-time references)
|
||||
var tcpPlugin = transportPluginLoader.GetPlugin("tcp");
|
||||
var tlsPlugin = transportPluginLoader.GetPlugin("tls");
|
||||
|
||||
if (tcpPlugin is not null)
|
||||
var pluginsPath = builder.Configuration["Gateway:TransportPlugins:Directory"];
|
||||
if (string.IsNullOrWhiteSpace(pluginsPath))
|
||||
{
|
||||
tcpPlugin.Register(new RouterTransportRegistrationContext(
|
||||
builder.Services, builder.Configuration, RouterTransportMode.Server)
|
||||
{
|
||||
ConfigurationSection = "Gateway:Transports:Tcp"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to compile-time registration
|
||||
builder.Services.AddTcpTransportServer();
|
||||
pluginsPath = Path.Combine(AppContext.BaseDirectory, "plugins", "router", "transports");
|
||||
}
|
||||
|
||||
if (tlsPlugin is not null)
|
||||
var transportSearchPattern = builder.Configuration["Gateway:TransportPlugins:SearchPattern"];
|
||||
if (string.IsNullOrWhiteSpace(transportSearchPattern))
|
||||
{
|
||||
tlsPlugin.Register(new RouterTransportRegistrationContext(
|
||||
builder.Services, builder.Configuration, RouterTransportMode.Server)
|
||||
{
|
||||
ConfigurationSection = "Gateway:Transports:Tls"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to compile-time registration
|
||||
builder.Services.AddTlsTransportServer();
|
||||
transportSearchPattern = "StellaOps.Router.Transport.*.dll";
|
||||
}
|
||||
|
||||
// Messaging transport (Valkey)
|
||||
if (bootstrapOptions.Transports.Messaging.Enabled)
|
||||
{
|
||||
builder.Services.AddMessagingTransport<ValkeyTransportPlugin>(builder.Configuration, "Gateway:Transports:Messaging");
|
||||
builder.Services.AddMessagingTransportServer();
|
||||
}
|
||||
transportPluginLoader.LoadFromDirectory(pluginsPath, transportSearchPattern);
|
||||
|
||||
RegisterGatewayTransportIfEnabled("tcp", bootstrapOptions.Transports.Tcp.Enabled, "Gateway:Transports:Tcp");
|
||||
RegisterGatewayTransportIfEnabled("tls", bootstrapOptions.Transports.Tls.Enabled, "Gateway:Transports:Tls");
|
||||
RegisterGatewayTransportIfEnabled("messaging", bootstrapOptions.Transports.Messaging.Enabled, "Gateway:Transports:Messaging");
|
||||
|
||||
builder.Services.AddSingleton<GatewayTransportClient>();
|
||||
builder.Services.AddSingleton<ITransportClient>(sp => sp.GetRequiredService<GatewayTransportClient>());
|
||||
|
||||
builder.Services.AddSingleton(new GatewayRouteCatalog(bootstrapOptions.Routes));
|
||||
builder.Services.AddSingleton<IOpenApiDocumentGenerator, OpenApiDocumentGenerator>();
|
||||
builder.Services.AddSingleton<IRouterOpenApiDocumentCache, RouterOpenApiDocumentCache>();
|
||||
|
||||
@@ -125,6 +118,10 @@ builder.Services.AddSingleton(new IdentityHeaderPolicyOptions
|
||||
{
|
||||
EnableLegacyHeaders = bootstrapOptions.Auth.EnableLegacyHeaders,
|
||||
AllowScopeHeaderOverride = bootstrapOptions.Auth.AllowScopeHeader,
|
||||
EmitIdentityEnvelope = bootstrapOptions.Auth.EmitIdentityEnvelope,
|
||||
IdentityEnvelopeSigningKey = bootstrapOptions.Auth.IdentityEnvelopeSigningKey,
|
||||
IdentityEnvelopeIssuer = bootstrapOptions.Auth.IdentityEnvelopeIssuer,
|
||||
IdentityEnvelopeTtl = TimeSpan.FromSeconds(Math.Max(1, bootstrapOptions.Auth.IdentityEnvelopeTtlSeconds)),
|
||||
JwtPassthroughPrefixes = bootstrapOptions.Routes
|
||||
.Where(r => r.PreserveAuthHeaders)
|
||||
.Select(r => r.Path)
|
||||
@@ -149,11 +146,11 @@ ConfigureAuthentication(builder, bootstrapOptions);
|
||||
ConfigureGatewayOptionsMapping(builder, bootstrapOptions);
|
||||
|
||||
// Stella Router integration
|
||||
var routerOptions = builder.Configuration.GetSection("Gateway:Router").Get<StellaRouterOptionsBase>();
|
||||
builder.Services.TryAddStellaRouter(
|
||||
var routerEnabled = builder.Services.AddRouterMicroservice(
|
||||
builder.Configuration,
|
||||
serviceName: "gateway",
|
||||
version: typeof(Program).Assembly.GetName().Version?.ToString() ?? "1.0.0",
|
||||
routerOptions: routerOptions);
|
||||
version: System.Reflection.CustomAttributeExtensions.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>(System.Reflection.Assembly.GetExecutingAssembly())?.InformationalVersion ?? "1.0.0",
|
||||
routerOptionsSection: "Router");
|
||||
|
||||
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
|
||||
|
||||
@@ -164,9 +161,14 @@ app.LogStellaOpsLocalHostname("router");
|
||||
// Force browser traffic onto HTTPS so auth (PKCE/DPoP/WebCrypto) always runs in a secure context.
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var isWebSocketUpgrade =
|
||||
context.WebSockets.IsWebSocketRequest ||
|
||||
string.Equals(context.Request.Headers.Upgrade, "websocket", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!context.Request.IsHttps &&
|
||||
context.Request.Host.HasValue &&
|
||||
!GatewayRoutes.IsSystemPath(context.Request.Path))
|
||||
!GatewayRoutes.IsSystemPath(context.Request.Path) &&
|
||||
!isWebSocketUpgrade)
|
||||
{
|
||||
var host = context.Request.Host.Host;
|
||||
var redirect = $"https://{host}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}";
|
||||
@@ -185,7 +187,7 @@ app.UseMiddleware<SenderConstraintMiddleware>();
|
||||
// It strips reserved identity headers and overwrites them from validated claims (security fix)
|
||||
app.UseMiddleware<IdentityHeaderPolicyMiddleware>();
|
||||
app.UseMiddleware<HealthCheckMiddleware>();
|
||||
app.TryUseStellaRouter(routerOptions);
|
||||
app.TryUseStellaRouter(routerEnabled);
|
||||
|
||||
// WebSocket support (before route dispatch)
|
||||
app.UseWebSockets();
|
||||
@@ -216,10 +218,34 @@ app.UseWhen(
|
||||
app.UseMiddleware<ErrorPageFallbackMiddleware>();
|
||||
|
||||
// Refresh Router endpoint cache
|
||||
app.TryRefreshStellaRouterEndpoints(routerOptions);
|
||||
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
void RegisterGatewayTransportIfEnabled(string transportName, bool enabled, string configurationSection)
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var plugin = transportPluginLoader.GetPlugin(transportName);
|
||||
if (plugin is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Gateway transport plugin '{transportName}' is not available. " +
|
||||
$"Provide a plugin assembly in '{pluginsPath}' or add the transport plugin dependency.");
|
||||
}
|
||||
|
||||
plugin.Register(new RouterTransportRegistrationContext(
|
||||
builder.Services,
|
||||
builder.Configuration,
|
||||
RouterTransportMode.Server)
|
||||
{
|
||||
ConfigurationSection = configurationSection
|
||||
});
|
||||
}
|
||||
|
||||
static void ConfigureAuthentication(WebApplicationBuilder builder, GatewayOptions options)
|
||||
{
|
||||
var authOptions = options.Auth;
|
||||
@@ -312,6 +338,7 @@ static void ConfigureGatewayOptionsMapping(WebApplicationBuilder builder, Gatewa
|
||||
{
|
||||
var routing = gateway.Value.Routing;
|
||||
options.RoutingTimeoutMs = (int)GatewayValueParser.ParseDuration(routing.DefaultTimeout, TimeSpan.FromSeconds(30)).TotalMilliseconds;
|
||||
options.GlobalTimeoutCapMs = (int)GatewayValueParser.ParseDuration(routing.GlobalTimeoutCap, TimeSpan.FromSeconds(120)).TotalMilliseconds;
|
||||
options.PreferLocalRegion = routing.PreferLocalRegion;
|
||||
options.AllowDegradedInstances = routing.AllowDegradedInstances;
|
||||
options.StrictVersionMatching = routing.StrictVersionMatching;
|
||||
@@ -346,51 +373,47 @@ static void ConfigureGatewayOptionsMapping(WebApplicationBuilder builder, Gatewa
|
||||
options.TokenUrl = openApi.TokenUrl;
|
||||
});
|
||||
|
||||
builder.Services.AddOptions<TcpTransportOptions>()
|
||||
.Configure<IOptions<GatewayOptions>>((options, gateway) =>
|
||||
{
|
||||
var tcp = gateway.Value.Transports.Tcp;
|
||||
options.Port = tcp.Port;
|
||||
options.ReceiveBufferSize = tcp.ReceiveBufferSize;
|
||||
options.SendBufferSize = tcp.SendBufferSize;
|
||||
options.MaxFrameSize = tcp.MaxFrameSize;
|
||||
options.BindAddress = IPAddress.Parse(tcp.BindAddress);
|
||||
});
|
||||
|
||||
builder.Services.AddOptions<TlsTransportOptions>()
|
||||
.Configure<IOptions<GatewayOptions>>((options, gateway) =>
|
||||
{
|
||||
var tls = gateway.Value.Transports.Tls;
|
||||
options.Port = tls.Port;
|
||||
options.ReceiveBufferSize = tls.ReceiveBufferSize;
|
||||
options.SendBufferSize = tls.SendBufferSize;
|
||||
options.MaxFrameSize = tls.MaxFrameSize;
|
||||
options.BindAddress = IPAddress.Parse(tls.BindAddress);
|
||||
options.ServerCertificatePath = tls.CertificatePath;
|
||||
options.ServerCertificateKeyPath = tls.CertificateKeyPath;
|
||||
options.ServerCertificatePassword = tls.CertificatePassword;
|
||||
options.RequireClientCertificate = tls.RequireClientCertificate;
|
||||
options.AllowSelfSigned = tls.AllowSelfSigned;
|
||||
});
|
||||
|
||||
builder.Services.AddOptions<MessagingTransportOptions>()
|
||||
.Configure<IOptions<GatewayOptions>>((options, gateway) =>
|
||||
{
|
||||
var messaging = gateway.Value.Transports.Messaging;
|
||||
options.RequestQueueTemplate = messaging.RequestQueueTemplate;
|
||||
options.ResponseQueueName = messaging.ResponseQueueName;
|
||||
options.ConsumerGroup = messaging.ConsumerGroup;
|
||||
options.RequestTimeout = GatewayValueParser.ParseDuration(messaging.RequestTimeout, TimeSpan.FromSeconds(30));
|
||||
options.LeaseDuration = GatewayValueParser.ParseDuration(messaging.LeaseDuration, TimeSpan.FromMinutes(5));
|
||||
options.BatchSize = messaging.BatchSize;
|
||||
options.HeartbeatInterval = GatewayValueParser.ParseDuration(messaging.HeartbeatInterval, TimeSpan.FromSeconds(10));
|
||||
});
|
||||
|
||||
builder.Services.AddOptions<ValkeyTransportOptions>()
|
||||
.Configure<IOptions<GatewayOptions>>((options, gateway) =>
|
||||
{
|
||||
var messaging = gateway.Value.Transports.Messaging;
|
||||
options.ConnectionString = messaging.ConnectionString;
|
||||
options.Database = messaging.Database;
|
||||
});
|
||||
}
|
||||
|
||||
static string? ResolveAuthorityClaimsUrl(GatewayAuthorityOptions authorityOptions)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(authorityOptions.ClaimsOverridesUrl))
|
||||
{
|
||||
return authorityOptions.ClaimsOverridesUrl.TrimEnd('/');
|
||||
}
|
||||
|
||||
var candidate = authorityOptions.Issuer;
|
||||
if (string.IsNullOrWhiteSpace(candidate))
|
||||
{
|
||||
candidate = authorityOptions.MetadataAddress;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(candidate))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(candidate, UriKind.Absolute, out var uri))
|
||||
{
|
||||
return candidate.TrimEnd('/');
|
||||
}
|
||||
|
||||
// Authority runs HTTP on the internal compose network by default.
|
||||
var builder = new UriBuilder(uri)
|
||||
{
|
||||
Path = string.Empty,
|
||||
Query = string.Empty,
|
||||
Fragment = string.Empty
|
||||
};
|
||||
|
||||
if (string.Equals(builder.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
builder.Scheme = Uri.UriSchemeHttp;
|
||||
builder.Port = builder.Port == 443 ? 80 : builder.Port;
|
||||
}
|
||||
|
||||
return builder.Uri.GetLeftPart(UriPartial.Authority).TrimEnd('/');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user