How to start an autoscaling group using a cross-account encrypted AMI

Cloud Migration Scenarios

Four scenarios to migrate to AWS – from infrastructure to ML

Using encrypted Amazon machine images from another account in an autoscaling group does not work out of the box. You need to create an explicit KMS grant to make it work. In this blog, I will show you how to configure this in CloudFormation using our custom KMS grant provider in CloudFormation.

To create autoscaling groups based upon an encrypted AMI from another AWS account, you need to take the following three
steps:

  1. Create a Customer Managed Key (CMK)
  2. Build the AMI using the key
  3. Grant autoscaling service access to the key

Create a Customer Managed KMS Key

To create an Amazon machine image which can be used across different accounts, you need to use a customer managed KMS key. The reason for this is that AWS managed keys can not be used outside the account they were created in.

The following CloudFormation resource defines a KMS encryption key policy which allows encrypted machine images to be
used in other accounts:

EncryptionKey:
  Type: AWS::KMS::Key
  Properties:
    Description: 'EBS encryption key'
    EnableKeyRotation: true
    KeyPolicy:
      Version: '2012-10-17'
      Statement:
    - Sid: Enable IAM User Permissions
      Effect: Allow
      Principal:
        AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
      Action: kms:*
      Resource: '*'
    - Sid: Allow use of the key
      Effect: Allow
      Principal:
        AWS:
          - arn:aws:iam::111111111111:root
          - arn:aws:iam::222222222222:root
      Action:
        - kms:Encrypt
        - kms:Decrypt
        - kms:ReEncrypt*
        - kms:GenerateDataKey*
        - kms:DescribeKey
      Resource: '*'
    - Sid: Allow attachment of persistent resources
      Effect: Allow
      Principal:
        AWS:
          - arn:aws:iam::111111111111:root
          - arn:aws:iam::222222222222:root
      Action:
        - kms:ListGrants
        - kms:CreateGrant
        - kms:RevokeGrant
      Resource: '*'

This key policy allows the key to be used in the target accounts 111111111111 and 222222222222. Now you can build
an encrypted image with this key.

Build the AMI using the key

To build the Amazon machine image with the key, you can use Hashicorp’s Packer AMI Builder. The following packer snippet shows you the
essential parts to create an encrypted image:

  ...
  "builders": [{
      "encrypt_boot": true,
      "kms_key_id": "{{user `kms_key`}}",
      "ami_regions": ["eu-central-1"],
      "region_kms_key_ids": {
        "eu-central-1": "{{user `kms_key`}}"
      },
      "ami_users": [ "111111111111", "222222222222" ],
      "snapshot_users":[ "111111111111", "222222222222" ], 
      ...

Lines 3-7 encrypts the image using our customer managed key. line 9 shares the AMI with our target accounts. line 10 ensures that the target account can read the snapshot too.

This image can already be used to start virtual machine instances in the target accounts. But any attempt to start an autoscaling group will fail with the following error message:

Client.InternalError: Client error on launch.

To resolve this, you need to grant the AWS autoscaling service access to the KMS key.

Grant the autoscaling service access to the key

To grant the autoscaling service in the target account access to the key, you create a KMS grant as follows:

  CustomAMI:
    Type: Custom::AMI
    Properties:
      Filters:
        name: your-custom-ami-name
      Owners:
        - '0000000000'    # source account of the ami
      ExpectedNumberOfKmsKeys: 1
      ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-ami-provider'

  KMSGrant:
  Type: Custom::KMSGrant
  Properties:
    Name: autoscaling-access-to-encrypted-custom-ami
    KeyId: !GetAtt CustomAMI.KmsKeyId
    GranteePrincipal: !Sub 'arn:aws:iam::${AWS::AccountId}:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling'
    Operations:
      - Encrypt
      - Decrypt
      - ReEncryptFrom
      - ReEncryptTo
      - GenerateDataKey
      - GenerateDataKeyWithoutPlaintext
      - DescribeKey
      - CreateGrant
    ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-kms-provider'

The KMS encryption key id of the machine image is retrieved using the Custom::AMI at line 1-9. The KMS grant to the key is defined in line 11-26 by the Custom::KMSGrant.

Now the autoscaling group will be able to start instances from your encrypted machine image. That is all there is to it. If you want to use these custom providers too, you need to install it.

Installation

To install the custom KMS grant provider, type:

git clone https://github.com/binxio/cfn-kms-provider.git
cd cfn-kms-provider
aws cloudformation deploy \
        --capabilities CAPABILITY_IAM \
        --stack-name cfn-kms-provider \
        --template-file ./cloudformation/cfn-resource-provider.yaml

To install the custom AMI provider, type:

git clone https://github.com/binxio/cfn-ami-provider.git
cd cfn-ami-provider
aws cloudformation deploy \
        --capabilities CAPABILITY_IAM \
        --stack-name cfn-ami-provider \
        --template-file ./cloudformation/cfn-resource-provider.yaml

This will install our pre-packaged provider from s3://binxio-public-${AWS_REGION}/lambdas/cfn-ami-provider-latest.zip.

Conclusion

If you want to use an encrypted machine image from another account in an autoscaling group, you need to create a KMS grant for the autoscaling service linked role. Using the Custom::AMI and
Custom::KMSGrant you can deploy
the required KMS grant in a CloudFormation template using our custom provider.

Mark van Holsteijn is a senior software systems architect, and CTO of binx.io. He is passionate about removing waste in the software delivery process and keeping things clear and simple.
Share this article: Tweet this post / Post on LinkedIn