Search code examples
amazon-web-servicesaws-cloudformationaws-codepipelineaws-cdkaws-codecommit

AWS pipeline with cross-account CodeCommit repo as Source


I need to create a pipeline with CDK that will trigger a deploy in CloudFormation based on a branch in a CodeCommit repo. If the CodeCommit repo was in the same account as the pipeline, I would use something like:

const codecommitRepo = codecommit.Repository.fromRepositoryName(
  this,
  'AppRepository',
  'REPO_NAME'
);

pipeline.addStage({
  stageName: 'Source',
  actions: [
    new codepipeline_actions.CodeCommitSourceAction({
      actionName: 'Source',
      repository: codecommitRepo,
      branch: 'BRANCH_NAME',
      output: sourceArtifact,
    }),
  ],
});

However, what if the CodeCommit repo is in a different account?

The architecture I'm trying to recreate is similar to the one shown in this article (image below), but with CDK and CloudFormation.

aws-architecture

How can I create that?


Solution

  • If someone has the same problem, I managed to do it with CDK with this example. It's outdated but I applied the same logic. Probably some of the steps mentioned in this answer are not necesary because of recent changes. I found a more recent example here, but I haven't tried it yet.

    Important: Make sure the resources in both accounts are in the same region.

    Let's call ID_ACC_WITH_REPO to the AWS account ID with the CodeCommit repo and ID_ACC_WITH_PIPELINE to the account ID with the pipeline and where we want to deploy the architecture.

    CDK code for the pipeline

    ACC_WITH_REPO

    1. Create a stack in ID_ACC_WITH_REPO. The region must be especified because it's required for cross-account pipelines.
    const repoAccStack = new cdk.Stack(app, 'RepoAccStack', {
        env: {
            account: ID_ACC_WITH_REPO,
            region: REPO_REGION
        }
    });
    
    1. Create a cross-account role in ACC_WITH_REPO and attach full access policies for S3 (to store the artifacts), CodeCommit and KMS (encryption). The role will be used by the pipeline in ACC_WITH_PIPELINE and the CodeCommit source action in the source stage. I guess you can restrict them more to be extra secure.
    // Create role
    const crossAccRole = new iam.Role(repoAccStack, 'OtherAccRole', {
        roleName: 'CrossAccountRole',
        assumedBy: new iam.AccountPrincipal(pipelineAcc),
    });
    
    // Attach policies
    const policy = new iam.PolicyStatement();
    policy.addAllResources();
    policy.addActions('s3:*', 'codecommit:*', 'kms:*');
    crossAccRole.addToPolicy(policy);
    
    1. Import the repo.
    const repo = codecommit.Repository.fromRepositoryArn(
      repoAccStack,
      'AppRepository',
      `arn:aws:codecommit:${REPO_REGION}:${ID_ACC_WITH_REPO}:${REPO_NAME}`
    );
    

    ACC_WITH_PIPELINE

    1. Create a stack for the pipeline in ID_ACC_WITH_PIPELINE.
    const pipelineAccStack = new cdk.Stack(app, 'PipelineAccStack', {
        env: {
            account: ID_ACC_WITH_PIPELINE,
            region: REGION_WITH_PIPELINE
        }
    });
    
    1. Create the KMS key. The method EncryptionKey used in the example is deprecated, use Key instead.
    const key = new kms.Key(pipelineAccStack, 'CrossAccountKmsKey');
    

    Actually I got a kms.model.MalformedPolicyDocumentException error when trying to create the key, so I did it manually from the AWS Console and then imported it with kms.Key.fromKeyArn. I probably did something wrong with my account (I got a lot of errors before getting to this solution), but if you get the same error it's a workaround. Just make sure of assigning usage permissions to the pipeline role.

    1. Create the S3 bucket in ACC_WITH_PIPELINE with the KMS created before. A bucket name is required. The HackyIdentity used in the example is not necessary, several methods used in the class implementation are now deprecated.
    const artifactsBucket = new s3.Bucket(pipelineAccStack, "ArtifactsBucket", {
        bucketName: BUCKET_NAME,
        encryptionKey: key,
        encryption: s3.BucketEncryption.KMS
    });
    
    artifactsBucket.grantReadWrite(new iam.ArnPrincipal(crossAccRole.roleArn));
    
    1. Create the pipeline and add the cross-account role created in step 5.
    // Create pipeline
    const pipeline = new codepipeline.Pipeline(pipelineAccStack, 'Pipeline', {
        pipelineName: 'CrossAccountPipeline',
        artifactBucket: artifactsBucket
    });
    
    // Add cross-account role
    const policy = new iam.PolicyStatement();
    policy.addResources(crossAccRole.roleArn)
    policy.addActions('s3:*', 'codecommit:*', 'kms:*');
    pipeline.addToRolePolicy(policy);
    
    1. Add the source stage to the pipeline with the CodeCommit repo imported in step 3.
    // Create artifact for source code
    const sourceArtifact = new codepipeline.Artifact();
    
    // Create source stage with role
    pipeline.addStage({
      stageName: 'Source',
      actions: [
        new codepipeline_actions.CodeCommitSourceAction({
          actionName: 'CodeCommit_Source',
          repository: repo,
          output: sourceArtifact,
          branch: 'dev',
          role: crossAccRole
        })
      ]
    });
    
    1. And finally add the build stage
    // Create CodeBuild project
    const buildProject = new codebuild.PipelineProject(this, 'Build', {
      environment: { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_2 }
    });
    
    // Create artifact for build
    const buildArtifact = new codepipeline.Artifact();
    
    // Add build stage
    pipeline.addStage({
      stageName: 'Build',
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: 'Build',
          project: buildProject,
          input: sourceArtifact,
          outputs: [buildArtifact],
        }),
      ],
    });
    

    Deploy

    When the CDK app contains more than one stack you can't just cdk deploy. This is explained in here. However if you try to cdk deploy '*' there's another error: Need to perform AWS calls for account ACCOUNT_ID, but the current credentials are for ACCOUNT_ID.

    I managed to deploy the stacks with cdk deploy -e and switching accounts with aws configure. There are three stacks, not two. An EventBusPolicy stack is automatically generated by CDK to create an event bus. The other two events are added (also automatically) by CDK in PipelineAccStack and RepoAccStack. Marcin's answer explain how cross-account events are configured. The EventBusPolicy stack should be created in ACC_WITH_PIPELINE. To get the exact name of the stack, use cdk list.

    Taking all of this into account, in this example I would deploy with:

    # with aws configure in ACC_WITH_PIPELINE
    cdk deploy -e "PipelineAccStack"
    cdk deploy -e "EventBusPolicy-$ID_ACC_WITH_REPO-$REGION-$ID_ACC_WITH_PIPELINE"
    
    # switch aws configure to ACC_WITH_REPO
    cdk deploy -e "RepoAccStack"