Skip to content

Commit

Permalink
If AssumeRoleActions is provided, use it for creating assume role policy
Browse files Browse the repository at this point in the history
  • Loading branch information
simonireilly committed Sep 30, 2021
1 parent 8d8367b commit ba0868b
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 25 deletions.
47 changes: 25 additions & 22 deletions packages/@aws-cdk/aws-iam/lib/principals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,14 @@ export abstract class PrincipalBase implements IPrincipal {
/**
* When this Principal is used in an AssumeRole policy, the action to use.
*/
public readonly assumeRoleActions: string[] = ['sts:AssumeRole'];
public readonly assumeRoleActions?: string[] = ['sts:AssumeRole'];

public addToPolicy(statement: PolicyStatement): boolean {
return this.addToPrincipalPolicy(statement).statementAdded;
}

public addToPrincipalPolicy(
_statement: PolicyStatement
_statement: PolicyStatement,
): AddToPrincipalPolicyResult {
// This base class is used for non-identity principals. None of them
// have a PolicyDocument to add to.
Expand Down Expand Up @@ -199,14 +199,14 @@ export class PrincipalWithConditions implements IPrincipal {
public get conditions() {
return this.mergeConditions(
this.principal.policyFragment.conditions,
this.additionalConditions
this.additionalConditions,
);
}

public get policyFragment(): PrincipalPolicyFragment {
return new PrincipalPolicyFragment(
this.principal.policyFragment.principalJson,
this.conditions
this.conditions,
);
}

Expand All @@ -219,7 +219,7 @@ export class PrincipalWithConditions implements IPrincipal {
}

public addToPrincipalPolicy(
statement: PolicyStatement
statement: PolicyStatement,
): AddToPrincipalPolicyResult {
return this.principal.addToPrincipalPolicy(statement);
}
Expand All @@ -240,7 +240,7 @@ export class PrincipalWithConditions implements IPrincipal {

private mergeConditions(
principalConditions: Conditions,
additionalConditions: Conditions
additionalConditions: Conditions,
): Conditions {
const mergedConditions: Conditions = {};
Object.entries(principalConditions).forEach(([operator, condition]) => {
Expand All @@ -264,7 +264,7 @@ export class PrincipalWithConditions implements IPrincipal {
cdk.Token.isUnresolved(existing)
) {
throw new Error(
`multiple "${operator}" conditions cannot be merged if one of them contains an unresolved token`
`multiple "${operator}" conditions cannot be merged if one of them contains an unresolved token`,
);
}

Expand Down Expand Up @@ -292,7 +292,7 @@ export class PrincipalPolicyFragment {
* The conditions under which the policy is in effect.
* See [the IAM documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html).
*/
public readonly conditions: Conditions = {}
public readonly conditions: Conditions = {},
) {}
}

Expand Down Expand Up @@ -334,8 +334,8 @@ export class AccountPrincipal extends ArnPrincipal {
constructor(public readonly accountId: any) {
super(
new StackDependentToken(
(stack) => `arn:${stack.partition}:iam::${accountId}:root`
).toString()
(stack) => `arn:${stack.partition}:iam::${accountId}:root`,
).toString(),
);
this.principalAccount = accountId;
}
Expand Down Expand Up @@ -374,7 +374,7 @@ export class ServicePrincipal extends PrincipalBase {
*/
constructor(
public readonly service: string,
private readonly opts: ServicePrincipalOpts = {}
private readonly opts: ServicePrincipalOpts = {},
) {
super();
}
Expand All @@ -386,7 +386,7 @@ export class ServicePrincipal extends PrincipalBase {
new ServicePrincipalToken(this.service, this.opts).toString(),
],
},
this.opts.conditions
this.opts.conditions,
);
}

Expand All @@ -410,7 +410,7 @@ export class OrganizationPrincipal extends PrincipalBase {
public get policyFragment(): PrincipalPolicyFragment {
return new PrincipalPolicyFragment(
{ AWS: ['*'] },
{ StringEquals: { 'aws:PrincipalOrgID': this.organizationId } }
{ StringEquals: { 'aws:PrincipalOrgID': this.organizationId } },
);
}

Expand Down Expand Up @@ -464,6 +464,7 @@ export class CanonicalUserPrincipal extends PrincipalBase {
*/
export class FederatedPrincipal extends PrincipalBase {
public readonly assumeRoleAction: string;
public readonly assumeRoleActions?: string[];

/**
*
Expand All @@ -474,17 +475,19 @@ export class FederatedPrincipal extends PrincipalBase {
constructor(
public readonly federated: string,
public readonly conditions: Conditions,
assumeRoleAction: string = 'sts:AssumeRole'
assumeRoleAction: string = 'sts:AssumeRole',
assumeRoleActions?: string[],
) {
super();

this.assumeRoleAction = assumeRoleAction;
this.assumeRoleActions = assumeRoleActions;
}

public get policyFragment(): PrincipalPolicyFragment {
return new PrincipalPolicyFragment(
{ Federated: [this.federated] },
this.conditions
this.conditions,
);
}

Expand All @@ -511,7 +514,7 @@ export class WebIdentityPrincipal extends FederatedPrincipal {
public get policyFragment(): PrincipalPolicyFragment {
return new PrincipalPolicyFragment(
{ Federated: [this.federated] },
this.conditions
this.conditions,
);
}

Expand All @@ -532,15 +535,15 @@ export class OpenIdConnectPrincipal extends WebIdentityPrincipal {
*/
constructor(
openIdConnectProvider: IOpenIdConnectProvider,
conditions: Conditions = {}
conditions: Conditions = {},
) {
super(openIdConnectProvider.openIdConnectProviderArn, conditions ?? {});
}

public get policyFragment(): PrincipalPolicyFragment {
return new PrincipalPolicyFragment(
{ Federated: [this.federated] },
this.conditions
this.conditions,
);
}

Expand Down Expand Up @@ -625,7 +628,7 @@ export class CompositePrincipal extends PrincipalBase {
super();
if (principals.length === 0) {
throw new Error(
'CompositePrincipals must be constructed with at least 1 Principal but none were passed.'
'CompositePrincipals must be constructed with at least 1 Principal but none were passed.',
);
}
this.assumeRoleAction = principals[0].assumeRoleAction;
Expand All @@ -643,15 +646,15 @@ export class CompositePrincipal extends PrincipalBase {
if (p.assumeRoleAction !== this.assumeRoleAction) {
throw new Error(
'Cannot add multiple principals with different "assumeRoleAction". ' +
`Expecting "${this.assumeRoleAction}", got "${p.assumeRoleAction}"`
`Expecting "${this.assumeRoleAction}", got "${p.assumeRoleAction}"`,
);
}

const fragment = p.policyFragment;
if (fragment.conditions && Object.keys(fragment.conditions).length > 0) {
throw new Error(
'Components of a CompositePrincipal must not have conditions. ' +
`Tried to add the following fragment: ${JSON.stringify(fragment)}`
`Tried to add the following fragment: ${JSON.stringify(fragment)}`,
);
}

Expand Down Expand Up @@ -707,7 +710,7 @@ class ServicePrincipalToken implements cdk.IResolvable {
public readonly creationStack: string[];
constructor(
private readonly service: string,
private readonly opts: ServicePrincipalOpts
private readonly opts: ServicePrincipalOpts,
) {
this.creationStack = cdk.captureStackTrace();
}
Expand Down
9 changes: 7 additions & 2 deletions packages/@aws-cdk/aws-iam/lib/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,12 @@ export interface IRole extends IIdentity {
function createAssumeRolePolicy(principal: IPrincipal, externalIds: string[]) {
const statement = new AwsStarStatement();
statement.addPrincipals(principal);
statement.addActions(principal.assumeRoleAction);

if (principal.assumeRoleActions !== undefined) {
statement.addActions(...principal.assumeRoleActions);
} else {
statement.addActions(principal.assumeRoleAction);
}

if (externalIds.length) {
statement.addCondition('StringEquals', { 'sts:ExternalId': externalIds.length === 1 ? externalIds[0] : externalIds });
Expand Down Expand Up @@ -540,4 +545,4 @@ export interface WithoutPolicyUpdatesOptions {
* @default false
*/
readonly addGrantsToResources?: boolean;
}
}
42 changes: 41 additions & 1 deletion packages/@aws-cdk/aws-iam/test/principals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,44 @@ test('PrincipalWithConditions inherits principalAccount from AccountPrincipal ',
// THEN
expect(accountPrincipal.principalAccount).toStrictEqual('123456789012');
expect(principalWithConditions.principalAccount).toStrictEqual('123456789012');
});
});

test('Principal assumeRoleAction(s) can be single or multiple', () => {
const stack = new Stack();
const federatedPrincipal = new iam.FederatedPrincipal(
'cognito-identity.amazonaws.com',
{ StringEquals: { hairColor: 'blond' } },
'',
[
'sts:AssumeRoleWithWebIdentity',
'sts:TagSession',
]);

new iam.Role(stack, 'Role', {
assumedBy: federatedPrincipal,
});

// THEN
expect(stack).toHaveResource('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Statement: [
{
Action: [
'sts:AssumeRoleWithWebIdentity',
'sts:TagSession',
],
Condition: {
StringEquals: {
hairColor: 'blond',
},
},
Effect: 'Allow',
Principal: {
Federated: 'cognito-identity.amazonaws.com',
},
},
],
Version: '2012-10-17',
},
});
});

0 comments on commit ba0868b

Please sign in to comment.