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:
@@ -870,71 +870,26 @@ async function setupHarness(page: Page): Promise<void> {
|
||||
});
|
||||
},
|
||||
);
|
||||
await page.route('**/api/v1/trust/dashboard**', (route) =>
|
||||
route.fulfill({
|
||||
await page.route('**/api/v1/administration/trust-signing', (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.endsWith('/api/v1/administration/trust-signing')) {
|
||||
return route.fallback();
|
||||
}
|
||||
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
keys: {
|
||||
total: 3,
|
||||
active: 1,
|
||||
expiringSoon: 1,
|
||||
expired: 1,
|
||||
revoked: 0,
|
||||
pendingRotation: 0,
|
||||
},
|
||||
issuers: {
|
||||
total: 3,
|
||||
fullTrust: 2,
|
||||
partialTrust: 1,
|
||||
minimalTrust: 0,
|
||||
untrusted: 0,
|
||||
blocked: 0,
|
||||
averageTrustScore: 87.3,
|
||||
},
|
||||
certificates: {
|
||||
total: 3,
|
||||
valid: 2,
|
||||
expiringSoon: 1,
|
||||
expired: 0,
|
||||
revoked: 0,
|
||||
invalidChains: 0,
|
||||
},
|
||||
recentEvents: [],
|
||||
expiryAlerts: [
|
||||
{
|
||||
keyId: 'key-002',
|
||||
keyName: 'SBOM Signing Key',
|
||||
expiresAt: '2026-04-15T00:00:00.000Z',
|
||||
daysUntilExpiry: 39,
|
||||
severity: 'warning',
|
||||
purpose: 'sbom_signing',
|
||||
suggestedAction: 'Schedule key rotation before expiry.',
|
||||
},
|
||||
],
|
||||
inventory: { keys: 3, issuers: 3, certificates: 3 },
|
||||
signals: [],
|
||||
legacyAliases: [],
|
||||
evidenceConsumerPath: '/evidence-audit/proofs',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
await page.route('**/api/v1/trust/keys/expiry-alerts**', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify([
|
||||
{
|
||||
keyId: 'key-002',
|
||||
keyName: 'SBOM Signing Key',
|
||||
purpose: 'sbom_signing',
|
||||
expiresAt: '2026-04-15T00:00:00.000Z',
|
||||
daysUntilExpiry: 39,
|
||||
severity: 'warning',
|
||||
suggestedAction: 'Schedule key rotation before expiry.',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
);
|
||||
await page.route('**/api/v1/trust/keys**', (route) => {
|
||||
});
|
||||
});
|
||||
await page.route('**/api/v1/administration/trust-signing/keys**', (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.endsWith('/api/v1/trust/keys')) {
|
||||
if (!pathname.endsWith('/trust-signing/keys')) {
|
||||
return route.fallback();
|
||||
}
|
||||
|
||||
@@ -945,44 +900,140 @@ async function setupHarness(page: Page): Promise<void> {
|
||||
items: [
|
||||
{
|
||||
keyId: 'key-001',
|
||||
tenantId: adminSession.tenant,
|
||||
name: 'Production Key',
|
||||
description: 'Main signing key',
|
||||
keyType: 'asymmetric',
|
||||
alias: 'Production Key',
|
||||
algorithm: 'RS256',
|
||||
keySize: 2048,
|
||||
purpose: 'attestation',
|
||||
status: 'active',
|
||||
publicKeyFingerprint: 'sha256:prod-key',
|
||||
currentVersion: 2,
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
expiresAt: '2027-01-01T00:00:00.000Z',
|
||||
lastUsedAt: '2026-03-06T10:00:00.000Z',
|
||||
usageCount: 100,
|
||||
updatedAt: '2026-03-06T10:00:00.000Z',
|
||||
updatedBy: 'operator',
|
||||
},
|
||||
{
|
||||
keyId: 'key-002',
|
||||
tenantId: adminSession.tenant,
|
||||
name: 'SBOM Signing Key',
|
||||
description: 'Secondary signing key',
|
||||
keyType: 'asymmetric',
|
||||
alias: 'SBOM Signing Key',
|
||||
algorithm: 'RS256',
|
||||
keySize: 2048,
|
||||
purpose: 'sbom_signing',
|
||||
status: 'expiring_soon',
|
||||
publicKeyFingerprint: 'sha256:sbom-key',
|
||||
currentVersion: 1,
|
||||
createdAt: '2026-01-15T00:00:00.000Z',
|
||||
expiresAt: '2026-04-15T00:00:00.000Z',
|
||||
lastUsedAt: '2026-03-05T09:00:00.000Z',
|
||||
usageCount: 50,
|
||||
updatedAt: '2026-03-05T09:00:00.000Z',
|
||||
updatedBy: 'operator',
|
||||
},
|
||||
],
|
||||
totalCount: 2,
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
totalPages: 1,
|
||||
count: 2,
|
||||
limit: 200,
|
||||
offset: 0,
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.route('**/api/v1/administration/trust-signing/issuers**', (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.endsWith('/trust-signing/issuers')) {
|
||||
return route.fallback();
|
||||
}
|
||||
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{
|
||||
issuerId: 'issuer-001',
|
||||
name: 'Core Root CA',
|
||||
issuerUri: 'https://issuer.example/root',
|
||||
trustLevel: 'full',
|
||||
status: 'active',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-02-01T00:00:00.000Z',
|
||||
updatedBy: 'operator',
|
||||
},
|
||||
{
|
||||
issuerId: 'issuer-002',
|
||||
name: 'Intermediate CA',
|
||||
issuerUri: 'https://issuer.example/intermediate',
|
||||
trustLevel: 'full',
|
||||
status: 'active',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-02-01T00:00:00.000Z',
|
||||
updatedBy: 'operator',
|
||||
},
|
||||
{
|
||||
issuerId: 'issuer-003',
|
||||
name: 'Partner CA',
|
||||
issuerUri: 'https://partner.example/ca',
|
||||
trustLevel: 'partial',
|
||||
status: 'active',
|
||||
createdAt: '2026-01-15T00:00:00.000Z',
|
||||
updatedAt: '2026-02-15T00:00:00.000Z',
|
||||
updatedBy: 'operator',
|
||||
},
|
||||
],
|
||||
count: 3,
|
||||
limit: 200,
|
||||
offset: 0,
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.route('**/api/v1/administration/trust-signing/certificates**', (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.endsWith('/trust-signing/certificates')) {
|
||||
return route.fallback();
|
||||
}
|
||||
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{
|
||||
certificateId: 'cert-001',
|
||||
keyId: 'key-001',
|
||||
issuerId: 'issuer-001',
|
||||
serialNumber: 'SER-001',
|
||||
status: 'active',
|
||||
notBefore: '2026-01-01T00:00:00.000Z',
|
||||
notAfter: '2027-01-01T00:00:00.000Z',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-02-01T00:00:00.000Z',
|
||||
updatedBy: 'operator',
|
||||
},
|
||||
{
|
||||
certificateId: 'cert-002',
|
||||
keyId: 'key-002',
|
||||
issuerId: 'issuer-002',
|
||||
serialNumber: 'SER-002',
|
||||
status: 'active',
|
||||
notBefore: '2026-01-15T00:00:00.000Z',
|
||||
notAfter: '2026-04-15T00:00:00.000Z',
|
||||
createdAt: '2026-01-15T00:00:00.000Z',
|
||||
updatedAt: '2026-02-15T00:00:00.000Z',
|
||||
updatedBy: 'operator',
|
||||
},
|
||||
{
|
||||
certificateId: 'cert-003',
|
||||
keyId: null,
|
||||
issuerId: 'issuer-003',
|
||||
serialNumber: 'SER-003',
|
||||
status: 'active',
|
||||
notBefore: '2026-02-01T00:00:00.000Z',
|
||||
notAfter: '2027-02-01T00:00:00.000Z',
|
||||
createdAt: '2026-02-01T00:00:00.000Z',
|
||||
updatedAt: '2026-03-01T00:00:00.000Z',
|
||||
updatedBy: 'operator',
|
||||
},
|
||||
],
|
||||
count: 3,
|
||||
limit: 200,
|
||||
offset: 0,
|
||||
}),
|
||||
});
|
||||
});
|
||||
await page.route('**/api/v1/administration/trust-signing/transparency-log**', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ item: null }),
|
||||
}),
|
||||
);
|
||||
await page.route('**/api/v1/integrations**', (route) => {
|
||||
const request = route.request();
|
||||
const pathname = new URL(request.url()).pathname;
|
||||
@@ -1435,7 +1486,7 @@ async function setupHarness(page: Page): Promise<void> {
|
||||
requestUrl.includes('/api/v2/security/sbom-explorer') ||
|
||||
requestUrl.includes('/policy/api/') ||
|
||||
requestUrl.includes('/api/v1/integrations') ||
|
||||
requestUrl.includes('/api/v1/trust/') ||
|
||||
requestUrl.includes('/api/v1/administration/trust-signing') ||
|
||||
requestUrl.includes('/api/v1/audit/') ||
|
||||
requestUrl.includes('/api/v1/authority/quotas') ||
|
||||
requestUrl.includes('/api/v1/gateway/rate-limits') ||
|
||||
|
||||
@@ -210,14 +210,79 @@ async function setupHarness(page: Page): Promise<void> {
|
||||
);
|
||||
await page.route('**/doctor/api/v1/doctor/trends**', (route) => fulfillJson(route, []));
|
||||
await page.route('**/api/v1/approvals**', (route) => fulfillJson(route, []));
|
||||
await page.route('**/api/v1/trust/dashboard**', (route) =>
|
||||
fulfillJson(route, {
|
||||
keys: { total: 12, active: 9, expiringSoon: 2, expired: 1, revoked: 0, pendingRotation: 1 },
|
||||
issuers: { total: 8, fullTrust: 3, partialTrust: 3, minimalTrust: 1, untrusted: 1, blocked: 0, averageTrustScore: 86.4 },
|
||||
certificates: { total: 5, valid: 4, expiringSoon: 1, expired: 0, revoked: 0, invalidChains: 0 },
|
||||
recentEvents: [],
|
||||
expiryAlerts: [],
|
||||
})
|
||||
await page.route('**/api/v1/administration/trust-signing', (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.endsWith('/api/v1/administration/trust-signing')) {
|
||||
return route.fallback();
|
||||
}
|
||||
return fulfillJson(route, {
|
||||
inventory: { keys: 12, issuers: 8, certificates: 5 },
|
||||
signals: [],
|
||||
legacyAliases: [],
|
||||
evidenceConsumerPath: '/evidence-audit/proofs',
|
||||
});
|
||||
});
|
||||
await page.route('**/api/v1/administration/trust-signing/keys**', (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.endsWith('/trust-signing/keys')) return route.fallback();
|
||||
return fulfillJson(route, {
|
||||
items: Array.from({ length: 12 }, (_, i) => ({
|
||||
keyId: `key-${String(i + 1).padStart(3, '0')}`,
|
||||
alias: `Key ${i + 1}`,
|
||||
algorithm: 'Ed25519',
|
||||
status: i < 9 ? 'active' : i < 11 ? 'expiring_soon' : 'expired',
|
||||
currentVersion: 1,
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
updatedAt: '2026-02-01T00:00:00Z',
|
||||
updatedBy: 'operator',
|
||||
})),
|
||||
count: 12,
|
||||
limit: 200,
|
||||
offset: 0,
|
||||
});
|
||||
});
|
||||
await page.route('**/api/v1/administration/trust-signing/issuers**', (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.endsWith('/trust-signing/issuers')) return route.fallback();
|
||||
return fulfillJson(route, {
|
||||
items: Array.from({ length: 8 }, (_, i) => ({
|
||||
issuerId: `issuer-${String(i + 1).padStart(3, '0')}`,
|
||||
name: `Issuer ${i + 1}`,
|
||||
issuerUri: `https://issuer.example/${i + 1}`,
|
||||
trustLevel: i < 3 ? 'full' : i < 6 ? 'partial' : i < 7 ? 'minimal' : 'untrusted',
|
||||
status: 'active',
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
updatedAt: '2026-02-01T00:00:00Z',
|
||||
updatedBy: 'operator',
|
||||
})),
|
||||
count: 8,
|
||||
limit: 200,
|
||||
offset: 0,
|
||||
});
|
||||
});
|
||||
await page.route('**/api/v1/administration/trust-signing/certificates**', (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.endsWith('/trust-signing/certificates')) return route.fallback();
|
||||
return fulfillJson(route, {
|
||||
items: Array.from({ length: 5 }, (_, i) => ({
|
||||
certificateId: `cert-${String(i + 1).padStart(3, '0')}`,
|
||||
keyId: `key-${String(i + 1).padStart(3, '0')}`,
|
||||
issuerId: `issuer-${String(i + 1).padStart(3, '0')}`,
|
||||
serialNumber: `SER-${String(i + 1).padStart(3, '0')}`,
|
||||
status: 'active',
|
||||
notBefore: '2026-01-01T00:00:00Z',
|
||||
notAfter: '2027-01-01T00:00:00Z',
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
updatedAt: '2026-02-01T00:00:00Z',
|
||||
updatedBy: 'operator',
|
||||
})),
|
||||
count: 5,
|
||||
limit: 200,
|
||||
offset: 0,
|
||||
});
|
||||
});
|
||||
await page.route('**/api/v1/administration/trust-signing/transparency-log**', (route) =>
|
||||
fulfillJson(route, { item: null })
|
||||
);
|
||||
await page.route(watchlistEntriesRoute, (route) =>
|
||||
fulfillJson(route, { items: watchlistEntries, totalCount: watchlistEntries.length })
|
||||
|
||||
Reference in New Issue
Block a user