Blog

Cross account deployments using a Customer Managed KMS key

27 Feb, 2023
Xebia Background Header Wave

In my previous blog I wrote about application pipelines. These CodePipeline use a S3 bucket. What if you have a need for a customer managed key instead of the Amazon managed key? And you want to deploy the CloudFormation templates cross account? In this blog I will explain how you can achieve this.

The key itself

Lets start with the key itself. When CodePipeline moves artifacts from one stage to another it uses S3 to transfer the data. For this reason we need to allow S3 and CodePipeline to decrypt and encrypt the data:

- Action:
    - kms:Decrypt
    - kms:DescribeKey
    - kms:Encrypt
    - kms:ReEncrypt*
    - kms:GenerateDataKey*
  Effect: Allow
  Principal:
    AWS: "*"
  Resource: "*"
  Condition:
    StringEquals:
      aws:PrincipalOrgID: !Ref OrgId
      kms:ViaService:
        - Fn::Sub: s3.${AWS::Region}.amazonaws.com
        - Fn::Sub: codepipeline.${AWS::Region}.amazonaws.com

Next, CloudFormation will deploy these templates across account. For this reason we need to allow CloudFormation to decrypt the data.

- Action: kms:Decrypt
  Effect: Allow
  Principal:
    AWS: "*"
  Resource: "*"
  Condition:
    StringEquals:
      aws:PrincipalOrgID: !Ref OrgId
      kms:ViaService: !Sub cloudformation.${AWS::Region}.amazonaws.com

The samples shown here will limit the use of the key to the AWS Services within your own AWS organization. The S3 bucket needs to use the KMS key for encryption.

The pipeline

The CodePipeline needs to know that we are using KMS as an encryption method. For this reason we need to tell what key to use.

Pipeline:
  Type: AWS::CodePipeline::Pipeline
  Properties:
    ArtifactStores:
      - Region: eu-west-1
        ArtifactStore:
          Location: !Sub codepipeline-artifacts-${AWS::AccountId}-eu-west-1
          Type: S3
          EncryptionKey:
            Type: KMS
            Id: alias/bucket-key
      - Region: eu-central-1
        ArtifactStore:
          Location: !Sub codepipeline-artifacts-${AWS::AccountId}-eu-central-1
          Type: S3
          EncryptionKey:
            Type: KMS
            Id: alias/bucket-key

We are using the alias so that we do not have to deal with the key id.

Deploy cross account/region

You might have seen it already in the previous code snippet. When you deploy across region you need to configure a bucket and key per region. When you deploy across account you will need 2 roles.

  1. A role to assume in the other account. (cross-account-role, configured on action level)
  2. A role to use for the CloudFormation execution. (cloudformation-execution-role, configured in the configuration)
Actions:
  - Name: Ireland-CreateChangeSet
    Region: eu-west-1
    RunOrder: 1
    RoleArn: !Sub arn:aws:iam::${DevelopmentAccountId}:role/cross-account-role
    InputArtifacts:
      - Name: BuildArtifactAsZip
    ActionTypeId:
      Category: Deploy
      Owner: AWS
      Provider: CloudFormation
      Version: "1"
    Configuration:
      ActionMode: CHANGE_SET_REPLACE
      RoleArn: !Sub arn:aws:iam::${DevelopmentAccountId}:role/cloudformation-execution-role
      StackName: !Sub ${ProjectName}-$development
      ChangeSetName: !Sub ${ProjectName}-development-ChangeSet
      TemplatePath: BuildArtifactAsZip::packaged-template.yaml
      Capabilities: CAPABILITY_NAMED_IAM
      ParameterOverrides: |
        {
          "EnvType": "development"
        }
  - Name: Ireland-ExecuteChangeSet
    Region: eu-west-1
    RunOrder: 2
    RoleArn: !Sub arn:aws:iam::${DevelopmentAccountId}:role/cross-account-role
    ActionTypeId:
      Category: Deploy
      Owner: AWS
      Provider: CloudFormation
      Version: "1"
    Configuration:
      ActionMode: CHANGE_SET_EXECUTE
      RoleArn: !Sub arn:aws:iam::${DevelopmentAccountId}:role/cloudformation-execution-role
      StackName: !Sub ${ProjectName}-development
      ChangeSetName: !Sub ${ProjectName}-development-ChangeSet

The cross-account-role needs the ability to perform crud operations on CloudFormation. Have access to the templates and the ability to pass the cloudformation-execution-role. The BuildAccountId is the account id that holds the KMS keys, S3 buckets and pipeline. This role needs to be able to access the S3 buckets and use the KMS key for decryption.

CrossAccountRole:
  Type: AWS::IAM::Role
  Properties:
    RoleName: cross-account-role
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            AWS: !Sub arn:aws:iam::${BuildAccountId}:root
    Policies:
      - PolicyName: AllowCloudFormationDeployments
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Effect: Allow
              Action: iam:PassRole
              Resource: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cloudformation-execution-role
            - Effect: Allow
              Action: cloudformation:DescribeStacks
              Resource:
                - !Sub arn:${AWS::Partition}:cloudformation:eu-west-1:${AWS::AccountId}:stack/*
                - !Sub arn:${AWS::Partition}:cloudformation:eu-central-1:${AWS::AccountId}:stack/*
            - Effect: Allow
              Action:
                - cloudformation:CreateStack
                - cloudformation:DeleteStack
                - cloudformation:UpdateStack
                - cloudformation:CreateChangeSet
                - cloudformation:ExecuteChangeSet
                - cloudformation:DeleteChangeSet
                - cloudformation:DescribeChangeSet
                - cloudformation:SetStackPolicy
                - cloudformation:SetStackPolicy
                - cloudformation:ValidateTemplate
              Resource:
                - !Sub arn:${AWS::Partition}:cloudformation:eu-west-1:${AWS::AccountId}:stack/*
                - !Sub arn:${AWS::Partition}:cloudformation:eu-central-1:${AWS::AccountId}:stack/*
      - PolicyName: AllowArtifactAccess
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Effect: Allow
              Action:
                - s3:GetObject
                - s3:GetObjectVersion
              Resource:
                - !Sub arn:${AWS::Partition}:s3:::codepipeline-artifacts-${BuildAccountId}-eu-west-1/*
                - !Sub arn:${AWS::Partition}:s3:::codepipeline-artifacts-${BuildAccountId}-eu-central-1/*
            - Effect: Allow
              Action: kms:Decrypt
              Resource:
                - !Sub arn:${AWS::Partition}:kms:eu-west-1:${BuildAccountId}:key/02d730ed-125b-4d2e-a498-7b0187298924
                - !Sub arn:${AWS::Partition}:kms:eu-central-1:${BuildAccountId}:key/e127b617-6951-42b7-83ae-12acc16d12a7

The execution role has all the needed rights to deploy the actual template:

CloudFormationServiceRole:
  Properties:
    RoleName: cloudformation-execution-role
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal: { Service: cloudformation.amazonaws.com }
    ManagedPolicyArns:
      - !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess

Conclusion

When using customer managed keys you need some extra configuration. The key is self, configuration where is is used and some logical permissions. Getting all this from the AWS documentation can be challenging as it is somewhat of an edge case.

Joris Conijn
Joris has been working with the AWS cloud since 2009 and focussing on building event driven architectures. While working with the cloud from (almost) the start he has seen most of the services being launched. Joris strongly believes in automation and infrastructure as code and is open to learn new things and experiment with them, because that is the way to learn and grow. In his spare time he enjoys running and runs a small micro brewery from his home.
Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts