Skip to content

Commit

Permalink
feat(iam): add Role.externalIds property (#3598)
Browse files Browse the repository at this point in the history
* deprecate Role.externalId
  • Loading branch information
Jimmy Gaussen authored and mergify[bot] committed Aug 9, 2019
1 parent 661a95e commit ba2a4df
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 6 deletions.
25 changes: 21 additions & 4 deletions packages/@aws-cdk/aws-iam/lib/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,22 @@ export interface RoleProps {
* If the configured and provided external IDs do not match, the
* AssumeRole operation will fail.
*
* @deprecated see {@link externalIds}
*
* @default No external ID required
*/
readonly externalId?: string;

/**
* List of IDs that the role assumer needs to provide one of when assuming this role
*
* If the configured and provided external IDs do not match, the
* AssumeRole operation will fail.
*
* @default No external ID required
*/
readonly externalIds?: string[];

/**
* A list of managed policies associated with this role.
*
Expand Down Expand Up @@ -225,7 +237,12 @@ export class Role extends Resource implements IRole {
physicalName: props.roleName,
});

this.assumeRolePolicy = createAssumeRolePolicy(props.assumedBy, props.externalId);
const externalIds = props.externalIds || [];
if (props.externalId) {
externalIds.push(props.externalId);
}

this.assumeRolePolicy = createAssumeRolePolicy(props.assumedBy, externalIds);
this.managedPolicies.push(...props.managedPolicies || []);
this.permissionsBoundary = props.permissionsBoundary;
const maxSessionDuration = props.maxSessionDuration && props.maxSessionDuration.toSeconds();
Expand Down Expand Up @@ -344,13 +361,13 @@ export interface IRole extends IIdentity {
grantPassRole(grantee: IPrincipal): Grant;
}

function createAssumeRolePolicy(principal: IPrincipal, externalId?: string) {
function createAssumeRolePolicy(principal: IPrincipal, externalIds: string[]) {
const statement = new PolicyStatement();
statement.addPrincipals(principal);
statement.addActions(principal.assumeRoleAction);

if (externalId !== undefined) {
statement.addCondition('StringEquals', { 'sts:ExternalId': externalId });
if (externalIds.length) {
statement.addCondition('StringEquals', { 'sts:ExternalId': externalIds.length === 1 ? externalIds[0] : externalIds });
}

const doc = new PolicyDocument();
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-iam/test/example.external-id.lit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class ExampleConstruct extends cdk.Construct {
/// !show
const role = new iam.Role(this, 'MyRole', {
assumedBy: new iam.AccountPrincipal('123456789012'),
externalId: 'SUPPLY-ME',
externalIds: ['SUPPLY-ME'],
});
/// !hide

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-iam/test/integ.role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ policy.attachToRole(role);
// Role with an external ID
new Role(stack, 'TestRole2', {
assumedBy: new AccountRootPrincipal(),
externalId: 'supply-me',
externalIds: ['supply-me'],
});

app.synth();
60 changes: 60 additions & 0 deletions packages/@aws-cdk/aws-iam/test/test.role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,66 @@ export = {
test.done();
},

'can supply single externalIds'(test: Test) {
// GIVEN
const stack = new Stack();

// WHEN
new Role(stack, 'MyRole', {
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
externalIds: ['SomeSecret'],
});

// THEN
expect(stack).to(haveResource('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Statement: [
{
Action: "sts:AssumeRole",
Condition: {
StringEquals: { "sts:ExternalId": "SomeSecret" }
},
Effect: "Allow",
Principal: { Service: "sns.amazonaws.com" }
}
],
Version: "2012-10-17"
}
}));

test.done();
},

'can supply multiple externalIds'(test: Test) {
// GIVEN
const stack = new Stack();

// WHEN
new Role(stack, 'MyRole', {
assumedBy: new ServicePrincipal('sns.amazonaws.com'),
externalIds: ['SomeSecret', 'AnotherSecret'],
});

// THEN
expect(stack).to(haveResource('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Statement: [
{
Action: "sts:AssumeRole",
Condition: {
StringEquals: { "sts:ExternalId": ["SomeSecret", "AnotherSecret"] }
},
Effect: "Allow",
Principal: { Service: "sns.amazonaws.com" }
}
],
Version: "2012-10-17"
}
}));

test.done();
},

'policy is created automatically when permissions are added'(test: Test) {
// by default we don't expect a role policy
const before = new Stack();
Expand Down

0 comments on commit ba2a4df

Please sign in to comment.