Skip to content

Commit 12484c4

Browse files
committed
feat(service): Add AWS service iamInstanceProfile
1 parent b3d0f55 commit 12484c4

17 files changed

Lines changed: 377 additions & 40 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,14 @@ CloudGraph AWS Provider will ask you what regions you would like to crawl and wi
115115
| glueJob | iamRole |
116116
| glueRegistry | |
117117
| guardDutyDetector | iamRole |
118+
| iamInstanceProfile | iamRole |
118119
| iamPasswordPolicy | |
119120
| iamSamlProvider | |
120121
| iamOpenIdConnectProvider | |
121122
| iamServerCertificate | |
122123
| iamUser | iamGroup |
123124
| iamPolicy | iamRole, iamGroup |
124-
| iamRole | codebuild, configurationRecorder, iamPolicy, eksCluster, ecsService, flowLog, glueJob, managedAirflow, sageMakerNotebookInstance, systemsManagerInstance guardDutyDetector |
125+
| iamRole | codebuild, configurationRecorder, iamInstanceProfile, iamPolicy, eksCluster, ecsService, flowLog, glueJob, managedAirflow, sageMakerNotebookInstance, systemsManagerInstance guardDutyDetector |
125126
| iamGroup | iamUser, iamPolicy |
126127
| igw | vpc |
127128
| iot | |

src/enums/schemasMap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export default {
6262
[services.iamPolicy]: 'awsIamPolicy',
6363
[services.iamSamlProvider]: 'awsIamSamlProvider',
6464
[services.iamServerCertificate]: 'awsIamServerCertificate',
65+
[services.iamInstanceProfile]: 'awsIamInstanceProfile',
6566
[services.igw]: 'awsIgw',
6667
[services.iot]: 'awsIotThingAttribute',
6768
[services.kinesisFirehose]: 'awsKinesisFirehose',

src/enums/serviceAliases.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default {
4343
[services.iamRole]: 'iamRoles',
4444
[services.iamSamlProvider]: 'iamSamlProviders',
4545
[services.iamServerCertificate]: 'iamServerCertificates',
46+
[services.iamInstanceProfile]: 'iamInstanceProfiles',
4647
[services.iamUser]: 'iamUsers',
4748
[services.kinesisStream]: 'kinesisStreams',
4849
[services.lambda]: 'lambdaFunctions',

src/enums/serviceMap.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import IamPasswordPolicy from '../services/iamPasswordPolicy'
6666
import IamSamlProvider from '../services/iamSamlProvider'
6767
import IamOpenIdConnectProvider from '../services/iamOpenIdConnectProvider'
6868
import IamServerCertificate from '../services/iamServerCertificate'
69+
import IamInstanceProfile from '../services/iamInstanceProfile'
6970
import SNS from '../services/sns'
7071
import EKSCluster from '../services/eksCluster'
7172
import Cloud9Environment from '../services/cloud9'
@@ -178,6 +179,7 @@ export default {
178179
[services.iamSamlProvider]: IamSamlProvider,
179180
[services.iamOpenIdConnectProvider]: IamOpenIdConnectProvider,
180181
[services.iamServerCertificate]: IamServerCertificate,
182+
[services.iamInstanceProfile]: IamInstanceProfile,
181183
[services.sns]: SNS,
182184
[services.ecsCluster]: EcsCluster,
183185
[services.ecsContainer]: EcsContainer,

src/enums/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export default {
5656
iamSamlProvider: 'iamSamlProvider',
5757
iamOpenIdConnectProvider: 'iamOpenIdConnectProvider',
5858
iamServerCertificate: 'iamServerCertificate',
59+
iamInstanceProfile: 'iamInstanceProfile',
5960
igw: 'igw',
6061
iot: 'iot',
6162
kinesisFirehose: 'kinesisFirehose',

src/properties/logger.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export default {
3737
lookingForIamServerCertificates: 'Looking for IAM Server Certificates to add',
3838
foundServerCertificates: (num: number): string =>
3939
`Found ${num} Server Certificates to add`,
40+
foundInstanceProfiles: (num: number): string =>
41+
`Found ${num} Instance Profiles to add`,
4042
/**
4143
* CloudFormation
4244
*/

src/services/account/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type awsAccount @key(fields: "id") {
5858
iamSamlProviders: [awsIamSamlProvider]
5959
iamServerCertificates: [awsIamServerCertificate]
6060
iamUsers: [awsIamUser]
61+
iamInstanceProfiles: [awsIamInstanceProfile]
6162
igw: [awsIgw]
6263
iot: [awsIotThingAttribute]
6364
kinesisFirehose: [awsKinesisFirehose]
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { ServiceConnection } from '@cloudgraph/sdk'
2+
import isEmpty from 'lodash/isEmpty'
3+
import services from '../../enums/services'
4+
import { RawAwsInstanceProfile } from './data'
5+
import { RawAwsIamRole } from '../iamRole/data'
6+
import { globalRegionName } from '../../enums/regions'
7+
8+
/**
9+
* IAM Instance Profiles
10+
*/
11+
12+
export default ({
13+
service: instanceProfile,
14+
data,
15+
}: {
16+
data: { name: string; data: { [property: string]: any[] } }[]
17+
service: RawAwsInstanceProfile
18+
account: string
19+
region: string
20+
}): { [key: string]: ServiceConnection[] } => {
21+
const connections: ServiceConnection[] = []
22+
23+
const { InstanceProfileId: id, Roles: instanceProfileRoles } = instanceProfile
24+
25+
/**
26+
* Find related IAM Roles
27+
*/
28+
29+
const rolesArn = instanceProfileRoles?.map(({ Arn }) => Arn)
30+
31+
const roles: { name: string; data: { [property: string]: any[] } } =
32+
data.find(({ name }) => name === services.iamRole)
33+
34+
if (roles?.data?.[globalRegionName]) {
35+
const dataAtRegion: RawAwsIamRole[] = roles.data[globalRegionName].filter(
36+
({ Arn }: RawAwsIamRole) => rolesArn.includes(Arn)
37+
)
38+
if (!isEmpty(dataAtRegion)) {
39+
for (const instance of dataAtRegion) {
40+
const { Arn: arn } = instance
41+
42+
connections.push({
43+
id: arn,
44+
resourceType: services.iamRole,
45+
relation: 'child',
46+
field: 'iamRole',
47+
})
48+
}
49+
}
50+
}
51+
52+
const result = {
53+
[id]: connections,
54+
}
55+
56+
return result
57+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import CloudGraph from '@cloudgraph/sdk'
2+
import groupBy from 'lodash/groupBy'
3+
import isEmpty from 'lodash/isEmpty'
4+
import IAM, {
5+
InstanceProfile,
6+
ListInstanceProfilesRequest,
7+
ListInstanceProfilesResponse,
8+
ListInstanceProfileTagsResponse,
9+
} from 'aws-sdk/clients/iam'
10+
11+
import { Config } from 'aws-sdk/lib/config'
12+
import { AWSError } from 'aws-sdk/lib/error'
13+
import { convertAwsTagsToTagMap } from '../../utils/format'
14+
import { AwsTag, TagMap } from '../../types'
15+
16+
import awsLoggerText from '../../properties/logger'
17+
import { initTestEndpoint, setAwsRetryOptions } from '../../utils'
18+
import AwsErrorLog from '../../utils/errorLog'
19+
import { globalRegionName } from '../../enums/regions'
20+
21+
import {
22+
IAM_CUSTOM_DELAY,
23+
MAX_FAILED_AWS_REQUEST_RETRIES,
24+
} from '../../config/constants'
25+
26+
const lt = { ...awsLoggerText }
27+
const { logger } = CloudGraph
28+
const serviceName = 'IAM Instace Profile'
29+
const errorLog = new AwsErrorLog(serviceName)
30+
const endpoint = initTestEndpoint(serviceName)
31+
const customRetrySettings = setAwsRetryOptions({
32+
maxRetries: MAX_FAILED_AWS_REQUEST_RETRIES,
33+
baseDelay: IAM_CUSTOM_DELAY,
34+
})
35+
36+
export const listInstancesProfiles = async (
37+
iam: IAM
38+
): Promise<InstanceProfile[]> =>
39+
new Promise(resolve => {
40+
const instanceProfileList: InstanceProfile[] = []
41+
let args: ListInstanceProfilesRequest = {}
42+
const listAllInstanceProfiles = (marker?: string) => {
43+
if (marker) {
44+
args = { ...args, Marker: marker }
45+
}
46+
try {
47+
iam.listInstanceProfiles(
48+
args,
49+
async (err: AWSError, data: ListInstanceProfilesResponse) => {
50+
if (err) {
51+
errorLog.generateAwsErrorLog({
52+
functionName: 'iam:listInstanceProfiles',
53+
err,
54+
})
55+
}
56+
57+
const { InstanceProfiles = [], IsTruncated, Marker } = data
58+
59+
instanceProfileList.push(...InstanceProfiles)
60+
61+
if (IsTruncated) {
62+
listAllInstanceProfiles(Marker)
63+
}
64+
65+
resolve(instanceProfileList)
66+
}
67+
)
68+
} catch (error) {
69+
resolve([])
70+
}
71+
}
72+
listAllInstanceProfiles()
73+
})
74+
75+
export const getTags = async ({
76+
iam,
77+
name,
78+
}: {
79+
iam: IAM
80+
name: string
81+
}): Promise<TagMap> =>
82+
new Promise(resolve => {
83+
try {
84+
iam.listInstanceProfileTags(
85+
{ InstanceProfileName: name },
86+
(err: AWSError, data: ListInstanceProfileTagsResponse) => {
87+
if (err) {
88+
errorLog.generateAwsErrorLog({
89+
functionName: 'iam:listInstanceProfileTags',
90+
err,
91+
})
92+
resolve({})
93+
}
94+
const { Tags = [] } = data || {}
95+
resolve(convertAwsTagsToTagMap(Tags as AwsTag[]))
96+
}
97+
)
98+
} catch (error) {
99+
resolve({})
100+
}
101+
})
102+
103+
/**
104+
* IAM Instance Profile
105+
*/
106+
107+
export interface RawAwsInstanceProfile extends Omit<InstanceProfile, 'Tags'> {
108+
region: string
109+
Tags?: TagMap
110+
}
111+
112+
export default async ({
113+
config,
114+
}: {
115+
regions: string
116+
config: Config
117+
}): Promise<{
118+
[region: string]: RawAwsInstanceProfile[]
119+
}> =>
120+
new Promise(async resolve => {
121+
const instancesProfilesResult: RawAwsInstanceProfile[] = []
122+
const tagsPromises = []
123+
124+
const client = new IAM({
125+
...config,
126+
region: globalRegionName,
127+
endpoint,
128+
...customRetrySettings,
129+
})
130+
131+
const instancesProfiles = await listInstancesProfiles(client)
132+
133+
if (!isEmpty(instancesProfiles)) {
134+
instancesProfiles.map(
135+
({ Tags, InstanceProfileName, ...instancesProfile }, idx) => {
136+
instancesProfilesResult.push({
137+
InstanceProfileName,
138+
...instancesProfile,
139+
region: globalRegionName,
140+
})
141+
142+
const tagsPromise = new Promise<void>(async resolveTags => {
143+
instancesProfilesResult[idx].Tags = await getTags({
144+
iam: client,
145+
name: InstanceProfileName,
146+
})
147+
resolveTags()
148+
})
149+
tagsPromises.push(tagsPromise)
150+
}
151+
)
152+
}
153+
154+
logger.debug(lt.foundInstanceProfiles(instancesProfiles.length))
155+
await Promise.all(tagsPromises)
156+
errorLog.reset()
157+
158+
resolve(groupBy(instancesProfilesResult, 'region'))
159+
})
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { AwsIamRole } from '../../types/generated'
2+
import { formatTagsFromMap } from '../../utils/format'
3+
4+
import { RawAwsInstanceProfile } from './data'
5+
6+
/**
7+
* IAM Instance Profile
8+
*/
9+
10+
export default ({
11+
service: rawData,
12+
account,
13+
}: {
14+
service: RawAwsInstanceProfile
15+
account: string
16+
region: string
17+
}): AwsIamRole => {
18+
const {
19+
InstanceProfileId: instanceProfileId,
20+
InstanceProfileName: instanceProfileName = '',
21+
Arn: arn = '',
22+
Path: path = '',
23+
CreateDate: createDate,
24+
Tags: tags = {},
25+
} = rawData
26+
27+
const role = {
28+
id: instanceProfileId,
29+
arn,
30+
accountId: account,
31+
path,
32+
name: instanceProfileName,
33+
createDate: createDate?.toISOString(),
34+
tags: formatTagsFromMap(tags),
35+
}
36+
return role
37+
}

0 commit comments

Comments
 (0)