I recently read through Chris Craig’s AWS Security Blog post about limiting S3 bucket access based on specific IAM credentials/roles. There are two parts especially worth mentioning that can be an effective solution for many needs (e.g., distributing secret key values programmatically).
Explicit Deny in an S3 Bucket Policy
First, you construct a specific S3 bucket policy that can be used for controlling access via IAM user id’s (IAM user, IAM instance role & instance profile) as well as the AWS root account. In the policy below, note the explicit Deny statement at the end, which is how you lock down access except for those IAM entities. Make sure you include yourself or root (and you have root access) otherwise you will lock yourself out of the bucket you just created. Best to work with a temp IAM user for testing, fyi.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::012345678901:role/my-role" }, "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::my-bucket" }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::012345678901:role/my-role" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-bucket/*" }, { "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": [ "arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*" ], "Condition": { "StringNotLike": { "aws:userId": [ "AIDAIDEADBEEF01234567", "AROAJABCD1234EF560123:*", "AIPAIBEA2510CD3498765:*", "012345678901" ] } } } ] }
Note: IAM objects in the Deny statement condition have tell-tale userId patterns as follows:
- “AIDAIDEADBEEF01234567″ – IAM user
- “AROAJABCD1234EF560123″ – IAM role (instance role in this case)
- “AIPAIBEA2510CD3498765″ – IAM instance profile
- “012345678901” – AWS account number, or root
This policy essentially prohibits all access to “my-bucket” and associated keys, except for those IAM objects listed in the Conditional to the Deny * statement.
Explicit Allow in an IAM Policy
To make sure that the IAM entities you are not Denying access to the S3 bucket in question, you must craft a specific IAM policy. Then attach the policy to the IAM object(s) that require access. This policy is straightforward and is the second piece of this solution:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListAllMyBuckets", "s3:GetBucketLocation" ], "Resource": "*" }, { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::my-bucket" }, { "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-bucket/*" } ] }
These two policies together form a powerful mechanism for creating a simple distribution point for secrets that you wish to use but not store locally in code or on an instance. A potential variation might include integration with KMS to provide at-rest encryption as well as programmatic decryption/encryption of your secrets as you move them in and out of S3.