AWS Security Blog

Back to School: Understanding the IAM Policy Grammar

by Brigid Johnson | on | in Best Practices, How-to guides | | Comments

Have you ever had to create access policies for users, groups, roles, or resources and wished you could learn more about the policy language? If so, you’ve come to the right place. In this blog, I’ll describe the attributes and structure of the Identity and Access Management (IAM) policy language. I’ll also include examples that may help you author policies that comply with the policy grammar. Along the way, I’ll provide some tips and guidance that will help you avoid some common pitfalls.

Keep in mind that this blog does not cover the entire list of policy components. For a complete list, I recommend taking a look at the IAM documentation.  Let’s get started with understanding the basic attributes of the policy language.

Valid JSON

IAM policies use JSON syntax and a policy must use correct JSON syntax. If you want to test your JSON syntax, you can use any JSON validator. Many code-editing tools include a JSON validation feature, and there are several JSON validation tools online.

Below is a list of the fundamental characteristics of JSON syntax:

  • Data is represented by a name-value pair separated by a colon. For example, “Effect”: “Allow” – in this example “Effect” is the name and “Allow” is the value.
  • When data is made up of multiple name-value pairs, the name-value pairs are separated by commas.
  • Braces, better known as “curly brackets” ({ }), denote an object. Objects can hold multiple name-value pairs.
  • Square brackets ([ ]) denote an array that holds one or multiple values, with values separated by commas.

Using arrays in policies

JSON arrays are a powerful way to specify multiple values in a policy. For example, you can grant users access to multiple resources using an array. An array is denoted with square brackets ([ ]) and the items in the array are separated by commas. Remember, there is no comma after the last item in the array.

You might find using arrays helpful to reduce the number of policies you need. For example, instead of writing two separate policies to grant access to different S3 buckets, you can write one policy and specify both S3 buckets in an array. Below is an example policy that uses an array for multiple actions and resources.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::dev-bucket/*",
        "arn:aws:s3:::qa-bucket/*"
      ]
    }
  ]
}

Policy blocks and statements

If you dissect any policy, you will see they always start and end with a curly brace. These curly braces denote a policy block. Each policy must have exactly one policy block.

Within the policy block, a single statement block is required. The statement block is where you specify your desired access – I will get to that part later. A statement block can be an array of statements to hold one or more statements.

In the following policy, the curly braces on lines 1 and 14 express the policy block. Lines 3 through 13 specify the statement block. Note that it is shown as an array (with [ ]) even though it contains only one statement.

1. { 
2.  "Version": "2012-10-17",
3.  "Statement": [
4.    {
5.      "Effect": "Allow",
6.      "Action": [
7.        "s3:PutObject",
8.        "s3:GetObject",
9.        "s3:DeleteObject"
10.      ],
11.      "Resource": "arn:aws:s3:::dev-bucket/*"
12.    }
13.  ]
14. }

Common errors with policy blocks and statements

Now that I have covered some of the basics, I am going to walk through two common pitfalls regarding policy blocks and statements.

1. Multiple policy blocks in one policy – The following policy is not valid because it contains more than one policy block (see the statement in red). There should be only one top level pair of curly braces ({ }).

{
 "Statement": [
   {
     "Effect":"Allow",
     "Action":"ec2:Describe*",
     "Resource":"*"
   }
 ]
}
{
 "Statement": [
   {
     "Effect": "Allow",
     "Action": "s3:*",
     "Resource": ["arn:aws:s3:::my-bucket/*"]
   }
 ]
}

To fix this policy, you can merge all the policy blocks together into a single array of statements, as in this example:

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ec2:Describe*",
      "Resource":"*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}
2. Multiple statement blocks in one policy – This policy includes multiple statements blocks as separate items.
{
  "Statement":[
    {
     "Effect":"Allow",
     "Action":["s3:ListBucket"],
     "Resource":["arn:aws:s3:::my-bucket"]
    }
  ],
  "Statement":[
    {
      "Effect":"Allow",
      "Action":["*"],
      "Resource":["arn:aws:s3:::my-bucket/*"]
    }
  ]
}

To fix the policy above, you can merge the statement blocks into an array, as shown in the following example:

{
"Statement": [
  {
    "Effect": "Allow",
    "Action": ["s3:ListBucket"],
    "Resource": ["arn:aws:s3:::my-bucket"]
  },
  {
    "Effect": "Allow",
    "Action": ["*"],
    "Resource": ["arn:aws:s3:::my-bucket/*"]
  }
]
}

Elements included in a statement block

Earlier in this post, I covered statement blocks. We refer to the data (i.e., name-value pairs) included inside the statement block as elements. It is important to note that only one element of each type can be specified in a statement block. However, you can use an array to specify multiple values in an element.

There are five core types of elements that you can use in your policy. Some are required and some are optional. Let’s walk through them.

  1. Effect (Required) – specifies whether the statement will explicitly allow (“Allow”) or deny (“Deny”) access. These are the only two values that are valid in this element.
  2. Action* (Required) – describes the type of access that should be allowed or denied.
  3. Resource* (Required) – specifies the object or objects that the statement covers.
  4. Principal* (Optional) – specifies the user, account, service, or other entity that is allowed or denied access to a resource. Principals can only be used for resource-based policies. For policies within IAM, the policy is attached to the Principal it applies to.
  5. Condition (Optional) – lets you specify conditions for when a policy is in effect.

*Note: You can also append a “Not” to this element type – which enables you to specify an exception to a list for the element. For example, “NotAction” will evaluate to all actions except those that you list.

Common errors with elements

Below are common errors when specifying elements in a policy.

1. Repeated elements in one statement block – This policy is incorrect because an element (in this case, Resource) appears multiple times in a statement.

{
 "Statement": [
    {
     "Effect": "Allow",
     "Action": "s3:*",
     "Resource": "arn:aws:s3:::my-bucket",
     "Resource": "arn:aws:s3:::my-bucket/*"
   }
 ]
}

This is a perfect place to use an array to denote multiple resource values. The following example shows how to use an array to update this policy to comply with JSON syntax:

{	
 "Statement": [
    {
     "Effect": "Allow",
     "Action": "s3:*",
     "Resource": ["arn:aws:s3:::my-bucket",
      "arn:aws:s3:::my-bucket/*"
     ]
   }
 ]
}

2. Multiple conflicting elements in one statement block – This issue is a variation of the preceding one (multiple repeated elements), but with the added complication that the repeated elements are contradictory. The policy is invalid because there are multiple Effect elements in one statement block. Right now, this policy would be evaluated using the last Effect in the policy, which in this case would be “Allow”. However, you might want to “Deny” access.

{
 "Statement": [
    {    
     "Effect": "Deny",
     "Effect": "Allow",
     "Action": "s3:*",
     "Resource": "arn:aws:s3:::my-bucket"
   }
 ]
}

In order to fix this policy, include only one Effect element, as in the following example:

{
 "Statement": [
    {
     "Effect": "Deny",
     "Action": "s3:*",
     "Resource": "arn:aws:s3:::my-bucket"
   }
 ]
}

Versions in policies

You may have noticed a version number in most policies. Here is what you need to know about the Version element in policies:

  1. The version specifies the policy language version used. It is not a version you create. Versions allow IAM to enhance the policy language while continuing to support existing policies.
  2. It is best practice to always specify the current version in your policies. At the time of publishing, this version is “2012-10-17”. This will enable you to use the most recent features in the policy language.
  3. If you have an older policy that uses version “2008-10-17,” it will continue to work. However, you will not be able to use newer features like policy variables in this policy. If you do not specify a version then it defaults to “2008-10-17.”

Common errors with versions

Missing version number – The policy below is incorrect because there is no version specified and therefore the policy defaults to version “2008-10-17,” which does not support policy variables.

{
  "Statement": [
    {
      "Action": [
        "iam:*AccessKey*"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:iam::accountid:user/${aws:username}"
      ]
    }
  ]
}

To enable policy variables, add the latest version, “2012-10-17”, to the policy.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "iam:*AccessKey*"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:iam::accountid:user/${aws:username}"
      ]
    }
  ]
}

Conclusion

In this blog post, I covered the core attributes and provided some guidance to help you write policies that comply with the policy grammar. If you want to learn more, I encourage you to refer to review our policy grammar documentation. Of course, if you have any questions, please don’t hesitate to post to the AWS IAM forum.

– Brigid