diff --git a/README.md b/README.md index 411d1ea1..1aa7a37d 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi | clientVpnEndpoint | securityGroup | | cloud9 | | | cloudformationStack | cloudformationStack, iamRole, sns | -| cloudformationStackSet | | +| cloudformationStackSet | iamRole | | cloudfront | elb, s3 | | cloudtrail | cloudwatch, cloudwatchLog, kms, s3, sns | | cloudwatch | cloudtrail, cloudwatchLog, sns | @@ -124,7 +124,7 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi | iamServerCertificate | | | iamUser | iamGroup | | iamPolicy | iamRole, iamGroup | -| iamRole | appSync, codebuild, configurationRecorder, ec2, iamInstanceProfile, iamPolicy, eksCluster, ecsService, flowLog, glueJob, managedAirflow, sageMakerNotebookInstance, systemsManagerInstance guardDutyDetector, lambda, kinesisFirehose, rdsCluster | +| iamRole | appSync, cloudformationStackSet, codebuild, configurationRecorder, ec2, iamInstanceProfile, iamPolicy, eksCluster, ecsService, flowLog, glueJob, managedAirflow, sageMakerNotebookInstance, systemsManagerInstance guardDutyDetector, lambda, kinesisFirehose, rdsCluster | | iamGroup | iamUser, iamPolicy | | igw | vpc | | iot | | diff --git a/src/services/cloudFormationStackSet/connections.ts b/src/services/cloudFormationStackSet/connections.ts index d35e823e..1b304e4d 100644 --- a/src/services/cloudFormationStackSet/connections.ts +++ b/src/services/cloudFormationStackSet/connections.ts @@ -1,34 +1,60 @@ -// TODO: Enable when IAM is added -// import { ServiceConnection } from '@cloudgraph/sdk'; -// import { Stack } from 'aws-sdk/clients/cloudformation'; -// import { TagMap } from '../../types' +import isEmpty from 'lodash/isEmpty' +import { ServiceConnection } from '@cloudgraph/sdk'; +import { StackSet } from 'aws-sdk/clients/cloudformation'; +import { TagMap } from '../../types' +import services from '../../enums/services' +import { RawAwsIamRole } from '../iamRole/data' +import { globalRegionName } from '../../enums/regions' -// /** -// * Cloud Formation StackSet -// */ +/** + * Cloud Formation StackSet + */ -// export default ({ -// service: cfStackSet, -// data, -// region, -// }: { -// data: { name: string; data: { [property: string]: any[] } }[] -// service: Stack & { -// region: string -// Tags: TagMap, -// }, -// region: string -// }): { [key: string]: ServiceConnection[] } => { -// const connections: ServiceConnection[] = [] +export default ({ + service: cfStackSet, + data, + region, +}: { + data: { name: string; data: { [property: string]: any[] } }[] + service: StackSet & { + region: string + Tags: TagMap, + }, + region: string +}): { [key: string]: ServiceConnection[] } => { + const connections: ServiceConnection[] = [] -// const { -// StackId: id, -// // TODO add connection role -// // AdministrationRoleARN: administrationRoleARN -// } = cfStackSet + const { + StackSetId: id, + AdministrationRoleARN: administrationRoleARN, + ExecutionRoleName: executionRoleName, + } = cfStackSet -// const cfStackSetResult = { -// [id]: connections, -// } -// return cfStackSetResult -// } \ No newline at end of file + /** + * Find related IAM Roles + */ + const roles: { name: string; data: { [property: string]: any[] } } = + data.find(({ name }) => name === services.iamRole) + if (roles?.data?.[globalRegionName]) { + const dataAtRegion: RawAwsIamRole[] = roles.data[globalRegionName].filter( + role => role.Arn === administrationRoleARN || role.RoleName === executionRoleName + ) + if (!isEmpty(dataAtRegion)) { + for (const instance of dataAtRegion) { + const { Arn: arn }: RawAwsIamRole = instance + + connections.push({ + id: arn, + resourceType: services.iamRole, + relation: 'child', + field: 'iamRoles', + }) + } + } + } + + const cfStackSetResult = { + [id]: connections, + } + return cfStackSetResult +} \ No newline at end of file diff --git a/src/services/cloudFormationStackSet/index.ts b/src/services/cloudFormationStackSet/index.ts index f5050f07..ba027f1f 100644 --- a/src/services/cloudFormationStackSet/index.ts +++ b/src/services/cloudFormationStackSet/index.ts @@ -2,7 +2,7 @@ import { Service } from '@cloudgraph/sdk'; import BaseService from '../base'; import format from './format'; import getData from './data'; -// import getConnections from './connections' +import getConnections from './connections' import mutation from './mutation'; export default class AwsCloudFormation extends BaseService implements Service { @@ -10,8 +10,7 @@ export default class AwsCloudFormation extends BaseService implements Service { getData = getData.bind(this); - // TODO: Enable when IAM is added - // getConnections = getConnections.bind(this) + getConnections = getConnections.bind(this) mutation = mutation; } diff --git a/src/services/cloudFormationStackSet/schema.graphql b/src/services/cloudFormationStackSet/schema.graphql index 0edf1146..5dbd9e5b 100644 --- a/src/services/cloudFormationStackSet/schema.graphql +++ b/src/services/cloudFormationStackSet/schema.graphql @@ -40,7 +40,7 @@ type awsCloudFormationStackAutoDeploymentConfig { enabled: String @search(by: [hash, regexp]) retainStacksOnAccountRemoval: String @search(by: [hash, regexp]) } -# TODO: add accountId to cloudFormationStackSet + type awsCloudFormationStackSet implements awsBaseService @key(fields: "arn") { name: String @search(by: [hash, regexp]) description: String @search(by: [hash, regexp]) @@ -55,6 +55,5 @@ type awsCloudFormationStackSet implements awsBaseService @key(fields: "arn") { autoDeploymentConfig: awsCloudFormationStackAutoDeploymentConfig permissionModel: String @search(by: [hash, regexp]) organizationalUnitIds: [String] @search + iamRoles: [awsIamRole] @hasInverse(field: cloudFormationStackSet) } - -# TODO: add iam role connection using AdministrationRoleARN (also see if a connection can be made using ExecutionRoleName) diff --git a/src/services/iamRole/schema.graphql b/src/services/iamRole/schema.graphql index d093d2db..cedfe10e 100644 --- a/src/services/iamRole/schema.graphql +++ b/src/services/iamRole/schema.graphql @@ -31,4 +31,5 @@ type awsIamRole implements awsBaseService @key(fields: "id") { rdsClusterMonitoringRole: [awsRdsCluster] @hasInverse(field: monitoringIamRole) rdsClusterIamRoles: [awsRdsCluster] @hasInverse(field: iamRoles) + cloudFormationStackSet: [awsCloudFormationStackSet] @hasInverse(field: iamRoles) } diff --git a/src/types/generated.ts b/src/types/generated.ts index 91b36dc5..fd4c9f7d 100644 --- a/src/types/generated.ts +++ b/src/types/generated.ts @@ -701,6 +701,7 @@ export type AwsCloudFormationStackSet = AwsBaseService & { description?: Maybe; driftDetectionDetail?: Maybe; executionRoleName?: Maybe; + iamRoles?: Maybe>>; name?: Maybe; organizationalUnitIds?: Maybe>>; parameters?: Maybe>>; @@ -3049,6 +3050,7 @@ export type AwsIamRole = AwsBaseService & { appSync?: Maybe>>; assumeRolePolicy?: Maybe; cloudFormationStack?: Maybe>>; + cloudFormationStackSet?: Maybe>>; codebuilds?: Maybe>>; cognitoUserPools?: Maybe>>; configurationRecorder?: Maybe>>; diff --git a/tests/aws_cloudFormationStackSet.test.ts b/tests/aws_cloudFormationStackSet.test.ts index 88839aaa..d40510e1 100644 --- a/tests/aws_cloudFormationStackSet.test.ts +++ b/tests/aws_cloudFormationStackSet.test.ts @@ -1,14 +1,16 @@ import CloudGraph from '@cloudgraph/sdk' - import CloudFormationClass from '../src/services/cloudFormationStackSet' - -import { credentials, region } from '../src/properties/test' +import IamRoleService from '../src/services/iamRole' +import { account, credentials, region } from '../src/properties/test' import { initTestConfig } from '../src/utils' import { RawAwsCloudFormationStackSet } from '../src/services/cloudFormationStackSet/data' +import services from '../src/enums/services' describe('Cloud formation Service Test: ', () => { let getDataResult let formatResult + let cfConnections + let cfStackSetId initTestConfig() @@ -16,6 +18,7 @@ describe('Cloud formation Service Test: ', () => { async () => new Promise(async resolve => { try { + const iamRoleService = new IamRoleService({ logger: CloudGraph.logger }) const cfClass = new CloudFormationClass({ logger: CloudGraph.logger }) getDataResult = await cfClass.getData({ @@ -27,6 +30,29 @@ describe('Cloud formation Service Test: ', () => { (item: RawAwsCloudFormationStackSet) => cfClass.format({ service: item, region }) ) + + // Get IAM Role data + const securityGroupData = await iamRoleService.getData({ + credentials, + regions: region, + }) + + const [cfStackSet] = getDataResult[region] + cfStackSetId = cfStackSet.StackSetId + + cfConnections = cfClass.getConnections({ + service: cfStackSet, + data: [ + { + name: services.iamRole, + data: securityGroupData, + account, + region, + }, + ], + region, + account, + }) } catch (error) { console.error(error) // eslint-disable-line no-console } @@ -67,4 +93,17 @@ describe('Cloud formation Service Test: ', () => { ) }) }) + + describe('connections', () => { + test('should verify the connection to iam roles', () => { + const iamRoleConnections = cfConnections[ + cfStackSetId + ]?.filter( + connection => connection.resourceType === services.iamRole + ) + + expect(iamRoleConnections).toBeDefined() + expect(iamRoleConnections.length).toBe(1) + }) + }) })