AWS CDK - IRole.addManagedPolicy() has no effect, IRole.attachInlinePolicy() works. Why?

Viewed 1247

I am working with two CDK stacks.

  • Stack A creates an iam.ManagedPolicy that grants permissions to a resource
  • Stack B attempts to import this managed policy (IManagedPolicy), import a role (IRole), and then attach the managed policy to the role using IRole.addManagedPolicy(IManagedPolicy)
// Stack B
const iam = require("@aws-cdk/aws-iam");

const ec2InstanceRole = iam.Role.fromRoleArn(this, 'my-role', 'the-arn-of-the-role');

const managedPolicy = iam.ManagedPolicy.fromManagedPolicyName(this, 'my-managed-policy', 'name-of-the-policy');

ec2InstanceRole.addManagedPolicy(managedPolicy);

When I deploy Stack B, there are no errors. However, the role is not modified either. If instead of the above, I do the following:

  • In Stack B, import the same role (IRole)
  • Call IRole.attachInlinePolicy(iam.Policy) and define the same permissions in the iam.Policy that are defined in the managed policy from Stack A, the role in Stack B is modified.
// Stack B
const iam = require("@aws-cdk/aws-iam");

const ec2InstanceRole = iam.Role.fromRoleArn(this, 'my-role', 'the-arn-of-the-role');

ec2InstanceRole.attachInlinePolicy(
    new iam.Policy(this, 'test-policy', {
        policyName: 'test-policy-name',
        statements: [
            new iam.PolicyStatement({
                effect: iam.Effect.ALLOW,
                actions: ['product:capability'],
                resources: ['arn-of-resource']
            })
        ]
    })
);

That works. The role is modified by adding a new inline policy that contains the defined permissions.

Some additional background info...

  • The role and managed policy are both in the same account.
  • I am using CDK 1.114.0.
  • The role referenced in Stack B is a pre-existing role that is not managed by CDK and is attached to an Ec2 instance functioning as its instance profile role.
  • The managed policy defined in Stack A is for a new service that is being developed which the service defined in Stack B needs to interact with.

Why does the first approach fail but the second succeed? Is the idea that it is 'safer' to attach an inline policy to a statically imported role because I have to explicitly define the permissions being added in the policy vs pulling in a managed policy from another stack? Is it just a matter of the policy being defined in the same stack as the attach operation is being performed in (again, some sort of security measure)? Is there something more fundamentally wrong with what I am attempting to do?

Any thoughts would be greatly appreciated.

3 Answers

This is unsupported with a "FIXME" in the code.

This comment suggests that it's due to a gap in the CDK and CloudFormation-related limitations. It's certainly possible to attach/detach managed policies to an existing role otherwise.

The workaround I found is below. It's not perfect, but it's pretty good:

  1. define the policy statement(s)
  2. create the managed policy with that policy statement
  3. attach the policy statement to the imported role.

Example

const statement = new PolicyStatement({
    effect: Effect.ALLOW,
    actions: ['execute-api:Invoke'],
    resources: [
        //SOME RESOURCE
    ]
})
this.cloudAuthAccessPolicy = new ManagedPolicy(this, 'FOO', {
    managedPolicyName: 'FOO',
    description: "BAR",
    statements: [
        statement
    ]
})

const role = Role.fromRoleArn(this, `name`, `arn:aws:iam::${props.account}:role/SOME_ROLE_NAME`)
role.addToPolicy(statement)

This is interesting. I suggest you read https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html if you haven't already for additional info regarding managed/inline policies.

Also, have you tried creating the ManagedPolicy in Stack B and applying it, rather than importing it from a different stack? It could be that the import is failing or something like that.

Related