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:
@@ -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 }) });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user