Chris White
Posted on May 19, 2023
AWS Identity and Access Management or IAM is a system to provide permissions management around AWS resources. Given how essential it is for getting most anything to work in AWS I consider it the #1 service anyone who is using or plans to use AWS needs to understand. This guide will break down some of the essential permissions concepts around IAM.
Datacenter Access Safeguards Example
I'll be using the example of physical access to a data center where applicable. The goal is to provide close to real world examples to help solidify the concepts used by IAM and explain why they work the way they do.
Principle of Least Privilege
To understand why IAM is required it's important to understand the principle of least privilege. Enough permissions should be granted for a user to perform their job. For example, someone on the networking team in a data center unplugging servers would be extremely suspicious. On the other hand, someone tasked with maintenance of servers might be unplugging one to swap out defective hardware.
With AWS it works in a similar principle. A web developer shouldn't be deleting tables from a database if a DBA is present. Administrators may require far reaching permissions to handle any issue with any service that comes up. This is not just for employees, but AWS services talking with each other as well. A Lambda that's part of a simple REST API might need access to DynamoDB to get/store information, but it shouldn't be spinning up EC2 instances.
Permissions Granting
Permissions are granted through the use of policy JSON in AWS. This includes not only approving or denying access, but setting constraints on what the permissions apply to. It's a very powerful system but also one that can be difficult to grasp depending on permission complexity.
Basic Permissions Structure
Basic IAM permissions are done through policy JSON which has the following skeleton structure:
{
"Version":"2012-10-17",
"Statement": []
}
Where version indicates how the JSON should be structured on the backend. It works somewhat like an API version. Statement is where permission clauses are actually made. As it is this policy doesn't actually do anything.
Implicit Deny
If no permissions are granted, permission is denied by default. Take a security guard who monitors individuals going in and out of an entrance gate. They have a list of authorized individuals to let in. Even though they aren't told, they know by default to not let anyone in who isn't on the list.
Explicit Deny
Now let's say the guard is told "don't let anyone in without an ID even if they're on the list". If the guard asks someone for their ID and they don't have it, there isn't a need to check the list it's a full deny. No amount of explanation on the side of the individual would let them in if there's no ID.
IAM permissions work much in the same way. There could be 10 different sources approving an action, but if one clause denies the action explicitly all logic stops and the result is a deny. Note that in a business organizations with standard needs the chances of seeing explicit denies are fairly low. It's easier to rely on implicit deny and simply whitelist what permissions should be granted. An example of an explicit deny:
{
"Sid": "DenyEC2",
"Effect": "Deny",
"Action": "ec2:*",
"Resource": "*"
}
Even if an ec2 API call is allowed in another statement the explicit deny will still override it.
Allow
Allow is the more commonly used action in declaring permissions. It overrides an implicit deny and is overridden by an explicit deny. This will allow the defined permission in question. The allowed permissions can have several formats with the option to use wildcards:
-
ec2:*
: Allow all ec2 actions -
ec2:RunInstances
: Only allow theRunInstances
API call (the most least privilege friendly declaration format) -
ec2:Describe*
: Allow any call that starts with Describe, such asDescribeInstances
andDescribeImages
An example of a basic allow statement would be:
{
"Effect": "Allow",
"Action": "ec2:RunInstances",
"Resource": "*"
}
You can also allow multiple actions:
{
"Effect": "Allow",
"Action": [
"ec2:TerminateInstances",
"ec2:StopInstances",
"ec2:StartInstances"
],
"Resource": "*"
}
Resource
Let's say that a data center facility has 3 different buildings. One of the buildings holds financial data and is more restricted on access. A network engineer with access to this facility can enter the facility through a guarded gate, and walk around the parking lot like everyone else. However, once they enter the secure building they are given very specific permissions on what they can and can't do based on the company policy of dealing with sensitive financial data.
IAM allows something similar called Resource designations. These are in the form of ARN (Amazon Resource Name) format, and can include wildcards. "Resource": "*"
indicates all available resources bound to the provided actions. Giving a specific resource such as an EC2 instance means that you'll want to be using EC2 instance based API calls in the permission definitions. An S3 action against an EC2 instance resource doesn't really make sense.
Take an example of a developer on a project that has a table in DynamoDB. Instead of giving all access to DynamoDB they instead give access to the specific project's table outlining specific table operations you'd expect for a CRUD like system:
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:ConditionCheckItem",
"dynamodb:PutItem",
"dynamodb:DescribeTable",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": "arn:aws:dynamodb:us-east-2:123456789012:table/ProjectTable"
}
Conclusion
This includes a basic look at permissions in IAM. It is my hope that the information presented is easy to digest. More details on other parts of IAM will be covered in future posts so be sure to follow so you'll get updates when they come!
Posted on May 19, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.