AWS IAM Policy Conditions and Restricting Access by Availability Zones

  • by Emre Yilmaz
  • Oct 31, 2017
  • AWSAWS IAM
  • Istanbul

AWS Identity and Access Management (IAM) is the foundation service to manage security of your resources on AWS. Custom IAM policies feature allows us to define our own policies according to our needs instead of using AWS Managed Policies. Normally, it is a best practice to use managed policies whenever possible, because AWS updates them automatically when a new service is launched. However, assigning least privileges is the most important principle in terms of security and sometimes it is better to prepare custom policies.

Conditions in IAM policies allow us to tailor permissions and grant access to users if the resource meets specific conditions. In this post, I will show you how to use conditions in an IAM policy to grant users permissions to start/stop EC2 instances if the instance is in a specific availability zone.

IAM Policy and Its Elements

First of all, let’s discuss about IAM policies. An IAM policy is a document where you define permissions for your AWS services. For example, you can define a policy to allow all actions for your EC2 instances and only read action for your RDS instances.

An IAM policy can be written using JSON or YAML. Actually, I prefer using JSON as I feel more comfortable with it, but it is OK to use YAML and may be simpler for you.

Let’s continue with elements in an IAM policy:

  • Version: It is the version of policy language we are using. For now, it has a default value of 2012-10-17 and we use this value. Version must come before ‘Statement’ element.
  • Id: An optional identifier for the policy. However, some services such as SQS requires this element to exist and be unique.
  • Statement: It is the most important part in a policy and mandatory. It contains an array of individual statements. Each individual statement may have these elements below:
    • Sid: An optional identifier for a statement and it must be unique in the policy if defined.
    • Effect: A required element which defines whether the policy allows or denies access and it can have only “Allow” or “Deny” values. By default, everything is denied. A statement with an “Allow” effect allows access. If there is an another statement that has “Deny” for the same action and resource, it overrides the previous allow.
    • Principal: It is used to define statements for a specific user, AWS account, AWS service or other principal entity. It is mainly used to define trust policies for IAM roles and for resource based policies such as Amazon S3 bucket policies and Amazon SNS topic policies.
    • NotPrincipal: This is the opposite of Principal and it excludes principals from the statement. In other words, if the principal is in this list, this statement is not applicable for it.
    • Action: It is a required element if there is no NotAction element in the statement. Every AWS service has specific actions, for example StartInstances action of EC2 service. Statement is valid for the actions defined in Action element which can be a single action as a string or an array of actions.
    • NotAction: The opposite of Action element. If you use Allow as effect, every action other than the actions defined in this element is allowed. It is useful for preparing shorter policies. However, you should be very careful for not to break least privileges principle while using this element.
    • Resource: This is the AWS resource or resources that the statement holds. It is basically the Amazon Resource Name (ARN) of the resource. It is used to limit the statement for certain resources in a service and you can use ‘*’ to make this statement valid for all resources. It is mandatory if there is no NotResource element in the statement.
    • NotResource: The opposite of Resource. The statement holds if the resource is not in this element. It is used to exclude resources from the statement.
    • Condition: An optional element to define conditions for the statement. Condition operators for matching global or service specific key-value pairs are used to specify conditions. I will show you an example to limit the statement by the availability zone of an EC2 instance.

Conditions in a Statement

Condition element in a statement allows us to make the IAM statement be effective if a certain condition is met. There is a Condition Operator that defines the matching operation such as equals, less than etc. and a key-value pair to apply this operator.

The general format is below:

"Condition": {
  "<conditionOperator>" : {
     "<key>" : "<value>"
   }
}

Here, <condition operator> must have a unique value and key can be either a global key or a key defined by the service used in the statement.

In cases where multiple conditions should match for same operator, you can use multiple key-value pairs under the same operator.

"Condition": {
  "<conditionOperator>" : {
     "<key1>" : "<value1>",
     "<key2>" : "<value2>"
   }
}

Some keys may require multiple values. This time value field becomes an array.

"Condition": {
  "<conditionOperator>" : {
     "<key1>" : [ "<value1>", "<value2>" ]
   }
}

You can also use multiple condition operators and all of these conditions should match. The comas (,) actually becomes an AND operator in this case.

"Condition": {
  "<conditionOperator1>" : {
     "<key1>" : "<value1>"
  },
  "<conditionOperator2>" : {
   "<key2>" : "<value2>"
  }
}

An EC2 example by Availability Zone

After discussing the format of the condition element in a statement, let’s see an example to this usage. In the example policy we will use conditions to allow StartInstances and StopInstances actions if the EC2 instance is in eu-central-1a availability zone. This will allow the users/roles who has this policy attached to start/stop instances that is launched in a specific availability zone.

Actually, availability zones may physically differ for each AWS account and eu-central-1a for an AWS account may not be the same availability zone as eu-central-1a for another AWS account. However, it is always the same availability zone for the same AWS account.

Our policy will be as below:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListAllInstances",
      "Action": "ec2:DescribeInstances",
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Sid": "StartStopOnlyInEuCentral1A",
      "Action": [
        "ec2:StartInstances",
        "ec2:StopInstances"
      ],
      "Effect": "Allow",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "ec2:AvailabilityZone": "eu-central-1a"
        }
      }
    }
  ]
}

If you attach this policy to a user, that user will be able to start and stop EC2 instances if they are in ‘eu-central-1a’ availability zone of your AWS account. If an instance is in another availability zone such as ‘eu-central-1b’ or another region’s availability zone like ‘eu-west-1a’, then user will not be able to start or stop that instance unless you attach another policy to the user allowing that action.

Of course the user should have permission to list EC2 instances to be able to display the instance on AWS Management Console and start/stop the instance. However, you cannot restrict ec2:DescribeInstances action by an availability zone. This is why we defined a separate statement without the condition to describe all instances.

In the end, our user will be able to see all instances, but only start/stop instances in ‘eu-central-1a’.

Conclusion

Custom IAM policies and conditions provide us to satisfy different needs to administer the security of our AWS resources. Of course, restricting this EC2 actions per Availability Zone may not be a useful solution for you, but you can always use different conditions for different resources.

I hope this example can give you an idea about how you can use conditions in your IAM policies.

Thanks for reading!

References

Emre Yilmaz

AWS Consultant • Instructor • Founder @ Shikisoft

Follow