From acc5b0d8ceadac5cfc7b153357c233d02249c002 Mon Sep 17 00:00:00 2001 From: Gaston Yelmini Date: Thu, 30 Mar 2023 16:10:35 -0300 Subject: [PATCH 1/2] INT-7619: fix duplicated Audit Config IAM Policy relationship key --- src/steps/resource-manager/index.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/steps/resource-manager/index.ts b/src/steps/resource-manager/index.ts index 78ff3ddc..fb5abc21 100644 --- a/src/steps/resource-manager/index.ts +++ b/src/steps/resource-manager/index.ts @@ -3,6 +3,7 @@ import { createDirectRelationship, createMappedRelationship, RelationshipDirection, + Relationship, } from '@jupiterone/integration-sdk-core'; import { ResourceManagerClient } from './client'; import { @@ -317,6 +318,8 @@ export async function fetchIamPolicyAuditConfig( } } + let iamTargetRelationships: Relationship[] = []; + for (const auditLogConfig of auditConfig.auditLogConfigs || []) { const exemptedMembers = auditLogConfig.exemptedMembers; const logType = auditLogConfig.logType; @@ -340,12 +343,32 @@ export async function fetchIamPolicyAuditConfig( relationshipClass: RelationshipClass.ALLOWS, }); - if (relationship) { - await jobState.addRelationship(relationship); + const foundIndex = iamTargetRelationships.findIndex( + (relationship) => relationship._key === relationship._key, + ); + + if (relationship && foundIndex === -1) { + iamTargetRelationships = [...iamTargetRelationships, relationship]; + } + + if (relationship && foundIndex > -1) { + const currentLogType = + iamTargetRelationships[foundIndex].additionalProperties![ + 'logType' + ]; + iamTargetRelationships[foundIndex].additionalProperties![ + 'logType' + ] = `${currentLogType}, ${logType}`; } } } } + + await Promise.all( + iamTargetRelationships.map((iamTargetRelationship) => + jobState.addRelationship(iamTargetRelationship), + ), + ); }); } From 4a83a10c549cb168839899ea859db5cbf4eb5822 Mon Sep 17 00:00:00 2001 From: Nick Dowmon Date: Thu, 6 Apr 2023 11:47:16 -0400 Subject: [PATCH 2/2] Add flattenAuditLogConfigs to fix duplicateKeyError --- src/steps/resource-manager/index.test.ts | 43 +++++++++ src/steps/resource-manager/index.ts | 110 +++++++++++++---------- 2 files changed, 106 insertions(+), 47 deletions(-) diff --git a/src/steps/resource-manager/index.test.ts b/src/steps/resource-manager/index.test.ts index f338252f..23352216 100644 --- a/src/steps/resource-manager/index.test.ts +++ b/src/steps/resource-manager/index.test.ts @@ -11,6 +11,7 @@ import { buildOrgFolderProjectMappedRelationships, fetchIamPolicyAuditConfig, AUDIT_CONFIG_ENTITY_TYPE, + flattenAuditLogConfigs, } from '.'; import { integrationConfig } from '../../../test/config'; import { @@ -31,6 +32,48 @@ import { fetchApiServices } from '../service-usage'; import { fetchIamManagedRoles, fetchIamServiceAccounts } from '../iam'; import { separateDirectMappedRelationships } from '../../../test/helpers/separateDirectMappedRelationships'; +describe('flattenAuditLogConfigs', () => { + test('should flatten multiple log types onto the same exempted member', () => { + expect( + flattenAuditLogConfigs([ + { exemptedMembers: ['dev@j1.co', 'prod@j1.co'], logType: 'type1' }, + { exemptedMembers: ['dev@j1.co', 'prod@j1.co'], logType: 'type2' }, + ]), + ).toEqual([ + { exemptedMember: 'dev@j1.co', logTypes: ['type1', 'type2'] }, + { exemptedMember: 'prod@j1.co', logTypes: ['type1', 'type2'] }, + ]); + }); + + test('should return empty array for `null` logType', () => { + expect( + flattenAuditLogConfigs([ + { exemptedMembers: ['dev@j1.co'], logType: null }, + ]), + ).toEqual([{ exemptedMember: 'dev@j1.co', logTypes: [] }]); + }); + + test('should return empty array for `undefined` logType', () => { + expect( + flattenAuditLogConfigs([ + { exemptedMembers: ['dev@j1.co'], logType: undefined }, + ]), + ).toEqual([{ exemptedMember: 'dev@j1.co', logTypes: [] }]); + }); + + test('should skip `null` exemptedMembers', () => { + expect( + flattenAuditLogConfigs([{ exemptedMembers: null, logType: 'type1' }]), + ).toEqual([]); + }); + + test('should skip `undefined` exemptedMembers', () => { + expect( + flattenAuditLogConfigs([{ exemptedMembers: null, logType: 'type1' }]), + ).toEqual([]); + }); +}); + describe('#fetchIamPolicyAuditConfig', () => { let recording: Recording; diff --git a/src/steps/resource-manager/index.ts b/src/steps/resource-manager/index.ts index fb5abc21..64cf763f 100644 --- a/src/steps/resource-manager/index.ts +++ b/src/steps/resource-manager/index.ts @@ -3,7 +3,6 @@ import { createDirectRelationship, createMappedRelationship, RelationshipDirection, - Relationship, } from '@jupiterone/integration-sdk-core'; import { ResourceManagerClient } from './client'; import { @@ -55,6 +54,7 @@ import { } from '../service-usage/constants'; import { getServiceApiEntityKey } from '../service-usage/converters'; import { buildIamTargetRelationship } from '../cloud-asset'; +import { cloudresourcemanager_v3 } from 'googleapis'; export * from './constants'; @@ -318,58 +318,74 @@ export async function fetchIamPolicyAuditConfig( } } - let iamTargetRelationships: Relationship[] = []; - - for (const auditLogConfig of auditConfig.auditLogConfigs || []) { - const exemptedMembers = auditLogConfig.exemptedMembers; - const logType = auditLogConfig.logType; - if (exemptedMembers) { - for (const exemptedMember of exemptedMembers) { - const parsedMember = parseIamMember(exemptedMember); - const { identifier: parsedIdentifier, type: parsedMemberType } = - parsedMember; - let principalEntity: Entity | null = null; - if (parsedIdentifier && parsedMemberType === 'serviceAccount') { - principalEntity = await jobState.findEntity(parsedIdentifier); - } + for (const { exemptedMember, logTypes } of flattenAuditLogConfigs( + auditConfig.auditLogConfigs || [], + )) { + const parsedMember = parseIamMember(exemptedMember); + const { identifier: parsedIdentifier, type: parsedMemberType } = + parsedMember; + let principalEntity: Entity | null = null; + if (parsedIdentifier && parsedMemberType === 'serviceAccount') { + principalEntity = await jobState.findEntity(parsedIdentifier); + } + const relationship = buildIamTargetRelationship({ + fromEntity: auditConfigEntity, + principalEntity, + parsedMember, + logger, + projectId: client.projectId, + additionalProperties: { logTypes }, + relationshipClass: RelationshipClass.ALLOWS, + }); + + if (relationship) { + await jobState.addRelationship(relationship); + } + } + }); +} - const relationship = buildIamTargetRelationship({ - fromEntity: auditConfigEntity, - principalEntity, - parsedMember, - logger, - projectId: client.projectId, - additionalProperties: { logType }, - relationshipClass: RelationshipClass.ALLOWS, - }); - - const foundIndex = iamTargetRelationships.findIndex( - (relationship) => relationship._key === relationship._key, - ); - - if (relationship && foundIndex === -1) { - iamTargetRelationships = [...iamTargetRelationships, relationship]; - } +/** + * Reorganizes from objects with an array of exemptedMembers and a single + * logType to a single exemptedMember and an array of logTypes + * + * [ + * { exemptedMembers: ['dev@j1.io'], logType: 'type1' } + * { exemptedMembers: ['dev@j1.io'], logType: 'type2' }, + * ] + * + * => + * + * [ + * { exemptedMember: ['dev@j1.io'], logTypes: ['type1', 'type2' ] } + * ] + * + */ +export function flattenAuditLogConfigs( + auditLogConfigs: cloudresourcemanager_v3.Schema$AuditLogConfig[], +): { + exemptedMember: string; + logTypes: string[]; +}[] { + const exemptedMemberToLogTypesMap: { [exemptedMember: string]: string[] } = + {}; + for (const { exemptedMembers, logType } of auditLogConfigs) { + for (const exemptedMember of exemptedMembers || []) { + if (exemptedMember) { + if (!exemptedMemberToLogTypesMap[exemptedMember]) { + exemptedMemberToLogTypesMap[exemptedMember] = []; + } - if (relationship && foundIndex > -1) { - const currentLogType = - iamTargetRelationships[foundIndex].additionalProperties![ - 'logType' - ]; - iamTargetRelationships[foundIndex].additionalProperties![ - 'logType' - ] = `${currentLogType}, ${logType}`; - } + if (logType) { + exemptedMemberToLogTypesMap[exemptedMember].push(logType); } } } + } - await Promise.all( - iamTargetRelationships.map((iamTargetRelationship) => - jobState.addRelationship(iamTargetRelationship), - ), - ); - }); + return Object.entries(exemptedMemberToLogTypesMap).map( + ([exemptedMember, logTypes]) => ({ exemptedMember, logTypes }), + ); } export const resourceManagerSteps: GoogleCloudIntegrationStep[] = [