3

When using the following bucket policy, I see that it restricts PUT access as expected - however GET is allowed on the created object, even though there is nothing which should allow this operation.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowPut",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::<BUCKET>/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "<IP ADDRESS>"
                    ]
                }
            }
        }
    ]
}

I am able to PUT files to <BUCKET> from <IP ADDRESS> using curl as follows:

curl https://<BUCKET>.s3-<REGION>.amazonaws.com/ --upload-file test.txt

The file uploads successfully, and appears in the S3 console. I am now for some reason able to GET the file from anywhere on the internet.

curl https://<BUCKET>.s3-<REGION>.amazonaws.com/test.txt -XGET

This only applies for files uploaded using the above method. When uploading a file in the S3 web console, I am not able to use curl to GET it (access denied). So I assume that it is an object level permission issue. Though I don't understand why the bucket policy would not implicitly deny this access.

When looking at the object level permissions in the console, the only differences between a file uploaded through the console (method 1), and one uploaded from the allowed <IP ADDRESS> (method 2) are that the file in method 2 does not have an 'Owner', Permissions, or Metadata - while the method 1 file has all of these.

Furthermore - when attempting to GET the objects using a Lambda script (boto3 download_file()) which assumes a role with full access to the bucket, it fails for objects uploaded with method 2. Though it succeeds for objects uploaded with method 1.

unclemeat
  • 5,029
  • 5
  • 28
  • 52

1 Answers1

3

Issue Summary

To summarise the issue:

  • you have a policy that permits anonymous upload of objects from a given source IP address
  • those objects are then not readable by your authenticated users (specifically an Iam Role adopted by your lambda function)
  • those objects ARE readable from ANY IP by unauthenticated users

Other observations

  • unauthenticated user is unable to delete the object

The desired outcome is:

  • objects can be uploaded by an unauthenticated user from a known IP address
  • objects are not then downloadable by unauthenticated users from any IP address
  • objects are retrievable by an authenticated Iam user

Root Cause

Here is what's happening:

  1. Anonymous user uploads the object

    1. The Anonymous user becomes the object owner
    2. Verifiable by retrieving the object acl (do a GET request for the object with query string ?acl) - you will receive:

      <?xml version="1.0" encoding="UTF-8"?>
      <AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
          <Owner>
              <ID>65a011a29cdf8ec533ec3d1ccaae921c</ID>
          </Owner>
          <AccessControlList>
              <Grant>
                  <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CanonicalUser"><ID>65a011a29cdf8ec533ec3d1ccaae921c</ID></Grantee>
                  <Permission>FULL_CONTROL</Permission>
              </Grant>
          </AccessControlList>
      </AccessControlPolicy>
      

      The Owner ID is the universal id of the anonymous user - I have seen the same id referenced in some AWS forum discussions.

  2. Being the object owner has the following impact:
    1. Anonymous user has FULL_CONTROL (see acl above)
    2. Anonymous user is unable to Delete - this appears to be an AWS blanket rule that cannot be changed - the anonymous user is never allowed to delete anything, even if they have FULL_CONTROL
    3. Anonymous user is, however, able to PUT an empty object over the top of the existing object, as a result of FULL_CONTROL
  3. When a bucket contains a object owned by a user who is not part of the bucket's account:
    1. Bucket owner has no permission on the object (not referenced in acl)
    2. Bucket owner is not able to read the object
    3. Bucket owner is able to see the object in a bucket list operation due to bucket acl
    4. Bucket owner is able to delete the object - this is a blanket rule that cannot be changed - as the person paying the bill, you always reserve the right to delete the object - even if you can't read it

Resolution

There is a way to achieve your desired outcome - unfortunately you have to reference the arn of the specific Iam entity (user, role, group) you want to be able to read the object in the bucket acl.

The key elements of the solution are:

  • Require the anonymous user to grant the bucket owner full access
    • This ensures the bucket owner and owner account Iam users aren't denied access by the object acl
  • Explicitly deny all non-PUT access to all users who aren't your nominated user/role
    • This ensure anonymous users can't read the object

Sample policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "allow-anonymous-put",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::<BUCKETNAME>/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "<IPADDRESS>"
                },
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }

        },
        {
            "Sid": "deny-not-my-user-everything-else",
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": "arn:aws:iam::<ACCOUNTNUMBER>:role/<ROLENAME>"
            },
            "NotAction": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::<BUCKETNAME>/*"
        }
    ]
}

The key to the second statement is the use of NotPrincipal and NotAction.

I've tested this locally, but only with a regular Iam user granted access, not with a Lamba function assuming a role - but the principal should hold. Good luck!

The following articles were helpful in understanding what was going on - they each present a scenario similar, but not quite the same as yours, but the methods they used to tackle their scenarios led the way:

Chris Simon
  • 6,185
  • 1
  • 22
  • 31
  • Thanks for your comments. Regarding the clarification you were after - I have amended the original post with some additional information. Basically just a role used by a Lambda function. Which is for some reason being denied for GET operations. This seems strange, it's as though the object level permissions are explicitly denying anything that isn't anonymous, or the bucket owner (as I can download through the console). So I'm not sure what is denying the Lambda role. – unclemeat Aug 22 '16 at 03:40
  • > it's as though the object level permissions are explicitly denying anything that isn't anonymous, or the bucket owner < That is exactly what's happening, I believe. The object is created with the 'anonymous' owner, and no other permissions. Can you paste the policy associated with the role assumed by the lambda function? – Chris Simon Aug 22 '16 at 03:47
  • I tried specifying specific access with an inline policy on the role, but ended up testing with the 'AmazonS3FullAccess' managed policy and I get the same behavior (you should be able to check that out yourself in IAM policies). – unclemeat Aug 22 '16 at 04:15
  • I just tried to delete the anonymously created objects, and it is not allowed as the anonymous user. So I suppose it only allows the GET operation by default. Just worth noting. – unclemeat Aug 22 '16 at 04:19
  • OK, I think I've cracked it - interesting challenge! Major edit to the answer to present a cleaner explanation of the cause and resolution. – Chris Simon Aug 22 '16 at 13:14
  • Excellent description. Thank you very much for your input. – unclemeat Aug 22 '16 at 23:32
  • Even when specifying the ACL on upload, the bucket owner doesn't seem to have permission to do anything to the file (including delete). – unclemeat Aug 24 '16 at 01:59
  • I believe if you are using an iam user, then the ACL doesn't necessary grant the right to do the action - it just prevents the action from being explicitly denied by the ACL layer. To permit the action, it needs to be permitted by a policy - e.g. I think if you have something like the policy above you will need to add `s3:DeleteObject` to the NotAction property - and even then it will only apply to the role or user referenced in that policy. I'll find some time to double check that later today. – Chris Simon Aug 24 '16 at 02:06
  • It also doesn't work with the role used by Lambda. Even though the role has a policy which allows the access. – unclemeat Aug 24 '16 at 02:15
  • What does the bucket policy look like, though? If you have something like the "deny-not-my-user-everything-else" in the statement above, it will block it even if it's permitted by the role policy. – Chris Simon Aug 24 '16 at 02:18
  • The bucket policy is exactly what you specified in your answer. Surely the 'deny-not-my-user-everything-else' is supposed to allow NotAction PutObject for 'user' (which in this case is my role)? I need for anonymous to not be able to do anything apart from PutObject, and my role to be able to do everything - if that clarifies anything. – unclemeat Aug 24 '16 at 02:23
  • That's right - and if you want to allow your iam role to delete the object, you will have to add `s3:DeleteObject` to the NotAction list. It's full of double negatives - basically says - Deny the operation if the user is NOT this user AND the action is NOT these actions. So right now, even though it's your user, the action is Delete which is NOT one of those actions, so it's denied. This will not grant anything to anonymous, which is only granted PutObject by the other statement (and even then, only from the noted IP). – Chris Simon Aug 24 '16 at 02:29
  • I've just tested, and adding GetObject and DeleteObject to the "deny-not-my-user-everything-else" gives that access to the anonymous user. Which makes sense, because you're denying everything except for get, put, and delete for every principal except the given role. And the role still does **not** have get, put, or delete access. – unclemeat Aug 24 '16 at 03:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121681/discussion-between-chris-simon-and-unclemeat). – Chris Simon Aug 24 '16 at 03:16