Handling aws-auth in EKS Clusters
If you use EKS then you have found yourself in a situation where a user can't access the cluster despite having all the IAM permissions and gets an Unauthorized message. AWS EKS uses IAM credentials for authentication and Kubernetes RBAC for authorization. As per EKS docs:
EKS uses IAM permissions for authentication of valid entities such IAM users or roles. All the permissions for interacting with the EKS cluster is managed through Kubernetes RBAC
or simply put, EKS doesn't work the same way as other services such as S3 where if you have AmazonS3FullAccess, you can access any S3 bucket and create or delete files/folders. In EKS, IAM permissions are only used to check if the user has valid IAM credentials and permissions to run any command using kubectl such as kubectl get pods is managed by Kubernetes API that uses RBAC to control the access.
By default, the IAM Role or IAM User that was used to create the cluster, is added to the system:masters group and gets cluster-wide admin permission with cluster-admin ClusterRole.
As per Kubernetes documentation system:masters group is one of the default ClusterRoleBindings available in the Kubernetes cluster, it's attached to the cluster-admin ClusterRole that gives the user admin permissions in the cluster.
| Default ClusterRole | Default ClusterRoleBinding | Description |
|---|---|---|
| cluster-admin | system:masters group | Allows super-user access to perform any action on any resource. |
Note: This mapping of creator IAM User or Role to system:masters group is not visible in any configuration such as aws-auth configmap.
EKS allows giving access to other users by adding them in a configmap aws-auth in kube-system namespace. By default, this configmap is empty. However, If you are using eksctl to create the cluster, this config map will have the role created by eksctl for the node group and this role is attached to the system:bootstrappers and system:nodes groups.
aws-auth configmap is based on aws-iam-authenticator and has several configuration options:
- mapRoles
- mapUsers
- mapAccounts
Using mapRoles to Map an IAM Role to the Cluster
mapRoles allows mapping an IAM role in the cluster to allow any entity or user assuming that role to access the cluster. After mapping an IAM role with mapRoles, any user or entity assuming this role is allowed to access the cluster, However, the level of access is defined by the groups attribute.
mapRoles has three attributes:
- rolearn - IAM Role ARN to map to EKS cluster.
- username - Username for the IAM Role to map in Kubernetes, this could be a static value like
eks-developerorci-accountor a templated variable like{{AccountID}}/{{SessionName}}/{{EC2PrivateDNSName}}or both. This value would be printed in theaws-authenticatorCloudwatch logs if logging is enabled. - groups - List of Kubernetes groups that are defined in
ClusterRoleBinding/RoleBinding. Example
subjects:
- kind: Group
name: "my-group"
apiGroup: ""
eks-admin and eks-dev and assume the eks-admin role to create a cluster with one node group:apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: iam-auth-cluster
region: us-east-1
version: "1.21"
availabilityZones:
- us-east-1a
- us-east-1b
- us-east-1c
cloudWatch:
clusterLogging:
enableTypes: ["authenticator"]
managedNodeGroups:
- name: managed-ng-1
instanceType: t2.micro
minSize: 1
maxSize: 4
desiredCapacity: 4
aws-auth configmap. Check the contents of aws-auth configMap :kubectl get configmap aws-auth -n kube-system -oyaml
apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/eksctl-iam-auth-cluster-nodegroup-NodeInstanceRole-1RNKIEA50ZD0B
username: system:node:{{EC2PrivateDNSName}}
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
IAM Role that created the cluster would have access to the cluster, any other IAM Role has to be added separately added in aws-auth. Let's try to assume the eks-developer IAM role and try to access the cluster with that role.kubectl get pods
error: You must be logged in to the server (Unauthorized)
eks-developer IAM role would not be allowed access. To allow eks-developer IAM role access to the cluster, add the mapping in the aws-auth configMap to map this role to eks-developer Kubernetes user. We can either directly edit the configMap or use eksctl to add this mapping:eksctl create iamidentitymapping \
--cluster iam-auth-cluster \
--region us-east-1 \
--arn "arn:aws:iam::<AWS_ACCOUNT_ID>:role/eks-developer" \
--username "eks-developer"
mapRoles section in aws-auth configMap :apiVersion: v1
data:
mapRoles: |
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/eksctl-iam-auth-cluster-nodegroup-NodeInstanceRole-1RNKIEA50ZD0B
username: system:node:{{EC2PrivateDNSName}}
- rolearn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/eks-developer
username: eks-developer
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
eks-developer IAM role:kubectl get pods
Error from server (Forbidden): pods is forbidden: User "eks-developer" cannot list resource "pods" in API group "" at the cluster scope
1. RBAC permissions with Kubernetes User
2. RBAC permissions with Kubernetes Groups
RBAC permissions with Kubernetes User
We can assign RBAC permissions to an IAM role by binding mapped Kubernetes User in aws-auth i.e eks-developer to a ClusterRole/Role.
Create a ClusterRole
eks-developer-cluster-rolewith permissions toget,listorwatchthepodsresources :apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: eks-developer-cluster-role rules: - apiGroups: [""] # Pod is part of Core API Group and "" indicates the core API group resources: ["pods"] # pods resource verbs: ["get", "list", "watch"] # Allow user to get, list of watch the pods.We have mapped IAM role
arn:aws:iam::<AWS_ACCOUNT_ID>:role/eks-developerto Kubernetes usereks-developerinaws-auth, now create a ClusterRoleBinding to binddeveloper-cluster-roleto the Kubernetes usereks-developer.apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: eks-developer-user-cluster-role-binding subjects: - kind: User name: eks-developer # Kubernetes User mapped to the IAM role in aws-auth configmap. apiGroup: "" roleRef: kind: ClusterRole name: eks-developer-cluster-role apiGroup: ""Access the cluster again by assuming the IAM role
eks-developerkubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system aws-node-f584p 1/1 Running 0 79m kube-system coredns-66cb55d4f4-8hjj2 1/1 Running 0 91m kube-system coredns-66cb55d4f4-vtf6j 1/1 Running 0 91m kube-system kube-proxy-psjk5 1/1 Running 0 79m
and this time it works!!!
RBAC permissions with Kubernetes Groups
While assigning permissions directly to the Kubernetes User works just fine for most of the use-cases however this approach is not so great if you want to audit who is assuming the IAM role and accessing the cluster and would like additional information captured in the audit logs.
One such use-case is AWS SSO, where many users are assigned to a permission set and whenever these users log in using their credentials, they assume the same IAM role.
We can assign IAM permissions to an IAM role by creating a Kubernetes Group and add it to the mapRoles.groups field of IAM Role mapping in aws-auth.
Let's take the earlier example of the eks-developer IAM role and create a ClusterRoleBinding with Kubernetes Group developer bound to eks-developer-cluster-role and add Kubernetes Group developer in the mapping of this IAM role in aws-auth.
First delete the earlier ClusterRoleBinding
eks-developer-user-cluster-role-binding:kubectl delete clusterrolebindings eks-developer-user-cluster-role-binding clusterrolebinding.rbac.authorization.k8s.io "eks-developer-user-cluster-role-binding" deletedAs soon as we delete the
ClusterRolebinding,eks-developerIAM role won't be able to list the pods, let's check the access by assuming theeks-developerIAM role :kubectl get pods -A Error from server (Forbidden): pods is forbidden: User "eks-developer" cannot list resource "pods" in API group "" in the namespace "default"Delete IAM Mapping from
aws-auth:eksctl delete iamidentitymapping \ --cluster iam-auth-cluster \ --region us-east-1 \ --arn "arn:aws:iam::<AWS_ACCOUNT_ID>:role/eks-developer"Create a ClusterRoleBinding to bind Kubernetes Group
developerto cluster roleeks-developer-cluster-role:apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: eks-developer-group-cluster-role-binding subjects: - kind: Group name: developer apiGroup: "" roleRef: kind: ClusterRole name: eks-developer-cluster-role apiGroup: ""Add Kubernetes Group
developerto IAM role mapping ofeks-developerinaws-authand include the session name in username using templated variable{{SessionName}}:eksctl create iamidentitymapping \ --cluster iam-auth-cluster \ --region us-east-1 \ --arn "arn:aws:iam::<AWS_ACCOUNT_ID>:role/eks-developer" \ --username "eks-developer:{{SessionName}}" \ --group "developer"this would create an entry under
mapRolessection inaws-authconfigmap as:mapRoles: | - groups: - developer rolearn: arn:aws:iam::017558828988:role/eks-developer username: eks-developer:{{SessionName}}
Check the Cloudwatch authenticator logs for the authenticated user assuming eks-developer IAM role and we can see that this time session name is appended to the username in logs:
time="2021-09-13T17:57:46Z" level=info msg="access granted" arn="arn:aws:iam::0175XXXXXXXX:role/eks-developer" client="127.0.0.1:48520" groups="[developer]" method=POST path=/authenticate sts=sts.us-east-1.amazonaws.com uid="heptio-authenticator-aws:0175XXXXXXXX:AROAQIFUWO66PDOXKSLMQ" username="eks-developer:eks-developer-session"
If the session name consists of @, it would be replaced with -. Let's assume the IAM role eks-developer with session name containing @ :
aws sts assume-role \
--role-arn "<IAM_ROLE_ARN>" \
--role-session-name "my-develper-session@123456789" \
--duration-seconds 3600Now Cloudwatch logs would have session name printed as eks-developer:my-developer-session-123456789.
time="2021-09-14T17:50:25Z" level=info msg="access granted" arn="arn:aws:iam::017558828988:role/eks-developer" client="127.0.0.1:57794" groups="[developer]" method=POST path=/authenticate sts=sts.us-east-1.amazonaws.com uid="heptio-authenticator-aws:017558828988:AROAQIFUWO66PDOXKSLMQ" username="eks-developer:my-develper-session-123456789"
There is one problem here, if your EKS cluster is being accessed from multiple AWS accounts, it would not be possible to track the AWS account of the user who accessed the EKS cluster just by session name.
{{AccountID}} comes to the rescue, we can use this templated variable to get the AWS account ID of the user who is assuming the role, so we can set the username to :
aws:{{AccountID}}:eks-developer:{{SessionName}}
iamidentitymapping can't be overridden with eksctl, so you have to delete it and create it again.eksctl delete iamidentitymapping \
--cluster "iam-auth-cluster" \
--region "us-east-1" \
--arn "arn:aws:iam::<AWS_ACCOUNT_ID>:role/eks-developer"
eksctl create iamidentitymapping \
--cluster "iam-auth-cluster" \
--region "us-east-1" \
--arn "arn:aws:iam::<AWS_ACCOUNT_ID>:role/eks-developer" \
--username "aws:{{AccountID}}:eks-developer:{{SessionName}}" \
--group "developer"
AWS Account ID along with Session Name in cloudwatch logs :time="2021-09-14T18:26:33Z" level=info msg="access granted" arn="arn:aws:iam::017558828988:role/eks-developer" client="127.0.0.1:39752" groups="[developer]" method=POST path=/authenticate sts=sts.us-east-1.amazonaws.com uid="heptio-authenticator-aws:0175XXXXXXXX:AROAQIFUWO66PDOXKSLMQ" username="aws:0175XXXXXXXX:eks-developer:my-develper-session-123456789"
Note: If you want session name in raw format, you can use templated variable {{SessionNameRaw}} instead. However as of EKS 1.21, these two variables {{AccessKeyID}} and {{SessionNameRaw}} don't work.
Using mapUser to Map an IAM User to the Cluster
mapUsers allows mapping an IAM User to the cluster and add the user to one or more Kubernetes Groups. It has 3 attributes :
- userarn - ARN of IAM User to map to EKS cluster. This could be an IAM user from the same AWS account or another account.
- username - Static username to map this IAM User to, in Kubernetes.
- groups - List of Kubernetes groups that are defined in
ClusterRoleBinding/RoleBinding.
Note: Templated variables are not supported in username field with mapUser.
To add an IAM user with ARN arn:aws:iam::<AWS_ACCOUNT_ID>:user/dev-user in aws-auth configmap, we can run the below command:
eksctl create iamidentitymapping \
--cluster iam-auth-cluster \
--region us-east-1 \
--arn "arn:aws:iam::<AWS_ACCOUNT_ID>:user/dev-user" \
--username "dev-user"this command would add these lines in aws-auth configMap:
mapUsers: |
- userarn: arn:aws:iam::<AWS_ACCOUNT_ID>:user/dev-user
username: dev-user
dev-user would be able to authenticate to the cluster, however wouldn't be able to list or get any resources.For users mapped using mapUsers, RBAC permission can be given in two ways :
1. RBAC permissions with Kubernetes User
2. RBAC permissions with Kubernetes Groups
RBAC permissions with Kubernetes User
We can assign RBAC permissions to an IAM user by binding mapped Kubernetes User in aws-auth i.e dev-user to a ClusterRole/Role.
Create a ClusterRoleBinding to bind Kubernetes user
dev-userto thedeveloper-cluster-role:apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: dev-user-cluster-role-binding subjects: - kind: User name: dev-user # Kubernetes User mapped to the IAM user in aws-auth configmap. apiGroup: "" roleRef: kind: ClusterRole name: eks-developer-cluster-role apiGroup: ""Map IAM user
arn:aws:iam::<AWS_ACCOUNT_ID>:user/dev-userto Kubernetes userdev-userinaws-authconfigMap:
eksctl create iamidentitymapping \
--cluster iam-auth-cluster \
--region us-east-1 \
--arn "arn:aws:iam::<AWS_ACCOUNT_ID>:user/dev-user" \
--username "dev-user"
ClusterRoleBinding is created and the IAM user is mapped in aws-auth, IAM user dev-user would be able to get, list, or watch pods in any namespace.RBAC permissions with Kubernetes Groups
If we need to give the same set of permissions to multiple users, then instead of creating multiple ClusterRoleBindings, we can use Kubernetes Groups and attach that group to the users for whom those permissions are required.
Create a ClusterRoleBinding to bind Kubernetes Group
developerto cluster roleeks-developer-cluster-role:apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: dev-user-group-cluster-role-binding subjects: - kind: Group name: dev apiGroup: "" roleRef: kind: ClusterRole name: eks-developer-cluster-role apiGroup: ""Map IAM user
arn:aws:iam::<AWS_ACCOUNT_ID>:user/dev-userto Kubernetes userdev-userwithdevgroup inaws-authconfigMap:eksctl create iamidentitymapping \ --cluster iam-auth-cluster \ --region us-east-1 \ --arn "arn:aws:iam::<AWS_ACCOUNT_ID>:role/dev-user" \ --username "dev-user" \ --group "dev"this would create an entry under
mapUserssection inaws-authconfigmap as:mapUsers: | - groups: - dev userarn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/dev-user username: dev-user
Using mapAccounts to Map IAM ARN in an AWS Account to the Cluster
mapAccounts allows mapping all the IAM Users or IAM Roles of an AWS account to the cluster. It accepts the list of AWS Account IDs:
mapAccounts: |
- "<AWS_ACCOUNT_ID_1>"
- "<AWS_ACCOUNT_ID_2>"