Creating a CI User for EKS

The problem -- we have a ci process that will need access to one namespace in an EKS cluster and must be able to deploy the new container as well as roll it back.

AuthN and AuthZ

We need to establish two things in relation to our interactions with EKS:

At the 10,000 foot view, AuthN gets solved using an IAM user and then we map that IAM user to internal EKS RBAC groups and bindings.

Setting up AuthN -- our IAM

For our specific use-case, we are using the IAM user to not just get access but also to generate our kubeconfig in our CI environment (thus the extra policy we tack on). In the general case, if you have a copy of kubeconfig that you can make available to your CI system in some other manner -- feel free and you won't need this policy.

Create a new IAM policy named: EKS_CICD_User_Policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "eks:DescribeCluster"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

This will allow you to request a kubeconfig later on with the aws-cli. That done, we want to create a user with credentials that we can store and use in the CI environment -- known as programatic access. This is well documented and standardized. Create this user as you will and attach the EKS_CICD_User_Policy. The name we're going to use for this user is my-test-iam-user (which is a horrible name but it'll make the later examples clearer... hopefully).

You should now have an access_key_id and a secret_access_key for this user.

User or ServiceAccount in K8s

We need to create an entity to take action on resources within the cluster. First off, do we create a k8s serviceaccount or a user? Here is the answer from the CNCF blog:

One topic that many Kubernetes users struggle with is the concept of subjects, but more specifically the difference between regular users and ServiceAccounts. In theory it looks simple:

    Users: These are global, and meant for humans or processes living outside the cluster.
    ServiceAccounts: These are namespaced and meant for intra-cluster processes running inside pods.

User it is.

Now time for AuthZ

We're going to create an RBAC rolebinding for a user constrained to one k8s namespace that will limit what our user can do in the k8s cluster. We'll create the namespace and the initial deployment of our application out of band.

Here's the namespace create statement so we have it in front of us to use in the rolebinding.

kubectl create namespace my-test-ns

Next we'll create the Role and bindings that correlate our user to that role:

---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: my-test-role
  namespace: my-test-ns
rules:
- apiGroups: ["extensions", "apps"]
  resources: ["replicasets"]
  verbs: ["list", "get", "create", "update", "delete", "watch", "patch"]
- apiGroups: ["extensions", "apps"]
  resources: ["deployments"]
  verbs: ["list", "get", "create", "update", "delete", "watch", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-test-user-role-binding
  namespace: my-test-ns
roleRef:
  kind: Role
  name: my-test-role
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: User
  name: my-test-user

Man, that's a lot of stuff, right? Callouts:

Now we have an IAM user and policy on one hand and then a Role and Rolebinding on the other. We have as our last step: tying the two together.

Mapping IAM user to RBAC

There are two broad ways to do this mapping: via kubectl or via eksctl.

Via kubectl, you'll be making changes to a configmap named aws_auth that is created by AWS for this very purpose. You can either edit the configmap or pull it down, make changes, and replace.

In place:

kubectl edit configmap aws-auth -n kube-system

or dumping locally and then reapplying after you make the change:

kubectl get configmap aws-auth -n kube-system -o yaml > aws-auth.yaml

The configmap will look like this. You want to edit and add an entry to the mapUsers section as you see below:

apiVersion: v1
data:
  mapRoles: |
  # <snip -- this is internal stuff.  don't touch it>
  mapUsers: |
    - userarn: arn:aws:iam::123412341234:user/my-test-iam-user
      username: my-test-user
      groups:
      - my-test-role

Just to be explicit as to where the values come from:

The third way to map your IAM user to internal EKS user is via the eksctl tool as detailed here and is basically:

eksctl create iamidentitymapping --cluster  my-cluster-1 --arn arn:aws:iam::123412341234:user/my-test-iam-user --group my-test-role --username my-test-user

That's it. You're ready to roll.