I have a role created by aws cdk
like below code.
const role = new iam.Role(scope, name, {
assumedBy: new iam.CompositePrincipal(
new iam.ServicePrincipal('codebuild.amazonaws.com'),
new iam.ServicePrincipal('codepipeline.amazonaws.com'),
),
});
role.grantAssumeRole(new iam.ArnPrincipal(role.roleArn));
I'd like it to be able to assume by itself. The use case is that the role will used by codebuild and inside the codebuild it needs to assume the role in order to pass credential to a docker container.
but after deploy, the principal of this role is:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codebuild.amazonaws.com"
},
"Action": "sts:AssumeRole"
},
{
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
It doesn't include the roleArn
in the principle. How can I make the arn appear in the principal policy?
I don't pass the role name into this code since I'd like to use auto generated role name. That's why I can't assemble the role arn.
I had a similar use case as yourself, where I needed to allow a role to assume itself. It is worth noting that AWS modified the default IAM role behavior and IAM roles are no longer implicitly allowed to assume themselves as of June 30th, 2023. In addition, the CDK docs are incorrect and do not let the user know that when calling grantAssumeRole the method will add an IAM::Policy to the role rather than adding a trust policy to the role. It seems AWS is aware of this behavior and has no plans to fix it, instead they recommend users leverage a custom resource to achieve the desired outcome. I was able to solve this by using SSM parameters and a custom resource; I had to use the SSM parameters to get a string representation of the required IAM role name and ARN as the custom resource call to IAM would fail due to the CDK token in the policy document. I would recommend to do this in a two step process:
The only callout to note is that the custom resource will replace the entire IAM role trust policy, so if you already had other statements on there you will need to include those in the policy document implemented by the custom resource.
Below is a code sample to create the SSM parameters and the custom resource that worked for me:
// Step 1: Create SSM parameter values in parent stack
// ssm EMR service role IAM parameter to be used in helper stack
this.ssmEmrServiceRoleArnParameterName = 'EmrServiceRoleArnParameter';
new ssm.StringParameter(this, `emrEtlClusterServiceRoleArn-${props.stage}`, {
parameterName: this.ssmEmrServiceRoleArnParameterName,
description: 'This IAM role ARN is used for the emr helper stack',
stringValue: this.emrEtlClusterServiceRole.roleArn,
});
// ssm EMR service role IAM parameter to be used in helper stack
this.ssmEmrServiceRoleNameParameterName = 'EmrServiceRoleNameParameter';
new ssm.StringParameter(this, 'emrEtlClusterServiceRoleName', {
parameterName: this.ssmEmrServiceRoleNameParameterName,
description: 'This IAM role name is used for the emr helper stack',
stringValue: this.emrEtlClusterServiceRole.roleName,
});
// Step 2: create child stack to consume the SSM parameter values and modify the IAM role trust policy using a custom resource
import {
App
} from 'aws-cdk-lib';
import * as cr from 'aws-cdk-lib/custom-resources';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as ssm from 'aws-cdk-lib/aws-ssm';
export interface EmrIamRoleHelperStackProps {
deploymentStackProps: DeploymentStackProps;
awsAccount: string;
region: string;
stage: string;
team: string;
emrServiceRoleArnParameterName: string;
emrServiceRoleNameParameterName: string;
}
export class EmrIamRoleHelperStack extends DeploymentStack {
constructor(scope: App, id: string, props: EmrIamRoleHelperStackProps) {
super(scope, id, props.deploymentStackProps);
const emrIamRoleArn: string = ssm.StringParameter.fromStringParameterName(
this,
'emrIamRoleArn',
props.emrServiceRoleArnParameterName,
).stringValue;
const emrIamRoleName: string = ssm.StringParameter.fromStringParameterName(
this,
'emrIamRoleName',
props.emrServiceRoleNameParameterName,
).stringValue;
// create an iam policy document to be used by aws custom resource
const emrIamPolicyDocument: iam.PolicyDocument = new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'sts:AssumeRole',
'sts:TagSession'
],
principals: [
new iam.ArnPrincipal(emrIamRoleArn)
]
}),
new iam.PolicyStatement({ // required because the existing trust policy is overwritten
effect: iam.Effect.ALLOW,
actions: [
'sts:AssumeRole',
],
principals: [
new iam.CompositePrincipal(
new iam.ServicePrincipal('elasticmapreduce.amazonaws.com'),
new iam.ServicePrincipal('ec2.amazonaws.com')
)
]
})
]
})
this.updateEmrIamRole(props, emrIamRoleName, emrIamPolicyDocument)
}
private updateEmrIamRole(
props: EmrIamRoleHelperStackProps,
emrIamRoleName: string,
iamPolicyDocument: iam.PolicyDocument
): void {
new cr.AwsCustomResource(
this,
'updateAssumeRolePolicy', {
onUpdate: { // will also be called for a CREATE event
service: 'IAM',
action: 'updateAssumeRolePolicy',
parameters: {
'RoleName': emrIamRoleName,
'PolicyDocument': JSON.stringify(iamPolicyDocument.toJSON())
},
physicalResourceId: cr.PhysicalResourceId.of('updateAssumeRolePolicy'),
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
}
);
}
}