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

@@ -105,7 +105,7 @@ const MOCK_DOMAIN_LIST = {
rateLimits: { indexRequestsPerHour: 60, downloadRequestsPerHour: 120 },
requireAuthentication: false,
signing: { enabled: true, algorithm: 'ES256', keyId: 'key-01' },
domainUrl: '/concelier/exports/security-advisories',
domainUrl: '/concelier/exports/mirror/security-advisories',
createdAt: new Date().toISOString(),
status: 'active',
},
@@ -150,7 +150,7 @@ function setupErrorCollector(page: import('@playwright/test').Page) {
/** Set up mocks for the mirror client setup wizard page. */
function setupWizardApiMocks(page: import('@playwright/test').Page) {
// Mirror test endpoint (connection check)
page.route('**/api/v1/mirror/test', (route) => {
page.route('**/api/v1/advisory-sources/mirror/test', (route) => {
if (route.request().method() === 'POST') {
route.fulfill({
status: 200,
@@ -163,7 +163,7 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
});
// Consumer discovery endpoint
page.route('**/api/v1/mirror/consumer/discover', (route) => {
page.route('**/api/v1/advisory-sources/mirror/consumer/discover', (route) => {
if (route.request().method() === 'POST') {
route.fulfill({
status: 200,
@@ -176,7 +176,7 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
});
// Consumer signature verification endpoint
page.route('**/api/v1/mirror/consumer/verify-signature', (route) => {
page.route('**/api/v1/advisory-sources/mirror/consumer/verify-signature', (route) => {
if (route.request().method() === 'POST') {
route.fulfill({
status: 200,
@@ -189,7 +189,7 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
});
// Consumer config GET/PUT
page.route('**/api/v1/mirror/consumer', (route) => {
page.route('**/api/v1/advisory-sources/mirror/consumer', (route) => {
const method = route.request().method();
if (method === 'GET') {
route.fulfill({
@@ -209,7 +209,7 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
});
// Mirror config
page.route('**/api/v1/mirror/config', (route) => {
page.route('**/api/v1/advisory-sources/mirror/config', (route) => {
const method = route.request().method();
if (method === 'GET') {
route.fulfill({
@@ -229,7 +229,7 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
});
// Mirror health summary
page.route('**/api/v1/mirror/health', (route) => {
page.route('**/api/v1/advisory-sources/mirror/health', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -238,7 +238,7 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
});
// Mirror domains
page.route('**/api/v1/mirror/domains', (route) => {
page.route('**/api/v1/advisory-sources/mirror/domains', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -247,7 +247,7 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
});
// Mirror import endpoint
page.route('**/api/v1/mirror/import', (route) => {
page.route('**/api/v1/advisory-sources/mirror/import', (route) => {
if (route.request().method() === 'POST') {
route.fulfill({
status: 200,
@@ -260,7 +260,7 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
});
// Mirror import status
page.route('**/api/v1/mirror/import/status', (route) => {
page.route('**/api/v1/advisory-sources/mirror/import/status', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -288,15 +288,15 @@ function setupWizardApiMocks(page: import('@playwright/test').Page) {
/** Set up mocks for catalog and dashboard pages that show mirror integration. */
function setupCatalogDashboardMocks(page: import('@playwright/test').Page) {
page.route('**/api/v1/sources/catalog', (route) => {
page.route('**/api/v1/advisory-sources/catalog', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(MOCK_SOURCE_CATALOG) });
});
page.route('**/api/v1/sources/status', (route) => {
page.route('**/api/v1/advisory-sources/status', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(MOCK_SOURCE_STATUS) });
});
page.route('**/api/v1/sources/check', (route) => {
page.route('**/api/v1/advisory-sources/check', (route) => {
if (route.request().method() === 'POST') {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ totalChecked: 3, healthyCount: 2, failedCount: 0 }) });
} else {
@@ -304,7 +304,7 @@ function setupCatalogDashboardMocks(page: import('@playwright/test').Page) {
}
});
page.route('**/api/v1/sources/*/check', (route) => {
page.route('**/api/v1/advisory-sources/*/check', (route) => {
if (route.request().method() === 'POST') {
route.fulfill({
status: 200,
@@ -316,7 +316,7 @@ function setupCatalogDashboardMocks(page: import('@playwright/test').Page) {
}
});
page.route('**/api/v1/sources/*/check-result', (route) => {
page.route('**/api/v1/advisory-sources/*/check-result', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -324,11 +324,11 @@ function setupCatalogDashboardMocks(page: import('@playwright/test').Page) {
});
});
page.route('**/api/v1/sources/batch-enable', (route) => {
page.route('**/api/v1/advisory-sources/batch-enable', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ results: [] }) });
});
page.route('**/api/v1/sources/batch-disable', (route) => {
page.route('**/api/v1/advisory-sources/batch-disable', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ results: [] }) });
});
@@ -445,7 +445,7 @@ test.describe('Mirror Client Setup Wizard', () => {
const ngErrors = setupErrorCollector(page);
// Override the mirror test endpoint to return failure
await page.route('**/api/v1/mirror/test', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/test', (route) => {
if (route.request().method() === 'POST') {
route.fulfill({
status: 200,
@@ -458,22 +458,22 @@ test.describe('Mirror Client Setup Wizard', () => {
});
// Set up remaining wizard mocks (excluding mirror/test which is overridden above)
await page.route('**/api/v1/mirror/consumer/discover', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/consumer/discover', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(MOCK_DISCOVERY_RESPONSE) });
});
await page.route('**/api/v1/mirror/consumer/verify-signature', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/consumer/verify-signature', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(MOCK_SIGNATURE_DETECTION) });
});
await page.route('**/api/v1/mirror/consumer', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/consumer', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(MOCK_CONSUMER_CONFIG) });
});
await page.route('**/api/v1/mirror/config', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/config', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(MOCK_MIRROR_CONFIG_DIRECT_MODE) });
});
await page.route('**/api/v1/mirror/health', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/health', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(MOCK_MIRROR_HEALTH) });
});
await page.route('**/api/v1/mirror/domains', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/domains', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(MOCK_DOMAIN_LIST) });
});
await page.route('**/api/v2/security/**', (route) => {
@@ -766,7 +766,7 @@ test.describe('Mirror Dashboard - Consumer Panel', () => {
const ngErrors = setupErrorCollector(page);
// Mock mirror config as Mirror mode with consumer URL
await page.route('**/api/v1/mirror/config', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/config', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -774,7 +774,7 @@ test.describe('Mirror Dashboard - Consumer Panel', () => {
});
});
await page.route('**/api/v1/mirror/health', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/health', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -782,7 +782,7 @@ test.describe('Mirror Dashboard - Consumer Panel', () => {
});
});
await page.route('**/api/v1/mirror/domains', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/domains', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -840,7 +840,7 @@ test.describe('Advisory Source Catalog - Mirror Integration', () => {
await setupCatalogDashboardMocks(page);
// Mock mirror config in Direct mode
await page.route('**/api/v1/mirror/config', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/config', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -848,7 +848,7 @@ test.describe('Advisory Source Catalog - Mirror Integration', () => {
});
});
await page.route('**/api/v1/mirror/health', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/health', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
@@ -856,7 +856,7 @@ test.describe('Advisory Source Catalog - Mirror Integration', () => {
});
});
await page.route('**/api/v1/mirror/domains', (route) => {
await page.route('**/api/v1/advisory-sources/mirror/domains', (route) => {
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ domains: [], totalCount: 0 }) });
});