Delegate AWS IAM User and Role Creation without Giving Away Admin Access

Up until a few years ago, if you were the administrator of your organization’s AWS accounts, the only way for anyone in your organization to get anything done in your AWS accounts, was to first approach you for the necessary IAM permissions, and only then could they use AWS services.

Even if you are one of those organizations where every AWS user fits neatly in a group and there exists a clearly defined list of permissions the group’s users need, there is still the case of users, say, developers, unable to create IAM resources needed by their workloads.

Consider an example: A developer is deploying a new workload to an EC2 instance and needs to assign an IAM role to the instance. What do they do? Approach you of course. You have to create the role for them because they don’t have the permissions to do it themselves. This requires that they know exactly what permissions the role needs, which is hard if they’re simply experimenting or trying out an idea.

Well, this is a hassle, isn’t it? Every developer in your org pinging you to create and update roles! Isn’t there a better way? What if you could give everyone permissions to create the roles they need? Well then, what is to stop them from creating an admin role for themselves? Nothing!

This is exactly the problem addressed by IAM “permission boundaries”. What are permission boundaries? How do they work? How do I use them? Let’s take a look.

Permission Boundaries

Permission boundaries in IAM, are managed IAM policies that act as a guardrail to prevent a user or role from getting permissions beyond a certain “boundary”.

The basic idea is as follows: If an IAM user or role has a permission boundary attached to it, the boundary defines the maximum permissions the user or role can ever have, even if they have admin access policies attached to them!

Let us look at how permission boundaries work in detail.

This is the “add permissions” page of the “create user” wizard in AWS:

Notice how we have granted admin permissions to this user. However, further down the page, in the permission boundaries section, we have only granted EC2 access:

If we finish creating this user, they will never be able to perform any non-EC2 actions in AWS, even though they have an admin access policy attached. That is the magic of permission boundaries!

The same principle applies to IAM roles as well:

Okay great! Now we know how permission boundaries work, but how does that help us with our earlier problem. We don’t want to be managing IAM users and roles for every AWS user in our organization!

What if we could force all role creators to use a permission boundary of our choosing? That way, no matter what permissions they grant their roles, our boundary will limit their permissions!

This is pretty straightforward to do. We can attach a policy to the users’ groups that enforces the use of a permission boundary when the users in that group create IAM roles. The policy would look something like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CreatePolicy",
      "Effect": "Allow",
      "Action": ["iam:CreatePolicy", "iam:CreatePolicyVersion", "iam:DeletePolicyVersion"],
      "Resource": "*"
    },
    {
      "Sid": "CreateRole",
      "Effect": "Allow",
      "Action": ["iam:CreateRole"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "iam:PermissionsBoundary": "arn:aws:iam::aws:policy/AmazonS3FullAccess"
        }
      }
    },
    {
      "Sid": "AttachDetachRolePolicy",
      "Effect": "Allow",
      "Action": ["iam:AttachRolePolicy", "iam:DetachRolePolicy"],
      "Resource": "*",
      "Condition": {
        "ArnEquals": {
          "iam:PolicyARN": ["arn:aws:iam::123456789012:policy/*", "arn:aws:iam::aws:policy/*"]
        }
      }
    }
  ]
}

The first statement in the above policy with the ID CreatePolicy allows the user to create any policy, even admin, but that’s okay because a policy by itself doesn’t grant any permissions, not unless it’s attached to a user, group, or role.

The second statement with ID CreateRole allows the user to create any role but only and only if it has the AmazonS3FullAccess policy attached to it as a permissions boundary.

Note that I’ve chosen AmazonS3FullAccess here just for simplicity. You could just as easily have created a custom policy of your own and put its ARN here instead of AmazonS3FullAccess. This is especially useful when you have restrictions like “allowed regions”, etc.

The third and final statement in the above policy with the ID AttachDetachRolePolicy allows a user to attach/detach policies to/from roles. We obviously need this statement so the user can attach policies to the roles they create. But there is one glaring flaw in this statement: it allows attaching/detaching ANY policy to/from ANY role, including roles that were not created by this user.

There are several ways to fix this. What we want is the user to be able to manipulate ONLY their roles, no one else’s. But we also want them to be able to manage roles created by other members of their team, for other resources in their workload, since they’re are all working on one application, serving a common goal.

Let’s explore the fixes available:

  • We could use the IAM condition key iam:ResourceTag/${TagKey} to restrict permissions to roles with a certain tag, assuming all resources (including roles) of a certain project are tagged accordingly.
  • Or we could impose a naming convention on the roles and policies created for an application, and use this naming convention to restrict the permissions.

Let’s explore that second solution in detail.

Say we require all roles and policies of App1 to be prefixed by “app1-“. We could then add this to our earlier policy to restrict access to only resources of App1. The policy would then look like so:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CreatePolicy",
      "Effect": "Allow",
      "Action": ["iam:CreatePolicy", "iam:CreatePolicyVersion", "iam:DeletePolicyVersion"],
      "Resource": "arn:aws:iam::123456789012:policy/app1-*"
    },
    {
      "Sid": "CreateRole",
      "Effect": "Allow",
      "Action": ["iam:CreateRole"],
      "Resource": "arn:aws:iam::123456789012:role/app1-*",
      "Condition": {
        "StringEquals": {
          "iam:PermissionsBoundary": "arn:aws:iam::aws:policy/AmazonS3FullAccess"
        }
      }
    },
    {
      "Sid": "AttachDetachRolePolicy",
      "Effect": "Allow",
      "Action": ["iam:AttachRolePolicy", "iam:DetachRolePolicy"],
      "Resource": "arn:aws:iam::123456789012:role/app1-*",
      "Condition": {
        "ArnEquals": {
          "iam:PolicyARN": ["arn:aws:iam::123456789012:policy/*", "arn:aws:iam::aws:policy/*"]
        }
      }
    }
  ]
}

Now that last statement can only work on roles with names starting with “app1-“. That’s exactly what we want!

Conclusion

In this article, we saw how to use permission boundaries to delegate IAM user and role creation to others without giving them the opportunity to extend their existing permission set.

About the Author ✍🏻

Harish KM is a Principal DevOps Engineer at QloudX & a top-ranked APN Ambassador. 👨🏻‍💻

With over a decade of industry experience as everything from a full-stack engineer to a cloud architect, Harish has built many world-class solutions for clients around the world! 👷🏻‍♂️

With over 20 certifications in cloud (AWS, Azure, GCP), containers (Kubernetes, Docker) & DevOps (Terraform, Ansible, Jenkins), Harish is an expert in a multitude of technologies. 📚

These days, his focus is on the fascinating world of DevOps & how it can transform the way we do things! 🚀

Leave a Reply

Your email address will not be published.