The AWS IAM (Identity & Access Management) service allows AWS services to interact with each other based on the policies given to its attached role(s).

AWS IAM

We can also use the IAM role with a Kubernetes (k8s) native Service Account (SA) which will allow the Pods running in the Kubernetes cluster or AWS Elastic Kubernetes Service (EKS) to talk to AWS service(s).
In this blog, we will see how we can allow a Pod running in AWS EKS to list the objects in the AWS S3 bucket by using the IAM role with Kubernetes native Service Account.
Go to AWS S3 service and create a bucket and then add few objects to it.

Image description

Image description

Image description

Create an Identity Provider:-
a) Copy the OIDC (OpenID Connect) provider URL from the existing AWS EKS cluster, for instance, in my case this is the URL https://oidc.eks.us-east-1.amazonaws.com/id/8B7D06AD395F38CE1EE8EF0AF2922255

Image description

b) Go to IAM -> Identity Providers and click on the ‘Add providers’ button and then select ‘OpenID Connect’.

Image description

Paste the copied OIDC URL (from EKS) under the ‘Provider URL’ option and click the ‘Get thumbprint’ button and put sts.amazonaws.com under the ‘Audience’ option as shown below. Give tags as necessary and click the ‘Add provider’ button at the end to add the identity provider.

Image description

Create an IAM Policy to read objects in the S3 bucket:-
Go to IAM Policy and create a custom policy with the following JSON to read the objects from the S3 bucket:-

{
 “Version”: “2012–10–17”,
 “Statement”: [
    {
      “Effect”: “Allow”,
      “Action”: [
               “s3:ListBucket”,
               “s3:GetObject”
       ],
       “Resource”: “arn:aws:s3:::k8s-nest”
    }
 ]
}

Create an IAM Role with Trust Relationship with Identity Provider:-
Go to the IAM and create a new role with ‘Web identity’ trust type and select the right Identity Provider (from the dropdown) that we created earlier and Audience as sts.amazonaws.com and click on the ‘Next: Permissions’ button as shown below:-

Image description

Attach the previously created custom policy to read the S3 bucket objects:-

Image description

Give a name to the role and create it.

Then open the same role again and edit its trust relationship to make sure that only a specific Kubernetes Service Account can assume this Role.

Image description

Image description

Image description

Following policy, will only allow the Kubernetes service account named ‘my-sa’ (in the namespace ‘dev’) to assume the role ‘custom-read-s3-bucket-objects’ using the AWS STS AssumeRoleWithWebIdentity.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::195725532069:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/8B7D06AD395F38CE1EE8EF0AF2922255"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-east-1.amazonaws.com/id/8B7D06AD395F38CE1EE8EF0AF2922255:sub": "system:serviceaccount:dev:my-sa"
        }
      }
    }
  ]
}

All configurations at the AWS side are done. Now head towards, your CLI (Command Line Interface) to create a Service Account and Pod in the same AWS EKS Cluster.

Create a namespace ‘dev’ in Kubernetes:-

kubectl create ns dev

Create a Service Account named ‘my-sa’:-
Create a service account annotating the IAM Role ARN created before. as shown below.

Image description

apiVersion: v1
kind: ServiceAccount
metadata:
 name: my-sa
 namespace: dev
 annotations:
   eks.amazonaws.com/role-arn:  arn:aws:iam::195725532069:role/custom-read-s3-bucket-objects
kubectl create -f my-sa.yaml

Image description

Schedule a new Pod using this Service Account:-
Instead of the default service account, use the one which was created above i.e. my-sa, and attach it to the Pod as shown below.

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: my-pod
  name: my-pod
  namespace: dev
spec:
  serviceAccountName: my-sa
  initContainers:
  - image: amazon/aws-cli
    name: my-aws-cli
    command: ['aws', 's3', 'ls', 's3://k8s-nest/']
  containers:
  - image: nginx
    name: my-pod
    ports:
    - containerPort: 80
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
kubectl create -f my-pod.yaml

Image description

Now, check the logs of the init container, you will notice that the Pod was successfully able to assume the role and communicate with AWS S3 to list all the objects in it securely with the principle of least privilege reducing the blast radius.

kubectl logs my-pod my-aws-cli -n dev

Hope you liked this article :)

Summary

In this blog, we learned that how we can use the AWS IAM Role and create Trust Relationship with the Kubernetes Service Account and use that service account with the Pods running inside the AWS EKS to make calls to AWS S3 with the principle of least privilege.
Behind the scene, the OIDC federation allows the Pod to assume the IAM role via the Kubernetes Service Account with AWS STS to receive a JSON Web Token (JWT). In Kubernetes, we then use the projected service account tokens which are valid OIDC tokens, giving each Pod encrypted signed tokens that can be verified by STS against the OIDC provider for establishing its identity by exchanging the OIDC tokens for IAM Role credentials using AssumeRoleWithWebIdentity of AWS STS.
As usual, you will find the complete source code here at my GitHub repository. Please feel free to fork it and add more IaC (Infrastructure as Code) to it:-
https://github.com/vinod827/cloudkit-lab/tree/main/iac/k8s/iam-roles-with-k8s-service-account