Question:
My policy is not working according to AWS. JSONlint says I have a valid json. There’s a grammar issue, but I’m not seeing it.
This policy contains the following error: Policy does not comply with the Identity and Access Management(IAM) policy grammar. For more information about the IAM policy grammar, see AWS IAM Policies.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ec2:*", "Resource": "*", "Condition": { "StringEquals": {"ec2:ResourceTag/sf_env": "dev", "StringEquals": {"ec2:Region": "us-west-2" } } } }, { "Effect": "Allow", "Action": "rds:*", "Resource": "*", "Condition": { "StringEquals": { "ec2:ResourceTag/sf_env": "dev", "StringEquals": { "ec2:Region": "us-west-2" } } } }, { "Sid": "AllowHealthCheckOnly", "Effect": "Allow", "Action": "elasticloadbalancing:Describe*", "Resource": "*", "Condition": { "StringEquals":{ "ec2:ResourceTag/sf_env": "dev", "StringEquals":{ "ec2:Region": "us-west-2" } } } }, { "Sid": "ConfigureHealthCheckOnly", "Effect": "Allow", "Action": "elasticloadbalancing:ConfigureHealthCheck", "Resource": "arn:aws:elasticloadbalancing:us-west-2:xxxxxxxxxxxx:loadbalancer/instance1", "Condition": { "StringEquals": { "ec2:ResourceTag/sf_env": "dev", "StringEquals": { "ec2:Region": "us-west-2" } } } }, { "Sid": "FullElasticCacheManagedPolicyPermissions", "Effect": "Allow", "Action": [ "elasticache:*", "ec2:DescribeAvailibilityZones", "ec2:DescribeVpcs", "ec2:DescribeAccountAttributes", "ec2:DescribeSeucrityGroups", "cloudwatch:GetMetricStatistics", "cloudwatch:DescribeAlarms", "sns:ListTopics", "sns:ListSubscriptions" ], "Resource": "*", "Condition": { "StringEquals": { "ec2:ResourceTag/sf_env": "dev", "StringEquals": { "ec2:Region": "us-west-2" } } } } ] |
}
Answer:
JSONLint says you have syntactically valid JSON, and you do… but the problem is that the data you successfully encoded… doesn’t make semantic sense.
Looking just at your last condition, you wrote:
1 2 3 4 5 6 7 |
"Condition": { "StringEquals": { "ec2:ResourceTag/sf_env": "dev", "StringEquals": { "ec2:Region": "us-west-2" } } } |
Please note: since the following are key/value snippets plucked out of a larger JSON object, you have to nest each of these in an additional outer pair of {
braces }
for JSONLint to parse it, and indeed for it to be a valid JSON representation, when evaluated on its own without the surrounding structure. I added those to the JSONLint input in each case when testing creating the examples, and removed them from the output, hopefully in the interest of clarity.
JSONLint reformats the above, to show you what you are communicating:
1 2 3 4 5 6 7 8 9 |
"Condition": { "StringEquals": { "ec2:ResourceTag/sf_env": "dev", "StringEquals": { "ec2:Region": "us-west-2" } } } |
JSON’s strength is in its simple structure and constraints: it has {
objects }
(key/value pairs, where the key is a string and the value is exactly one of any of the types of things mentioned anywhere in this sentence) … [
arrays ]
(lists of values) … "
strings "
… numbers (unquoted) … booleans (true
and false
, unquoted) … and null (null
, unquoted).
StringEquals
, as you have expressed it, above, is an object with two keys, ec2:ResourceTag/sf_env
(which has a string as its value)… and StringEquals
(second appearance, which has another nested object as its value).
Clearly, this is not what you intended, but it is the correct interpretation of what you supplied.
Note that the indentation is entirely optional, but the output format of JSONLint uses the indentation in a meaningful way. Noting how "ec2:ResourceTag/sf_env"
and "ec2:Region"
are (to use a non-JSON term) “siblings” (at the same level of the data structure), the different indentation is your red flag that all is not well.
Correcting the placement of the braces, you probably intended to write something more like this:
1 2 3 4 5 |
"Condition": { "StringEquals": { "ec2:ResourceTag/sf_env": "dev" }, "StringEquals": { "ec2:Region": "us-west-2" } } |
That looks more sensible, though it is still almost certainly incorrect, because of the way deserialization works: now you have two StringEquals
keys in the same object, and although the JSON standard does not appear to prohibit this, the behavior is undefined at best, and many or most libraries that deserialize this would interpret the above to mean this:
1 2 3 4 5 6 |
"Condition": { "StringEquals": { "ec2:Region": "us-west-2" } } |
Later keys and their values can reasonably be expected to supersede identical earlier keys and their values.
That’s how JSONLint interprets it, too. In a JSON object, a single key can only ever have have exactly one value — remembering that “value” means exactly one object, array, string, number, boolean, or null. When more than one value is present, it has to be nested, correctly, inside an inner structure.
So, what’s the correct representation?
1 2 3 4 5 6 7 |
"Condition": { "StringEquals": { "ec2:ResourceTag/sf_env": ["dev"], "ec2:Region": ["us-west-2"] } } |
The object that is the value of Condition
has one key, StringEquals
, whose value is an object with two keys, ec2:ResourceTag/sf_env
and ec2:Region
; each of those keys has, as its value, an array of one or more strings.
So, not only can you use multiple StringEquals
tests, each of those tests can also match any of several values, if desired, when the values are presented inside the array I have shown. For example, ["dev","prod"]
in place of ["dev"]
would match either dev
or prod
.
If you only have one value for each, IAM appears to support using just a string instead of an array, e.g. "dev"
replacing ["dev"]
(which is also valid JSON) but the documented examples I have observed tend to show it as I have documented above, as { key1: [ "list" ], key2: ["list"] }
… etc., and if you format it that way now, it will be much more intuitive if you want to allow more possible values later.