Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CodePipeline/CodeBuild: Maximum policy size of 10240 bytes exceeded for role xxx-role #4465

Open
markusl opened this issue Oct 11, 2019 · 11 comments · May be fixed by #4501

Comments

@markusl
Copy link

@markusl markusl commented Oct 11, 2019

We are deploying a set of CodePipeline & CodeBuild stacks with AWS CDK and hit the following errors:

Maximum policy size of 10240 bytes exceeded for role xxx-CodeBuildRole (Service: AmazonIdentityManagement; Status Code: 409; Error Code: LimitExceeded; Request ID: )
Maximum policy size of 10240 bytes exceeded for role xxx-CodePipelineRole (Service: AmazonIdentityManagement; Status Code: 409; Error Code: LimitExceeded; Request ID: )
Maximum policy size of 10240 bytes exceeded for role xxx-CrossAccountDeployerRole (Service: AmazonIdentityManagement; Status Code: 409; Error Code: LimitExceeded; Request ID:)

As you can see, we are trying to use our custom build, pipeline and deployment roles for our setup.

We have setup the IAM roles so that they are limited to certain resources with certain prefixes in certain regions. The IAM roles are already suitable for pipeline, build and deployment usage. The problem seems to be that CDK adds more policies to the roles until the stack update is cancelled because the resource limit is exceeded.

In our use case we are forced to use hand-crafter CloudFormation templates until we can fully move to CDK. This is currently a blocker for us.

Reproduction Steps

Creating multiple pipelines with following code:

    const pipeline = new codepipeline.Pipeline(stack, `Pipeline`, {
        pipelineName: name,
        role: codePipelineRole(stack), // Import role
        artifactBucket: bucket,
    });
    const source = new codepipeline.Artifact('Source');
    pipeline.addStage({
        stageName: 'Source',
        actions: [
            new codepipeline_actions.CodeCommitSourceAction({
                actionName: 'Source',
                repository,
                output: source,
                role: codeCommitRole(stack), // Import role
            }),
        ]
    });
    const buildOutput = new codepipeline.Artifact();
    pipeline.addStage({
        stageName: 'Build',
        actions: [
            new codepipeline_actions.CodeBuildAction({
                actionName: 'Build',
                input: source,
                project,
                outputs: [buildOutput],
                role: pipeline.role,
            }),
        ]
    });

    pipeline.addStage({
        stageName: 'UpdateDev',
        actions: [
// See https://github.com/aws/aws-cdk/issues/4375
            new MyEcsDeployAction({
                actionName: 'DeployImage',
                clusterName: clusterNameDev,
                serviceName: name,
                input: buildOutput,
                role: crossAccountDeployerRole(stack), // Import role
            }),
        ]
    });

Proposed solution

Two possibilities come to my mind:

  • Provide a way to use the role as-is, without adding any new policies and assume it has been already set up properly.

  • Another way could possibly be to create new policies rather than inline policies that fill up the limited space very quickly.

Environment

  • **CLI Version : 1.12.0
  • **Framework Version: 1.12.0
  • **OS : MacOS
  • **Language : TypeScript

This is 🐛 Bug Report

@skinny85

This comment has been minimized.

Copy link
Contributor

@skinny85 skinny85 commented Oct 11, 2019

Thanks for opening the issue @markusl . We are aware of this problem, and are planning to tackle it in #2985. In the meantime, you can add the following "wrapper" class for IRole:

class ImmutableRole implements iam.IRole {
    private readonly role: iam.IRole;

    constructor(role: iam.IRole) {
        this.role = role;
    }

    readonly assumeRoleAction = this.role.assumeRoleAction;
    readonly grantPrincipal = this.role.grantPrincipal;
    readonly node = this.role.node;
    readonly policyFragment = this.role.policyFragment;
    readonly roleArn = this.role.roleArn;
    readonly roleName = this.role.roleName;
    readonly stack = this.role.stack;

    addManagedPolicy(policy: iam.IManagedPolicy): void {
        // do nothing
    }

    addToPolicy(statement: iam.PolicyStatement): boolean {
        return false;
    }

    attachInlinePolicy(policy: iam.Policy): void {
        // do nothing
    }

    grant(grantee: iam.IPrincipal, ...actions: string[]): iam.Grant {
        return this.role.grant(grantee, ...actions);
    }

    grantPassRole(grantee: iam.IPrincipal): iam.Grant {
        return this.role.grantPassRole(grantee);
    }
}

And use it in your code:

    const pipeline = new codepipeline.Pipeline(stack, `Pipeline`, {
        pipelineName: name,
        role: new ImmutableRole(codePipelineRole(stack)), // Import role
        artifactBucket: bucket,
    });
    const source = new codepipeline.Artifact('Source');
    pipeline.addStage({
        stageName: 'Source',
        actions: [
            new codepipeline_actions.CodeCommitSourceAction({
                actionName: 'Source',
                repository,
                output: source,
                role: new ImmutableRole(codeCommitRole(stack)), // Import role
            }),
        ],
    });
    const buildOutput = new codepipeline.Artifact();
    pipeline.addStage({
        stageName: 'Build',
        actions: [
            new codepipeline_actions.CodeBuildAction({
                actionName: 'Build',
                input: source,
                project,
                outputs: [buildOutput],
                role: new ImmutableRole(pipeline.role),
            }),
        ],
    });

    pipeline.addStage({
        stageName: 'UpdateDev',
        actions: [
          // See https://github.com/aws/aws-cdk/issues/4375
          new MyEcsDeployAction({
                actionName: 'DeployImage',
                clusterName: clusterNameDev,
                serviceName: name,
                input: buildOutput,
                role: new ImmutableRole(crossAccountDeployerRole(stack)), // Import role
          }),
        ],
    });

Hope this helps!

Thanks,
Adam

@skinny85

This comment has been minimized.

Copy link
Contributor

@skinny85 skinny85 commented Oct 11, 2019

Also, if the roles that you're using are all imported, you can pass the mutable: false switch when importing them:

const importedRole = iam.Role.fromRoleArn(this, 'R',
  'arn:aws:iam::123456789012:role/role-name', {
    mutable: false,
  }
);
@markusl

This comment has been minimized.

Copy link
Author

@markusl markusl commented Oct 14, 2019

Thanks for quick answer @skinny85 !

For the roles used in CodeCommitSourceAction, EcsDeployAction and codepipeline.Pipeline importing as immutable works.

Our CodeBuildRole used for codebuild.Project fails the build if imported as immutable:

Error: Validation failed with the following errors:
  [pipelines/pipelineProject/PolicyDocument] Policy must be attached to at least one principal: user, group or role
// The error is repeated for each pipeline

I also tried the ImmutableRole construct you suggested but I couldn't get it working. At least with imported roles.

        this.assumeRoleAction = this.role.assumeRoleAction;
                                          ^

TypeError: Cannot read property 'assumeRoleAction' of undefined
@skinny85

This comment has been minimized.

Copy link
Contributor

@skinny85 skinny85 commented Oct 14, 2019

Is your Project in a VPC, by any chance?

@markusl

This comment has been minimized.

Copy link
Author

@markusl markusl commented Oct 14, 2019

Yes it is. Sorry that I forgot to include this information before.

// Here's a snippet from our templates
 const project = new codebuild.Project(stack, `Project`, {
    role: codeBuildRole(stack),
    encryptionKey: artifactBucket.encryptionKey,
    cache: codebuild.Cache.local(codebuild.LocalCacheMode.DOCKER_LAYER, codebuild.LocalCacheMode.SOURCE),
    environment: {
        buildImage: codebuild.LinuxBuildImage.STANDARD_2_0,
        computeType: codebuild.ComputeType.MEDIUM,
        privileged: true, // Needed for building Docker images within Docker
        environmentVariables: { ... },
    },
    source: codebuild.Source.codeCommit({
        repository
    }),
    vpc: ec2.Vpc.fromVpcAttributes(stack, `ProjectVpc`, Vpc),
  });
@skinny85

This comment has been minimized.

Copy link
Contributor

@skinny85 skinny85 commented Oct 14, 2019

Yeah, that won't work with { mutable: false }, because there's a Policy that needs to be created before the project.

@skinny85

This comment has been minimized.

Copy link
Contributor

@skinny85 skinny85 commented Oct 14, 2019

Can you show me the code that leads to this error you posted above?

        this.assumeRoleAction = this.role.assumeRoleAction;
                                          ^

TypeError: Cannot read property 'assumeRoleAction' of undefined

This looks like you've passed undefined to the ImmutableRole constructor, but maybe I'm missing something...

@markusl

This comment has been minimized.

Copy link
Author

@markusl markusl commented Oct 14, 2019

That is actually weird one because it probably should be null.

Here is a short repro for what I'm seeing:

  const role = new ImmutableRole(
    iam.Role.fromRoleArn(stack, `CodeBuildRole`,
        `arn:aws:iam::${DevopsAccountNumber}:role/CodeBuildRole`));

If I split it to two parts I still get the error during synth:

  const rawRole = iam.Role.fromRoleArn(stack, `CodeBuildRole`, `arn:aws:iam::${DevopsAccountNumber}:role/CodeBuildRole`);
  if (rawRole === undefined) {
    throw new Error('undefined'); // This one is not thrown
  }
  const role = new ImmutableRole(rawRole);
/*
TypeError: Cannot read property 'assumeRoleAction' of undefined
    at new ImmutableRole (pipeline-master.js:67:43)
    at Object.exports.createCodeBuildProject (pipeline-master.js:99:18)
*/
@skinny85

This comment has been minimized.

Copy link
Contributor

@skinny85 skinny85 commented Oct 14, 2019

Got it. I think it was a simple TypeScript error on my part. Try this:

export class ImmutableRole implements iam.IRole {
    constructor(private readonly role: iam.IRole) {
    }

    readonly assumeRoleAction = this.role.assumeRoleAction;
    readonly grantPrincipal = this.role.grantPrincipal;
    readonly node = this.role.node;
    readonly policyFragment = this.role.policyFragment;
    readonly roleArn = this.role.roleArn;
    readonly roleName = this.role.roleName;
    readonly stack = this.role.stack;

    addManagedPolicy(policy: iam.IManagedPolicy): void {
        // do nothing
    }

    addToPolicy(statement: iam.PolicyStatement): boolean {
        return false;
    }

    attachInlinePolicy(policy: iam.Policy): void {
        // do nothing
    }

    grant(grantee: iam.IPrincipal, ...actions: string[]): iam.Grant {
        return this.role.grant(grantee, ...actions);
    }

    grantPassRole(grantee: iam.IPrincipal): iam.Grant {
        return this.role.grantPassRole(grantee);
    }
}
@markusl

This comment has been minimized.

Copy link
Author

@markusl markusl commented Oct 14, 2019

Thanks for looking into this!

The immutable role works now in a similar way than the imported immutable role which results in an error for each pipeline:

 throw new Error(`Validation failed with the following errors:\n  ${errorList}`);
 ^

Error: Validation failed with the following errors:
  [pipelines/Project1/PolicyDocument] Policy must be attached to at least one principal: user, group or role
  [pipelines/Project2/PolicyDocument] Policy must be attached to at least one principal: user, group or role
etc...
@skinny85

This comment has been minimized.

Copy link
Contributor

@skinny85 skinny85 commented Oct 14, 2019

Yeah, I know :/ This is kind of a miss on our part, but I have an idea for a fix. Stay tuned.

skinny85 added a commit to skinny85/aws-cdk that referenced this issue Oct 14, 2019
This adds an IRole implementation that ignores all mutating operations.
To accommodate this new behavior, add a new method to IIdentity:
createAndAttachPolicy, which is meant to replace attachInlinePolicy,
which can leave Policy resources unattached, which is illegal in CloudFormation.

Fixes aws#2985
Fixes aws#4465
@skinny85 skinny85 linked a pull request that will close this issue Oct 14, 2019
skinny85 added a commit to skinny85/aws-cdk that referenced this issue Oct 18, 2019
This adds an IRole implementation that ignores all mutating operations.
To accommodate this new behavior, add a new method to IIdentity:
createAndAttachPolicy, which is meant to replace attachInlinePolicy,
which can leave Policy resources unattached, which is illegal in CloudFormation.

Fixes aws#2985
Fixes aws#4465
skinny85 added a commit to skinny85/aws-cdk that referenced this issue Oct 21, 2019
This adds an IRole implementation that ignores all mutating operations.
To accommodate this new behavior, add a new method to IIdentity:
createAndAttachPolicy, which is meant to replace attachInlinePolicy,
which can leave Policy resources unattached, which is illegal in CloudFormation.

Fixes aws#2985
Fixes aws#4465
skinny85 added a commit to skinny85/aws-cdk that referenced this issue Oct 23, 2019
This adds an IRole implementation that ignores all mutating operations.
To accommodate this new behavior, add a new method to IIdentity:
createAndAttachPolicy, which is meant to replace attachInlinePolicy,
which can leave Policy resources unattached, which is illegal in CloudFormation.

Fixes aws#2985
Fixes aws#4465
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.