Fri, 27 Jul 2018

Access management is a key attribute to the security of any enterprise IT infrastructure. Amazon Web Services (AWS) offers a variety of tools to address access management. Having a wide variety of options available provides flexibility and agility to the customer but can also add confusion as it is sometimes unclear how these access management services interact. Specifically, S3 access management can get quite overwhelming. Object ACLs, Bucket ACLs, IAM Policies, Bucket Policies, Bucket Ownership, and Object Ownership all effect who has access to an object stored in S3 and it can be unclear how they interact.

When interacting with s3 permissions, this AWS blog post is my goto for a basic understanding of the interactions between the three access controls (IAM policies, bucket policies, ACLs), but it doesn’t cover every use case, notably it does not mention that the object owner can have an effect on the permissions.

In an attempt to add some clarity to the s3 permissions, we created a script to test all relevant combinations of IAM policy, bucket policy, object owner and ACL. The results are summarized in the table at the end of this post.

Method

For simplicity we only looked at the GetObject api operation. We created multiple buckets in one account, each with a different bucket policy. In each bucket, we created multiple objects with different object owners and ACLs. We created IAM users in two seperate accounts (one account also holds the bucket) to test cross account access. We created objects owned by three accounts (two of the previously mentioned accounts and a third because sometimes objects are owned by an AWS controlled account, billing reports and some access logs are notable examples of this). We used three IAM policies: one with an explicit deny, one with an explicit allow, and one with no policy. (Policy statements are detailed at the end). We also had four bucket policies: one with no policy, one with an explicit deny for everyone, one that allowed access to everyone, and one that allowed access to either of the two user accounts. Again, policies are detailed a the end of the post. We set four different object ACLs: private, public-read, authenticated-read, and bucket-owner-read. We initially started with different bucket ACLs but they had no effect (since we only test the GetObject api). Thus they are not included in the results. Finally, we also made unauthenticated get requests for each of the objects.

The table shows the results every combination of the above resources: every combination of AWS account and IAM policy made a getObject call on every combination of bucket policy, object acl, and object owner.

Highlights

  • Bucket acls have no effect on get object call success or failure.
  • Deny Bucket Policy or Deny User Policy - always no access, even if the object has a public ACL.
  • The various allow bucket policies only apply if the object owner is the same as the bucket owner.
  • The object owner can have an effect on the permission.

Results

The results are recorded in the following table. We have reduced the results using wildcards where appropriate.

Key

SymbolMeaning
*Any possible value for the given column
! VALUEAny possible value for the given column except the given VALUE

Result Tables

This first table demonstrates how deny policies completely deny access, regardless of other factors.

User AccountUser PolicyBucket PolicyObject Owner AccountObject ACLCaller Owns BucketCaller Owns ObjectBucket Owner Owns ObjectHas Access
**deny*****AccessDenied
*deny******AccessDenied

This table shows calls made from the first account, which is the same account as the bucket owner.

User AccountUser PolicyBucket PolicyObject Owner AccountObject ACLCaller Owns BucketCaller Owns ObjectBucket Owner Owns ObjectHas Access
1st Acctallow! deny*! privateYes**Success
1st Acctallow! deny1st AcctprivateYesYesYesSuccess
1st Acctallow*2nd AcctprivateYesNoNoAccessDenied
1st Acctnoneallow-any1st Acct*YesYesYesSuccess
1st Acctnoneallow-any2nd Acct! privateYesNoNoSuccess
1st Acctnoneallow-any2nd AcctprivateYesNoNoAccessDenied
1st Acctnoneallow-calling-accounts OR none1st Acctauthenticated-read OR public-readYesYesYesSuccess
1st Acctnoneallow-calling-accounts OR none1st Acctbucket-owner-read OR privateYesYesYesAccessDenied
1st Acctnoneallow-calling-accounts OR none2nd Acct*YesNoNoAccessDenied

This table shows calls made from the second account, which demonstrates cross account access.

User AccountUser PolicyBucket PolicyObject Owner AccountObject ACLCaller Owns BucketCaller Owns ObjectBucket Owner Owns ObjectHas Access
2nd Acctallow! deny2nd Acct*NoYesNoSuccess
2nd Acctallowallow-any OR allow-calling-accounts1st Acct*NoNoYesSuccess
2nd Acctallowallow-any OR allow-calling-accounts3rd Acctauthenticated-read OR public-readNoNoNoSuccess
2nd Acctallowallow-any OR allow-calling-accounts3rd Acctbucket-owner-read OR privateNoNoNoAccessDenied
2nd Acctallownone1st Acct OR 3rd Acctauthenticated-read OR public-readNoNoYesSuccess
2nd Acctallownone1st Acct OR 3rd Acctbucket-owner-read OR privateNoNoYesAccessDenied
2nd Acctnone! deny2nd Acctauthenticated-read OR public-readNoYesNoSuccess
2nd Acctnone! deny2nd Acctbucket-owner-read OR privateNoYesNoAccessDenied
2nd Acctnone*1st Acct*NoNoYesAccessDenied
2nd Acctnone*3rd Acct*NoNoNoAccessDenied

This table shows calls made from an unauthenticated user.

User AccountUser PolicyBucket PolicyObject Owner AccountObject ACLCaller Owns BucketCaller Owns ObjectBucket Owner Owns ObjectHas Access
Unauth Usernone! deny*public-readNoNo*Success
Unauth Usernoneallow-any1st Acct*NoNoYesSuccess
Unauth Usernoneallow-any2nd Acct! public readNoNoNoAccessDenied
Unauth Usernoneallow-calling-accounts*! public readNoNo*AccessDenied
Unauth Usernonenone*! public readNoNo*AccessDenied

Resource descriptions

The following are the possible values we used for each coloumn.

User Account

  • 1st Acct: This indicates the that caller is in the same account as the s3 bucket.
  • 2nd Acct: This indicates that that caller is in a different account than the s3 bucket, thus making cross account requests.
  • Unauth User: This indicates an unauthenticated call was made.

User Policy

  • allow “Statement”: [ { “Effect”: “Allow”, “Action”: “s3:GetObject”, “Resource”: ”*” } ]
  • deny<pre><code>"Statement": [ { "Effect": "Deny", "Action": "s3:GetObject", "Resource": "*" }</code></pre>

    ]

  • none - There is no IAM policy attached.

Bucket ACLs

  • We found that bucket ACLs didn’t have an effect on whether or not a call was successful.

Bucket Policy

  • none - There is no bucket policy affecting the GetObject operation (there is a base bucket policy affecting the put operation).
  • allow-any<pre><code>{ "Sid":"Allow any principal s3 getObject", "Effect":"Allow", "Principal": "*", "Action":["s3:GetObject"], "Resource":["arn:aws:s3:::*"]}</code></pre>
  • allowcallingaccounts_statement<pre><code>{ "Sid": "Allow s3 get object", "Effect": "Allow", "Principal": {"AWS": ["arn:aws:iam::1stAcctNumber:root", "arn:aws:iam::2ndAcctNumber:root"]}, "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::*"]}</code></pre>
  • deny
    { "Sid":"Deny s3 get object", "Effect":"Deny", "Principal": "*", "Action":["s3:GetObject"], "Resource":["arn:aws:s3:::*"]}

Object Owner Account

  • 1st Acct: The object is owned by the same account as the bucket
  • 2nd Acct: The object is owned by the same account account that has the users that was used for cross account requests
  • 3rd Acct: The object is owned by an account that is different than both the bucket account and cross account

Object ACL (these are just the canned ACLs provided by AWS)

  • private
  • public-read
  • authenticated-read
  • bucket-owner-read

Script + Complete Results

Every possible combination of these features were tested in the script. The code and the complete, unreduced results can be found here.

Author
Brenden Judson Trek10
Brenden Judson