Provisioning AWS KMS-Encrypted Buckets with Cross Account Access
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
andKeyId
from the output returned - Create an alias
alias/3-account-a-bucket-encryption
for the key by specifying theKeyId
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 theArn
as theKMSMasterKeyID
:
$ 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.