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:
master
2026-03-16 08:12:39 +02:00
parent 602df77467
commit da76d6e93e
223 changed files with 24763 additions and 489 deletions

View File

@@ -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);
}
}