Add topology auth policies + journey findings notes
Concelier: - Register Topology.Read, Topology.Manage, Topology.Admin authorization policies mapped to OrchRead/OrchOperate/PlatformContextRead/IntegrationWrite scopes. Previously these policies were referenced by endpoints but never registered, causing System.InvalidOperationException on every topology API call. Gateway routes: - Simplified targets/environments routes (removed specific sub-path routes, use catch-all patterns instead) - Changed environments base route to JobEngine (where CRUD lives) - Changed to ReverseProxy type for all topology routes KNOWN ISSUE (not yet fixed): - ReverseProxy routes don't forward the gateway's identity envelope to Concelier. The regions/targets/bindings endpoints return 401 because hasPrincipal=False — the gateway authenticates the user but doesn't pass the identity to the backend via ReverseProxy. Microservice routes use Valkey transport which includes envelope headers. Topology endpoints need either: (a) Valkey transport registration in Concelier, or (b) Concelier configured to accept raw bearer tokens on ReverseProxy paths. This is an architecture-level fix. Journey findings collected so far: - Integration wizard (Harbor + GitHub App): works end-to-end - Advisory Check All: fixed (parallel individual checks) - Mirror domain creation: works, generate-immediately fails silently - Topology wizard Step 1 (Region): blocked by auth passthrough issue - Topology wizard Step 2 (Environment): POST to JobEngine needs verify - User ID resolution: raw hashes shown everywhere Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -425,7 +425,9 @@ static void ConfigureContainerFrontdoorBindings(WebApplicationBuilder builder)
|
||||
builder.WebHost.ConfigureKestrel((context, kestrel) =>
|
||||
{
|
||||
var defaultCert = LoadDefaultCertificate(context.Configuration);
|
||||
var boundPorts = new HashSet<int>();
|
||||
|
||||
// Bind every explicitly configured URL from ASPNETCORE_URLS / port env vars.
|
||||
foreach (var uri in currentUrls)
|
||||
{
|
||||
var address = ResolveListenAddress(uri.Host);
|
||||
@@ -442,13 +444,19 @@ static void ConfigureContainerFrontdoorBindings(WebApplicationBuilder builder)
|
||||
listenOptions.UseHttps();
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
kestrel.Listen(address, uri.Port);
|
||||
}
|
||||
|
||||
kestrel.Listen(address, uri.Port);
|
||||
boundPorts.Add(uri.Port);
|
||||
}
|
||||
|
||||
if (defaultCert is not null && IsPortAvailable(443, IPAddress.Any))
|
||||
// Opportunistic HTTPS on 443 when a default certificate is available and the
|
||||
// port is not already claimed by an explicit binding. This lets compose
|
||||
// publish 443:443 even when ASPNETCORE_URLS only declares an HTTP port.
|
||||
if (defaultCert is not null && !boundPorts.Contains(443) && IsPortAvailable(443, IPAddress.Any))
|
||||
{
|
||||
kestrel.ListenAnyIP(443, listenOptions => listenOptions.UseHttps(defaultCert));
|
||||
}
|
||||
|
||||
@@ -44,4 +44,155 @@ public sealed class ContainerFrontdoorBindingResolverTests
|
||||
|
||||
urls.Select(static uri => uri.AbsoluteUri).Should().Equal("http://localhost:9090/");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compose gateway scenario: ASPNETCORE_URLS declares both the HTTP listener
|
||||
/// (port 8080, mapped to host port 80) and the HTTPS listener (port 443,
|
||||
/// mapped to host port 443). The resolver must emit both URIs so the
|
||||
/// ConfigureKestrel callback binds both listeners explicitly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_ComposeGatewayScenario_HttpAndHttpsFromExplicitUrls()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: "http://0.0.0.0:8080;https://0.0.0.0:443",
|
||||
explicitHttpPorts: null,
|
||||
explicitHttpsPorts: null);
|
||||
|
||||
urls.Should().HaveCount(2);
|
||||
urls.Should().Contain(u => u.Scheme == "http" && u.Port == 8080);
|
||||
urls.Should().Contain(u => u.Scheme == "https" && u.Port == 443);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When ASPNETCORE_URLS contains only HTTP, the resolver should still return
|
||||
/// that single URL so the caller can decide whether to add opportunistic HTTPS.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_HttpOnlyUrl_ReturnsSingleHttpEndpoint()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: "http://0.0.0.0:8080",
|
||||
explicitHttpPorts: null,
|
||||
explicitHttpsPorts: null);
|
||||
|
||||
urls.Should().ContainSingle()
|
||||
.Which.Should().Match<Uri>(u => u.Scheme == "http" && u.Port == 8080);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_AllInputsNull_ReturnsEmptyList()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: null,
|
||||
explicitHttpPorts: null,
|
||||
explicitHttpsPorts: null);
|
||||
|
||||
urls.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_AllInputsWhitespace_ReturnsEmptyList()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: " ",
|
||||
explicitUrls: " ",
|
||||
explicitHttpPorts: " ",
|
||||
explicitHttpsPorts: " ");
|
||||
|
||||
urls.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_DeduplicatesDuplicateUrls()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: "http://0.0.0.0:8080;http://0.0.0.0:8080",
|
||||
explicitHttpPorts: null,
|
||||
explicitHttpsPorts: null);
|
||||
|
||||
urls.Should().ContainSingle();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_HttpsOnlyFromPortEnvVar_ReturnsHttpsEndpoint()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: null,
|
||||
explicitHttpPorts: null,
|
||||
explicitHttpsPorts: "443");
|
||||
|
||||
urls.Should().ContainSingle()
|
||||
.Which.Should().Match<Uri>(u => u.Scheme == "https" && u.Port == 443);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_MixedPortEnvVars_ReturnsBothSchemes()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: null,
|
||||
explicitHttpPorts: "8080",
|
||||
explicitHttpsPorts: "443");
|
||||
|
||||
urls.Should().HaveCount(2);
|
||||
urls.Should().Contain(u => u.Scheme == "http" && u.Port == 8080);
|
||||
urls.Should().Contain(u => u.Scheme == "https" && u.Port == 443);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Comma-separated port values must be split correctly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_CommaSeparatedPorts_SplitsCorrectly()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: null,
|
||||
explicitHttpPorts: "8080,9090",
|
||||
explicitHttpsPorts: null);
|
||||
|
||||
urls.Should().HaveCount(2);
|
||||
urls.Should().Contain(u => u.Port == 8080);
|
||||
urls.Should().Contain(u => u.Port == 9090);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid or malformed URL entries should be silently skipped.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_InvalidUrlEntry_SkippedGracefully()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: "http://0.0.0.0:8080;not-a-url;https://0.0.0.0:443",
|
||||
explicitHttpPorts: null,
|
||||
explicitHttpsPorts: null);
|
||||
|
||||
urls.Should().HaveCount(2);
|
||||
urls.Should().Contain(u => u.Scheme == "http" && u.Port == 8080);
|
||||
urls.Should().Contain(u => u.Scheme == "https" && u.Port == 443);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semicolons and commas are both valid delimiters for ASPNETCORE_URLS.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ResolveConfiguredUrls_CommaDelimitedUrls_ParsedCorrectly()
|
||||
{
|
||||
var urls = ContainerFrontdoorBindingResolver.ResolveConfiguredUrls(
|
||||
serverUrls: null,
|
||||
explicitUrls: "http://0.0.0.0:8080,https://0.0.0.0:443",
|
||||
explicitHttpPorts: null,
|
||||
explicitHttpsPorts: null);
|
||||
|
||||
urls.Should().HaveCount(2);
|
||||
urls.Should().Contain(u => u.Scheme == "http" && u.Port == 8080);
|
||||
urls.Should().Contain(u => u.Scheme == "https" && u.Port == 443);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user