Brenden Judson in aws 8 minutes to read

S3 Permissions

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

Symbol Meaning
* Any possible value for the given column
! VALUE Any 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 Account User Policy Bucket Policy Object Owner Account Object ACL Caller Owns Bucket Caller Owns Object Bucket Owner Owns Object Has Access
* * deny * * * * * AccessDenied
* deny * * * * * * AccessDenied


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

User Account User Policy Bucket Policy Object Owner Account Object ACL Caller Owns Bucket Caller Owns Object Bucket Owner Owns Object Has Access
1st Acct allow ! deny * ! private Yes * * Success
1st Acct allow ! deny 1st Acct private Yes Yes Yes Success
1st Acct allow * 2nd Acct private Yes No No AccessDenied
1st Acct none allow-any 1st Acct * Yes Yes Yes Success
1st Acct none allow-any 2nd Acct ! private Yes No No Success
1st Acct none allow-any 2nd Acct private Yes No No AccessDenied
1st Acct none allow-calling-accounts OR none 1st Acct authenticated-read OR public-read Yes Yes Yes Success
1st Acct none allow-calling-accounts OR none 1st Acct bucket-owner-read OR private Yes Yes Yes AccessDenied
1st Acct none allow-calling-accounts OR none 2nd Acct * Yes No No AccessDenied


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

User Account User Policy Bucket Policy Object Owner Account Object ACL Caller Owns Bucket Caller Owns Object Bucket Owner Owns Object Has Access
2nd Acct allow ! deny 2nd Acct * No Yes No Success
2nd Acct allow allow-any OR allow-calling-accounts 1st Acct * No No Yes Success
2nd Acct allow allow-any OR allow-calling-accounts 3rd Acct authenticated-read OR public-read No No No Success
2nd Acct allow allow-any OR allow-calling-accounts 3rd Acct bucket-owner-read OR private No No No AccessDenied
2nd Acct allow none 1st Acct OR 3rd Acct authenticated-read OR public-read No No Yes Success
2nd Acct allow none 1st Acct OR 3rd Acct bucket-owner-read OR private No No Yes AccessDenied
2nd Acct none ! deny 2nd Acct authenticated-read OR public-read No Yes No Success
2nd Acct none ! deny 2nd Acct bucket-owner-read OR private No Yes No AccessDenied
2nd Acct none * 1st Acct * No No Yes AccessDenied
2nd Acct none * 3rd Acct * No No No AccessDenied


This table shows calls made from an unauthenticated user.

User Account User Policy Bucket Policy Object Owner Account Object ACL Caller Owns Bucket Caller Owns Object Bucket Owner Owns Object Has Access
Unauth User none ! deny * public-read No No * Success
Unauth User none allow-any 1st Acct * No No Yes Success
Unauth User none allow-any 2nd Acct ! public read No No No AccessDenied
Unauth User none allow-calling-accounts * ! public read No No * AccessDenied
Unauth User none none * ! public read No No * 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

      "Statement": [
        {
          "Effect": "Deny",
          "Action": "s3:GetObject",
          "Resource": "*"
        } ]
    
  • 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

      {
        "Sid":"Allow any principal s3 getObject",
        "Effect":"Allow",
        "Principal": "*",
        "Action":["s3:GetObject"],
        "Resource":["arn:aws:s3:::*"]
      }
    
  • allow_calling_accounts_statement

      {
        "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:::*"]
      }
    
  • 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.