21

I am trying to write AWS S3 bucket policy that denies all traffic except when it comes from two VPCs. The policy I'm trying to write looks like the one below, with a logical AND between the two StringNotEquals (except it's an invalid policy):

{
   "Version": "2012-10-17",
   "Id": "Policy1415115909152",
   "Statement": [
     {
       "Sid": "Allow-access-only-from-two-VPCs",
       "Action": "s3:*",
       "Effect": "Deny",
       "Resource": ["arn:aws:s3:::my-bucket",
                    "arn:aws:s3:::my-bucket/*"],
       "Condition": {
         "StringNotEquals": {
           "aws:sourceVpc": "vpc-111bbccc"
         },
         "StringNotEquals": {
           "aws:sourceVpc": "vpc-111bbddd"
         }
       },
       "Principal": "*"
     }
   ]
}

If I use this:

"StringNotEquals": {
       "aws:sourceVpc": ["vpc-111bbccc", "vpc-111bbddd"]
     }

then at least one of the string comparisons returns true and the S3 bucket is not accessible from anywhere.

ikh
  • 2,336
  • 2
  • 19
  • 28
  • This conclusion isn't correct (or isn't correct anymore) for `StringNotEquals` - check [my answer below](https://stackoverflow.com/a/71531863/1060004). – ustulation Oct 26 '22 at 19:32

3 Answers3

27

Never tried this before.But the following should work. From: Using IAM Policy Conditions for Fine-Grained Access Control

    "Condition": {
        "ForAllValues:StringNotEquals": {
            "aws:sourceVpc": [
                "vpc-111bbccc",
                "vpc-111bbddd"
            ]
        },
helloV
  • 50,176
  • 7
  • 137
  • 145
18

The problem with your original JSON:

"Condition": {
    "StringNotEquals": {
        "aws:sourceVpc": "vpc-111bbccc"
    },
    "StringNotEquals": {
        "aws:sourceVpc": "vpc-111bbddd"
    }
}

You can't have duplicate keys named StringNotEquals.

But there are a few ways to solve your problem.

Flip the conditional and specify Allow rather than Deny permissions

Self-explanatory: Use an Allow permission instead of Deny and then use StringEquals with an array. All the values will be taken as an OR condition.

{
   "Version": "2012-10-17",
   "Id": "Policy1415115909152",
   "Statement": [
     {
       "Sid": "Allow-access-only-from-two-VPCs",
       "Action": "s3:*",
       "Effect": "Allow",
       "Resource": ["arn:aws:s3:::my-bucket",
                    "arn:aws:s3:::my-bucket/*"],
       "Condition": {
         "StringEquals": {
           "aws:sourceVpc": ["vpc-111bbccc", "vpc-111bbddd"]
         }
       },
       "Principal": "*"
     }
   ]
}

Use a set operator

IAM policies allow the use of ForAnyValue and ForAllValues, which lets you test multiple values inside a Condition.

{
   "Version": "2012-10-17",
   "Id": "Policy1415115909152",
   "Statement": [
     {
       "Sid": "Deny-access-except-from-two-VPCs",
       "Action": "s3:*",
       "Effect": "Deny",
       "Resource": ["arn:aws:s3:::my-bucket",
                    "arn:aws:s3:::my-bucket/*"],
       "Condition": {
         "ForAllValues:StringNotEquals": {
           "aws:sourceVpc": ["vpc-111bbccc", "vpc-111bbddd"]
         }
       },
       "Principal": "*"
     }
   ]
}

Use a hack combination of StringNotEquals and StringNotEqualsIgnoreCase

I'm fairly certain this works, but it will only limit you to 2 VPCs in your conditionals.

{
   "Version": "2012-10-17",
   "Id": "Policy1415115909152",
   "Statement": [
     {
       "Sid": "Deny-access-except-from-two-VPCs",
       "Action": "s3:*",
       "Effect": "Deny",
       "Resource": ["arn:aws:s3:::my-bucket",
                    "arn:aws:s3:::my-bucket/*"],
       "Condition": {
         "StringNotEquals": {
           "aws:sourceVpc": ["vpc-111bbccc"]
         },
         "StringNotEqualsIgnoreCase": {
           "aws:sourceVpc": ["vpc-111ddeee"]
         }
       },
       "Principal": "*"
     }
   ]
}
robe007
  • 3,523
  • 4
  • 33
  • 59
wkl
  • 77,184
  • 16
  • 165
  • 176
3

I don't know if it was different back when the question was asked, but the conclusion that StringNotEqual works as if it's doing:

incoming-value != value0 OR incoming-value != value1 OR ...

is incorrect.

The negation happens after the normal comparison of what is being negated. So it's effectively:

!(incoming-value == value0 OR incoming-value == value1 OR ...)

which is the same as:

incoming-value != value0 AND incoming-value != value1 AND ...

This means that for StringNotEqual to return true for a key with multiple values, the incoming value must have not matched any of the given multiple values.

So DENY on StringNotEqual on a key aws:sourceVpc with values ["vpc-111bbccc", "vpc-111bbddd"] will work as you are expecting (did you actually try it out?). The condition will only return true none of the values you supplied could be matched to the incoming value at that key and in that case (of true evaluation), the DENY will take effect, just like you wanted.

Other answers might work, but using ForAllValues serves a different purpose, not this. ForAllValues is more like: if the incoming key has multiple values itself then make sure that that set is a subset of the values for the key that you are putting in the condition. For a single valued incoming-key, there is probably no reason to use ForAllValues.

As background, I have used this behaviour of StringNotEqual in my API Gateway policy to deny API calls from everyone except the matching vpces - so pretty similar to yours. That's all working fine.

ustulation
  • 3,600
  • 25
  • 50