Skip to content

Commit 8b1fae3

Browse files
authored
Merge pull request #95 from cloudgraphdev/fix/CG-1262
fix(iamRole): relate iamPermissionsBoundary policy, include inlinePolicies JSON objects
2 parents 0895b29 + a6f66c4 commit 8b1fae3

File tree

6 files changed

+118
-45
lines changed

6 files changed

+118
-45
lines changed

src/services/iamPolicy/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type awsIamPolicy implements awsBaseService @key(fields: "id") {
88
iamRoles: [awsIamRole] @hasInverse(field: iamAttachedPolicies)
99
iamGroups: [awsIamGroup] @hasInverse(field: iamAttachedPolicies)
1010
iamUsers: [awsIamUser] @hasInverse(field: iamAttachedPolicies)
11+
permissionBoundaryOf: [awsIamRole] @hasInverse(field: iamPermissionBoundaryPolicy)
1112
}
1213

1314
type awsIamJSONPolicy

src/services/iamRole/connections.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,35 @@ export default ({
3434
region: string
3535
}): { [key: string]: ServiceConnection[] } => {
3636
const connections: ServiceConnection[] = []
37-
const { Arn: id, ManagedPolicies: managedPolicies } = role
37+
const {
38+
Arn: id,
39+
ManagedPolicies: managedPolicies,
40+
PermissionsBoundaryArn,
41+
} = role
3842

3943
const policies: RawAwsIamPolicy[] =
4044
flatMap(
4145
data.find(({ name: serviceName }) => serviceName === services.iamPolicy)
4246
?.data
4347
) || []
4448

49+
/** Find Permission Boundary Policy
50+
* related to this IAM Role
51+
*/
52+
53+
const permissionBoundaryPolicy = policies.find(
54+
({ Arn: arn }: RawAwsIamPolicy) => PermissionsBoundaryArn === arn
55+
)
56+
57+
if (permissionBoundaryPolicy) {
58+
connections.push({
59+
id: PermissionsBoundaryArn,
60+
resourceType: services.iamPolicy,
61+
relation: 'child',
62+
field: 'iamPermissionBoundaryPolicy',
63+
})
64+
}
65+
4566
/**
4667
* Find Managed Policies
4768
* related to this IAM Role

src/services/iamRole/data.ts

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { AWSError } from 'aws-sdk/lib/error'
66

77
import IAM, {
88
AttachedPolicy,
9+
GetAccountAuthorizationDetailsResponse,
910
GetRoleResponse,
1011
ListAttachedRolePoliciesResponse,
11-
ListRolePoliciesResponse,
1212
ListRolesResponse,
1313
ListRoleTagsResponse,
1414
Role,
15+
RoleDetail,
1516
} from 'aws-sdk/clients/iam'
1617
import { Config } from 'aws-sdk/lib/config'
1718

@@ -38,10 +39,11 @@ const customRetrySettings = setAwsRetryOptions({
3839
})
3940

4041
export interface RawAwsIamRole extends Omit<Role, 'Tags'> {
41-
Policies: string[]
4242
ManagedPolicies: AttachedPolicy[]
4343
region: string
4444
Tags?: TagMap
45+
PermissionsBoundaryArn: string
46+
InlinePolicies: Array<{ name: string; document: string }>
4547
}
4648

4749
const roleByRoleName = async (
@@ -99,68 +101,73 @@ const tagsByRoleName = async (
99101
)
100102
})
101103

102-
const policiesByRoleName = async (
104+
const managedPoliciesByRoleName = async (
103105
iam: IAM,
104106
{ RoleName }: Role
105-
): Promise<{ RoleName: string; Policies: string[] }> =>
107+
): Promise<{ RoleName: string; ManagedPolicies: AttachedPolicy[] }> =>
106108
new Promise(resolve => {
107-
iam.listRolePolicies(
109+
iam.listAttachedRolePolicies(
108110
{ RoleName },
109-
(err: AWSError, data: ListRolePoliciesResponse) => {
111+
(err: AWSError, data: ListAttachedRolePoliciesResponse) => {
110112
if (err) {
111113
errorLog.generateAwsErrorLog({
112-
functionName: 'iam:listRolePolicies',
114+
functionName: 'iam:listAttachedRolePolicies',
113115
err,
114116
})
115117
}
116118

117119
if (!isEmpty(data)) {
118-
const { PolicyNames = [] } = data
120+
const { AttachedPolicies = [] } = data
119121

120-
resolve({ RoleName, Policies: PolicyNames })
122+
resolve({
123+
RoleName,
124+
ManagedPolicies: AttachedPolicies,
125+
})
121126
}
122127

123128
resolve(null)
124129
}
125130
)
126131
})
127132

128-
const managedPoliciesByRoleName = async (
133+
export const getAccountAuthorizationDetails = async (
129134
iam: IAM,
130-
{ RoleName }: Role
131-
): Promise<{ RoleName: string; ManagedPolicies: AttachedPolicy[] }> =>
135+
marker?: string
136+
): Promise<RoleDetail[]> =>
132137
new Promise(resolve => {
133-
iam.listAttachedRolePolicies(
134-
{ RoleName },
135-
(err: AWSError, data: ListAttachedRolePoliciesResponse) => {
138+
const result: RoleDetail[] = []
139+
iam.getAccountAuthorizationDetails(
140+
{ Filter: ['Role'], Marker: marker },
141+
async (err: AWSError, data: GetAccountAuthorizationDetailsResponse) => {
136142
if (err) {
137143
errorLog.generateAwsErrorLog({
138-
functionName: 'iam:listAttachedRolePolicies',
144+
functionName: 'iam:getAccountAuthorizationDetails',
139145
err,
140146
})
141147
}
142-
143-
if (!isEmpty(data)) {
144-
const { AttachedPolicies = [] } = data
145-
146-
resolve({
147-
RoleName,
148-
ManagedPolicies: AttachedPolicies,
149-
})
148+
if (!isEmpty(data) && !isEmpty(data.RoleDetailList)) {
149+
const { Marker, IsTruncated, RoleDetailList } = data
150+
result.push(...RoleDetailList)
151+
if (IsTruncated) {
152+
result.push(...(await getAccountAuthorizationDetails(iam, Marker)))
153+
}
154+
resolve(result)
150155
}
151-
152-
resolve(null)
153156
}
154157
)
155158
})
156159

157-
export const listIamRoles = async (
158-
iam: IAM,
160+
export const listIamRoles = async ({
161+
iam,
162+
marker,
163+
roleAuthorizationDetails,
164+
}: {
165+
iam: IAM
159166
marker?: string
160-
): Promise<RawAwsIamRole[]> =>
167+
roleAuthorizationDetails: RoleDetail[]
168+
}): Promise<RawAwsIamRole[]> =>
161169
new Promise(resolve => {
162170
const result: RawAwsIamRole[] = []
163-
const policiesByRoleNamePromises = []
164171
const tagsByRoleNamePromises = []
165172
const managedPoliciesByRoleNamePromises = []
166173
const roleByRoleNamePromises: Promise<{ RoleName: string; Role: Role }>[] =
@@ -180,23 +187,27 @@ export const listIamRoles = async (
180187

181188
roles.map(role => {
182189
tagsByRoleNamePromises.push(tagsByRoleName(iam, role))
183-
policiesByRoleNamePromises.push(policiesByRoleName(iam, role))
184190
managedPoliciesByRoleNamePromises.push(
185191
managedPoliciesByRoleName(iam, role)
186192
)
187193
roleByRoleNamePromises.push(roleByRoleName(iam, role))
188194
})
189195

190196
const tags = await Promise.all(tagsByRoleNamePromises)
191-
const policies = await Promise.all(policiesByRoleNamePromises)
192197
const managedPolicies = await Promise.all(
193198
managedPoliciesByRoleNamePromises
194199
)
195200
const detailedRoles = await Promise.all(roleByRoleNamePromises)
196201

197202
result.push(
198203
...roles.map(
199-
({ RoleName, AssumeRolePolicyDocument, Tags, ...role }) => {
204+
({
205+
RoleName,
206+
AssumeRolePolicyDocument,
207+
PermissionsBoundary,
208+
Tags,
209+
...role
210+
}) => {
200211
return {
201212
RoleName,
202213
AssumeRolePolicyDocument: decodeURIComponent(
@@ -207,24 +218,33 @@ export const listIamRoles = async (
207218
RoleLastUsed: detailedRoles?.find(
208219
r => r?.RoleName === RoleName
209220
)?.Role.RoleLastUsed,
210-
Policies:
211-
policies
212-
?.filter(p => p?.RoleName === RoleName)
213-
.map(p => p.Policies)
214-
.reduce((current, acc) => [...acc, ...current], []) || [],
215221
ManagedPolicies:
216222
managedPolicies
217223
?.filter(p => p?.RoleName === RoleName)
218224
.map(p => p.ManagedPolicies)
219225
.reduce((current, acc) => [...acc, ...current], []) || [],
220226
Tags: tags.find(t => t?.RoleName === RoleName)?.Tags || {},
227+
PermissionsBoundaryArn:
228+
PermissionsBoundary.PermissionsBoundaryArn,
229+
InlinePolicies: roleAuthorizationDetails
230+
.find(rAD => rAD.RoleName === RoleName)
231+
.RolePolicyList.map(rPl => ({
232+
name: rPl.PolicyName,
233+
document: rPl.PolicyDocument,
234+
})),
221235
}
222236
}
223237
)
224238
)
225239

226240
if (IsTruncated) {
227-
result.push(...(await listIamRoles(iam, Marker)))
241+
result.push(
242+
...(await listIamRoles({
243+
iam,
244+
marker: Marker,
245+
roleAuthorizationDetails,
246+
}))
247+
)
228248
}
229249

230250
resolve(result)
@@ -259,8 +279,12 @@ export default async ({
259279

260280
logger.debug(lt.lookingForIamRoles)
261281

282+
// Fetch role authorization details first
283+
const roleAuthorizationDetails = await getAccountAuthorizationDetails(
284+
client
285+
)
262286
// Fetch IAM Roles
263-
rolesData = await listIamRoles(client)
287+
rolesData = await listIamRoles({ iam: client, roleAuthorizationDetails })
264288

265289
errorLog.reset()
266290
logger.debug(lt.foundRoles(rolesData.length))

src/services/iamRole/format.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import cuid from 'cuid'
12
import { AwsIamRole } from '../../types/generated'
23
import { formatTagsFromMap, formatIamJsonPolicy } from '../../utils/format'
34

@@ -24,7 +25,7 @@ export default ({
2425
RoleLastUsed,
2526
AssumeRolePolicyDocument: assumeRolePolicy = '',
2627
MaxSessionDuration: maxSessionDuration = 0,
27-
Policies: inlinePolicies = [],
28+
InlinePolicies: inlinePolicies = [],
2829
Tags: tags = {},
2930
} = rawData
3031

@@ -43,7 +44,13 @@ export default ({
4344
rawPolicy: assumeRolePolicy,
4445
assumeRolePolicy: formatIamJsonPolicy(assumeRolePolicy),
4546
maxSessionDuration,
46-
inlinePolicies,
47+
inlinePolicies: inlinePolicies.map(
48+
({ name: inlinePolicyName, document: inlinePolicyDocument }) => ({
49+
id: cuid(),
50+
name: inlinePolicyName,
51+
document: formatIamJsonPolicy(inlinePolicyDocument),
52+
})
53+
) ?? [],
4754
tags: roleTags,
4855
}
4956
return role

src/services/iamRole/schema.graphql

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
type awsIamRoleInlinePolicy
2+
@generate(
3+
query: { get: false, query: true, aggregate: false }
4+
mutation: { add: false, delete: false }
5+
subscription: false
6+
) {
7+
id: String! @id @search(by: [hash])
8+
name: String @search(by: [hash, regexp])
9+
document: awsIamJSONPolicy
10+
}
11+
112
type awsIamRole implements awsBaseService @key(fields: "id") {
213
name: String @search(by: [hash, regexp])
314
path: String @search(by: [hash, regexp])
@@ -8,7 +19,7 @@ type awsIamRole implements awsBaseService @key(fields: "id") {
819
lastUsedDate: DateTime @search(by: [day])
920
maxSessionDuration: Int @search
1021
tags: [awsRawTag]
11-
inlinePolicies: [String]
22+
inlinePolicies: [awsIamRoleInlinePolicy]
1223
cloudFormationStack: [awsCloudFormationStack] @hasInverse(field: iamRole)
1324
codebuilds: [awsCodebuild] @hasInverse(field: iamRoles)
1425
configurationRecorder: [awsConfigurationRecorder] @hasInverse(field: iamRole)
@@ -20,6 +31,8 @@ type awsIamRole implements awsBaseService @key(fields: "id") {
2031
glueJobs: [awsGlueJob] @hasInverse(field: iamRole)
2132
guardDutyDetectors: [awsGuardDutyDetector] @hasInverse(field: iamRole)
2233
iamAttachedPolicies: [awsIamPolicy] @hasInverse(field: iamRoles)
34+
iamPermissionBoundaryPolicy: [awsIamPolicy]
35+
@hasInverse(field: permissionBoundaryOf)
2336
iamInstanceProfiles: [awsIamInstanceProfile] @hasInverse(field: iamRole)
2437
managedAirflows: [awsManagedAirflow] @hasInverse(field: iamRoles)
2538
sageMakerNotebookInstances: [awsSageMakerNotebookInstance]

src/types/generated.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3150,6 +3150,7 @@ export type AwsIamPolicy = AwsBaseService & {
31503150
iamUsers?: Maybe<Array<Maybe<AwsIamUser>>>;
31513151
name?: Maybe<Scalars['String']>;
31523152
path?: Maybe<Scalars['String']>;
3153+
permissionBoundaryOf?: Maybe<Array<Maybe<AwsIamRole>>>;
31533154
policyContent?: Maybe<AwsIamJsonPolicy>;
31543155
rawPolicy?: Maybe<Scalars['String']>;
31553156
tags?: Maybe<Array<Maybe<AwsRawTag>>>;
@@ -3182,7 +3183,8 @@ export type AwsIamRole = AwsBaseService & {
31823183
guardDutyDetectors?: Maybe<Array<Maybe<AwsGuardDutyDetector>>>;
31833184
iamAttachedPolicies?: Maybe<Array<Maybe<AwsIamPolicy>>>;
31843185
iamInstanceProfiles?: Maybe<Array<Maybe<AwsIamInstanceProfile>>>;
3185-
inlinePolicies?: Maybe<Array<Maybe<Scalars['String']>>>;
3186+
iamPermissionBoundaryPolicy?: Maybe<Array<Maybe<AwsIamPolicy>>>;
3187+
inlinePolicies?: Maybe<Array<Maybe<AwsIamRoleInlinePolicy>>>;
31863188
kinesisFirehose?: Maybe<Array<Maybe<AwsKinesisFirehose>>>;
31873189
lambda?: Maybe<Array<Maybe<AwsLambda>>>;
31883190
lastUsedDate?: Maybe<Scalars['DateTime']>;
@@ -3199,6 +3201,11 @@ export type AwsIamRole = AwsBaseService & {
31993201
tags?: Maybe<Array<Maybe<AwsRawTag>>>;
32003202
};
32013203

3204+
export type AwsIamRoleInlinePolicy = {
3205+
document?: Maybe<AwsIamJsonPolicy>;
3206+
name?: Maybe<Scalars['String']>;
3207+
};
3208+
32023209
export type AwsIamSamlProvider = AwsOptionalService & {
32033210
awsCognitoIdentityPool?: Maybe<Array<Maybe<AwsCognitoIdentityPool>>>;
32043211
createdDate?: Maybe<Scalars['String']>;

0 commit comments

Comments
 (0)