POSTS
AWS S3 Cross Account Bucket Permissions
Granting a third party account access to your AWS S3 bucket can be quite useful.
Introduction
Imagine you want your firm to be able to deliver files to a customer by writing data to their AWS S3 bucket. You can do this with “cross account bucket permissions” as described below. You can also refer to various bits of AWS documentation if you prefer, but there are some tricky steps that may not be clear in those articles.
In the General Idea and Preparation section we provide an overview of what is going on and the single step both your firm and the customer need to execute. If you are an expert AWS user, this may be all you need. But we also provide Detailed CLI Operations so you can start from scratch, create test users, grant the permissions, and verify everything works yourself. These detailed operations are particularly useful if you want to change this setup slightly to suit your situation.
The source code for this document is available in the aocks/aws-examples repo on GitHub and discussed (along with other information) in this article on the AOCKS blog.
General Idea and Preparation
Imagine you an AWS account for your firm and your customer has an AWS account. What you want to do is roughly the following
- Have the Customer Account grant your Firm Account’s root user access to the customer’s bucket.
- Have your Firm Account grant an IAM account access to the
customer’s bucket.
- This is the main stumbling block that confuses a lot of users and often isn’t documented well. If you try to have the Customer Account grant access to a non-root IAM account in your firm, you will generally get various errors.
- Roughly speaking this step is necessary because a non-root IAM account in your Firm Account doesn’t have any permissions unless you grant them from an admin user in your Firm Account.
Customer Action
The customer needs to create a policy to grant the firm account’s root
user bucket access and apply the policy to the bucket. This is
illustrated below where you would replace ${FIRM_ACCT}
with the AWS
account number for your firm and replace ${BUCKET}
with the name of
the AWS bucket.
cat > /tmp/writer-policy.json<<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${FIRM_ACCT}:root"
},
"Action": [
"s3:GetObject",
"s3:ListBucket",
"s3:GetLifecycleConfiguration",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::${BUCKET}",
"arn:aws:s3:::${BUCKET}/*"
]
}
]
}
EOF
and apply the policy:
aws s3api put-bucket-policy \
--bucket ${BUCKET} --policy file:///tmp/writer-policy.json
Firm Action
After completing the required Customer Action, your firm’s root user will have access to the customer’s bucket. Generally, however, you should not be using your firm’s root user to do anything but delegate appropriate permissions to an IAM account in your firm.
Below we create an inline policy to allow bucket access and grant it
to an IAM user in your firm’s account named $FIRM_IAM_USER
:
cat > /tmp/writer-policy-iam.json<<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "allowwriteruseriamaccess",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::${BUCKET}",
"arn:aws:s3:::${BUCKET}/*"
]
}
]
}
EOF
Now we apply the policy.
aws iam put-user-policy --user-name $FIRM_IAM_USER \
--policy-name writer-policy \
--policy-document file:///tmp/writer-policy-iam.json
Detailed CLI Operations
The following provide documentation and commands to use in a command line interface (CLI). These should work on Linux, Mac, or WSL (if you are using Windows, I highly recommend WSL). You should basically be able to just cut and paste these commands into your terminal (with obvious minor modifications like setting environment variables to point to your accounts).
Setup variables and software
The first step is to setup a python virtual environment and install the AWS CLI into it:
python3 -m venv venv_aws
. venv_aws/bin/activate
pip install boto3 awscli
Next you will want to setup some environment variables:
CUSTOMER_PROFILE
: This should be the name of the profile in the~/.aws/credentials
for the customer’s account. In the example below, this is set tomy_awesome_customer
; you should change this to whatever account you are using to test or for the real customer’s account.FIRM_PROFILE
: This should be the name of the profile in your~/.aws/credentials
for your firm’s account. In the example below, this is set tomy_firm
.CUSTOMER_USER
: This is the IAM account that you or your customer will use for testing.CUSTOMER_AWS_ACCT
: This is the account ID for the customer’s AWS account.BUCKET
: Name of the bucket you want to use.FIRM_ACCT
: This is the account ID for your firm’s AWS account.FIRM_IAM_USER
: This is the name of the IAM user in your firm that will be granted ability to write to the customer’s bucket (by your firm’s root account).
Once you have decided on the values above, you should execute like the following to store these as environment variables so you can use them in the remainder of this example.
export CUSTOMER_PROFILE=my_awesome_customer
export CUSTOMER_USER=test-customer
export CUSTOMER_AWS_ACCT=ACCT_ID_FOR_THE_CUSTOMER
export BUCKET=my.test.bucket
export FIRM_ACCT=ACCT_ID_FOR_THE_BROAD
export FIRM_PROFILE=my_firm
export FIRM_IAM_USER=test-writer
Note: if you type something like cat ~/.aws/credentials
, that
command should show you something like
[my_awesome_customer]
aws_access_key_id=...
aws_secret_access_key=...
[my_firm]
aws_access_key_id=...
aws_secret_access_key=...
where the ellipsis are replaced with the appropriate access keys for
your customer and your firm. If not, make sure to setup your
~/.aws/credentials
appropriately.
Customer Granting Access to Firm
Here we basically just execute the Customer Action mentioned earlier. We create a policy so that the firm’s root user can read/write to the desired bucket:
cat > /tmp/policy-for-firm-root.json<<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${FIRM_ACCT}:root"
},
"Action": [
"s3:GetObject",
"s3:ListBucket",
"s3:GetLifecycleConfiguration",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::${BUCKET}",
"arn:aws:s3:::${BUCKET}/*"
]
}
]
}
EOF
Now the customer applies the policy:
aws --profile ${CUSTOMER_PROFILE} s3api put-bucket-policy \
--bucket $BUCKET --policy file:///tmp/policy-for-firm-root.json
Create Customer User
The steps below are not strictly necessary. They are provided so you
or the customer can do testing. Note that these are executed by the
customer so they use --profile $CUSTOMER_PROFILE
. We create an IAM
user on the customer side for testing reading and writing to the
customer’s bucket:
aws --profile $CUSTOMER_PROFILE iam create-user \
--user-name ${CUSTOMER_USER} | tee /tmp/customer-user.json
Next we will create credentials for the customer user:
aws --profile ${CUSTOMER_PROFILE} iam create-access-key \
--user-name ${CUSTOMER_USER} | tee /tmp/creds_customer_user.json
Now we can create the policy for the bucket so that the customer’s IAM user can
read/write files to the bucket (note that this overwrites the
previously applied policy-for-firm-root.json
so we will need to add
that back after testing):
cat > /tmp/policy.json<<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "statement1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${CUSTOMER_AWS_ACCT}:user/${CUSTOMER_USER}"
},
"Action": [
"s3:GetBucketLocation",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::${BUCKET}"
]
},
{
"Sid": "statement2",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${CUSTOMER_AWS_ACCT}:user/${CUSTOMER_USER}"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::${BUCKET}/*"
]
}
]
}
EOF
Now apply policy so customer can access bucket:
aws --profile ${CUSTOMER_PROFILE} s3api put-bucket-policy \
--bucket $BUCKET --policy file:///tmp/policy.json
Verify customer can write to the bucket
echo "Example file with date `date`" > /tmp/example-file.txt
AWS_ACCESS_KEY_ID=`jq -r .AccessKey.AccessKeyId /tmp/creds_customer_user.json` \
AWS_SECRET_ACCESS_KEY=`jq -r .AccessKey.SecretAccessKey /tmp/creds_customer_user.json` \
aws s3api put-object --bucket $BUCKET --key example-file.txt \
--body /tmp/example-file.txt
AWS_ACCESS_KEY_ID=`jq -r .AccessKey.AccessKeyId /tmp/creds_customer_user.json` \
AWS_SECRET_ACCESS_KEY=`jq -r .AccessKey.SecretAccessKey /tmp/creds_customer_user.json` \
aws s3api get-object --bucket $BUCKET --key example-file.txt /tmp/out.txt
cat /tmp/out.txt
Now that our testing is done, add back the policy for the firm root:
aws --profile ${CUSTOMER_PROFILE} s3api put-bucket-policy \
--bucket $BUCKET --policy file:///tmp/policy-for-firm-root.json
Create Firm User and Policy
In this section we create an IAM user account in the firm’s AWS
account that can access the customer’s bucket. Note that most of these
commands use --profile ${FIRM_PROFILE}
since they are executing in
the firm’s AWS account.
Generally you would only need to create this firm user once. For this setup, you would need to apply the policy to the firm user for each customer bucket or create a more sophisticated policy with wild cards that would apply to all customer buckets but that is beyond the scope of this article.
Now we make an IAM account for your firm who can write to the desired bucket:
aws --profile ${FIRM_PROFILE} iam create-user \
--user-name $FIRM_IAM_USER | tee /tmp/writer-user.json
Now we create access keys for the new user:
aws --profile ${FIRM_PROFILE} iam create-access-key \
--user-name $FIRM_IAM_USER | tee /tmp/writer-creds.json
Now we setup a policy for the firm’s non-root user so it can access the bucket which root has been granted access to buy the customer.
cat > /tmp/writer-policy-iam.json<<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "allowwriteruseriamaccess",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::${BUCKET}",
"arn:aws:s3:::${BUCKET}/*"
]
}
]
}
EOF
Now we apply the policy where the firm’s admin is granting access to
the non-root $FIRM_IAM_USER
.
aws --profile ${FIRM_PROFILE} \
iam put-user-policy --user-name $FIRM_IAM_USER --policy-name writer-policy \
--policy-document file:///tmp/writer-policy-iam.json
Now we test writing and reading.
rm -f /tmp/out.txt
echo "Example file with date `date`" > /tmp/example-file.txt
AWS_ACCESS_KEY_ID=`jq -r .AccessKey.AccessKeyId /tmp/writer-creds.json` \
AWS_SECRET_ACCESS_KEY=`jq -r .AccessKey.SecretAccessKey /tmp/writer-creds.json` \
aws s3api put-object --bucket $BUCKET --key example-file.txt \
--body /tmp/example-file.txt
AWS_ACCESS_KEY_ID=`jq -r .AccessKey.AccessKeyId /tmp/writer-creds.json` \
AWS_SECRET_ACCESS_KEY=`jq -r .AccessKey.SecretAccessKey /tmp/writer-creds.json` \
aws s3api get-object --bucket $BUCKET --key example-file.txt /tmp/out.txt
cat /tmp/out.txt
Cleanup
Now clean-up the user we created for testing on the customer side.
aws --profile ${CUSTOMER_PROFILE} iam delete-access-key \
--user-name ${CUSTOMER_USER} --access-key-id \
`jq -r .AccessKey.AccessKeyId /tmp/creds_customer_user.json`
aws --profile ${CUSTOMER_PROFILE} iam delete-user \
--user-name $CUSTOMER_USER
Now clean-up the user we created for the firm.
aws --profile ${FIRM_PROFILE} iam delete-user-policy \
--user-name $FIRM_IAM_USER --policy-name writer-policy
aws --profile ${FIRM_PROFILE} iam delete-access-key \
--access-key-id `jq -r .AccessKey.AccessKeyId /tmp/writer-creds.json` \
--user-name $FIRM_IAM_USER
aws --profile ${FIRM_PROFILE} iam delete-user \
--user-name $FIRM_IAM_USER
You may also want to delete the bucket if desired.