stabilizaiton work - projects rework for maintenanceability and ui livening

This commit is contained in:
master
2026-02-03 23:40:04 +02:00
parent 074ce117ba
commit 557feefdc3
3305 changed files with 186813 additions and 107843 deletions

View File

@@ -21,3 +21,8 @@
## Sprint Discipline
- Record decisions and risks for security-sensitive changes in the sprint file.
## Service Endpoints
- Development: https://localhost:10020, http://localhost:10021
- Local alias: https://authority.stella-ops.local, http://authority.stella-ops.local
- Env var: STELLAOPS_AUTHORITY_URL

View File

@@ -30,6 +30,7 @@
<ProjectReference Include="..\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.AspNet.Extensions/StellaOps.AspNet.Extensions.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

View File

@@ -0,0 +1,240 @@
// Copyright (c) StellaOps. All rights reserved.
// Licensed under BUSL-1.1. See LICENSE in the project root.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Auth.ServerIntegration;
/// <summary>
/// Provides two extension methods for the <c>.stella-ops.local</c> hostname convention:
/// <list type="bullet">
/// <item>
/// <see cref="TryAddStellaOpsLocalBinding"/> — called on <see cref="WebApplicationBuilder"/>
/// before <c>Build()</c>; binds both <c>https://{serviceName}.stella-ops.local</c> (port 443)
/// and <c>http://{serviceName}.stella-ops.local</c> (port 80).
/// </item>
/// <item>
/// <see cref="LogStellaOpsLocalHostname"/> — called on <see cref="WebApplication"/>
/// after <c>Build()</c>; checks DNS for the friendly hostname and logs the result.
/// </item>
/// </list>
/// </summary>
public static class StellaOpsLocalHostnameExtensions
{
private const string DomainSuffix = ".stella-ops.local";
private const int HttpsPort = 443;
private const int HttpPort = 80;
private const string SetupDocPath = "docs/technical/architecture/port-registry.md";
/// <summary>
/// Configuration key used to communicate local-binding status
/// from the builder phase to the app phase.
/// </summary>
internal const string LocalBindingBoundKey = "StellaOps:LocalBindingBound";
/// <summary>
/// Configuration key storing the service name for use in the app phase.
/// </summary>
internal const string LocalBindingServiceKey = "StellaOps:LocalBindingService";
/// <summary>
/// Resolves <c>{serviceName}.stella-ops.local</c> to its dedicated loopback IP
/// (from the hosts file), then binds <c>https://{hostname}</c> (port 443) and
/// <c>http://{hostname}</c> (port 80) on that IP. Each service uses a unique
/// loopback address (e.g. 127.1.0.2) so ports never collide.
/// </summary>
public static WebApplicationBuilder TryAddStellaOpsLocalBinding(
this WebApplicationBuilder builder, string serviceName)
{
builder.Configuration[LocalBindingServiceKey] = serviceName;
var hostname = serviceName + DomainSuffix;
// Resolve the hostname to find its dedicated loopback IP from the hosts file.
IPAddress? resolvedIp = null;
try
{
var addresses = Dns.GetHostAddresses(hostname);
resolvedIp = addresses.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork);
}
catch
{
// Hostname does not resolve; skip binding (will warn at startup).
}
if (resolvedIp == null)
{
builder.Configuration[LocalBindingBoundKey] = "false";
return builder;
}
var httpsAvailable = IsPortAvailable(HttpsPort, resolvedIp);
var httpAvailable = IsPortAvailable(HttpPort, resolvedIp);
if (!httpsAvailable && !httpAvailable)
{
builder.Configuration[LocalBindingBoundKey] = "false";
return builder;
}
builder.Configuration[LocalBindingBoundKey] = "true";
// Bind to the specific loopback IP (not hostname) so Kestrel uses only
// this address, leaving other 127.1.0.x IPs available for other services.
// UseUrls("https://hostname") would bind to [::]:443 (all interfaces).
//
// When ConfigureKestrel uses explicit Listen() calls, Kestrel ignores UseUrls.
// So we must also re-add the dev-port bindings from launchSettings.json.
var currentUrls = builder.WebHost.GetSetting(WebHostDefaults.ServerUrlsKey) ?? "";
var ip = resolvedIp;
builder.WebHost.ConfigureKestrel((context, kestrel) =>
{
// Re-add dev-port bindings from launchSettings.json / ASPNETCORE_URLS
foreach (var rawUrl in currentUrls.Split(';', StringSplitOptions.RemoveEmptyEntries))
{
if (Uri.TryCreate(rawUrl.Trim(), UriKind.Absolute, out var uri))
{
var isHttps = string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase);
var addr = uri.Host == "localhost"
? IPAddress.Loopback
: IPAddress.Parse(uri.Host);
if (isHttps)
{
kestrel.Listen(addr, uri.Port, lo => lo.UseHttps());
}
else
{
kestrel.Listen(addr, uri.Port);
}
}
}
// Add .stella-ops.local bindings on the dedicated loopback IP
if (httpsAvailable)
{
kestrel.Listen(ip, HttpsPort, listenOptions =>
{
listenOptions.UseHttps();
});
}
if (httpAvailable)
{
kestrel.Listen(ip, HttpPort);
}
});
return builder;
}
/// <summary>
/// Backwards-compatible overload — reads the service name from configuration
/// set by <see cref="TryAddStellaOpsLocalBinding"/>.
/// </summary>
[System.Obsolete("Use TryAddStellaOpsLocalBinding(builder, serviceName) instead of TryAddStellaOpsSharedPort(). This overload will be removed.")]
public static WebApplicationBuilder TryAddStellaOpsSharedPort(
this WebApplicationBuilder builder)
{
// No-op fallback for any callers not yet migrated; the binding happens
// in TryAddStellaOpsLocalBinding which should be called instead.
return builder;
}
/// <summary>
/// Registers a startup callback that checks DNS for
/// <c>{serviceName}.stella-ops.local</c> and logs the result.
/// Also warns if the local bindings were skipped.
/// </summary>
public static WebApplication LogStellaOpsLocalHostname(
this WebApplication app, string serviceName)
{
var hostname = serviceName + DomainSuffix;
var localBindingBound = string.Equals(
app.Configuration[LocalBindingBoundKey], "true", StringComparison.OrdinalIgnoreCase);
var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStarted.Register(() =>
{
var logger = app.Services.GetRequiredService<ILoggerFactory>()
.CreateLogger("StellaOps.LocalHostname");
if (!localBindingBound)
{
logger.LogWarning(
"Ports {HttpsPort}/{HttpPort} are already in use; skipping {Hostname} bindings. " +
"This is expected when running multiple services locally.",
HttpsPort, HttpPort, hostname);
}
_ = Task.Run(async () =>
{
try
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
var resolved = await Dns.GetHostAddressesAsync(hostname, cts.Token);
if (resolved.Length > 0)
{
if (localBindingBound)
{
logger.LogInformation(
"Also accessible at https://{Hostname} and http://{Hostname}",
hostname, hostname);
}
else
{
logger.LogInformation(
"Hostname {Hostname} resolves but ports {HttpsPort}/{HttpPort} are unavailable; " +
"use the dev port instead.",
hostname, HttpsPort, HttpPort);
}
}
}
catch
{
var hostsPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? @"C:\Windows\System32\drivers\etc\hosts"
: "/etc/hosts";
logger.LogWarning(
"Hostname {Hostname} does not resolve. " +
"To enable friendly .stella-ops.local URLs, add hosts-file entries " +
"as described in {SetupDoc}. " +
"Edit {HostsFile} and add a unique 127.1.0.x entry for {Hostname}",
hostname, SetupDocPath, hostsPath, hostname);
}
});
});
return app;
}
private static bool IsPortAvailable(int port, IPAddress address)
{
try
{
using var listener = new TcpListener(address, port);
listener.Start();
listener.Stop();
return true;
}
catch
{
return false;
}
}
}

View File

@@ -290,6 +290,7 @@ var pluginRegistrationSummary = AuthorityPluginLoader.RegisterPlugins(
builder.Services.AddSingleton(pluginRegistrationSummary);
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
builder.Services.AddRouting(options => options.LowercaseUrls = true);
builder.Services.AddProblemDetails();
builder.Services.AddAuthentication();
@@ -414,7 +415,9 @@ builder.Services.Configure<OpenIddictServerOptions>(options =>
options.DisableRollingRefreshTokens = false;
});
builder.TryAddStellaOpsLocalBinding("authority");
var app = builder.Build();
app.LogStellaOpsLocalHostname("authority");
var serviceAccountStore = app.Services.GetRequiredService<IAuthorityServiceAccountStore>();
if (authorityOptions.Delegation.ServiceAccounts.Count > 0)
@@ -1716,6 +1719,7 @@ app.UseLegacyAuthDeprecation();
app.UseRouting();
app.UseAuthorityRateLimiterContext();
app.UseRateLimiter();
app.UseStellaOpsCors();
app.UseAuthentication();
app.UseAuthorization();

View File

@@ -1,23 +1,27 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5165",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7182;http://localhost:5165",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:10021",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"STELLAOPS_WEBSERVICES_CORS": "true",
"STELLAOPS_WEBSERVICES_CORS_ORIGIN": "https://stella-ops.local,https://stella-ops.local:10000,https://localhost:10000"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:10020;http://localhost:10021",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"STELLAOPS_WEBSERVICES_CORS": "true",
"STELLAOPS_WEBSERVICES_CORS_ORIGIN": "https://stella-ops.local,https://stella-ops.local:10000,https://localhost:10000"
}
}
}
}

View File

@@ -32,6 +32,7 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography.Kms/StellaOps.Cryptography.Kms.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Configuration.AuthorityPlugin/StellaOps.Configuration.AuthorityPlugin.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj" />
<ProjectReference Include="../../../Attestor/StellaOps.Attestation/StellaOps.Attestation.csproj" />
</ItemGroup>