Scenario 1: you're creating a role and a policy in the same stack, and the policy will not be reused in any other stack.
In this case use iam.Policy and attach it in-line:
export class CdkGetLogicalIdExampleStack extends cdk.Stack {
role: iam.IRole;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const policy = new iam.Policy(this, 'policy', {
policyName: 'policy',
statements: [
new iam.PolicyStatement({
actions: ["s3:*"],
resources: ['*'],
effect: iam.Effect.ALLOW
})
]
});
this.role = new iam.Role(this, 'my-role', {
roleName: 'my-role',
assumedBy: new iam.AccountPrincipal(this.account),
});
// Alternatively: this.role.attachInlinePolicy(policy);
policy.attachToRole(this.role);
}
}
The role is kept as stack's attribute, so it's easy to refer to it in tests:
test('Empty Stack', () => {
const app = new cdk.App();
const stack = new CdkGetLogicalIdExample.CdkGetLogicalIdExampleStack(app, 'MyTestStack');
const roleId = stack.getLogicalId(stack.role.node.findChild('Resource') as cdk.CfnElement);
expectCDK(stack).to(haveResource("AWS::IAM::Policy", {
"PolicyDocument": {
"Statement": [
{
"Action": "s3:*",
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"PolicyName": "policy",
"Roles": [
{
"Ref": roleId
}
]
}));
});
Scenario 2: you're creating a policy that can be used in various stacks, so you want to make it a managed policy.
Testing this scenario is a little bit more complex because iam.ManagedPolicy implements the IManagedPolicy interface, which only provides the managedPolicyArn attribute (I am using CDK 1.124.0).
Nevertheless, the iam.ManagedPolicy extends the cdk.Resource, so we can trick the casting mechanism of TypeScript in the following way:
export class CdkGetLogicalIdExampleStack extends cdk.Stack {
// note the type here
managedPolicy: cdk.Resource | iam.IManagedPolicy;
role: iam.IRole;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.managedPolicy = new iam.ManagedPolicy(this, 'managed-policy', {
managedPolicyName: 'managed-policy',
document: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ["dynamodb:*"],
resources: ["*"],
effect: iam.Effect.ALLOW
})
]
})
});
this.role = new iam.Role(this, 'my-role', {
roleName: 'my-role',
assumedBy: new iam.AccountPrincipal(this.account),
});
// here we need to cast to iam.IManagedPolicy
this.role.addManagedPolicy(this.managedPolicy as iam.IManagedPolicy);
}
}
Now, testing it is possible because we can access managedPolicy attribute as cdk.Resource:
const roleId = stack.getLogicalId(stack.role.node.findChild('Resource') as cdk.CfnElement);
// here we do the casting to cdk.Resource
const managedPolicyResource = stack.managedPolicy as cdk.Resource;
const managedPolicyId = stack.getLogicalId(managedPolicyResource.node.findChild('Resource') as cdk.CfnElement);
expectCDK(stack).to(haveResource("AWS::IAM::ManagedPolicy", {
"PolicyDocument": {
"Statement": [
{
"Action": "dynamodb:*",
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"Description": "",
"ManagedPolicyName": "managed-policy",
"Path": "/"
}));
expectCDK(stack).to(haveResource("AWS::IAM::Role", {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"AWS": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::",
{
"Ref": "AWS::AccountId"
},
":root"
]
]
}
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Ref": managedPolicyId
}
],
"RoleName": "my-role"
}));
In this way you can test that your policies have all the necessary data and the relations between roles and policies are correct.
You can access a full working example here.