Kubernetes: part 5 — RBAC authorization with a Role and RoleBinding example

By | 03/26/2020

The next task is to add a new user who will have access to check pods state and watch logs – any other operations must be prohibited.

AWS EKS uses AWS IAM for authentification in a Kubernetes cluster (check the Kubernetes: part 4 – AWS EKS authentification, aws-iam-authenticator and AWS IAM post for details), bot the authorization, e.g. to determine if a user has permissions for specific operations – Kubernetes uses its own Role-Based Authorization Control mechanism.

Previous parts of this series:

Let’s first take a quick overview of RBAC in general, and then create a new user with necessary permissions.

Kubernetes RBAC overview

Documentation – Authorization Overview and Using RBAC Authorization.

RBAC model in Kubernetes consists of the three main components:

  • Roles: defines permissions boundaries
  • Subjects: Users (human or an application), or user groups
  • RoleBingdings: specifies which Subjects have which Roles

RBAC Role

A Role example named example-role which allows access to the mynamespace with get, watch, and list operations:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: mynamespace
  name: example-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

To obtain Kubernetes apiGroups one can use kubectl api-resources:

[simterm]

$ kubectl api-resources -o wide
NAME                              SHORTNAMES   APIGROUP                       NAMESPACED   KIND                             VERBS
...
pods                              po                                          true         Pod                              [create delete deletecollection get list patch update watch]
...

[/simterm]

In the rules above we:

  1. apiGroups: [""] – set core API group
  2. resources: ["pods"] – which resources are allowed for access
  3. ["get", "watch", "list"] – which actions are allowed over the resources above

RBAC RoleBingding

To “map” those permissions to users we are using Kubernetes RoleBingding, which sets example-role in the mynamespace for the example-user user:

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-rolebinding
  namespace: mynamespace
subjects:
- kind: User
  name: example-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: example-role
  apiGroup: rbac.authorization.k8s.io

Here we set:

  • subjects:
    • kind: User – an object type which will have access, in our case this is a regular user
    • name: example-user – a user’s name to set the permissions
  • roleRef:
    • kind: Role – what exactly will be attached to the user, in this case, it is the Role object type
    • name: example-role – and the role name as it was set in the name: example-role in the example above

Role vs ClusterRole

Alongside with the Role and ClusterRole which are set of rules to describe  permissions – Kubernetes also has RoleBinding and ClusterRoleBinding objects.

The difference is that Role is used inside of a namespace, while ClusterRole is cluster-wide permission without a namespace boundaries, for example:

  • allow access to a cluster nodes
  • resources in all namespaces
  • allow access to endpoints like /healthz

A ClusterRole looks similar to a Role with the only difference that we have to set its kind as ClusterRole:

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-clusterrole
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

And a ClusterRoleBinding example:

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-clusterrolebinding
subjects:
- kind: User
  name: example-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: example-clusterrole
  apiGroup: rbac.authorization.k8s.io

Keep in mind that once you’ll create a Binding you’ll not be able to edit its roleRef value – instead, you’ll have to delete a Binding and recreate and again

EKS Authentification and Authorization

In a short, the Authentification and Authorization process flow is the next:

  1. Authentification
    1. a client makes a request to a Kubernetes cluster passing the client’s token with a user’s ID
    2. Kubernetes using aws-iam-authenticator asks AWS IAM to check if such a user really exist and is he is really who he claims to be
  2. Authorization
    1. if the user passed the Authentification step – Kubernetes sens him over the RBAC mechanism with all user’s data and action requests
    2. Kubernetes looks for a RoleBinding which maps a user with a Role
    3. by a Rolу name, Kubernetes will check the user’s permissions
    4. and finally, Kubernreteds will allow or decline the user’s request

 

Next, we will:

  1. create an IAM user
  2. configure AWS CLI
  3. add an RBAC Role with the read-only permissions to pods
  4. add an RBAC RoleBinding to connect our user and the Role

IAM user

Let’s begin by creating an IAM user.

Add a new one with the only Programmatic Access:

Save its keys:

IAM policy

If a user will need to have access to the AWS API, for example, to get a list of clusters – need to add an IAM policy for him.

Go to the Policies – Add Policy:

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

Here we are allowing only two API calls – eks:DescribeCluster and eks:ListClusters, in all regions for all EKS clusters.

Save it and attach to the user:

AWS CLI config

To configure kubectl, first we need to configure our AWS CLI, we can do it in a dedicated CLI profile, check the AWS: CLI named profiles post:

[simterm]

$ aws configure --profile eks-ro-user
AWS Access Key ID [None]: AKI***FYC
AWS Secret Access Key [None]: SzH***VGi
Default region name [None]: eu-north-1
Default output format [None]: json

[/simterm]

Check the user ID you are using now:

[simterm]

$ aws --profile eks-ro-user sts get-caller-identity 
{
    "UserId": "AID***XGK",
    "Account": "534***385",
    "Arn": "arn:aws:iam::534***385:user/eks-ro-user"
}

[/simterm]

And check access to the EKS clusters (better to say access to the AWS API):

[simterm]

$ aws --profile eks-ro-user eks list-clusters --output text
CLUSTERS        eks-alb-testing-3
CLUSTERS        eks-alb-testing-2

[/simterm]

Kubernetes RBAC – an example

Role

Now, let’s create a real user and a Role.

We’ll begin from creating a Role to allow access to pods, their logs and to create a port-forwarding, and only for  the get, list, create operations:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: eks-ro-role
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log", "pods/portforward"]
  verbs: ["get", "list", "create"]

Apply it:

[simterm]

$ kubectl apply -f rbac-role.yml 
role.rbac.authorization.k8s.io/eks-ro-role created

[/simterm]

And check:

[simterm]

$ kubectl get roles -o yaml
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
  kind: Role
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"rbac.authorization.k8s.io/v1","kind":"Role","metadata":{"annotations":{},"name":"eks-ro-role","namespace":"default"},"rules":[{"apiGroups":[""],"resources":["pods","pods/log"],"verbs":["get","list"]}]}
    creationTimestamp: "2020-03-24T10:34:27Z"
    name: eks-ro-role
    namespace: default
    resourceVersion: "681997"
    selfLink: /apis/rbac.authorization.k8s.io/v1/namespaces/default/roles/eks-ro-role
    uid: 09a78b6f-6dbb-11ea-827f-0a9eb3e1782e
  rules:
  - apiGroups:
    - ""
    resources:
    - pods
    - pods/log
    verbs:
    - get
    - list
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

[/simterm]

RoleBinding

The next thing is to add a  RoleBinding to map our username and the Role created above:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: eks-ro-role-binding
subjects:
- kind: User
  name: eks-ro-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: eks-ro-role
  apiGroup: rbac.authorization.k8s.io

Apply:

[simterm]

$ kubectl apply -f rbac-rolebinding.yml 
rolebinding.rbac.authorization.k8s.io/eks-ro-role-binding created

[/simterm]

aws-auth ConfigMap

As we are using AWS – need to update the aws-auth ConfigMap (see the AWS EKS aws-auth ConfigMap):

[simterm]

$ kubectl edit configmap aws-auth -n kube-system

[/simterm]

Add the mapUsers and specify the user’s ARN, name and his group(s):

apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::534***385:role/eksctl-eks-alb-testing-2-nodegrou-NodeInstanceRole-M6BS1WV48RLR
      username: system:node:{{EC2PrivateDNSName}}
  mapUsers: |
    - userarn: arn:aws:iam::534***385:user/eks-ro-user
      username: eks-ro-user
      groups: eks-ro-role

Save, and check it:

[simterm]

$ kubectl get pods --as eks-ro-user
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7db9fccd9b-7d4rq   1/1     Running   0          58m

[/simterm]

Try to get Worker Nodes – we did not allowed access to this resource:

[simterm]

$ kubectl get nodes --as eks-ro-user
Error from server (Forbidden): nodes is forbidden: User "eks-ro-user" cannot list resource "nodes" in API group "" at the cluster scope

[/simterm]

Great!

Now, let’s see a pod’s logs – run a port-forwarding to a testing pod with NGINX:

[simterm]

$ kubectl --as eks-ro-user port-forward nginx-7db9fccd9b-7d4rq 8000:80
Forwarding from 127.0.0.1:8000 -> 80
Forwarding from [::1]:8000 -> 80

[/simterm]

Make a request to the pod:

[simterm]

$ curl -I localhost:8000
HTTP/1.1 200 OK

[/simterm]

And check its logs:

[simterm]

$ kubectl --as eks-ro-user logs -f nginx-7db9fccd9b-7d4rq
127.0.0.1 - - [25/Mar/2020:07:29:06 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.69.1" "-"

[/simterm]

Done.

Useful links