From 4ee0cbeb76dcbb9c41c72ee5f0669b4ab6eeb378 Mon Sep 17 00:00:00 2001 From: vitaliytv Date: Fri, 25 Sep 2026 14:20:16 +0300 Subject: [PATCH] fix: resolve KCC referenced resource identities --- lib/get-resources.mjs | 32 +++++++++++++++++++++--------- test/iam-policy-inventory.test.mjs | 22 ++++++++++++++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/lib/get-resources.mjs b/lib/get-resources.mjs index 236bb25..18198e8 100644 --- a/lib/get-resources.mjs +++ b/lib/get-resources.mjs @@ -334,7 +334,16 @@ function kccSaId(ref, project) { return '?'; } -function kccPolicyMemberId(item, project, runServiceRefs) { +export function kccBucketId(ref, bucketRefs) { + return ref.external || bucketRefs.get(ref.name) || ref.name || '?'; +} + +export function canonicalPubSubId(id, project, collection) { + if (!id || id.startsWith('projects/')) return id; + return `projects/${project}/${collection}/${id}`; +} + +function kccPolicyMemberId(item, project, runServiceRefs, bucketRefs) { const ref = (item.spec && item.spec.resourceRef) || {}; switch (ref.kind) { case 'Project': @@ -344,7 +353,7 @@ function kccPolicyMemberId(item, project, runServiceRefs) { case 'ArtifactRegistryRepository': return 'ar/' + kccArId(ref); case 'StorageBucket': - return 'bucket/' + (ref.external || ref.name || '?'); + return 'bucket/' + kccBucketId(ref, bucketRefs); case 'RunService': { const external = ref.external || ''; const m = external.match(/projects\/[^/]+\/locations\/([^/]+)\/services\/([^/]+)$/); @@ -524,10 +533,8 @@ export async function collectNamespace(namespace, { includeSystem = false } = {} let liveBucket = byType('storage.googleapis.com/Bucket').map((a) => a.displayName); if (!includeSystem) liveBucket = liveBucket.filter((b) => !isSystemBucket(b, project)); push('StorageBucket', liveBucket, 'gcp'); - push('StorageBucket', await kccField( - 'StorageBucket', namespace, - (i) => i.spec && i.spec.resourceID, - ), 'kcc'); + const storageBucketItems = await listCustomObjects('StorageBucket', namespace); + push('StorageBucket', storageBucketItems.map((i) => resourceId(i)).filter(Boolean), 'kcc'); // --- ComputeAddress (регіональні й глобальні) ------------------------------- // Asset Inventory інколи повертає вже видалені адреси (перевірено @@ -631,9 +638,15 @@ export async function collectNamespace(namespace, { includeSystem = false } = {} push('EventarcTrigger', await kccField('EventarcTrigger', namespace, (i) => kccScopedId(i)), 'kcc'); push('PubSubTopic', byType('pubsub.googleapis.com/Topic').map((a) => a.displayName), 'gcp'); - push('PubSubTopic', await kccField('PubSubTopic', namespace, resourceId), 'kcc'); + push('PubSubTopic', await kccField( + 'PubSubTopic', namespace, + (i) => canonicalPubSubId(resourceId(i), project, 'topics'), + ), 'kcc'); push('PubSubSubscription', byType('pubsub.googleapis.com/Subscription').map((a) => a.displayName), 'gcp'); - push('PubSubSubscription', await kccField('PubSubSubscription', namespace, resourceId), 'kcc'); + push('PubSubSubscription', await kccField( + 'PubSubSubscription', namespace, + (i) => canonicalPubSubId(resourceId(i), project, 'subscriptions'), + ), 'kcc'); push('SecretManagerSecret', byType('secretmanager.googleapis.com/Secret').map((a) => a.displayName), 'gcp'); push('SecretManagerSecret', await kccField('SecretManagerSecret', namespace, resourceId), 'kcc'); @@ -719,10 +732,11 @@ export async function collectNamespace(namespace, { includeSystem = false } = {} push('IAMPolicyMember', liveIamPolicyResources(iamPolicies, project, includeSystem), 'gcp'); const runServiceRefs = new Map(runServiceItems.map((i) => [i.metadata && i.metadata.name, kccScopedId(i)])); + const bucketRefs = new Map(storageBucketItems.map((i) => [i.metadata && i.metadata.name, resourceId(i)])); push('IAMPolicyMember', await kccField( 'IAMPolicyMember', namespace, (item) => { - const rid = kccPolicyMemberId(item, project, runServiceRefs); + const rid = kccPolicyMemberId(item, project, runServiceRefs, bucketRefs); const spec = item.spec || {}; return { id: `${rid}/${spec.role}/${spec.member}`, diff --git a/test/iam-policy-inventory.test.mjs b/test/iam-policy-inventory.test.mjs index 4ab7d8c..23a6a3f 100644 --- a/test/iam-policy-inventory.test.mjs +++ b/test/iam-policy-inventory.test.mjs @@ -1,11 +1,33 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { + canonicalPubSubId, + kccBucketId, liveIamPolicyIds, liveIamPolicyResources, replaceBucketIamPolicies, } from '../lib/get-resources.mjs'; +test('canonicalizes short KCC Pub/Sub IDs to Cloud Asset full paths', () => { + assert.equal( + canonicalPubSubId('download-record', 'abie-ua', 'topics'), + 'projects/abie-ua/topics/download-record', + ); + assert.equal( + canonicalPubSubId('projects/abie-ua/topics/download-record', 'abie-ua', 'topics'), + 'projects/abie-ua/topics/download-record', + ); +}); + +test('resolves KCC bucket references through the bucket resourceID', () => { + const refs = new Map([['dev-nessie-warehouse', 'dev-nessie-warehouse-abie-ua']]); + + assert.equal( + kccBucketId({ name: 'dev-nessie-warehouse' }, refs), + 'dev-nessie-warehouse-abie-ua', + ); +}); + const searchEntries = [ { assetType: 'cloudresourcemanager.googleapis.com/Project',