Implement AWS IAM Least Privilege at Scale
Maintaining least privilege access every day!
7 min read
Creating an AWS IAM user is a common activity to access the AWS resources. Most often, the IAM user created is given the largest group of permissions. More often than not, the AdministratorAccess policy is attached if the user doesn't know which granular permissions are required. Once a policy is attached, the user rarely thinks of going back to fine-tune the policies attached.
Consider a scenario where the credentials (AccessKeys or console password) are compromised and the attacker gets access to the AWS account. The consequence could be catastrophic.
In this blog post, we will learn how to create an automation that will monitor every day if there are any such users with extra permissions. If it finds such a user, it will remove the extra permissions (without modifying the already attached policies).
As per the AWS Documentation, StackSet is defined as follows:
AWS CloudFormation StackSets extends the functionality of stacks by enabling you to create, update, or delete stacks across multiple accounts and AWS Regions with a single operation. Using an administrator account, you define and manage an AWS CloudFormation template, and use the template as the basis for provisioning stacks into selected target accounts across specified AWS Regions.
AWS Stack and StackSet will help us deploy our solution to all the accounts that are a part of the Organization. (Assuming AWS Organization is enabled).
If AWS organization is not enabled then the manual overhead of deploying the Stack to every account increases.
As per the AWS Documentation, EventBridge is defined as:
Amazon EventBridge is a serverless event bus that makes it easier to build event-driven applications at scale using events generated from your applications, integrated Software-as-a-Service (SaaS) applications, and AWS services.
We will use AWS Eventbridge to create an EventBridge rule that runs on a schedule. This schedule is like a Cron Job present in Linux/Unix Operating Systems.
We will define a schedule that will trigger our Lambda once every day at a given time.
As per the AWS Documentation, a Lambda is defined as follows:
AWS Lambda is a serverless, event-driven compute service that lets you run code for virtually any type of application or backend service without provisioning or managing servers.
We will define an AWS Lambda function, that will implement applying Least Privilege to every IAM user in the account.
AWS IAM Access Advisor
The use-case of IAM Access Advisor as per the AWS Documentation is as follows:
AWS Identity and Access Management (IAM) access advisor uses data analysis to help you set permission guardrails confidently by providing service last accessed information for your accounts, organizational units (OUs), and your organization managed by AWS Organizations.
We will leverage the information given by Access Advisor to check which services were accessed in the last 90 days. The services that were accessed will only be allowed to be accessed in the future.
All the API calls have been referred from boto3 Python library.
Our first task is to get the IAM users list for the account where the Lambda will be executed. To get the list of IAM users, we will use the
Once we get the list of users, we will iterate through every user to get Service Last Accessed Details. This list consists of all the services to which the user has access. Basically, all the services for which there's a policy with Allow Action in the Policy Document. This list will contain the data such as the service name, and when was it last accessed.
We can see this data from the console as well. To check this data from the console, go to IAM and click on Users. Select a user and click on the Access Advisor tab. You will see data something as follows:
To get similar data, we will use Access Advisor APIs provided by Boto3. Two important methods need to be looked at:
generate_service_last_accessed_details- generates the service last accessed data for an IAM resource (user, role, group, or policy). You need to call this API first to start a job that generates the service's last accessed data for the IAM resource. This API returns a JobId that you will use for the other APIs, such as get_service_last_accessed_details, to determine the status of the job completion.
get_service_last_accessed_details- use this to retrieve the service's last accessed data for an IAM resource based on the JobID you pass in.
Logic to create Explicit Deny Policy
Once you get that data from the above APIs, you can check the number of days the AWS service was last accessed. If that service was last accessed more than 90 days ago, we will create an AWS-managed policy with an explicit Deny Action and add those services in the policy.
Once the managed policy is created, we will attach the Policy to the specific IAM user. This will be done for every IAM user.
To make sure a unique policy gets created for every IAM user, we append the IAM username to the policy name.
- AWS limits attaching managed policies to the IAM user. Only 10 managed policies can be attached to the user. So we will put a check for that as well. We will calculate the number of policies that are already attached to the user. But, in doing so, we will have to factor in the fact that our Explicit Deny Policy might have been attached as well.
If this policy is attached, we will ignore that policy. The following code snippet is used for the same:
# Get number of managed Policies attached to the user def get_attached_policy_count(username): iam_client = get_iam_client() managed_user_policies = iam_client.list_attached_user_policies(UserName=username) deny_policy_name = 'explicitDenyExtraPrivilegesLambdaPolicy-' + username attached_policies = managed_user_policies['AttachedPolicies'] policy_count = len(attached_policies) for policy in attached_policies: # This is to make sure we don't count our very own attached policy. Because that can be deleted and attached again after updating if policy['PolicyName'] == deny_policy_name: policy_count = policy_count - 1 return policy_count
Since our Lambda will check every day, it will produce the report from the Access Advisor every day, it makes no sense to re-attach the Explicit Deny Policy if the services have not changed. For this reason, we will compare the previously attached policy and the policy that is created newly. If there's a difference, only then we will re-attach the policy.
Whitelisted users: Sometimes you would want to whitelist users for some Business Requirements. I have provided a whitelisting feature by leveraging tags. You can add a tag to the user and provide that tag as an environment variable.
You can find the source code for the above implemented Lambda in my repository here
We will leverage AWS Eventbridge to schedule and trigger the above-defined Lambda. Since we're automating our solution and deploying it at scale, we will use CloudFormation Stacks and StackSets.
This template will be similar to the one we defined in the Previous Blog.
Upload the code on AWS S3 in
.zip format and copy S3 Bucket Name and S3 Key. Enter this data as parameter values in the CloudFormation template.
You can find the CloudFormation template here
Note: Make sure to deploy this as a StackSet for all the Child accounts, and then Deploy a stack for the Root account
Whenever there's an IAM user that violates Least Privilege Access, our automation will attach an Explicit Deny Policy to that user and fix it!
Next, it will send us an alert that looks as follows:
We have successfully deployed our solution to all the cloud accounts using Cloudformation Stacks and StackSets. To verify that we have deployed it correctly, you can visit any account and check for the Lambda that is created, the Cloudwatch event rule, and the IAM Role created for the Lambda.
We used Cloudformation StackSets to implement AWS security measures that are scalable. In the future, if there is any new account added to the Organization, the same stack will be created for that account automatically.
Lastly, you can find the entire code on my repository here