From 832fc7bdff1a02d89fbdbf50d1d030669d7568b9 Mon Sep 17 00:00:00 2001 From: "mariano.pizarro" Date: Wed, 6 Apr 2022 17:05:49 -0300 Subject: [PATCH 1/3] feat: Handle TODOs for EC2 --- src/services/ec2/connections.ts | 74 ++++++++++++++++--- src/services/ec2/schema.graphql | 6 +- src/services/ec2/utils.ts | 35 +++++++++ src/services/ecsContainer/schema.graphql | 2 +- src/services/eksCluster/connections.ts | 2 +- src/services/eksCluster/schema.graphql | 3 +- .../schema.graphql | 3 +- src/services/subnet/schema.graphql | 4 +- src/types/generated.ts | 12 ++- 9 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 src/services/ec2/utils.ts diff --git a/src/services/ec2/connections.ts b/src/services/ec2/connections.ts index 45a20eca..af031d19 100644 --- a/src/services/ec2/connections.ts +++ b/src/services/ec2/connections.ts @@ -16,6 +16,9 @@ import { RawAwsSubnet } from '../subnet/data' import { RawAwsEcsContainer } from '../ecsContainer/data' import { RawAwsSystemsManagerInstance } from '../systemsManagerInstance/data' import { ssmManagedInstanceArn } from '../../utils/generateArns' +import { RawAwsElasticBeanstalkEnv } from '../elasticBeanstalkEnvironment/data' +import { RawAwsEksCluster } from '../eksCluster/data' +import { getEksClusterName, getElasticBeanstalkEnvId } from './utils' /** * EC2 @@ -44,6 +47,7 @@ export default ({ SecurityGroups: instanceSecurityGroups = [], NetworkInterfaces: instanceNetworkInterfaces = [], SubnetId: subnetId, + Tags: tags, } = instance /** @@ -189,17 +193,37 @@ export default ({ id: subnet.SubnetId, resourceType: services.subnet, relation: 'child', - field: 'subnet', + field: 'subnets', }) } } } /** - * Find EKS + * Find EKS cluster * related to this EC2 */ - // TODO: Implement when eks service is ready + const eksClusterName = getEksClusterName(tags) + const eksClusters: { + name: string + data: { [property: string]: any[] } + } = data.find(({ name }) => name === services.eksCluster) + if (eksClusters?.data?.[region]) { + const eksClustersInRegion: RawAwsEksCluster[] = eksClusters.data[ + region + ].filter(({ name }: RawAwsEksCluster) => name === eksClusterName) + + if (!isEmpty(eksClustersInRegion)) { + for (const eksCluster of eksClustersInRegion) { + connections.push({ + id: eksCluster.arn, + resourceType: services.eksCluster, + relation: 'child', + field: 'eksCluster', + }) + } + } + } /** * Find ECS Container @@ -210,9 +234,9 @@ export default ({ data: { [property: string]: any[] } } = data.find(({ name }) => name === services.ecsContainer) if (ecsContainers?.data?.[region]) { - const containersInRegion: RawAwsEcsContainer[] = ecsContainers.data[region].filter( - ({ ec2InstanceId }) => ec2InstanceId === id - ) + const containersInRegion: RawAwsEcsContainer[] = ecsContainers.data[ + region + ].filter(({ ec2InstanceId }) => ec2InstanceId === id) if (!isEmpty(containersInRegion)) { for (const container of containersInRegion) { @@ -230,18 +254,24 @@ export default ({ * Find SSM managed instances * related to this EC2 instance */ - const instances: { + const instances: { name: string data: { [property: string]: any[] } } = data.find(({ name }) => name === services.systemsManagerInstance) if (instances?.data?.[region]) { - const dataInRegion: RawAwsSystemsManagerInstance[] = instances.data[region].filter( + const dataInRegion: RawAwsSystemsManagerInstance[] = instances.data[ + region + ].filter( ({ InstanceId }: RawAwsSystemsManagerInstance) => InstanceId === id ) if (!isEmpty(dataInRegion)) { for (const ssmInstance of dataInRegion) { - const arn = ssmManagedInstanceArn({ region, account, name: ssmInstance.InstanceId }) + const arn = ssmManagedInstanceArn({ + region, + account, + name: ssmInstance.InstanceId, + }) connections.push({ id: arn, resourceType: services.systemsManagerInstance, @@ -254,9 +284,31 @@ export default ({ /** * Find Elastic Beanstalk - * related to this EC2 loadbalancer + * related to this EC2 instance */ - // TODO: Implement when eb service is ready + const elasticBeanstalkEnvId = getElasticBeanstalkEnvId(tags) + const elasticBeanstalkEnvs: { + name: string + data: { [property: string]: any[] } + } = data.find(({ name }) => name === services.elasticBeanstalkEnv) + if (elasticBeanstalkEnvs?.data?.[region]) { + const elasticBeanstalkEnvsInRegion: RawAwsElasticBeanstalkEnv[] = + elasticBeanstalkEnvs.data[region].filter( + ({ EnvironmentId }: RawAwsElasticBeanstalkEnv) => + elasticBeanstalkEnvId === EnvironmentId + ) + + if (!isEmpty(elasticBeanstalkEnvsInRegion)) { + for (const elasticBeanstalkEnv of elasticBeanstalkEnvsInRegion) { + connections.push({ + id: elasticBeanstalkEnv.EnvironmentId, + resourceType: services.elasticBeanstalkEnv, + relation: 'child', + field: 'elasticBeanstalkEnv', + }) + } + } + } const ec2Result = { [id]: connections, diff --git a/src/services/ec2/schema.graphql b/src/services/ec2/schema.graphql index bbbb778a..b298273c 100644 --- a/src/services/ec2/schema.graphql +++ b/src/services/ec2/schema.graphql @@ -116,8 +116,10 @@ type awsEc2 @key(fields: "arn") { eip: [awsEip] @hasInverse(field: ec2Instance) networkInterfaces: [awsNetworkInterface] @hasInverse(field: ec2Instance) securityGroups: [awsSecurityGroup] @hasInverse(field: ec2Instance) - subnet: [awsSubnet] @hasInverse(field: ec2Instance) #change to plural - ecsContainer: [awsEcsContainer] @hasInverse(field: ec2Instance) #change to plural + subnets: [awsSubnet] @hasInverse(field: ec2Instances) + ecsContainer: [awsEcsContainer] @hasInverse(field: ec2Instances) emrInstance: [awsEmrInstance] @hasInverse(field: ec2Instance) systemsManagerInstance: [awsSystemsManagerInstance] @hasInverse(field: ec2Instance) + eksCluster: [awsEksCluster] @hasInverse(field: ec2Instances) + elasticBeanstalkEnv: [awsElasticBeanstalkEnv] @hasInverse(field: ec2Instances) } diff --git a/src/services/ec2/utils.ts b/src/services/ec2/utils.ts new file mode 100644 index 00000000..6dd3f04e --- /dev/null +++ b/src/services/ec2/utils.ts @@ -0,0 +1,35 @@ +import { Tag } from 'aws-sdk/clients/ec2' +import { isEmpty, last } from 'lodash' + +const clusterTag = 'kubernetes.io/cluster/' +const environmentIdTag = 'elasticbeanstalk:environment-id' + +export const getEksClusterName = (tags?: Tag[]): string => { + if (isEmpty(tags)) { + return '' + } + let eksClusterName = '' + Object.keys(tags)?.some(key => { + const isMatch = key.includes(clusterTag) + if (isMatch) { + eksClusterName = last(key.split('/')) + } + return isMatch + }) + return eksClusterName +} + +export const getElasticBeanstalkEnvId = (tags?: Tag[]): string => { + if (isEmpty(tags)) { + return '' + } + let elasticBeanstalkId = '' + Object.keys(tags)?.some(key => { + const isMatch = key.includes(environmentIdTag) + if (isMatch) { + elasticBeanstalkId = tags[key] + } + return isMatch + }) + return elasticBeanstalkId +} diff --git a/src/services/ecsContainer/schema.graphql b/src/services/ecsContainer/schema.graphql index fc44e88d..8b2bb345 100644 --- a/src/services/ecsContainer/schema.graphql +++ b/src/services/ecsContainer/schema.graphql @@ -18,7 +18,7 @@ type awsEcsContainer @key(fields: "arn") { registeredAt: String @search(by: [hash, regexp]) attachments: [awsEcsAttachment] tags: [awsRawTag] - ec2Instance: [awsEc2] @hasInverse(field: ecsContainer) + ec2Instances: [awsEc2] @hasInverse(field: ecsContainer) ecsTask: [awsEcsTask] @hasInverse(field: ecsContainer) } diff --git a/src/services/eksCluster/connections.ts b/src/services/eksCluster/connections.ts index 41acd9d0..d5c56d09 100644 --- a/src/services/eksCluster/connections.ts +++ b/src/services/eksCluster/connections.ts @@ -120,7 +120,7 @@ export default ({ id: subnet.SubnetId, resourceType: services.subnet, relation: 'child', - field: 'subnet', + field: 'subnets', }) } } diff --git a/src/services/eksCluster/schema.graphql b/src/services/eksCluster/schema.graphql index e4a6a990..87aebdd8 100644 --- a/src/services/eksCluster/schema.graphql +++ b/src/services/eksCluster/schema.graphql @@ -20,8 +20,9 @@ type awsEksCluster @key(fields: "arn") { iamRoles: [awsIamRole] @hasInverse(field: eksCluster) kms: [awsKms] @hasInverse(field: eksCluster) securityGroups: [awsSecurityGroup] @hasInverse(field: eksCluster) - subnet: [awsSubnet] @hasInverse(field: eksCluster) #change to plural + subnets: [awsSubnet] @hasInverse(field: eksCluster) vpc: [awsVpc] @hasInverse(field: eksCluster) + ec2Instances: [awsEc2] @hasInverse(field: eksCluster) } type awsEksVpcConfigResponse { diff --git a/src/services/elasticBeanstalkEnvironment/schema.graphql b/src/services/elasticBeanstalkEnvironment/schema.graphql index 8e82839b..fa2ee2e7 100644 --- a/src/services/elasticBeanstalkEnvironment/schema.graphql +++ b/src/services/elasticBeanstalkEnvironment/schema.graphql @@ -14,8 +14,9 @@ type awsElasticBeanstalkEnv @key(fields: "arn") { solutionStackName: String @search(by: [hash, regexp]) tier: String @search(by: [hash]) versionLabel: String @search(by: [hash, regexp]) - elasticBeanstalkApp: [awsElasticBeanstalkApp] @hasInverse(field: elasticBeanstalkEnv) tags: [awsRawTag] + elasticBeanstalkApp: [awsElasticBeanstalkApp] @hasInverse(field: elasticBeanstalkEnv) + ec2Instances: [awsEc2] @hasInverse(field: elasticBeanstalkEnv) } type awsElasticBeanstalkEnvSetting { diff --git a/src/services/subnet/schema.graphql b/src/services/subnet/schema.graphql index baee31c2..75490efa 100644 --- a/src/services/subnet/schema.graphql +++ b/src/services/subnet/schema.graphql @@ -14,7 +14,7 @@ type awsSubnet @key(fields: "id") { tags: [awsRawTag] alb: [awsAlb] @hasInverse(field: subnet) #change to plural asg: [awsAsg] @hasInverse(field: subnet) #change to plural - ec2Instance: [awsEc2] @hasInverse(field: subnet) #change to plural + ec2Instances: [awsEc2] @hasInverse(field: subnets) #change to plural elb: [awsElb] @hasInverse(field: subnet) #change to plural lambda: [awsLambda] @hasInverse(field: subnet) #change to plural natGateway: [awsNatGateway] @hasInverse(field: subnet) #change to plural @@ -22,7 +22,7 @@ type awsSubnet @key(fields: "id") { routeTable: [awsRouteTable] @hasInverse(field: subnet) vpc: [awsVpc] @hasInverse(field: subnet) rdsDbInstance: [awsRdsDbInstance] @hasInverse(field: subnet) #change to plural - eksCluster: [awsEksCluster] @hasInverse(field: subnet) #change to plural + eksCluster: [awsEksCluster] @hasInverse(field: subnets) ecsService: [awsEcsService] @hasInverse(field: subnet) #change to plural efsMountTarget: [awsEfsMountTarget] @hasInverse(field: subnet) #change to plural flowLogs: [awsFlowLog] @hasInverse(field: subnet) #change to singular diff --git a/src/types/generated.ts b/src/types/generated.ts index 2e759360..aa33fe12 100644 --- a/src/types/generated.ts +++ b/src/types/generated.ts @@ -1512,6 +1512,8 @@ export type AwsEc2 = { ebsOptimized?: Maybe; ecsContainer?: Maybe>>; eip?: Maybe>>; + eksCluster?: Maybe>>; + elasticBeanstalkEnv?: Maybe>>; elasticIps?: Maybe; emrInstance?: Maybe>>; ephemeralBlockDevices?: Maybe>>; @@ -1540,7 +1542,7 @@ export type AwsEc2 = { securityGroupIds?: Maybe>>; securityGroups?: Maybe>>; sourceDestCheck?: Maybe; - subnet?: Maybe>>; + subnets?: Maybe>>; systemsManagerInstance?: Maybe>>; tags?: Maybe>>; tenancy?: Maybe; @@ -1685,8 +1687,8 @@ export type AwsEcsContainer = { attachments?: Maybe>>; attributes?: Maybe>>; capacityProviderName?: Maybe; - ec2Instance?: Maybe>>; ec2InstanceId?: Maybe; + ec2Instances?: Maybe>>; ecsTask?: Maybe>>; id: Scalars['String']; pendingTasksCount?: Maybe; @@ -2321,6 +2323,7 @@ export type AwsEksCluster = { certificateAuthority?: Maybe; clientRequestToken?: Maybe; createdAt?: Maybe; + ec2Instances?: Maybe>>; encryptionConfig?: Maybe>>; endpoint?: Maybe; iamRoles?: Maybe>>; @@ -2335,7 +2338,7 @@ export type AwsEksCluster = { resourcesVpcConfig?: Maybe; securityGroups?: Maybe>>; status?: Maybe; - subnet?: Maybe>>; + subnets?: Maybe>>; tags?: Maybe>>; version?: Maybe; vpc?: Maybe>>; @@ -2594,6 +2597,7 @@ export type AwsElasticBeanstalkEnv = { arn: Scalars['String']; cname?: Maybe; description?: Maybe; + ec2Instances?: Maybe>>; elasticBeanstalkApp?: Maybe>>; endpointUrl?: Maybe; id: Scalars['String']; @@ -4141,7 +4145,7 @@ export type AwsSubnet = { codebuilds?: Maybe>>; defaultForAz?: Maybe; dmsReplicationInstances?: Maybe>>; - ec2Instance?: Maybe>>; + ec2Instances?: Maybe>>; ecsService?: Maybe>>; efsMountTarget?: Maybe>>; eksCluster?: Maybe>>; From 652f2f7b9bf551cab37afa2893bea5bb504699c9 Mon Sep 17 00:00:00 2001 From: "mariano.pizarro" Date: Thu, 7 Apr 2022 11:19:53 -0300 Subject: [PATCH 2/3] feat: Added iamInstanceProfile connection --- src/services/ec2/connections.ts | 31 ++++++++++++++++++- src/services/ec2/format.ts | 2 -- src/services/ec2/schema.graphql | 2 +- .../iamInstanceProfile/schema.graphql | 1 + src/types/generated.ts | 3 +- 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/services/ec2/connections.ts b/src/services/ec2/connections.ts index af031d19..6041e3f1 100644 --- a/src/services/ec2/connections.ts +++ b/src/services/ec2/connections.ts @@ -19,6 +19,8 @@ import { ssmManagedInstanceArn } from '../../utils/generateArns' import { RawAwsElasticBeanstalkEnv } from '../elasticBeanstalkEnvironment/data' import { RawAwsEksCluster } from '../eksCluster/data' import { getEksClusterName, getElasticBeanstalkEnvId } from './utils' +import { RawAwsInstanceProfile } from '../iamInstanceProfile/data' +import { globalRegionName } from '../../enums/regions' /** * EC2 @@ -48,6 +50,7 @@ export default ({ NetworkInterfaces: instanceNetworkInterfaces = [], SubnetId: subnetId, Tags: tags, + IamInstanceProfile: iamInstanceProfile, } = instance /** @@ -293,7 +296,7 @@ export default ({ } = data.find(({ name }) => name === services.elasticBeanstalkEnv) if (elasticBeanstalkEnvs?.data?.[region]) { const elasticBeanstalkEnvsInRegion: RawAwsElasticBeanstalkEnv[] = - elasticBeanstalkEnvs.data[region].filter( + elasticBeanstalkEnvs.data[region].filter( ({ EnvironmentId }: RawAwsElasticBeanstalkEnv) => elasticBeanstalkEnvId === EnvironmentId ) @@ -310,6 +313,32 @@ export default ({ } } + /** + * Find IAM Instance Profiles + * related to this EC2 instance + */ + const iamInstanceProfiles: { + name: string + data: { [property: string]: any[] } + } = data.find(({ name }) => name === services.iamInstanceProfile) + if (iamInstanceProfiles?.data?.[globalRegionName]) { + const dataAtRegion: RawAwsInstanceProfile[] = iamInstanceProfiles.data[ + globalRegionName + ].filter(instanceProfile => instanceProfile.Arn === iamInstanceProfile?.Arn) + if (!isEmpty(dataAtRegion)) { + for (const iamInstance of dataAtRegion) { + const { InstanceProfileId: instanceProfileId }: RawAwsInstanceProfile = iamInstance + + connections.push({ + id: instanceProfileId, + resourceType: services.iamInstanceProfile, + relation: 'child', + field: 'iamInstanceProfile', + }) + } + } + } + const ec2Result = { [id]: connections, } diff --git a/src/services/ec2/format.ts b/src/services/ec2/format.ts index a9058b5e..6d56ff4d 100644 --- a/src/services/ec2/format.ts +++ b/src/services/ec2/format.ts @@ -45,7 +45,6 @@ export default ({ fingerprint, tags: keyPairTags = {}, } = {}, - IamInstanceProfile: iamInstanceProfile, cloudWatchMetricData, PlatformDetails: platformDetails, InstanceLifecycle: instanceLifecycle, @@ -114,7 +113,6 @@ export default ({ sourceDestCheck: sourceDestCheck ? t.yes : t.no, availabilityZone: placement?.AvailabilityZone || '', cpuThreadsPerCore: cpuOptions?.ThreadsPerCore || 0, - iamInstanceProfile: iamInstanceProfile?.Arn || '', deletionProtection: deletionProtection ? t.yes : t.no, primaryNetworkInterface: networkInterfaceId, metadataOptions: { diff --git a/src/services/ec2/schema.graphql b/src/services/ec2/schema.graphql index b298273c..7f66f582 100644 --- a/src/services/ec2/schema.graphql +++ b/src/services/ec2/schema.graphql @@ -96,7 +96,6 @@ type awsEc2 @key(fields: "arn") { sourceDestCheck: String @search(by: [hash, regexp]) availabilityZone: String @search(by: [hash, regexp]) cpuThreadsPerCore: Int @search - iamInstanceProfile: String @search(by: [hash, regexp]) # TODO: use to make a connection to a iamRole deletionProtection: String @search(by: [hash, regexp]) dailyCost: awsTotalBillingInfo primaryNetworkInterface: String @search(by: [hash, regexp]) @@ -122,4 +121,5 @@ type awsEc2 @key(fields: "arn") { systemsManagerInstance: [awsSystemsManagerInstance] @hasInverse(field: ec2Instance) eksCluster: [awsEksCluster] @hasInverse(field: ec2Instances) elasticBeanstalkEnv: [awsElasticBeanstalkEnv] @hasInverse(field: ec2Instances) + iamInstanceProfile: [awsIamInstanceProfile] @hasInverse(field: ec2Instances) } diff --git a/src/services/iamInstanceProfile/schema.graphql b/src/services/iamInstanceProfile/schema.graphql index f33a3dfe..46682050 100644 --- a/src/services/iamInstanceProfile/schema.graphql +++ b/src/services/iamInstanceProfile/schema.graphql @@ -7,4 +7,5 @@ type awsIamInstanceProfile @key(fields: "id") { createDate: DateTime tags: [awsRawTag] iamRole: [awsIamRole] @hasInverse(field: iamInstanceProfiles) + ec2Instances: [awsEc2] @hasInverse(field: iamInstanceProfile) } diff --git a/src/types/generated.ts b/src/types/generated.ts index aa33fe12..2b271bee 100644 --- a/src/types/generated.ts +++ b/src/types/generated.ts @@ -1518,7 +1518,7 @@ export type AwsEc2 = { emrInstance?: Maybe>>; ephemeralBlockDevices?: Maybe>>; hibernation?: Maybe; - iamInstanceProfile?: Maybe; + iamInstanceProfile?: Maybe>>; id: Scalars['String']; instanceLifecycle?: Maybe; instanceState?: Maybe; @@ -3110,6 +3110,7 @@ export type AwsIamInstanceProfile = { accountId: Scalars['String']; arn: Scalars['String']; createDate?: Maybe; + ec2Instances?: Maybe>>; iamRole?: Maybe>>; id: Scalars['String']; name?: Maybe; From 4ff22ecb130345de06cd6afd06c15f2978c8a4c7 Mon Sep 17 00:00:00 2001 From: "mariano.pizarro" Date: Thu, 7 Apr 2022 16:38:17 -0300 Subject: [PATCH 3/3] feat: Added IamRole connection to ec2 --- README.md | 10 ++-- src/enums/relations.ts | 1 + src/services/ec2/connections.ts | 28 +++++++++ src/services/ec2/data.ts | 25 ++++++++ src/services/ec2/schema.graphql | 1 + src/services/iamInstanceProfile/data.ts | 79 ++++++++++++++----------- src/services/iamRole/schema.graphql | 1 + src/types/generated.ts | 3 +- 8 files changed, 109 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index f40bb1b4..8c49fbfa 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi | dynamodb | appSync | | dmsReplicationInstance | securityGroup, subnet, vpc, kms | | ebs | asg, ec2, emrInstance | -| ec2 | alb, asg, ebs, eip, emrInstance, networkInterface, securityGroup, subnet, systemsManagerInstance, vpc, ecsContainer | +| ec2 | alb, asg, ebs, eip, emrInstance, eksCluster, elasticBeanstalkEnv, iamInstanceProfile, iamRole, networkInterface, securityGroup, subnet, systemsManagerInstance, vpc, ecsContainer | | ecr | | | ecsCluster | ecsService, ecsTask, ecsTaskSet | | ecsContainer | ecsTask, ec2 | @@ -101,11 +101,11 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi | efs | kms | | efsMountTarget | networkInterface, subnet, vpc | | eip | ec2, networkInterface, vpc | -| eksCluster | iamRole, kms, securityGroup, subnet, vpc | +| eksCluster | ec2, iamRole, kms, securityGroup, subnet, vpc | | elastiCacheCluster | securityGroup, subnet, vpc | | elastiCacheReplicationGroup | kms | | elasticBeanstalkApp | elasticBeanstalkEnv | -| elasticBeanstalkEnv | elasticBeanstalkApp | +| elasticBeanstalkEnv | ec2, elasticBeanstalkApp | | elasticSearchDomain | kms, securityGroup, subnet, vpc | | elb | cloudfront, ecsService, securityGroup, subnet, vpc | | emrCluster | kms, subnet | @@ -115,14 +115,14 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi | glueJob | iamRole | | glueRegistry | | | guardDutyDetector | iamRole | -| iamInstanceProfile | iamRole | +| iamInstanceProfile | ec2, iamRole | | iamPasswordPolicy | | | iamSamlProvider | | | iamOpenIdConnectProvider | | | iamServerCertificate | | | iamUser | iamGroup | | iamPolicy | iamRole, iamGroup | -| iamRole | codebuild, configurationRecorder, iamInstanceProfile, iamPolicy, eksCluster, ecsService, flowLog, glueJob, managedAirflow, sageMakerNotebookInstance, systemsManagerInstance guardDutyDetector | +| iamRole | codebuild, configurationRecorder, ec2, iamInstanceProfile, iamPolicy, eksCluster, ecsService, flowLog, glueJob, managedAirflow, sageMakerNotebookInstance, systemsManagerInstance guardDutyDetector | | iamGroup | iamUser, iamPolicy | | igw | vpc | | iot | | diff --git a/src/enums/relations.ts b/src/enums/relations.ts index e5d3cb40..ef236020 100644 --- a/src/enums/relations.ts +++ b/src/enums/relations.ts @@ -8,4 +8,5 @@ export default { route53HostedZone: ['route53Record'], emrCluster: ['emrInstance', 'emrStep'], ecsService: ['ecsTaskSet', 'ecsTaskDefinition'], + iamInstanceProfile: ['ec2Instance'], } diff --git a/src/services/ec2/connections.ts b/src/services/ec2/connections.ts index 6041e3f1..36ff64c5 100644 --- a/src/services/ec2/connections.ts +++ b/src/services/ec2/connections.ts @@ -21,6 +21,7 @@ import { RawAwsEksCluster } from '../eksCluster/data' import { getEksClusterName, getElasticBeanstalkEnvId } from './utils' import { RawAwsInstanceProfile } from '../iamInstanceProfile/data' import { globalRegionName } from '../../enums/regions' +import { RawAwsIamRole } from '../iamRole/data' /** * EC2 @@ -40,6 +41,7 @@ export default ({ KeyPairName?: string Tags?: TagList IamInstanceProfile: IamInstanceProfile + IamRolesArn?: string[] } region: string }): { [key: string]: ServiceConnection[] } => { @@ -51,6 +53,7 @@ export default ({ SubnetId: subnetId, Tags: tags, IamInstanceProfile: iamInstanceProfile, + IamRolesArn: rolesArn, } = instance /** @@ -339,6 +342,31 @@ export default ({ } } + /** + * Find IAM Roles + * related to this EC2 instance + */ + 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( + ({ Arn }: RawAwsIamRole) => rolesArn?.includes(Arn) + ) + if (!isEmpty(dataAtRegion)) { + for (const iamRole of dataAtRegion) { + const { Arn: arn } :RawAwsIamRole = iamRole + + connections.push({ + id: arn, + resourceType: services.iamRole, + relation: 'child', + field: 'iamRole', + }) + } + } + } + const ec2Result = { [id]: connections, } diff --git a/src/services/ec2/data.ts b/src/services/ec2/data.ts index 73d7d5b4..b60d460b 100644 --- a/src/services/ec2/data.ts +++ b/src/services/ec2/data.ts @@ -31,6 +31,9 @@ import awsLoggerText from '../../properties/logger' import { initTestEndpoint } from '../../utils' import AwsErrorLog from '../../utils/errorLog' import { convertAwsTagsToTagMap } from '../../utils/format' +import getIamInstanceProfiles, { + RawAwsInstanceProfile, +} from '../iamInstanceProfile/data' const lt = { ...awsLoggerText } const { logger } = CloudGraph @@ -54,6 +57,7 @@ export interface RawAwsEC2 extends Omit { IamInstanceProfile?: IamInstanceProfile cloudWatchMetricData?: any PlatformDetails?: string + IamRolesArn?: string[] } /** @@ -63,9 +67,11 @@ export interface RawAwsEC2 extends Omit { export default async ({ regions, config, + rawData, }: { regions: string config: Config + rawData: any }): Promise<{ [region: string]: RawAwsEC2[] }> => @@ -625,6 +631,25 @@ export default async ({ iamInstanceProfile[InstanceId] || {} }) + // populate ec2Instances with the iamRoles Arn + const iamInstancesProfiles: RawAwsInstanceProfile[] = + Object.values( + await getIamInstanceProfiles({ + config, + rawData, + }) + )?.reduce((acc, val) => acc.concat(val), []) || [] + + ec2Instances.map(({ IamInstanceProfile: instanceProfile }, ec2Idx) => { + const instance = iamInstancesProfiles.find( + i => i.Arn === instanceProfile?.Arn + ) + if (instance) { + ec2Instances[ec2Idx].IamRolesArn = + instance?.Roles?.map(r => r.Arn) || [] + } + }) + errorLog.reset() /** diff --git a/src/services/ec2/schema.graphql b/src/services/ec2/schema.graphql index dbb7e910..21b7c201 100644 --- a/src/services/ec2/schema.graphql +++ b/src/services/ec2/schema.graphql @@ -118,4 +118,5 @@ type awsEc2 implements awsBaseService @key(fields: "arn") { eksCluster: [awsEksCluster] @hasInverse(field: ec2Instances) elasticBeanstalkEnv: [awsElasticBeanstalkEnv] @hasInverse(field: ec2Instances) iamInstanceProfile: [awsIamInstanceProfile] @hasInverse(field: ec2Instances) + iamRole: [awsIamRole] @hasInverse(field: ec2Instances) } diff --git a/src/services/iamInstanceProfile/data.ts b/src/services/iamInstanceProfile/data.ts index b1f5a3c5..8e5fea22 100644 --- a/src/services/iamInstanceProfile/data.ts +++ b/src/services/iamInstanceProfile/data.ts @@ -10,6 +10,7 @@ import IAM, { import { Config } from 'aws-sdk/lib/config' import { AWSError } from 'aws-sdk/lib/error' +import { flatMap } from 'lodash' import { convertAwsTagsToTagMap } from '../../utils/format' import { AwsTag, TagMap } from '../../types' @@ -17,6 +18,7 @@ import awsLoggerText from '../../properties/logger' import { initTestEndpoint, setAwsRetryOptions } from '../../utils' import AwsErrorLog from '../../utils/errorLog' import { globalRegionName } from '../../enums/regions' +import services from '../../enums/services' import { IAM_CUSTOM_DELAY, @@ -111,49 +113,60 @@ export interface RawAwsInstanceProfile extends Omit { export default async ({ config, + rawData, }: { - regions: string config: Config + rawData: any }): Promise<{ [region: string]: RawAwsInstanceProfile[] }> => new Promise(async resolve => { - const instancesProfilesResult: RawAwsInstanceProfile[] = [] + let instancesProfilesResult: RawAwsInstanceProfile[] = [] const tagsPromises = [] - const client = new IAM({ - ...config, - region: globalRegionName, - endpoint, - ...customRetrySettings, - }) - - const instancesProfiles = await listInstancesProfiles(client) - - if (!isEmpty(instancesProfiles)) { - instancesProfiles.map( - ({ Tags, InstanceProfileName, ...instancesProfile }, idx) => { - instancesProfilesResult.push({ - InstanceProfileName, - ...instancesProfile, - region: globalRegionName, - }) - - const tagsPromise = new Promise(async resolveTags => { - instancesProfilesResult[idx].Tags = await getTags({ - iam: client, - name: InstanceProfileName, + const existingData: RawAwsInstanceProfile[] = + flatMap( + rawData.find(({ name }) => name === services.iamInstanceProfile)?.data + ) || [] + + if (isEmpty(existingData)) { + const client = new IAM({ + ...config, + region: globalRegionName, + endpoint, + ...customRetrySettings, + }) + + const instancesProfiles = await listInstancesProfiles(client) + + if (!isEmpty(instancesProfiles)) { + instancesProfiles.map( + ({ Tags, InstanceProfileName, ...instancesProfile }, idx) => { + instancesProfilesResult.push({ + InstanceProfileName, + ...instancesProfile, + region: globalRegionName, }) - resolveTags() - }) - tagsPromises.push(tagsPromise) - } - ) - } - logger.debug(lt.foundInstanceProfiles(instancesProfiles.length)) - await Promise.all(tagsPromises) - errorLog.reset() + const tagsPromise = new Promise(async resolveTags => { + instancesProfilesResult[idx].Tags = await getTags({ + iam: client, + name: InstanceProfileName, + }) + resolveTags() + }) + tagsPromises.push(tagsPromise) + } + ) + } + + logger.debug(lt.foundInstanceProfiles(instancesProfiles.length)) + await Promise.all(tagsPromises) + errorLog.reset() + } else { + // Uses existing data + instancesProfilesResult = existingData + } resolve(groupBy(instancesProfilesResult, 'region')) }) diff --git a/src/services/iamRole/schema.graphql b/src/services/iamRole/schema.graphql index c11be041..866ae374 100644 --- a/src/services/iamRole/schema.graphql +++ b/src/services/iamRole/schema.graphql @@ -22,4 +22,5 @@ type awsIamRole implements awsBaseService @key(fields: "id") { systemsManagerInstances: [awsSystemsManagerInstance] @hasInverse(field: iamRole) iamInstanceProfiles: [awsIamInstanceProfile] @hasInverse(field: iamRole) + ec2Instances: [awsEc2] @hasInverse(field: iamRole) } diff --git a/src/types/generated.ts b/src/types/generated.ts index 0d918a61..3fa073ab 100644 --- a/src/types/generated.ts +++ b/src/types/generated.ts @@ -1441,7 +1441,7 @@ export type AwsEc2 = AwsBaseService & { ephemeralBlockDevices?: Maybe>>; hibernation?: Maybe; iamInstanceProfile?: Maybe>>; - id: Scalars['String']; + iamRole?: Maybe>>; instanceLifecycle?: Maybe; instanceState?: Maybe; instanceType?: Maybe; @@ -3020,6 +3020,7 @@ export type AwsIamRole = AwsBaseService & { configurationRecorder?: Maybe>>; createdAt?: Maybe; description?: Maybe; + ec2Instances?: Maybe>>; ecsService?: Maybe>>; eksCluster?: Maybe>>; flowLogs?: Maybe>>;