Provisioning AWS KMS-Encrypted Buckets with Cross Account Access

Tony Tannous
6 min readApr 7, 2021

What follows is a walkthrough outlining the steps involved in implementing AWS cross-account access to an encrypted S3 bucket.

The following is a summary which describes the scenario used for the walkthrough:

  • An S3 bucket, s3://account-a-bucket, is to be created in account-a and made accessible to an external AWS account, account-b
  • A new KMS-CMK key needs to be created, with bucket encryption enabled using this key
  • IAM user Ann, in account-a, requires access to the bucket
  • IAM user Dilip, in account-b, also requires the same level of access to the bucket
  • Level of access required includes, ListBucket/GetObject/PutObject

Overview of Activities Required for each Account

Below diagram shows an overview of tasks each account Administrator would be expected to perform.

Activities for account-a

  • Create KMS-CMK and attach Key policy document, which grants
    account-b access to cryptographic operations using this Key
  • Create the S3 bucket and attach appropriate bucket policy, granting account-b with permissions to ListBucket/GetObject/PutObject
  • Enable bucket encryption with the KMS-CMK key created
  • Attach an inline policy allowing IAM user, Ann access to the KMS-CMK key and bucket

Activities for account-b

Once account-a activities are complete, an Administrator in account-b will have access to the bucket/key, and will also have authority to delegate the access to nominated users within this account. In the scenario described earlier, the nominated IAM user is, Dilip.

  • The Administrator attaches an inline policy to user Dilip, granting him the access on the bucket and KMS-CMK key
  • The inline policy required would be identical to the one applied to IAM user, Ann

Prerequisites: AWS CLI Credentials for Admin Tasks

The AWS CLI will be used perform the tasks required in each account.

In the context of this article, an Administrator of an account is a user whose IAM profile includes access to predefined policy ARN arn:aws:iam::aws:policy/AdministratorAccess.

It’s assumed that each account Administrator has generated AWS Secret/Access keys for the admin user, and has included credentials in ~/.aws/config. The following shows the profiles used during the examples to follow.

account-a:

[profile account-a-admin]
region = <region>
aws_access_key_id = <access key id of account-a admin user>
aws_secret_access_key = <aws secret key for account-a admin user>

account-b:

[profile account-b-admin]
region = <region>
aws_access_key_id = <access key id of account-b admin user>
aws_secret_access_key = <aws secret key for account-b admin user>

Executing Activities Required for account-a

1. Create KMS Key and Policy

We can use the following policy when creating the KMS key. The policy contains two action statements:

  • The first of these permits management of access for this key from the account itself
  • The second action grants account-b access to the key for the purposes of cryptographic operations
{
"Version": "2012-10-17",
"Id": "key-policy-account-a-bucket",
"Statement": [
{
"Sid": "Enable IAM user permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::account-a:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::account-b:root"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}
  • Using the aws cli, create the key and attach corresponding policy by running:
$ aws --profile account-a-admin \
create-key \
--tags TagKey=Purpose,TagValue=AWS-KMS-Key \
--description "Key used for s3://account-a-bucket encryption" \
--key-usage "ENCRYPT_DECRYPT" \
--policy '{
"Version": "2012-10-17",
"Id": "key-policy-account-a-bucket",
"Statement": [
{
"Sid": "Enable IAM user permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::account-a:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::account-b:root"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}'
  • This should return the details of the key created.
{
"KeyMetadata": {
"AWSAccountId": "account-a",
"KeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"Arn": "arn:aws:kms:<region>:account-a:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",

"CreationDate": "2021-04-06T18:21:45+10:00",
"Enabled": true,
"Description": "Key used for s3://account-a-bucket bucket encryption",
"KeyUsage": "ENCRYPT_DECRYPT",
"KeyState": "Enabled",
"Origin": "AWS_KMS",
"KeyManager": "CUSTOMER",
"CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
"EncryptionAlgorithms": [
"SYMMETRIC_DEFAULT"
]
}
}
  • Note down the key Arn and KeyId from the output returned
  • Create an alias alias/3-account-a-bucket-encryption for the key by specifying the KeyId value for parameter --target-key-id:
$ aws --profile account-a-admin \
kms create-alias \
--alias-name alias/s3-account-a-bucket-encryption \
--target-key-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

2. Create S3 Bucket and Policy

  • Create the target bucket, s3://account-a-bucket:
$ aws --profile account-a-admin \
s3 mb s3://account-a-bucket
  • The following shows a sample bucket policy. It contains three Actions to allow access to the bucket from account-b:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::account-b:root"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::account-a-bucket"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::account-b:root"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::account-a-bucket/*"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::account-b:root"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::account-a-bucket/*"
}
]
}
  • Attach the policy to the bucket:
$ aws --profile account-a-admin \
s3api put-bucket-policy \
--bucket account-a-bucket \
--policy '{ \
"Version": "2012-10-17", \
"Statement": [ \
{ \
"Effect": "Allow", \
"Principal": { \
"AWS": "arn:aws:iam::account-b:root" \
}, \
"Action": "s3:ListBucket", \
"Resource": "arn:aws:s3:::account-a-bucket" \
}, \
{ \
"Effect": "Allow", \
"Principal": { \
"AWS": "arn:aws:iam::account-b:root" \
}, \
"Action": "s3:GetObject", \
"Resource": "arn:aws:s3:::account-a-bucket/*" \
}, \
{ \
"Effect": "Allow", \
"Principal": { \
"AWS": "arn:aws:iam::account-b:root" \
}, \
"Action": "s3:PutObject", \
"Resource": "arn:aws:s3:::account-a-bucket/*" \
} \
] \
}'

3. Enable Bucket Encryption and Attach KMS Key

  • Using the key Arn noted earlier, enable bucket encryption by supplying the Arn as the KMSMasterKeyID :
$ aws --profile account-a-admin \
s3api put-bucket-encryption \
--bucket account-a-bucket \
--server-side-encryption-configuration '{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:<region>:account-a:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"BucketKeyEnabled": true
}
]
}'

4. Attach Inline Policy to IAM User, Ann

  • Assuming IAM user, Ann has already been created, we can use the following sample policy to delegate the required access to Ann.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucketContents",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::account-a-bucket",
]
},
{
"Sid": "UploadDownloadBucketPermissions",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject""
],
"Resource": [
"arn:aws:s3:::account-a-bucket/*"
]
},
{
"Sid": "EncryptDecryptBucketContents",
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": [
"arn:aws:kms:<region>:account-a:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
]
}
]
}
  • The first three actions allow Ann to ListBucket/GetObject/PutObject. The last Action allows access to the KMS key.
  • Create a file named access-s3-account-a-bucket.json containing the policy definition above
  • Attach the policy Ann’s username:
$ aws --profile account-a-admin \
iam put-user-policy \
--user-name ann \
--policy-name access-s3-account-a-bucket \
--policy-document file://access-s3-account-a-bucket.json

If access is managed through roles, the policy should be attached to the appropriate IAM role.

Executing Activities Required for account-b

Once all account-a activities have successfully completed, the account-b Administrator will have the access required to the bucket/KMS key, with authority to delegate to nominated users.

1. Attach Inline Policy to IAM User, Dilip

To grant access to IAM User, Dilip, we can use the same inline policy that was used for Ann, i.e. access-s3-account-a-bucket.json.

$ aws --profile account-b-admin \
iam put-user-policy \
--user-name dilip \
--policy-name access-s3-account-a-bucket \
--policy-document file://access-s3-account-a-bucket.json

Attach this policy to the appropriate IAM role if access to resources is managed via roles.

Conclusion

By now, the requirements for the scenario we defined before commencing, should all be fulfilled, that is:

  • An encrypted S3 bucket has been created in AWS account-a
  • The bucket is accessible from account-b
  • account-a user, Ann, and account-b user, Dilip both have the access required to the bucket

From my own experience, the main sticking points I had to get past were those surrounding KMS. Hopefully, the detail included in this article should help preventing others from making similar mistakes.

--

--

Tony Tannous

Learner. Interests include Cloud and Devops technologies.