Skip to content

Commit 264c02e

Browse files
authored
fix(iam): referencing the same immutable role twice makes it mutable (#20497)
The solution I went with in this PR was to try and keep the provided id set on the `ImmutableRole` instead of the `Import` role. This should also keep backwards compatibility by only changing the id of the `Import` role if we are returning an `ImmutableRole`. fixes #7255 ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 9a23575 commit 264c02e

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

packages/@aws-cdk/aws-iam/lib/role.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,15 +277,20 @@ export class Role extends Resource implements IRole {
277277
throw new Error('\'addGrantsToResources\' can only be passed if \'mutable: false\'');
278278
}
279279

280-
const importedRole = new Import(scope, id);
281-
const roleArnAndScopeStackAccountComparison = Token.compareStrings(importedRole.env.account, scopeStack.account);
280+
const roleArnAndScopeStackAccountComparison = Token.compareStrings(roleAccount ?? '', scopeStack.account);
282281
const equalOrAnyUnresolved = roleArnAndScopeStackAccountComparison === TokenComparison.SAME ||
283282
roleArnAndScopeStackAccountComparison === TokenComparison.BOTH_UNRESOLVED ||
284283
roleArnAndScopeStackAccountComparison === TokenComparison.ONE_UNRESOLVED;
284+
285+
// if we are returning an immutable role then the 'importedRole' is just a throwaway construct
286+
// so give it a different id
287+
const mutableRoleId = (options.mutable !== false && equalOrAnyUnresolved) ? id : `MutableRole${id}`;
288+
const importedRole = new Import(scope, mutableRoleId);
289+
285290
// we only return an immutable Role if both accounts were explicitly provided, and different
286291
return options.mutable !== false && equalOrAnyUnresolved
287292
? importedRole
288-
: new ImmutableRole(scope, `ImmutableRole${id}`, importedRole, options.addGrantsToResources ?? false);
293+
: new ImmutableRole(scope, id, importedRole, options.addGrantsToResources ?? false);
289294
}
290295

291296
/**
@@ -655,4 +660,4 @@ export interface WithoutPolicyUpdatesOptions {
655660
* @default false
656661
*/
657662
readonly addGrantsToResources?: boolean;
658-
}
663+
}

packages/@aws-cdk/aws-iam/test/immutable-role.test.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Template } from '@aws-cdk/assertions';
1+
import { Template, Match } from '@aws-cdk/assertions';
22
import { Stack } from '@aws-cdk/core';
33
import * as iam from '../lib';
44

@@ -53,6 +53,57 @@ describe('ImmutableRole', () => {
5353
});
5454
});
5555

56+
test('id of mutable role remains unchanged', () => {
57+
iam.Role.fromRoleName(stack, 'TestRole123', 'my-role');
58+
expect(stack.node.tryFindChild('TestRole123')).not.toBeUndefined();
59+
expect(stack.node.tryFindChild('MutableRoleTestRole123')).toBeUndefined();
60+
});
61+
62+
test('remains mutable when called multiple times', () => {
63+
const user = new iam.User(stack, 'User');
64+
const policy = new iam.Policy(stack, 'Policy', {
65+
statements: [new iam.PolicyStatement({
66+
resources: ['*'],
67+
actions: ['s3:*'],
68+
})],
69+
users: [user],
70+
});
71+
72+
function findRole(): iam.IRole {
73+
const foundRole = stack.node.tryFindChild('MyRole') as iam.IRole;
74+
if (foundRole) {
75+
return foundRole;
76+
}
77+
return iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::12345:role/role-name', { mutable: false });
78+
}
79+
80+
let foundRole = findRole();
81+
foundRole.attachInlinePolicy(policy);
82+
foundRole = findRole();
83+
foundRole.attachInlinePolicy(policy);
84+
85+
expect(stack.node.tryFindChild('MutableRoleMyRole')).not.toBeUndefined();
86+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
87+
'PolicyDocument': {
88+
'Statement': [
89+
{
90+
'Action': 's3:*',
91+
'Resource': '*',
92+
'Effect': 'Allow',
93+
},
94+
],
95+
'Version': '2012-10-17',
96+
},
97+
'PolicyName': 'Policy23B91518',
98+
'Roles': Match.absent(),
99+
'Users': [
100+
{
101+
'Ref': 'User00B015A1',
102+
},
103+
],
104+
});
105+
});
106+
56107
test('ignores calls to addManagedPolicy', () => {
57108
mutableRole.addManagedPolicy({ managedPolicyArn: 'Arn1' });
58109

@@ -117,4 +168,4 @@ describe('ImmutableRole', () => {
117168
new Construct(immutableRole as unknown as Construct, 'Child');
118169
new Construct(mutableRole.withoutPolicyUpdates() as unknown as Construct, 'Child2');
119170
});
120-
});
171+
});

0 commit comments

Comments
 (0)