Skip to content

Commit ba2a4df

Browse files
Jimmy Gaussenmergify[bot]
authored andcommitted
feat(iam): add Role.externalIds property (#3598)
* deprecate Role.externalId
1 parent 661a95e commit ba2a4df

File tree

4 files changed

+83
-6
lines changed

4 files changed

+83
-6
lines changed

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,22 @@ export interface RoleProps {
2525
* If the configured and provided external IDs do not match, the
2626
* AssumeRole operation will fail.
2727
*
28+
* @deprecated see {@link externalIds}
29+
*
2830
* @default No external ID required
2931
*/
3032
readonly externalId?: string;
3133

34+
/**
35+
* List of IDs that the role assumer needs to provide one of when assuming this role
36+
*
37+
* If the configured and provided external IDs do not match, the
38+
* AssumeRole operation will fail.
39+
*
40+
* @default No external ID required
41+
*/
42+
readonly externalIds?: string[];
43+
3244
/**
3345
* A list of managed policies associated with this role.
3446
*
@@ -225,7 +237,12 @@ export class Role extends Resource implements IRole {
225237
physicalName: props.roleName,
226238
});
227239

228-
this.assumeRolePolicy = createAssumeRolePolicy(props.assumedBy, props.externalId);
240+
const externalIds = props.externalIds || [];
241+
if (props.externalId) {
242+
externalIds.push(props.externalId);
243+
}
244+
245+
this.assumeRolePolicy = createAssumeRolePolicy(props.assumedBy, externalIds);
229246
this.managedPolicies.push(...props.managedPolicies || []);
230247
this.permissionsBoundary = props.permissionsBoundary;
231248
const maxSessionDuration = props.maxSessionDuration && props.maxSessionDuration.toSeconds();
@@ -344,13 +361,13 @@ export interface IRole extends IIdentity {
344361
grantPassRole(grantee: IPrincipal): Grant;
345362
}
346363

347-
function createAssumeRolePolicy(principal: IPrincipal, externalId?: string) {
364+
function createAssumeRolePolicy(principal: IPrincipal, externalIds: string[]) {
348365
const statement = new PolicyStatement();
349366
statement.addPrincipals(principal);
350367
statement.addActions(principal.assumeRoleAction);
351368

352-
if (externalId !== undefined) {
353-
statement.addCondition('StringEquals', { 'sts:ExternalId': externalId });
369+
if (externalIds.length) {
370+
statement.addCondition('StringEquals', { 'sts:ExternalId': externalIds.length === 1 ? externalIds[0] : externalIds });
354371
}
355372

356373
const doc = new PolicyDocument();

packages/@aws-cdk/aws-iam/test/example.external-id.lit.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export class ExampleConstruct extends cdk.Construct {
88
/// !show
99
const role = new iam.Role(this, 'MyRole', {
1010
assumedBy: new iam.AccountPrincipal('123456789012'),
11-
externalId: 'SUPPLY-ME',
11+
externalIds: ['SUPPLY-ME'],
1212
});
1313
/// !hide
1414

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ policy.attachToRole(role);
1818
// Role with an external ID
1919
new Role(stack, 'TestRole2', {
2020
assumedBy: new AccountRootPrincipal(),
21-
externalId: 'supply-me',
21+
externalIds: ['supply-me'],
2222
});
2323

2424
app.synth();

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,66 @@ export = {
8080
test.done();
8181
},
8282

83+
'can supply single externalIds'(test: Test) {
84+
// GIVEN
85+
const stack = new Stack();
86+
87+
// WHEN
88+
new Role(stack, 'MyRole', {
89+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
90+
externalIds: ['SomeSecret'],
91+
});
92+
93+
// THEN
94+
expect(stack).to(haveResource('AWS::IAM::Role', {
95+
AssumeRolePolicyDocument: {
96+
Statement: [
97+
{
98+
Action: "sts:AssumeRole",
99+
Condition: {
100+
StringEquals: { "sts:ExternalId": "SomeSecret" }
101+
},
102+
Effect: "Allow",
103+
Principal: { Service: "sns.amazonaws.com" }
104+
}
105+
],
106+
Version: "2012-10-17"
107+
}
108+
}));
109+
110+
test.done();
111+
},
112+
113+
'can supply multiple externalIds'(test: Test) {
114+
// GIVEN
115+
const stack = new Stack();
116+
117+
// WHEN
118+
new Role(stack, 'MyRole', {
119+
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
120+
externalIds: ['SomeSecret', 'AnotherSecret'],
121+
});
122+
123+
// THEN
124+
expect(stack).to(haveResource('AWS::IAM::Role', {
125+
AssumeRolePolicyDocument: {
126+
Statement: [
127+
{
128+
Action: "sts:AssumeRole",
129+
Condition: {
130+
StringEquals: { "sts:ExternalId": ["SomeSecret", "AnotherSecret"] }
131+
},
132+
Effect: "Allow",
133+
Principal: { Service: "sns.amazonaws.com" }
134+
}
135+
],
136+
Version: "2012-10-17"
137+
}
138+
}));
139+
140+
test.done();
141+
},
142+
83143
'policy is created automatically when permissions are added'(test: Test) {
84144
// by default we don't expect a role policy
85145
const before = new Stack();

0 commit comments

Comments
 (0)