Authors – All

If you have a few years of experience in the DevOps ecosystem, and you're interested in sharing that experience with the community, have a look at our Contribution Guidelines.

1. Overview

In the context of computer security, role-based access control (RBAC) is a mode of access control that restricts access based on the roles and responsibilities of an entity. For example, an administrator role in a system in general has more access to the system than a guest role.

In this tutorial, we’ll learn about the Role-Based access control (RBAC) in Kubernetes.

2. Role-Based Access Control in Kubernetes

The RBAC mechanism offers the possibility to enforce the principle of least privilege. The principle states that any users on a system should only be given just enough permissions to perform their tasks. This greatly reduces the potential security risks, as well as the surface area of attack in case a credential is compromised.

In Kubernetes, the RBAC mechanism is one crucial security aspect. Specifically, the proper management and definition of the roles and their assignments are important to ensure the cluster is secured.

To that end, Kubernetes offers the Role and ClusterRole API objects that we can use to define permissions. Specifically, we can specify what actions (known as verbs in Kubernetes documentation) are allowed on which resources.

Then, we can use the RoleBinding and ClusterRoleBinding API objects to assign a role to a subject. The subject in Kubernetes refers to an entity that interacts with the cluster, such as the user or the application processes.

3. Namespace Scope vs. Cluster Scope

Before we dive into the details of those APIs, it’s important to first understand the concept of namespace-scoped and cluster-scoped resources. In Kubernetes, a namespace-scoped resource like the Service resource exists only in a specific namespace within the cluster. For example, if we create a Service object in the dev namespace with the name web, that object exists solely in the dev namespace. It’s not possible to access the resource from another namespace, such as test.

On the other hand, cluster-scoped resources are resources that exist across all the different namespaces within the cluster. In Kubernetes, resources like NodePersistentVolume, and Namespace are cluster scope. In other words, the same Node object can be referenced from all the different namespaces in the same cluster.

Importantly, the Role object can define the permission for both the namespace-scoped and cluster-scoped resources. On the other hand, the ClusterRole object can only define the permission for cluster-scoped resources. Therefore, we must be able to identify the scope of resources before we decide which API to use for managing the access.

4. Role

The Role object is a Kubernetes resource that defines a set of rules. In each of the rules, we can define the target resources and the permissible verbs, or actions. Additionally, the rules we define are always additive and there’s no support for the deny rule.

4.1. Creating a Role Object

For example, we can create a role, dev-intern, that has read-only access to pods and services resources:

$ cat dev-intern-role.yaml << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev-intern
  namespace: dev
rules:
- apiGroups: [""]
  resources: ["pods","services"]
  verbs: ["get", "list", "watch"]
EOF
$ kubectl apply -f dev-intern-role.yaml
role.rbac.authorization.k8s.io/dev-intern created

Let’s break down the example above.

First, the manifest defines a new Role object with the name dev-intern. Additionally, we scope the Role to the dev namespace. Then, we define a single rule in the rules field. The rule grants the getlist, and watch access to the pods and services resources for the dev-intern role. Notably, the names of the resources we specify here should follow what we get from the kubectl api-resources command:

$ kubectl api-resources
NAME                              SHORTNAMES   APIVERSION                        NAMESPACED   KIND
bindings                                       v1                                true         Binding
componentstatuses                 cs           v1                                false        ComponentStatus
...

Finally, the apiGroups field is left as an empty string to indicate that the resources belong to the core API group. If we’re specifying resources from other API groups, we must set the apiGroups value to the appropriate value.

The example above creates the dev-intern role declaratively using a YAML manifest and then applies it using kubectl apply. Alternatively, we can create the role imperatively using kubectl create role:

$ kubectl create role dev-intern --verb=get,list,watch --resource=pods --namespace=dev
role.rbac.authorization.k8s.io/dev-intern created

However, it’s generally preferable to create Kubernetes resources using the declarative approach because we can check the YAML manifest into a version control system to ensure the changes are traceable and reproducible.

4.2. All Resources and All Verbs

Both the verbs and resources fields of the Role object accept the asterisk as a valid value. When an asterisk is specified, the object considers that the role has access to all the resources and can invoke all the verbs.

For example, let’s create a role, dev, that has list access to all the resources in the production namespace:

$ cat dev-role.yaml << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev
  namespace: production
rules:
- apiGroups: [""]
  resources: ["*"]
  verbs: ["list"]
EOF
$ kubectl apply -f dev-role.yaml
role.rbac.authorization.k8s.io/dev created

We can also create a role, tech-lead, that can invoke all the verbs on all the resources in the production environment by specifying an asterisk on both of the fields:

$ cat dev-role.yaml << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: tech-lead
  namespace: production
rules:
- apiGroups: [""]
  resources: ["*"]
  verbs: ["*"]
EOF
$ kubectl apply -f tech-lead-role.yaml
role.rbac.authorization.k8s.io/tech-lead created

Although the asterisk is convenient, it should be used carefully and sparingly to comply with the principle of least privilege.

5. ClusterRole

The ClusterRole object is similar to the Role object, which defines a set of rules for granting permissions to resources. One major distinction between the ClusterRole and the Role object is that the ClusterRole can grant permission to the cluster-scoped resources like Node, on top of namespace-scoped resources.

When we grant namespace-scoped resources to a ClusterRole object, it effectively means that the role has access to the resource across the entire cluster, regardless of the namespace.

For example, let’s create a role cluster-admin-intern that has the list and get permissions on the Pod and Node resources:

$ cat cluster-admin-intern.yaml << EOF
cat cluster-admin-intern.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-admin-intern
rules:
- apiGroups: [""]
  resources: ["nodes","pods"]
  verbs: ["get","list"]
EOF
$ kubectl apply -f cluster-admin-intern.yaml
clusterrole.rbac.authorization.k8s.io/cluster-admin-intern created

Notably, we don’t specify the namespace field for the ClusterRole object as the nature of the role is cluster-scoped. With the manifest applied, we’ve created a role, cluster-admin-intern, that can list and get the nodes on the cluster. Furthermore, the role can also access the pods resources on all the namespaces in the cluster.

6. Binding

To assign a role to a subject in the cluster, we’ll have to create a binding object. By binding a role to a subject, we’re effectively granting the subject whatever rules have been defined on the role to the subject. We can use two different binding API objects: RoleBinding and ClusterRoleBinding.

6.1. RoleBinding

The RoleBinding object binds a Role or ClusterRole object to one or more subjects. The type of the subjects can be UserGroup, or ServiceAccount.

When a RoleBinding object binds to a ClusterRole, it grants the subject all the namespace-scoped resources permissions in the namespace the RoleBinding is created in. This is useful for defining a commonly shared set of permissions, and then selectively granting them to different subjects on different namespaces.

Let’s look at an example of creating a RoleBinding that binds a subject to a Role object:

$ cat john-dev-intern-binding.yaml << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-intern-bindings
  namespace: dev
subjects:
- kind: User
  name: john
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: dev-intern
  apiGroup: rbac.authorization.k8s.io
EOF

The manifest above defines a RoleBindingdev-intern-bindings, in the dev namespace. In the binding, we’re referencing the dev-intern Role object, which exists in the same namespace. Notably, the roleRef field doesn’t have a namespace field.

The implication is that a RoleBinding object can only refer to a Role object in the same namespace or to a ClusterRole. Lastly, the subjects field defines a single User subject with the name john.

To create a RoleBinding that binds a ClusterRole to subjects, we’ll need to specify the value of the field roleRef.kind as ClusterRole:

roleRef: 
  kind: ClusterRole
  name: cluster-admin-intern
  apiGroup: rbac.authorization.k8s.io

6.2. ClusterRoleBinding

The ClusterRoleBinding object binds a ClusterRole or a Role to a subject. With ClusterRoleBinding, the subject gets the permissions on the cluster level. In other words, when we bind a subject to a Role using ClusterRoleBinding, the namespace value of the Role will not have an effect.

For example, we can grant the permissions defined in the cluster-admin-intern object to the cluster-admin user jane:

$ cat jane-cluster-admin-intern-binding.yaml << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin-intern-cluster-binding
subjects:
- kind: User
  name: jane
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin-intern
  apiGroup: rbac.authorization.k8s.io
EOF
$ kubectl apply -f jane-cluster-admin-intern-binding.yaml
clusterrolebinding.rbac.authorization.k8s.io/cluster-admin-intern-cluster-binding created

With the binding, the subject jane will now be able to invoke the list verb on the nodes resources. Additionally, the subject can also list and get the pods resources in all the namespaces.

For example, when we bind the dev-intern object using ClusterRoleBinding, the subject will get the listwatch, and get access to the pods and services resources on the entire cluster.

7. Conclusion

In this tutorial, we’ve learned about role-based access control and how it empowers the principle of least privilege. Then, we learned the RBAC mechanism in Kubernetes is supported by the use of the RoleClusterRoleRoleBinding, and ClusterRoleBinding API objects.

We’ve first looked at how to define rules on the namespace level using the Role. Then, we’ve demonstrated that the ClusterRole object can be used to define permissions for cluster-scoped resources. Finally, we’ve learned that the RoleBinding and ClusterRoleBinding objects can tie a subject to a role, thereby granting permissions to the subject.

Authors – All

If you have a few years of experience in the DevOps ecosystem, and you're interested in sharing that experience with the community, have a look at our Contribution Guidelines.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.