AWS
Cloud Native
Containers

IAM Roles for Service Accounts

In AWS, there are two ways to apply the Principle of Least Privilege, which are through IAM to provide granular permissions to access the AWS resources
July 2, 2024

One of the main security principles in technology is called The Principle of Least Privilege, which states that each user or application should only have strict access to the resources that it really needs. For example, if a user is only intended to Read from a file store and only within a specific folder, that user should have write access to the same folder, or read access to all the folders, or even more administrative permissions that could be a focus for permission escalation attacks. 

In AWS, there are two ways to apply the Principle of Least Privilege, which are through IAM to provide granular permissions to access the AWS resources at the API level (interact with AWS itself), and the other way is through Network VPC but leveraging strict security groups, NACLs, VPC connections, VPNs, etc. 

In this blog, we want to focus on the IAM part specially for the workloads deployed in Amazon EKS. The idea is to restrict permissions on those applications that are running in Kubernetes that require access to AWS resources, like an S3 bucket, an SNS topic, an SQS queue, etc.

Although historically there has been multiple solutions to approach this securely, like Kube2IAM and Kiam, AWS themselves have developed native solutions using their own services, which provide high availability and increased security. 

One of those methods is called IAM Roles for Service Accounts, or IRSA. This method aligns the OpenID capabilities of Kubernetes with the ones of IAM, creating an IAM Identity Provider and connecting it to the EKS cluster to provide better connection and allow specific pods with specific Kubernetes Service Accounts to assume IAM Roles so that each pod could have its own set of IAM permissions. Let's dig deeper into it.

Requirements

First, to achieve this, you need an EKS cluster that can be created in multiple ways. We always recommend EKSCTL as the official EKS CLI tool to not only create cluster but also manage them throughout the live of the cluster.

Also, you'll need an IAM Identity Provider configured to work with your EKS Cluster. You can read more about the Identity Providers in IAM here: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html.

But don't worry! we have you covered. Later in the blog we'll show you an easy way to create one with EKSCTL and automatically bind it with your cluster.

But first, let's focus on how these components interact with IAM Roles, policies, and Kubernetes through Service Accounts and the actual pod that ends up running there.

How does it work?

Let's explain how all of this works behind the scenes to understand what it can do:

Figure 1  Diagram explaining how IRSA works
Figure 1  Diagram explaining how IRSA works

1. Youʼll create the IAM Identity Provider with the EKS information to connect both OpenID worlds. Explained later in the blog).

2. After you have that, you can start create IAM Roles that trust the IAM Identity Provider and, once assumed, those are going to be authenticated with Kubernetes as well.

3. Create an IAM Policy for the IAM role giving it access to, for example S3.

4. Assign the IAM Role using its ARN to a Service Account in Kubernetes.

5. Create the Pod that requires access to AWS, for example to S3.

6. Your pod, using the AWS SDK, AWS CLI, or whatever other method to access AWS, will be able to smoothly assume the IAM role related to the Service Account and authenticate against S3 successfully. 

Steps 2 and 4 can be easily setup and its called the IAM Service Account, because is the mix of an IAM Role with the Kubernetes Service Account. Weʼll review how to do this easily later in the blog.

How to enable OIDC with IAM and EKS?

The easiest way to create the OIDC connection between IAM and EKS is using the eksctl tool. There are two methods for enabling this.

If you don’t have the EKS cluster yet, you can create it with the iam.withOIDC flag set to “true” in your cluster.yaml file, like this:

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: test-cluster
  region: us-east-2
iam:
  withOIDC: true

If your cluster already exists, you can either update it with the previous cluster.yaml file, or you can simply use the following command:

eksctl utils associate-iam-oidc-provider --cluster=test-cluster

Both of these methods will create the OIDC provider in AWS 

How to create IAM Service Accounts?

As expected with EKSCTL there are two ways for doing this:

Using the cluster.yaml file and creating the IAM Service Accounts in one place:

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:  
  name: test-cluster  
  region: us-east-1
iam:  
  withOIDC: true  
  serviceAccounts:  
  - metadata:      
    name: read-from-s3      
    labels: {app: "s3"}    
  attachPolicyARNs:    
  - "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"

Using the EKSCTL command directly:

eksctl create iamserviceaccount --cluster=test-cluster 
--name=read-from-s3 
--attach-policy-arn=arn:aws:iam::aws:policy/AmazonS3ReadOnlyAcces

Note: The IAM Policy should have already been created. So you can use AWS Managed Policies, like in the example, which are pre-configured policies provided by AWS and available in any AWS account, or you can create your own policies.

What do we have after the creation of the IAM Service Account?

After the creation of the IAM Service Account, youʼll have the IAM Role already configured that looks like this:

Figure 2 - IAM Role created with EKSCTL and its attached policy
Figure 2 - IAM Role created with EKSCTL and its attached policy

An important detail here will be under the Trust relationships tab, which has all the details of who can assume this role and basically itʼll be the Kubernetes Service Account “read-from-s3ˮ. This Trust relationship policy is really important because itʼs the actually connection between Kubernetes and IAM.

Figure 3 - Trust relationship of the brand new IAM Role connecting it with Kubernetes
Figure 3 - Trust relationship of the brand new IAM Role connecting it with Kubernetes

Also, youʼll have the Service Account with a special annotation called eksamazonaws.com/rol-arn. This one has the ARN of the IAM Role it will assume:

apiVersion: v1
kind: ServiceAccount
metadata:  
  annotations:    
    eks.amazonaws.com/role-arn: 
      arn:aws:iam::471112632504:role/eksctl-cluster-test-addon-iamserviceaccount-d-Role1-sc6PEboZnlXH  
  labels:    
    app: s3    
    app.kubernetes.io/managed-by: eksctl  
  name: read-from-s3  
  namespace: default

How to assign IAM Service Accounts to the pod?

At this level, all the remaining work is at the Kubernetes level. Just adding the serviceAccountName spec in the Pod/Deployment will make sure the service account is assigned.

apiVersion: v1
kind: Pod
metadata:  
  name: test-pod
spec:  
  serviceAccountName: read-from-s3
...

After the pod is created, youʼll see that the IRSA integration kicks in when you get the details of the brand new pod:

spec:    
  containers:    
  - env:      
    - name: AWS_STS_REGIONAL_ENDPOINTS        
      value: regional      
    - name: AWS_DEFAULT_REGION        
      value: us-east-1      
    - name: AWS_REGION        
      value: us-east-1      
    - name: AWS_ROLE_ARN        
      value: arn:aws:iam::471112632504:role/eksctl-cluster-test-addon-iamserviceaccount-d-Role1-sc6PEboZnlXH      
    - name: AWS_WEB_IDENTITY_TOKEN_FILE        
      value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token

To the pod itself were added new environment variables automatically with information about the IAM Role. For example, you can find the AWS_ROLE_ARN which is the actual ARN of the Role that we created in previous steps. Another important one is AWS_WEB_IDENTITY_TOKEN_FILE  which has the location of the JWT token injected to the pod which will be used for the authentication. Some other environment variables like the AWS Region are added.

Conclusion

As mentioned before, this is not the only way to give IAM access to pods in Kubernetes, but definitely one of the most important and smooth ones. Itʼs always great to have multiple ways to create these resources as it gives you flexibility for integrating in CICD environments for example. 

And now that you are all set! your pod has native IAM access without granting it to other pods that wonʼt needed it. Enjoy your least privilege posture!