aws-cdk icon indicating copy to clipboard operation
aws-cdk copied to clipboard

codebuild: project creation fails when GitHub source webhook is enabled

Open tmokmss opened this issue 1 year ago • 3 comments

Describe the bug

CloudFormation throws an error when we try to create a CodeBuild project with GitHub webhook is configured.

16:13:04 | CREATE_FAILED | AWS::CodeBuild::Project | MyProject2B52B17CC Failed to call CreateWebhook, reason: Access denied to connection arn:aws:codeconnections:us-west-2:REDACTED:connection/REDACTED (Service: AWSCodeBuild; Status Cod e: 400; Error Code: InvalidInputException; Request ID: REDACTED; Proxy: null)

Regression Issue

  • [ ] Select this option if this issue appears to be a regression.

Last Known Working CDK Version

No response

Expected Behavior

CodeBuild project is successfully created.

Current Behavior

CloudFormation throws an error when creating a project.

Reproduction Steps

  1. First, create a GitHub app connection from the management console. https://docs.aws.amazon.com/codebuild/latest/userguide/connections-github-app.html
  2. Deploy the below stack, and you'll get an error. (replace YOUR_GITHUB_NAME and YOUR_REPOSITORY_NAME to an existing repo.)
import * as codebuild from 'aws-cdk-lib/aws-codebuild';

export class CodebuildGhaCdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    const project = new codebuild.Project(this, 'MyProject', {
      source: codebuild.Source.gitHub({
        owner: 'YOUR_GITHUB_NAME',
        repo: 'YOUR_REPOSITORY_NAME',
        webhookFilters: [codebuild.FilterGroup.inEventOf(codebuild.EventAction.WORKFLOW_JOB_QUEUED)],
      }),
    });
    // uncommenting this makes it work
    // project.role!.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'));
  }
}

Possible Solution

https://github.com/aws/aws-cdk/issues/31726#issuecomment-2413937735

This was a problem of both permission and dependency.

Additional Information/Context

When I added Administrator permission to the project, CFn successfully deployed the project. But not sure exactly which permission is missing.

// WORKS
project.role!.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'));

Allowing codeconnections did not work:

    // NOT WORK
    project.addToRolePolicy(
      new PolicyStatement({
        actions: [
          'codeconnections:*',
          'codestar-connections:*',
        ],
        resources: ['*'],
      })
    );

CDK CLI Version

2.162.0

Framework Version

No response

Node.js Version

20

OS

macos

Language

TypeScript

Language Version

No response

Other information

We need to enable webhook to use a CodeBuild-hosted GitHub Actions runner.

tmokmss avatar Oct 11 '24 07:10 tmokmss

@tmokmss Good afternoon. Somehow, I'm unable to reproduce issue at my end using CDK version 2.162.0 (build c8d7dd3):

import * as cdk from 'aws-cdk-lib';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';

export class CdktestStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // This should be one time task.
    new codebuild.GitHubSourceCredentials(this, 'CodeBuildGitHubCreds', {
      accessToken: cdk.SecretValue.unsafePlainText('<<REDACTED>>')
    });

    const project = new codebuild.Project(this, 'MyProject', {
      source: codebuild.Source.gitHub({
        owner: 'mygithubname',
        repo: 'testrepo',
        webhookFilters: [codebuild.FilterGroup.inEventOf(codebuild.EventAction.WORKFLOW_JOB_QUEUED)],
      }),
    });
  }
}

gives below error during deployment:

✨  Synthesis time: 4.25s

CdktestStack: start: Building 551fa8c3d398f2112a97f5c3b96f2d1da537f7c06ff3f1345a1a3e63aa0377ad:139480602983-us-east-2
CdktestStack: success: Built 551fa8c3d398f2112a97f5c3b96f2d1da537f7c06ff3f1345a1a3e63aa0377ad:139480602983-us-east-2
CdktestStack: start: Publishing 551fa8c3d398f2112a97f5c3b96f2d1da537f7c06ff3f1345a1a3e63aa0377ad:139480602983-us-east-2
CdktestStack: success: Published 551fa8c3d398f2112a97f5c3b96f2d1da537f7c06ff3f1345a1a3e63aa0377ad:139480602983-us-east-2
Stack undefined
CdktestStack: deploying... [1/1]
CdktestStack: creating CloudFormation changeset...

 ✅  CdktestStack

✨  Deployment time: 55.54s

Stack ARN:
arn:aws:cloudformation:us-east-2:<<ACCOUNT-ID>>:stack/CdktestStack/1079e520-8a77-11ef-89fa-020707ed96ab

✨  Total time: 59.79s

I'm unsure if you added necessary permissions mentioned at https://docs.aws.amazon.com/codebuild/latest/userguide/connections-github-app.html#connections-github-role-access to the IAM role used to deploy CDK stack.

Thanks, Ashish

ashishdhingra avatar Oct 14 '24 22:10 ashishdhingra

@ashishdhingra Thank you for the investigation!

Can you try GitHub App connections instead of a PAT credential? You also need to set the connection as an account level credential. With this configuraiton, the issue should reproduce.

And thank you for pointing out the necessary permission. It seems we need to create the policy before creating a project. So I confirmed something like this worked:

    const project = new codebuild.Project(this, 'MyProject', {
      source: codebuild.Source.gitHub({
        owner: 'redacted',
        repo: 'redacted',
        webhookFilters: [codebuild.FilterGroup.inEventOf(codebuild.EventAction.WORKFLOW_JOB_QUEUED)],
      }),
    });
    const codeConnectionPolicy = new Policy(this, 'CodeConnectionPolicy', {
      statements: [
        new PolicyStatement({
          actions: ['codeconnections:GetConnectionToken', 'codeconnections:GetConnection'],
          resources: ['*'],
        }),
      ],
    });
    project.role!.attachInlinePolicy(codeConnectionPolicy);
    // create the policy before the project
    (project.node.defaultChild as cdk.CfnResource).addDependency(codeConnectionPolicy.node.defaultChild as cdk.CfnResource);

I think this should be automatically configured in L2 code, or at least be clearly described in the doc.

tmokmss avatar Oct 15 '24 13:10 tmokmss

I was having a similar issue, however I also needed to update the CfnProject to include the missing Auth

(project.node.defaultChild as CfnProject).addPropertyOverride('Source.Auth', {
            Type: 'CODECONNECTIONS',
            Resource:
                'arn:aws:codeconnections:<region>:<account-id>:connection/<connection-id>',
        })

Agree with @tmokmss that the L2 construct should be updated because this use case, which would be very common, is missing from any of the existing constructs

patrickdorival avatar Oct 19 '24 01:10 patrickdorival

@patrickdorival I configured account level credential to avoid from setting connection per project, but yeah it's disappointing that current L2 doesn't support Source.Auth property. I'll create a seprarate issue for this.

edit) We already have this one: #31236

tmokmss avatar Oct 23 '24 01:10 tmokmss

Yes, I saw that issue as well. Seems to be a few things missing from the L2 construct when you want to use Code Connections for your auth

patrickdorival avatar Oct 23 '24 01:10 patrickdorival

Is this only being picked up if there is lot of thumbs up?

macdrorepo avatar Dec 02 '24 07:12 macdrorepo

I actually got this all working (finally).

Here is the setup I am using (python)

# This one is technically in a different stack as it is "shared"
# this is also configured as an installation vs a user-auth.
self.github_connection = code_connections.CfnConnection(
            self, 'GitHubConnection',
            connection_name='My Github Connection',
            provider_type='GitHub',
        )

# Really unsure if this is even needed.. We had it created for out GitLab setup (non-CDK)
source_credential = code_build.CfnSourceCredential(
            self, 'MySourceCredential',
            auth_type='CODECONNECTIONS',
            server_type='GITHUB',
            token=self.github_connection.attr_connection_arn,
        )

my_project =  code_build.Project(
            self, 'MyProject',
            description='Some awesome project',
            environment=code_build.BuildEnvironment(
                compute_type=code_build.ComputeType.SMALL,
                privileged=True,
                build_image=code_build.LinuxBuildImage.from_code_build_image_id(
                    'aws/codebuild/amazonlinux2-x86_64-standard:5.0'),
                environment_variables=dict(
                   # Some ENV variables
            ),
            build_spec=code_build.BuildSpec.from_source_filename('ci/buildspec.yml'),
            source=code_build.Source.git_hub(
                owner='mygithuborg',
                repo='mygithubrepo',
                branch_or_ref='refs/heads/main',
                webhook_filters=[
                    code_build.FilterGroup.in_event_of(code_build.EventAction.PUSH).and_branch_is('main')
                ]
            )
        )
      # this was the required policy for Github connection (more than what GitLab required)
 code_connection_policy = iam.Policy(
            self, 'MyProjectRoleCodeConnectionPolicy',
            statements=[
                iam.PolicyStatement(
                    effect=iam.Effect.ALLOW,
                    actions=[
                       # through a lot of jiggling the handle I believe this is the required permission set.
                        'codeconnections:UseConnection',
                        'codeconnections:GetConnection',
                        'codeconnections:GetConnectionToken',
                    ],
                    resources=[
                        self.github_connection.attr_connection_arn
                    ]
                )
            ]
        )

# Attach the policy to the generated role
my_project.role.attach_inline_policy(code_connection_policy)
# set dependencies to ensure the policy is created BEFORE the project gets created.
# This was one KEY item here otherwise it will ALWAYS fail
my_project.node.default_child.add_dependency(code_connection_policy.node.default_child)
# Finally associate the connection on the project
my_project.node.default_child.add_property_override('Source.Auth', dict(
            Type= 'CODECONNECTIONS',
            Resource=self.github_connection.attr_connection_arn,
        ))

urkle avatar Dec 04 '24 04:12 urkle

We're experiencing the same exact issue as the OP. @urkle 's solution doesn't seem to work in our case. We've unblocked ourselves by manually creating the webhooks, but that's obviously not a long term solution.

mbparker avatar Dec 12 '24 07:12 mbparker

@mbparker bummer.. I thought I had the right privileges.. I bet what happened is I had ran it once with codeconnections:* as the allowed actions and it worked. And probably fully destroying the project resource did not remove that webhook.

Having better documentation from AWS as to what privileges are actually needed to do certain things would be immensely helpful. Right now most errors do not even report what privilege is needed to allow them to succeed, which makes triaging and fixing things near impossible.

urkle avatar Dec 12 '24 16:12 urkle

We're using a very ugly construct to implement this. It's automated but it's ugly. (we're deploying organizational runners)

import { RemovalPolicy, CfnResource } from 'aws-cdk-lib';
import { CfnProject, Project, Source, IBuildImage, ComputeType } from 'aws-cdk-lib/aws-codebuild';
import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export interface GitHubRunnerProjectProps {
  runnerName: string;
  gitHubOrgName: string;
  codeConnectionArn: string;
  buildImage: IBuildImage;
  computeType: ComputeType;
}

export class GitHubRunnerProject extends Construct {
  public readonly project: Project;

  constructor(scope: Construct, id: string, props: GitHubRunnerProjectProps) {
    super(scope, id);

    const { runnerName, gitHubOrgName, codeConnectionArn, buildImage, computeType } = props;

    this.project = new Project(this, 'OrgShRunnerCodeBuildProject', {
      projectName: runnerName,
      source: Source.gitHub({
        owner: 'dummy',
        repo: 'dummy',
      }),
      environment: {
        buildImage: buildImage,
        computeType: computeType,
      },
      logging: {
        cloudWatch: {
          logGroup: new LogGroup(this, 'OrgShRunnerCodeBuildProjectLogGroup', {
            retention: RetentionDays.ONE_MONTH,
            removalPolicy: RemovalPolicy.DESTROY,
          }),
        },
      },
    });

    const codeConnectionPolicy = new Policy(this, 'CodeConnectionPolicy', {
      statements: [
        new PolicyStatement({
          actions: ['codeconnections:GetConnectionToken', 'codeconnections:GetConnection'],
          resources: ['*'],
        }),
      ],
    });

    this.project.role!.attachInlinePolicy(codeConnectionPolicy);
    (this.project.node.defaultChild as CfnResource).addDependency(
      codeConnectionPolicy.node.defaultChild as CfnResource,
    );

    const cfnProject = this.project.node.defaultChild as CfnProject;

    cfnProject.addPropertyOverride('Source', {
      Type: 'GITHUB',
      Location: 'CODEBUILD_DEFAULT_WEBHOOK_SOURCE_LOCATION',
      Auth: {
        Type: 'CODECONNECTIONS',
        Resource: codeConnectionArn,
      },
    });

    cfnProject.addPropertyOverride('Triggers', {
      Webhook: true,
      ScopeConfiguration: {
        Name: gitHubOrgName,
      },
      FilterGroups: [
        [
          {
            Type: 'EVENT',
            Pattern: 'WORKFLOW_JOB_QUEUED',
          },
        ],
      ],
    });
  }
}

And calling

    new GitHubRunnerProject(this, 'GitHubRunnerAmznLinux2023Arm64Small', {
      runnerName: 'yyy',
      gitHubOrgName: gitHubOrgName,
      codeConnectionArn: codeConnectionArn,
      buildImage: LinuxArmBuildImage.AMAZON_LINUX_2023_STANDARD_3_0,
      computeType: ComputeType.SMALL,
    });

    new GitHubRunnerProject(this, 'GitHubRunnerUbuntuExtX86small', {
      runnerName: 'xxx',
      gitHubOrgName: gitHubOrgName,
      codeConnectionArn: codeConnectionArn,
      buildImage: LinuxBuildImage.STANDARD_7_0,
      computeType: ComputeType.SMALL,
    });

lvthillo avatar Mar 12 '25 15:03 lvthillo