Got spare 10 minutes? Let's recap AWS IAM!

Got spare 10 minutes? Let's recap AWS IAM!

Β·

11 min read

It's been a while since I started to work with AWS. I like it a lot, but several things used to be particularly challenging to me, and IAM was one of them. More often than not, I had issues with services refusing to work together due to incorrectly set permissions.

Currently, I am preparing for the AWS Solutions Architect Associate exam. This blog post is a compilation of the notes I have regarding IAM. I know a lot more now, and this blog post is a piece that I wish I had available a year ago. If you need a quick refresher on IAM, it's the right place to skim through the article and get an overview of the service's capabilities.

Given all of that, I focus on the theory and definitions in this post. I think it's crucial to know what you're working with before you dive deeper into it. So, if you want to sharpen your understanding of the service, give it a read! And then, carry on the knowledge and practice a lot πŸ˜‰

πŸ€” I think, therefore IAM

IAM stands for Identity and Access Management. The majority of AWS services are region-based, but IAM is a global service. It means that all users, roles, et al. will be available in all regions accessible by your account.

IAM responsibilities revolve around two main areas:

  • It allows you to create users, manage their access to various services, and organize them into groups.
  • It controls communication between AWS applications and services so that only the desired components of your system can talk to one another.

By default, all entities you create in the system have no access to anything else in AWS. It means that as an admin, you have total control over the access grants in your AWS account. It's best to assign only the bare minimum of permissions to the users and services so that they can only perform the actions you intend them to. Having a too relaxed approach to permission restrictions can cause extra trouble in case of a breach in your system.

Let's take a look at the individual components of IAM, starting with the users.

πŸ‘₯ Users and groups

IAM has a robust suite of tools to manage the users and the groups belonging to your application. Let's define these concepts a bit more.

Users are the people from your organization taking care of the applications hosted in your AWS accounts. You can manage them individually by assigning permissions directly to them. However, usually, it's better to organize them into groups.

Groups will help you divide your organization into manageable entities. When you assign permissions to a group, all of the group members will have access to them. One important thing is that a group can only contain users; you can't nest groups within other groups. On the other hand, a user can belong to multiple groups. When that happens, such a user can enjoy access rights from all of the groups.

🚦 Permissions and policies

Permissions control access to AWS services. They are granular, which means that you can be very specific with the level of access right you want to give to a user or service. For example, you can allow users to read from S3 buckets but prevent them from uploading new files. You can also go deeper and allow access to a subset of buckets, prevent deletion of items, etc. The possibilities are virtually endless.

You can store sets of permissions in JSON documents called policies. The example below shows a policy that grants full access to the S3 service.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "FullAccess",
            "Principal": "*",
            "Effect": "Allow",
            "Action": ["s3:*"],
            "Resource": ["*"]
        }
    ]
}

Let's take a look at the individual parts of a policy and describe what does each of them do:

πŸ”Έ Version [Required]

  • Should always equal "2012-10-17".

πŸ”Έ Id [Optional]

  • A custom ID of the policy.

πŸ”Έ Statement [Required]

  • This element can contain either a single statement or an array of them. Each one can grant a different set of permissions per distinct resource.

Statements have their schema too. Let's take a look at it and try to describe each element.

πŸ”Έ Sid [Optional]

  • An optional ID of the statement.

πŸ”Έ Effect [Required]

  • Can have either Allow or Deny as a value. As a result, the principals referred to in the statement will (or will not) be able to access the resources.

πŸ”Έ Principal [Required]

  • Describes who is going to be affected by this policy. You can provide * to allow everyone to use the policy or limit it to specific entities. For users, you can provide their ARNs, which are identifiers starting with arn:aws, e.g. arn:aws:iam::123456789012:root. For AWS services, you can provide their identifiers in service-name.amazonaws.com format, e.g. ecs.amazonaws.com. When the policy is attached to a user, you can omit a principal field.

πŸ”Έ Action [Required]

  • It describes a set of actions that the principal will be able to perform on the referred resource. For example, if you want the policy to control who can read items from an S3 bucket, you should use s3:GetObject. All of the action identifiers that you can use are listed here along with their capabilities: docs.aws.amazon.com/service-authorization/l.. You can put more than one action in this element.

πŸ”Έ Resource [Required]

  • A list of resources that the principal will be able to access. Usually, you should provide a list of ARNs here, e.g. "arn:aws:s3:::example-bucket".

πŸ”Έ Condition [Optional]

  • This element can restrict the validity of the policy to certain conditions. For example, you might want to make the permission temporary by specifying dates, like that:
"Condition" :  {
      "DateGreaterThan" : {
         "aws:CurrentTime" : "2021-10-16T12:00:00Z"
       },
      "DateLessThan": {
         "aws:CurrentTime" : "2021-10-16T15:00:00Z"
       }
}

πŸ” Let's see an example!

Now, let's try to build a policy ourselves to hone the understanding of the topic. We'll start with the one that allows everyone to do everything. Then we will restrict the policy on various levels, and I'll explain what's going on. Without further ado, here's the worst nightmare of every AWS user, even though quite insecure! πŸ‘

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Principal": "*",
            "Effect": "Allow",
            "Action": ["*"],
            "Resource": ["*"]
        }
    ]
}

Let's say that we want to limit the number of actions performed using this policy. AWS provides two actions to grant read access to S3 buckets: s3:GetObject and s3:GetObjectVersion. So, if we want to change the policy to allow everyone to read items from all S3 buckets, it will look like the one below. Note that you can use wildcards so that you don't need to list every permission.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Principal": "*",
            "Effect": "Allow",
            "Action": ["s3:GetObject*"],
            "Resource": ["*"]
        }
    ]
}

Great! Now we can get even more strict and limit the buckets that users can read. Let's assume that all of our bucket names have a my-awesome-app prefix. To apply the policy only for these buckets, we should modify the Resource element:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Principal": "*",
            "Effect": "Allow",
            "Action": ["s3:GetObject", "s3:GetObjectVersion"],
            "Resource": ["arn:aws:s3:::my-awesome-app*"]
        }
    ]
}

Looks even better! As a final step, let's allow access to the buckets only to your servers hosted in EC2. That means we should change the Principal element:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Principal": "ec2.amazonaws.com",
            "Effect": "Allow",
            "Action": ["s3:GetObject", "s3:GetObjectVersion"],
            "Resource": ["arn:aws:s3:::my-awesome-app*"]
        }
    ]
}

Voila! You've just built a policy πŸŽ‰ I hope that this example clarifies the responsibilities of each element and will give you more confidence to create your policies.

😎 Three extra things

The first thing! As you might have noticed in the example above, I used a sweet trick - your policies can leverage wildcards, making building policies easier. It applies to both actions and resources. For example, let's take a look at these three permissions: s3:GetObject, s3:GetBucketLocation, and s3:ListBucket. If you want to apply all of these permissions to a resource, you can use s3:* in the policy. If you only want to apply s3:GetObject and s3:GetBucketLocation, you can use s3:Get*.

The second thing is that you can attach policies both to individual users and groups. When assigned to a group, all the group members will access the resources referred to in the policy.

The final thing - you don't have to create a new inline policy with every new user or group. Instead, you can store them as customer-managed policies and attach them to all entities at will!

Now, let's move on to the roles. They give a whole new level to this already complex system πŸ˜‰

🎭 Roles

Roles are a more abstract layer of IAM permission management. While IAM roles have access to particular policy documents, they are not bound to a specific user. Instead, anyone user or service provided by AWS can assume them.

Users usually use roles whenever they want to access resources between accounts. However, a more common use case is to assign an IAM role to a service to interact with another service. By default, AWS services can't communicate with each other unless explicitly allowed to through policies. But if, for example, you need your Lambdas to upload files to S3 buckets, you can create a role with permissions that allow you to do so.

Whenever you try to access another service using a role, AWS will provide a set of temporary credentials to communicate between the services. That means you don't need to (AND MUST NOT) upload your credentials anywhere to make things work in AWS.

βœ‹ Permissions boundaries

You might still want to introduce higher levels of security to your account. Permissions boundaries are a special kind of policy that allows you to specify the maximum permissions that a user or role can have. As I mentioned above, if a user belongs to several groups, they will have access to the policies from all groups. Still, there might be scenarios in which it's reasonable to set some boundaries on that. Let's take a look at an example.

Let's assume that we have two groups in our organization: software engineers, who have some permissions, and DevOps, having a different access level, including EC2 instance deletion rights. One of our software engineers, Bob, would greatly benefit from having access to both groups. However, we don't want him to be able to wipe out our infrastructure. To solve this problem, we can assign Bob to both groups and attach a permissions boundary, disallowing him to delete EC2 instances. A permissions boundary is a policy, which means you can attach it to a user through a JSON document. So, to achieve the outcome described above, we can limit Bob's ability to interact with EC2 to read-only access using a policy like that:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances", 
                "ec2:DescribeImages",
                "ec2:DescribeTags", 
                "ec2:DescribeSnapshots"
            ],
            "Resource": "*"
        }
    ]
}

πŸ”‘ Credentials

As the final thing, I'd like to focus on how users can authenticate themselves in AWS. You can do this in two ways - through your email or by using keys provided by AWS.

To log in to AWS Management Console through your web browser, you can use a combination of an email and a password. An account administrator can set up a password policy, which will force the users to set them in a specific way. For example, they might need to have upper- and lowercase letters and special characters. Account admins can also set up SSO to use instead of passwords.

Account admins can enforce further security by turning on multi-factor authentication. It requires the user to have a second device to provide a token for the second step of the login process. AWS allows using both virtual and physical MFA devices. A virtual device is usually an app on your smartphone like Google Authenticator. Physical devices are usually USB dongles.

AWS access keys are used to connect with AWS using CLI or from applications built by you. They consist of two parts: an access key ID and a secret access key. They look like this:

  • access key ID: AKIAIOSFODNN7EXAMPLE
  • secret access key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

You must provide both keys simultaneously to interact with AWS. Just like your username and password, you should not share them with anyone. In case they become compromised, the keys can be disabled in the IAM console. Moreover, your secret access keys are visible only during creation. That means that if they are lost, you won't be able to recover them, so you need to generate a new key pair to work with AWS.


AWS IAM is a complex service, but luckily it makes perfect sense after you put in the time to understand how it operates. I hope that this piece helps organize the variety of the concepts present in IAM into manageable buckets. Whether you want to dip your toes in authentication and authorization in AWS, or you need a quick refresher on IAM, this is a good read for you!

If you have any questions or feedback, feel free to share them with me in the comments! This post started as a collection of my notes, which I refined the best I could, but if you have any insights, you're welcome to let me know about them. And if you wish to, you can also find me on Twitter: @kampikd. Good luck on your journey with AWS! πŸ™‚

Did you find this article valuable?

Support Damian Kampik by becoming a sponsor. Any amount is appreciated!