Understanding Kubernetes RBAC

Understanding RBAC
Understanding RBAC

In Kubernetes, before you can do anything, you need to log in. Think of it like needing a key to start a car. The person in charge of the Kubernetes “parking lot” (the cluster administrator) can go anywhere and do anything, much like having a master key. The simplest way to get things moving is to give everyone their own master key. However, just like in real life, if everyone has a master key, things can go wrong quickly.

Someone might accidentally throw away something important (like a Secret object in Kubernetes), which can cause a lot of trouble, breaking apps and upsetting users. This is why giving everyone full control is not smart, especially when you’re running important apps

Just like any other serious system, only a few people should have the keys to the kingdom, while most should only be able to look but not touch (or make small changes, depending on their job). For instance, the people making the apps don’t need to worry about the big-picture stuff like managing the servers. They just need to focus on their piece of the puzzle.

To manage who can do what, Kubernetes uses something called RBAC (Role-Based Access Control). It’s like setting rules about who can enter certain rooms or use certain tools. Turning on RBAC and setting it up right is a must-do for any group that cares about keeping things secure. For tests and real-life use, you’ll need to know what kinds of rules you can make (the RBAC resources) and how to put them into action in various situations.

RBAC High-Level Overview

ckas 0201

Creating a Subject

Within RBAC, the entities that can be granted permissions include user accounts, service accounts, and groups, known as subjects. User accounts and groups, designed for operations external to the Kubernetes cluster, are not kept in Kubernetes’ etcd database. On the other hand, service accounts are created as Kubernetes objects for use by internal cluster processes. This section will guide you on setting up these subjects.

User accounts and groups

Kubernetes does not represent a user as with an API resource. The user is meant to be managed by the administrator of a Kubernetes cluster, which then distributes the credentials of the account to the real person or to be used by an external process.

Calls to the API server with a user need to be authenticated. Kubernetes offers a variety of authentication methods for those API requests. Table 2-1 shows different ways of authenticating RBAC subjects.

Authentication strategyDescription
X.509 client certificateUses an OpenSSL client certificate to authenticate
Basic authenticationUses username and password to authenticate
Bearer tokensUses OpenID (a flavor of OAuth2) or webhooks as a way to authenticate

For authentication via OpenSSL client certificates, such tasks need to be executed under the cluster-admin Role. During your exam, it’s assumed that user creation has been pre-configured for you, so learning the below steps by heart is unnecessary:

  1. Access the Kubernetes control plane node and initiate a new directory for storing keys. Enter this directory with the commands: mkdir cert && cd cert
  2. Generate a private key using the openssl tool. Name the file thoughtfully, like <username>.key: openssl genrsa -out johndoe.key 2048 This creates a 2048-bit RSA private key, confirmed by a message and the file’s presence in the directory.
  3. Craft a Certificate Signing Request (CSR) with the .csr file extension, incorporating the previously created private key. Use -subj to specify the username (CN) and group (O), like so: openssl req -new -key johndoe.key -out johndoe.csr -subj "/CN=johndoe/O=cka-study-guide" Omit the /O part if not associating the user with a group.
  4. Sign the CSR using the Kubernetes cluster’s Certificate Authority (CA), typically found at /etc/kubernetes/pki or ~/.minikube for minikube users. This validates the CSR for 364 days: openssl x509 -req -in johndoe.csr -CA ~/.minikube/ca.crt -CAkey ~/.minikube/ca.key -CAcreateserial -out johndoe.crt -days 364 This process confirms the signature and retrieves the CA private key.
  5. In Kubernetes, create a user profile for johndoe in kubeconfig, linking to the CRT and key. Also, set up a specific context for this user : kubectl config set-credentials johndoe --client-certificate=johndoe.crt --client-key=johndoe.key kubectl config set-context johndoe-context --cluster=minikube --user=johndoe This modifies the context to johndoe-context.
  6. Switch to the newly created user by activating the johndoe-context. Verify the current context with:kubectl config use-context johndoe-context kubectl config current-context johndoe-context This completes the switch to the “johndoe-context”.

ServiceAccount

In Kubernetes, users are individuals who interact with the cluster via tools like kubectl or the dashboard. Service applications, such as Helm within Pods, communicate with the Kubernetes API through RESTful HTTP calls. Kubernetes authenticates these applications using a ServiceAccount, which also links to specific permissions via RBAC rules. Every cluster includes a default ServiceAccount in the default namespace, used by Pods that don’t specify a different ServiceAccount.

To create a custom ServiceAccount imperatively, run the create serviceaccount command:

$ kubectl create serviceaccount build-bot
serviceaccount/build-bot created

The declarative way to create a ServiceAccount looks very straightforward. You simply provide the appropriate kind and a name, as shown in Example 2-1.

Example 2-1. A YAML manifest defining a ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-bot

Listing ServiceAccounts

Listing the ServiceAccounts can be achieved with the get serviceaccounts command. As you can see in the following output, the default namespace lists the default ServiceAccount and the custom ServiceAccount we just created:

$ kubectl get serviceaccounts
NAME        SECRETS   AGE
build-bot   1         78s
default     1         93d

Rendering ServiceAccount Details

Upon object creation, the API server creates a Secret holding the API token and assigns it to the ServiceAccount. The Secret and token names use the ServiceAccount name as a prefix. You can discover the details of a ServiceAccount using the describe serviceaccount command, as shown here:

$ kubectl describe serviceaccount build-bot
Name:                build-bot
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   build-bot-token-rvjnz
Tokens:              build-bot-token-rvjnz
Events:              <none>

Consequently, you should be able to find a Secret object for the default and the build-bot ServiceAccount:

$ kubectl get secrets
NAME                    TYPE                                  DATA   AGE
build-bot-token-rvjnz   kubernetes.io/service-account-token   3      20m
default-token-qgh5n     kubernetes.io/service-account-token   3      93d

Assigning a ServiceAccount to a Pod

For a ServiceAccount to take effect, it needs to be assigned to a Pod running the application intended to make API calls. Upon Pod creation, you can use the command-line option --serviceaccount in conjunction with the run command:

$ kubectl run build-observer --image=alpine --restart=Never \
  --serviceaccount=build-bot
pod/build-observer created

Alternatively, you can directly assign the ServiceAccount in the YAML manifest of a Pod, Deployment, Job, or CronJob using the field serviceAccountNameExample 2-2 shows the definition of a ServiceAccount to a Pod.

Example 2-2. A YAML manifest assigning a ServiceAccount to a Pod
apiVersion: v1
kind: Pod
metadata:
  name: build-observer
spec:
  serviceAccountName: build-bot
...

Understanding RBAC API Primitives

In Kubernetes, RBAC is managed through two main API objects:

  • Role: Defines permissions for specific actions on resources, like allowing pod listings or log watches. Anything not explicitly allowed is denied.
  • RoleBinding: Links Roles to subjects (users, groups, or service accounts), activating the permissions specified in the Role.

These elements work together to set and enforce access controls within Kubernetes, ensuring subjects have only the permissions they need.

ckas 0202

Default User-Facing Roles

Kubernetes defines a set of default Roles. You can assign them to a subject via a RoleBinding or define your own, custom Roles depending on your needs. Table 2-2 describes the default user-facing Roles.

Default ClusterRoleDescription
cluster-adminAllows read and write access to resources across all namespaces.
adminAllows read and write access to resources in namespace including Roles and RoleBindings.
editAllows read and write access to resources in namespace except Roles and RoleBindings. Provides access to Secrets.
viewAllows read-only access to resources in namespace except Roles, RoleBindings, and Secrets.

To define new Roles and RoleBindings, you will have to use a context that allows for creating or modifying them, that is, cluster-admin or admin.

Creating Roles

Roles can be created imperatively with the create role command. The most important options for the command are --verb for defining the verbs aka operations, and --resource for declaring a list of API resources. The following command creates a new Role for the resources Pod, Deployment, and Service with the verbs listget, and watch:

$ kubectl create role read-only --verb=list,get,watch \
  --resource=pods,deployments,services
role.rbac.authorization.k8s.io/read-only created


For a single kubectl create role command, you can specify multiple verbs and resources either as a comma-separated list within the same option or as separate instances of the option. For instance, --verb=list,get,watch is equivalent to specifying --verb=list --verb=get --verb=watch. The wildcard symbol * can be used to indicate all verbs or resources.

The --resource-name option allows you to specify one or several specific object names to which the role’s permissions should apply, like a Pod named nginx. Listing resource names is optional; omitting them means the permissions apply to all instances of the resource type.

In a declarative setup, defining resources and verbs might get a bit verbose. For resources belonging to a specific API group, such as Deployments in apps/v1, you must declare the group under apiGroups. Resources without an API group, like Pods and Services, use an empty string for their API version. Note that when creating a Role through the command line, the API group is automatically identified.

Example 2-3. A YAML manifest defining a Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: read-only
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - services
  verbs:
  - list
  - get
  - watch
- apiGroups:
  - apps
  resources:
  - deployments
  verbs:
  - list
  - get
  - watch

Listing Roles

Once the Role has been created, its object can be listed. The list of Roles renders only the name and the creation timestamp. Each of the listed roles does not give away any of its details:

$ kubectl get roles
NAME        CREATED AT
read-only   2021-06-23T19:46:48Z

Rendering Role Details

You can inspect the details of a Role using the describe command. The output renders a table that maps a resource to its permitted verbs. This cluster has no resources created, so the list of resource names in the following console output is empty:

$ kubectl describe role read-only
Name:         read-only
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources         Non-Resource URLs  Resource Names  Verbs
  ---------         -----------------  --------------  -----
  pods              []                 []              [list get watch]
  services          []                 []              [list get watch]
  deployments.apps  []                 []              [list get watch]

Creating RoleBindings

The imperative command creating a RoleBinding object is create rolebinding. To bind a Role to the RoleBinding, use the --role command-line option. The subject type can be assigned by declaring the options --user--group, or --serviceaccount. The following command creates the RoleBinding with the name read-only-binding to the user called johndoe:

$ kubectl create rolebinding read-only-binding --role=read-only --user=johndoe
rolebinding.rbac.authorization.k8s.io/read-only-binding created

Example 2-4 shows a YAML manifest representing the RoleBinding. You can see from the structure that a role can be mapped to one or many subjects. The data type is an array indicated by the dash character under the attribute subjects. At this time, only the user johndoe has been assigned.

Example 2-4. A YAML manifest defining a RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-only-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: read-only
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: johndoe

Listing RoleBindings

The most important information the list of RoleBindings gives away is the associated Role. The following command shows that the RoleBinding read-only-binding has been mapped to the Role read-only:

$ kubectl get rolebindings
NAME                ROLE             AGE
read-only-binding   Role/read-only   24h

The output does not provide an indication of the subjects. You will need to render the details of the object for more information, as described in the next section.

Rendering RoleBinding Details

RoleBindings can be inspected using the describe command. The output renders a table of subjects and the assigned role. The following example renders the descriptive representation of the RoleBinding named read-only-binding:

$ kubectl describe rolebinding read-only-binding
Name:         read-only-binding
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  read-only
Subjects:
  Kind  Name     Namespace
  ----  ----     ---------
  User  johndoe

Seeing the RBAC Rules in Effect

Let’s see how Kubernetes enforces the RBAC rules for the scenario we set up so far. First, we’ll create a new Deployment with the cluster-admin credentials. In Minikube, this user is assigned to the context minikube:

$ kubectl config current-context
minikube
$ kubectl create deployment myapp --image=nginx --port=80 --replicas=2
deployment.apps/myapp created

Now, we’ll switch the context for the user johndoe:

$ kubectl config use-context johndoe-context
Switched to context "johndoe-context".

Remember that the user johndoe is permitted to list deployments. We’ll verify that by using the get deployments command:

$ kubectl get deployments
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
myapp   2/2     2            2           8s

The RBAC rules only allow listing Deployments, Pods, and Services. The following command tries to list the ReplicaSets, which results in an error:

$ kubectl get replicasets
Error from server (Forbidden): replicasets.apps is forbidden: User "johndoe" \
cannot list resource "replicasets" in API group "apps" in the namespace "default"

A similar behavior can be observed when trying to use other verbs than listget, or watch. The following command tries to delete a Deployment:

$ kubectl delete deployment myapp
Error from server (Forbidden): deployments.apps "myapp" is forbidden: User \
"johndoe" cannot delete resource "deployments" in API group "apps" in the \
namespace "default"

At any given time, you can check a user’s permissions with the auth can-i command. The command gives you the option to list all permissions or check a specific permission:

$ kubectl auth can-i --list --as johndoe
Resources          Non-Resource URLs   Resource Names   Verbs
...
pods               []                  []               [list get watch]
services           []                  []               [list get watch]
deployments.apps   []                  []               [list get watch]
$ kubectl auth can-i list pods --as johndoe
yes

Namespace-wide and Cluster-wide RBAC

Roles and RoleBindings are tied to specific namespaces, requiring you to define the namespace during their setup. However, for scenarios where you need these roles and bindings to cover multiple namespaces or the entire cluster, Kubernetes introduces ClusterRole and ClusterRoleBinding. These cluster-level resources mirror the configuration structure of their namespace-specific versions, with the distinction lying in their kind attribute:

  • To create a Role with cluster-wide scope, either employ the clusterrole imperative command or use ClusterRole as the kind in a YAML document.
  • For establishing a RoleBinding that spans the whole cluster, utilize the clusterrolebinding command or specify ClusterRoleBinding as the kind in a YAML document.

Aggregating RBAC Rules

Existing ClusterRoles can be aggregated to avoid having to redefine a new, composed set of rules that likely leads to duplication of instructions. For example, say you wanted to combine a user-facing role with a custom Role. An aggregated ClusterRule can merge rules via label selection without having to copy-paste the existing rules into one.

Say we defined two ClusterRoles shown in Examples 2-5 and 2-6. The ClusterRole list-pods allows for listing Pods and the ClusterRole delete-services allows for deleting Services.

Example 2-5. A YAML manifest defining a ClusterRole for listing Pods
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: list-pods
  namespace: rbac-example
  labels:
    rbac-pod-list: "true"
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - list
Example 2-6. A YAML manifest defining a ClusterRole for deleting Services
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: delete-services
  namespace: rbac-example
  labels:
    rbac-service-delete: "true"
rules:
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - delete

To aggregate those rules, ClusterRoles can specify an aggregationRule. This attribute describes the label selection rules. Example 2-7 shows an aggregated ClusterRole defined by an array of matchLabels criteria. The ClusterRole does not add its own rules as indicated by rules: []; however, there’s no limiting factor that would disallow it.

Example 2-7. A YAML manifest defining a ClusterRole with aggregated rules
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pods-services-aggregation-rules
  namespace: rbac-example
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac-pod-list: "true"
  - matchLabels:
      rbac-service-delete: "true"
rules: []

We can verify the proper aggregation behavior of the ClusterRole by describing the object. You can see in the following output that both ClusterRoles, list-pods and delete-services, have been taken into account:

$ kubectl describe clusterroles pods-services-aggregation-rules -n rbac-example
Name:         pods-services-aggregation-rules
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  services   []                 []              [delete]
  pods       []                 []              [list]

For more information on ClusterRole label selection rules, see the official documentation. The page also explains how to aggregate the default user-facing ClusterRoles.

Author

  • Mohamed BEN HASSINE

    Mohamed BEN HASSINE is a Hands-On Cloud Solution Architect based out of France. he has been working on Java, Web , API and Cloud technologies for over 12 years and still going strong for learning new things. Actually , he plays the role of Cloud / Application Architect in Paris ,while he is designing cloud native solutions and APIs ( REST , gRPC). using cutting edge technologies ( GCP / Kubernetes / APIGEE / Java / Python )

    View all posts
0 Shares:
You May Also Like