Advanced AWS CLI with Jupyter Notebooks (Part 3) [Cross-account S3 Bucket Permissions & working with IAM using AWS CLI]
In a previous posts we saw how to setup Jupyter Notebooks to work with the AWS CLI natively. We also saw how to use AWS CI with Amazon S3 and bring some python and jupyter notebook magic to help us.
In this installment, we will work with IAM service and work through some examples. Let’s start.
Table Of Contents
Setup Notebook for AWS CLI
First we quickly go through the same steps as we did previously to get this notebook to work with AWS CLI – setting up enviornment variables, iPython shell aliases, and enabling auto-magic.
%env AWS_PROFILE = handsonaws-demo-a
%rehashx
%automagic 1
aws whoami
As noted in previous post, even though we have enabled auto-magic here same commands will still work better only with the shell magic prefix !
. Luckily if you run into one of those, the fix is as simple as inserting the !
prefix for that cell alone. You do not need to turn off auto-magic for the notebook or do anything else that impacts other cells.
Overview
In this demo scenario we have two users Alice and Bob in Account A and Account B respectively. These accounts have nothing in common - they are not part of any AWS Organizations setup.
In our use case Bob and Alice need to exchange some objects securely through S3, without requiring new IAM users or permanent access keys to be created or exchanged.
Bob creates a bucket in Account B and applies a bucket policy that grants access to user Alice from Account B.
Also, Alice is part of s3customusers group in Account B that grants S3 permissions to members though a permissions policy named s3custompolicy
.
We will go through these steps through the AWS CLI – using our favorite method of using Jupyter notebooks to do so.
IAM Basics
We first run through some basic commands of listing users, groups and their permissions.
One useful reference is the help documentation available through the CLI itself. The subcommand help
displays the help documentation for each command and the list of all subcommands available under it.
Here is one place where the regular automagic doesn’t work (At least for me. The aws ... help
command seems to internally invoke the cat
command to display the help manual pages – which for some reason does not work from within my notebook setup on Win10, even when I alias cat
command to type
. Need to look deeper into this.)
The cell magic %%bash
comes in handy here. The output is verbose – collapsing the output or enabling auto-scroll on the output makes it more manageable in the notebook context. You can scroll-up to this cell whenever you need to refer to it.
%%bash aws s3 help
%%bash aws iam help
Fetching users and groups
List all users in the account as follows – remember we started in Account A. Currently there is only an Admin user and no Alice yet.
aws iam list-users
aws iam list-users --output yaml
Basic operations of getting users and groups –
aws iam get-user
aws iam list-groups
aws iam list-groups-for-user --user iamadmin
aws iam list-group-policies --group administrators
There are no inline policies applied to this group. But there is a “managed policy” attached which we can retrieve as follows.
Note that retrieving the policy document requires a couple of steps – first you get the PolicyArn(s) attached, then you get policy metadata using that ARN, and finally fetch the current policy version using the default version id returned in the previous response.
Also note that the get-policy
family of commands uses the ARN and not the name of the policy as a parameter to uniquely identify the policy resource. This is because there are ‘AWS-managed’ policies and ‘Customer-managed’ policies that could have the same policy name per-se.
ARN for AWS-managed policy –
arn:aws:iam::aws:policy/POLICYNAME
ARN for Customer-managed policy –
arn:aws:iam::ACCOUNT-ID:policy/POLICYNAME
As you can see, using just a policy-name instead of ARN would be ambiguous here. or Users, Roles and Groups, there is no such ambiguity. Therefore the corresponding get-user
, get-role
, and get-group
commands use just the resource name as parameter instead of ARN.
aws iam get-policy --policy-arn "arn:aws:iam::aws:policy/AdministratorAccess"
aws iam list-attached-group-policies --group administrators
aws iam get-policy-version --policy-arn "arn:aws:iam::aws:policy/AdministratorAccess" --version-id "v1" --query "PolicyVersion.Document"
Creating Users, Groups, and Permissions Policies
Let us use a bit of python to quickly create a JSON
document on similar lines – to serve as the permissions policy for the user group we are going to create for Alice.
For simplicity we will create a policy that grants full access to S3. In practice you would trim this down to the bare essentials needed (the principle of least privilege).
json_policy_s3fullaccess = """{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:*", "Resource": "*" } ] }"""
print (json_policy_s3fullaccess)
We write this string to a local text file and use that in the CLI command to create a customer-managed policy.
policy_filename = "policy.json" with open(policy_filename, 'w') as the_file: the_file.write(json_policy_s3fullaccess)
!type {policy_filename}
aws iam create-policy --policy-name "s3custom" --policy-document file://{policy_filename}
We will create a user group that we will eventually attach the above policy to.
aws iam create-group --group-name "s3customusers"
%%bash aws iam create-user help
aws iam create-user --user-name "alice"
Now that we have the new user Alice created we are ready to add this user to the group s3customusers
that we created above.
%%bash aws iam add-user-to-group help
aws iam add-user-to-group --group-name "s3customusers" --user-name "alice"
aws iam list-groups-for-user --user-name "alice"
We will also attach the s3custom
IAM policy to the s3customusers
group.
%%bash aws iam attach-group-policy help
aws iam attach-group-policy --group-name "s3customusers" --policy-arn "arn:aws:iam::9REDACTED5541:policy/s3custom"
We can verify that the new policy got correctly attached as follows –
aws iam list-attached-group-policies --group-name "s3customusers"
aws iam get-policy --policy-arn "arn:aws:iam::9REDACTED5541:policy/s3custom"
aws iam get-policy-version --policy-arn "arn:aws:iam::9REDACTED5541:policy/s3custom" --version-id "v1" --query "PolicyVersion.Document"
Creating Access Keys (and Configuring AWS Credentials for CLI)
We will also created a new access key for the new user Alice.
%%bash aws iam create-access-key help
aws iam create-access-key --user-name alice
I created a small helper function as shown below to append the new credentials programmatically to the AWS CLI’s local credentials file. this is not necessary especially for adding one or two users but can come in handly should you need to automate it for any reason.
import os credentials_file = os.path.expanduser('~/.aws/credentials')
credentials_file
def format_aws_profile (profile,accesskey,secret): return "[{profile}]\naws_access_key_id={accesskey}\naws_secret_access_key={secret}\n".format(profile=profile,accesskey=accesskey,secret=secret) def write_aws_profile (filename,profile,accesskey,secret,comment=""): with open(filename, 'a') as f: f.write("\n# =====================================") if comment !="" : f.write("\n# "+comment) f.write("\n\n") f.write(format_aws_profile (profile,accesskey,secret))
write_aws_profile(credentials_file,"demo-alice","AKIAXXREDACTEDXXLGGA","ual+jz9HyxxxxxREDACTEDxxxxxxxdkPaevq","UserName : alice | Demo Account for IAM & S3 Cross-Account Access (S3 custom user in Account A)")
aws whoami --profile demo-alice
aws s3 ls --profile demo-alice
Setup Bucket & Bucket Policy in Account B
Now we switch to Account B and setup the bucket and bucket policy.
%env AWS_PROFILE = demo-bob
aws whoami
bucketname = "bobs-demo-bucket"
aws s3 mb s3://{bucketname} --region us-east-1
We use python again to create the bucket policy text file locally and then use the CLI to apply it to the bucket.
json_bucketpolicy_caa = """{ "Version": "2012-10-17", "Statement": [ { "Sid": "Cross-account S3 permissions on Bucket", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::9REDACTED5541:user/alice" }, "Action": [ "s3:GetBucketLocation", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::bobs-demo-bucket" ] }, { "Sid": "Cross-account S3 permissions on Bucket objects", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::9REDACTED5541:user/alice" }, "Action": [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl" ], "Resource": [ "arn:aws:s3:::bobs-demo-bucket/*" ] } ] }"""
policy_filename = "bucketpolicy.json" with open(policy_filename, 'w') as the_file: the_file.write(json_bucketpolicy_caa)
!type {policy_filename}
aws s3api put-bucket-policy --bucket {bucketname} --policy file://{policy_filename} --profile demo-bob
echo "helloworld" > test.txt
aws s3 cp test.txt s3://{bucketname}
Verify Cross-Account Access from Account A
We now switch to user Alice / account A
%env AWS_PROFILE = demo-alice
aws whoami
aws s3 ls
aws s3 ls s3://{bucketname}
echo "hello from the other side" > hello.txt
aws s3 cp hello.txt s3://{bucketname}
We verified that Alice is able to download as well as upload files to Bob’s bucket. However we still need to test it from Bob’s side.
Testing and Troubleshooting - Bucket & Object Ownership and ACLs
We access bucket as Bob from Account B (the bucket owner) and try to download the object uploaded by Alice.
aws s3 ls s3://{bucketname} --profile demo-bob
aws s3 cp s3://{bucketname}/hello.txt ./download.txt --profile demo-bob
We see that even though Bob / Account B own the bucket they do not seem to have permissions to he newly uploaded object.
At this point Alice (A) still owns the objects they uploaded although Bob (B) owns the bucket itself.
For this to work as expected, Alice should also grant full ACL permissions to the bucket owner on the uploaded objects. This will allow Bob to download those objects.
aws s3api put-object-acl --bucket {bucketname} --key hello.txt --acl bucket-owner-full-control --profile demo-alice
The above command by Alice puts the bucket-owner-full-control
ACL on the speciifc object hello.txt
. Note that this operation is not available throuh the higher-level S3
command and you need to use the s3api
command from CLI.
With this, Bob (B) should now be able to download that object.
aws s3 cp s3://{bucketname}/hello.txt ./download.txt --profile demo-bob
!type download.txt
Advanced - Bucket Policy to Enforce bucket-owner-full-control
ACL on Upload
We will now update the bucket policy to enforce this specific ACL on every upload. This accomplished by explicitly denying any put object operations that are missing this ACL.
json_bucketpolicy_caa = """{ "Version": "2012-10-17", "Statement": [ { "Sid": "Cross-account S3 permissions on Bucket", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::9REDACTED5541:user/alice" }, "Action": [ "s3:GetBucketLocation", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::bobs-demo-bucket" ] }, { "Sid": "Cross-account S3 permissions on Bucket objects GET", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::9REDACTED5541:user/alice" }, "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::bobs-demo-bucket/*" ] }, { "Sid": "Cross-account S3 permissions on Bucket objects PUT with ACL condition", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::9REDACTED5541:user/alice" }, "Condition": { "StringEquals": { "s3:x-amz-acl": "bucket-owner-full-control" } }, "Action": [ "s3:PutObject", "s3:PutObjectAcl" ], "Resource": [ "arn:aws:s3:::bobs-demo-bucket/*" ] } ] }"""
policy_filename = "bucketpolicy2.json" with open(policy_filename, 'w') as the_file: the_file.write(json_bucketpolicy_caa)
!type {policy_filename}
aws s3api put-bucket-policy --bucket {bucketname} --policy file://{policy_filename} --profile demo-bob
Testing this new bucket policy, upload an object as Alice first WITHOUT supplying the ACL, and it should fail.
aws whoami
echo "I must've called a thousand times" > hello2.txt
aws s3 cp hello2.txt s3://{bucketname}
Uploading the same object again as Alice but this time with the correct ACL applied (using the s3api
command), you see successful upload.
aws s3api put-object --bucket {bucketname} --key hello2.txt --body hello2.txt --acl bucket-owner-full-control
aws s3 ls s3://{bucketname}
Verifying that Bob can download this object as well.
aws s3 ls s3://{bucketname} --profile demo-bob
aws s3 cp s3://{bucketname}/hello2.txt ./download2.txt --profile demo-bob
!type download2.txt
That brings us to the end of this demo. We will look at a few other use-cases for AWS CLI and explore some other services in subsequent posts.
Additional Tips & Notes
- Useful extensions for JupyterLab
- Collapsible Headings : https://github.com/aquirdTurtle/Collapsible_Headings
- Spellecheck : https://github.com/jupyterlab-contrib/spellchecker
- GNU Utils for Win32 – https://sourceforge.net/projects/unxutils/
- Git-bash for Windows – https://gitforwindows.org/
References
- AWS CLI (v2) reference for IAM – https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/index.html
- S3 Cross-Account Permissions – https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-walkthroughs-managing-access-example2.html