Assuming IAM Roles Using Service Accounts on Amazon EKS
Colin J. Ihrig
EKS has a nice feature called IAM Roles for Service Accounts (IRSA) that allows Kubernetes service accounts to assume AWS IAM roles using annotations. This is nice because it allows you to avoid long-term credentials like access keys in your applications. Unfortunately, like many things on AWS, IRSA can be a bit tedious to configure properly.
In this post we will create an S3 bucket and configure IRSA such that a Kubernetes pod has full access to the bucket. Note that portions of this post are adapted from the AWS documentation.
Create a Kubernetes service account
First, create a service account to test with using the following commands:
$ cat >my-service-account.yaml <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
namespace: default
EOF
$ kubectl apply -f my-service-account.yaml
Create the S3 bucket
Create an S3 bucket and add a file to it using the AWS CLI:
aws s3api create-bucket --bucket my-test-bucket --region us-east-1
echo Hello | aws s3 cp - s3://my-test-bucket/my-test-file.txt
Try to access the bucket from Kubernetes
Deploy a Kubernetes pod containing the AWS CLI. We will use this pod to test our access to the S3 bucket:
$ cat >my-pod.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: awscli
namespace: default
spec:
serviceAccountName: my-service-account
containers:
- name: awscli
image: amazon/aws-cli:latest
command:
- "/bin/bash"
- "-c"
- "sleep infinity"
EOF
$ kubectl apply -f my-pod.yaml
Once the pod is running, attempt to list the contents of the bucket. You should get a permission error:
$ kubectl exec -it awscli -n default -- aws s3 ls my-test-bucket
Create an OIDC provider
To use IRSA, the EKS cluster needs an OIDC provider associated with its OIDC issuer URL. You can follow the official guide here, but it essentially boils down to the following.
Check if you already have an OIDC provider associated with the cluster:
$ oidc_id=$(aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text | cut -d '/' -f 5)
$ aws iam list-open-id-connect-providers | grep $oidc_id | cut -d "/" -f4
If there is output, then you already have an OIDC provider for the cluster. If there is NOT output, then create one using the following command. Note that this command uses eksctl
instead of the AWS CLI. I'm not sure how to do it using the AWS CLI.
$ eksctl utils associate-iam-oidc-provider --cluster my-cluster --approve
Configure IRSA
Now it's time to begin configuring IRSA. First, create an IAM policy that grants access to your S3 bucket and its contents:
$ cat >my-test-policy.json <<EOF
{
"Version":"2012-10-17",
"Statement": [
{
"Action": "s3:*",
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::my-test-bucket",
"arn:aws:s3:::my-test-bucket/*"
]
}
]
}
EOF
$ aws iam create-policy --policy-name my-test-policy --policy-document file://my-test-policy.json
Next, create the role that the service account will assume. This role needs to define a trust relationship with the EKS cluster's OIDC provider that we configured earlier.
$ account_id=$(aws sts get-caller-identity --query "Account" --output text)
$ oidc_provider=$(aws eks describe-cluster --name my-cluster --region us-east-1 --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")
$ cat >trust-relationship.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::$account_id:oidc-provider/$oidc_provider"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"$oidc_provider:aud": "sts.amazonaws.com",
"$oidc_provider:sub": "system:serviceaccount:default:my-service-account"
}
}
}
]
}
EOF
$ aws iam create-role --role-name my-test-role --assume-role-policy-document file://trust-relationship.json --description "my-irsa-role"
Attach the policy we created to the role:
$ aws iam attach-role-policy --role-name my-test-role --policy-arn=arn:aws:iam::$account_id:policy/my-test-policy
Add the eks.amazonaws.com/role-arn
annotation to the service account. This is where the IRSA magic happens:
$ kubectl annotate serviceaccount -n default my-service-account eks.amazonaws.com/role-arn=arn:aws:iam::$account_id:role/my-test-role
Validate the configuration
If everything was configured properly, you should be able to successfully run this command from the beginning of this blog post:
$ kubectl exec -it awscli -n default -- aws s3 ls my-test-bucket
If you still get a permission error, here are a few things you can try to debug the issue:
kubectl describe pod awscli -n default
should show that the pod is running under the service account that we created.kubectl describe serviceaccount my-service-account -n default
should show theeks.amazonaws.com/role-arn
annotation, and it should refer to the correct IAM role ARN.aws iam list-attached-role-policies --role-name my-test-role --query AttachedPolicies[].PolicyArn --output text
should show the IAM policy that we created.aws iam get-policy --policy-arn arn:aws:iam::$account_id:policy/my-test-policy
should find the policy that we created.aws iam get-policy-version --policy-arn arn:aws:iam::$account_id:policy/my-test-policy --version-id v1
should show the JSON body of the policy that we created (assuming you didn't create any other policy versions). Make sure that the policy looks correct, and grants permissions on botharn:aws:s3:::my-test-bucket
andarn:aws:s3:::my-test-bucket/*
.aws iam get-role --role-name my-test-role --query Role.AssumeRolePolicyDocument
should match the trust policy that we created. Verify that the various ARNs and other identifiers look correct.