4

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?

Andrea
  • 141
  • 1
  • 8

2 Answers2

6

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"
Andrea
  • 141
  • 1
  • 8
0

CodePipeline (CP) is trigger by CC through CloudWatch Event rule when both are in the same account:

CC ---> CW Event rule ---> CP

To do it across account's you have to adjust the CW Events. Namely, CW Event rule in CC account must forward events to CW Events in CP account:

CP ---> CW Event rule ---> CW Event bus in CP acc ---> CW rule --> CP 

I don't have the code for you, but this is how it should be done. The link you've provided gives more specific details of this architecture.

I guess that when using CDK, you will have to "manually" create all the CW Events rules as this is not something that is normally done out of the box.

Marcin
  • 215,873
  • 14
  • 235
  • 294