Kubernetes Series: Securing Access to the k8s API through RBAC

Sylia CHIBOUB
10 min readJun 7, 2023

Introduction

This article covers the k8s Role-based access control (RBAC). It is a continuation of two previous articles of the k8s series:

Section 1 explains k8s RBAC resources, Section 2 and 3 describe how to refer to RBAC resources and subjects. Section 4 shows some useful RBAC command line utilities Furthermore, Section 5 and 6 illustrate RBAC usage for normal users and ServiceAccounts. Section 7 explicates Aggregated ClusterRoles. Finally, this article ends with a conclusion

1. Role-based access control (RBAC)

Role-based access control (RBAC) is a method of regulating access to a system based on the roles of individual users within an organization [1].

RBAC authorization uses the rbac.authorization.k8s.io API group to drive authorization decisions. It allows k8s admins to dynamically configure permissions through the k8s API [1].

As discussed in the previous articles mentioned above, the k8s API Server is responsible for checking authorizations. You can see which authorization mode is enabled on the API Server through the following command.

cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep authorization

#OUTPUT:
- --authorization-mode=Node,RBAC

The RBAC API declares four kinds of k8s objects: Role, ClusterRole, RoleBinding and ClusterRoleBinding [1].

1.1 Role and ClusterRole

An RBAC Role or ClusterRole contains rules that represent a set of permissions. Permissions are purely additive (there are no “deny” rules).[1]

A Role always sets permissions within a particular namespace. When you create a Role, you have to specify the namespace it belongs in. [1]

ClusterRole, by contrast, is a non-namespaced (cluster-scoped) resource. It can be used:

  • define permissions on namespaced resources and be granted access within individual namespace(s) ( Same as a Role ) [1]
  • define permissions on namespaced resources and be granted access across all namespaces (cluster-scoped) [1]
  • define permissions on cluster-scoped resources (i.e nodes resources) [1]

If you want to define a set of rules within a namespace, use a Role. If you want to define a set of cluster-wide rules, use a ClusterRole. [1]

The resources have different names (Role and ClusterRole) because a Kubernetes object always has to be either namespaced or not namespaced (cluster-wide), it can’t be both.[1]

1.2. RoleBinding and ClusterRoleBinding

A RoleBinding grants permissions defined in a Role or ClusterRole to a user or set of users within a specific namespace. It holds: [1]

  • A list of subjects : users, groups, or service accounts
  • A reference to the Role or ClusterRole being granted.

A RoleBinding may reference any Role in the same namespace. Alternatively, a RoleBinding can reference a ClusterRole and bind that ClusterRole to the namespace of the RoleBinding. [1]

A ClusterRoleBinding grants permissions defined in a ClusterRole to a user or set of users. It grants cluster-wide access. [1]

RBAC k8s objects

Important

After you create a binding, you cannot change the Role or ClusterRole that it refers to. If you try to change a binding’s roleRef, you get a validation error. If you do want to change the roleRef for a binding, you need to remove the binding object and create a replacement. [1]

The kubectl auth reconcile command-line utility creates or updates a manifest file containing RBAC objects [1]

2. Referring to resources in RBAC

As explained in the k8s API article, most k8s resources are represented and accessed using a string representation of their object name. For example to access logs of a pod within a particular namespace , you need to follow the underlying structure:

GET /api/v1/namespaces/{namespace}/pods/{name}/log

In this case, pods is the namespaced resource for Pod resources, and log is a subresource of pods. To represent this in an RBAC role, use a slash (/) to delimit the resource and subresource. To allow a subject to read pods and also access the log subresource for each of those Pods, you write: [1]

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: read-pods
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]

You can also refer to resources by name for certain requests through the resourceNames list. However, not all verbs can be specified when resourceNamesis used. [1]

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: configmap-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["configmap-test"]
verbs: ["get", "list"]
RBAC Restriction when using resourceNames attribute [1]

Furthermore, rather than referring to individual resources, apiGroups, and verbs, you can use the wildcard * symbol to refer to all objects. [1]

Here is an example that allows performing any action on all k8s resources in the core API group. [1]

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: example.com-superuser # DO NOT USE THIS ROLE, IT IS JUST AN EXAMPLE
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["*"]
RBAC Recommendation [1]

3. Referring to subjects in RBAC

A RoleBinding or ClusterRoleBinding binds a role to subjects. Subjects can be normal users or ServiceAccounts. [1]

Kubernetes represents normal users usernames as strings. These can be:

  • Username: a string which identifies the end user. Common values might be kube-admin or jane@example.com. [2]
  • UID: a string which identifies the end user and attempts to be more consistent and unique than username.[2]

ServiceAccounts have names prefixed with system:serviceaccount:. Their names follow the underlying pattern:

system:serviceaccount:namespace:serviceaccountname

# For example, a serviceaccount named prod-sa deployed
# within the namespace production will be named as follows:

system:serviceaccount:production:prod-sa

The following RoleBinding only show the subjects section for a user named alice that belongs to a group named developers

subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: developers

The following RoleBinding only show the subjects section for a ServiceAccount named system:serviceaccount:prod:prod-sa

subjects:
- kind: ServiceAccount
name: prod-sa
namespace: prod

The following RoleBinding only show the subjects section for all authenticated users. [1]

subjects:
- kind: Group
name: system:authenticated
apiGroup: rbac.authorization.k8s.io

The following RoleBinding only show the subjects section for all unauthenticated users. [1]

subjects:
- kind: Group
name: system:unauthenticated
apiGroup: rbac.authorization.k8s.io

The following RoleBinding only show the subjects section for all users. [1]

subjects:
- kind: Group
name: system:authenticated
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: system:unauthenticated
apiGroup: rbac.authorization.k8s.io

4. RBAC Command-line utilities

# Creates a Role
kubectl create role
# Creates a ClusterRole
kubectl create clusterrole
# Grants a Rolebinding
kubectl create rolebinding
# Grants a ClusterRolebinding
kubectl create clusterrolebinding
# Creates or updates rbac.authorization.k8s.io/v1 API objects from a manifest file.
kubectl auth reconcile
# Example of auth reconcile:
kubectl auth reconcile -f roleA.yaml

5. RBAC Practical Examples for Normal Users

Let’s say, following the steps explained in managing Normal Users Access to the k8s API article, we have created a user named Alice.

Use-Case 1: Usage of a Role with a RoleBinding

Granting Access to namespaced k8s resources

Grant read access to pods in the namespace prod to user Alicewithin the developerteam.

# create a role
kubectl create role alice-role --verb=get,list,watch --resource=pods --namespace=prod --dry-run=client -o yaml > alice-role.yaml
kubectl apply -f alice-role.yaml

# OUTPUT

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: alice-role
namespace: prod
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
# create a roleBinding
kubectl create rolebinding alice-rolebinding --role=alice-role --namespace=prod --user=alice --group=developers --dry-run=client -o yaml > alice-rolebinding.yaml
kubectl apply -f alice-rolebinding.yaml

# OUTPUT

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alice-rolebinding
namespace: prod
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: alice-role
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: developers
# When Alice runs the following, she should see only pods within the namespace
# prod

kubectl get pods -n prod

Use-Case 2: Usage of a ClusterRole with a ClusterRoleBinding

Granting access to k8s resources across all namespaces (cluster-scoped)

Grant read access to pods across all namespaces to Alice

# create a clusterrole 

kubectl create clusterrole alice-clusterrole --verb=get,list,watch --resource=pods --dry-run=client -o yaml > alice-clusterrole.yaml
kubectl apply -f alice-clusterrole.yaml

# OUTPUT:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: alice-clusterrole
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
# create a clusterrolebinding  

kubectl create clusterrolebinding alice-clusterrolebinding --clusterrole=alice-clusterrole --user=alice --group=developers --dry-run=client -o yaml > alice-clusterrolebinding.yaml
kubectl apply -f alice-clusterolebinding.yaml

# OUTPUT

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: alice-clusterrolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: alice-clusterrole
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: developers
# When Alice runs the following, she should see all pods across all namespaces

kubectl get pods --all-namespaces

Use-Case 3: Usage of a ClusterRole with a RoleBinding

Grant access to k8s resources within a specific namespace

If we take the already created ClusterRole : alice-clusterrole and instead of binding it to a ClusterRoleBinding , we bind it to the previously created RoleBinding: alice-rolebinding as follows:

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

---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alice-rolebinding
namespace: prod
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: alice-clusterrole
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: developers

This time, if Alice tries to access pods within all namespaces ( cluster scoped), she will get rejected:

# List all pods across all namespaces
kubectl get pods --all-namespaces


# OUTPUT:
Error from server (Forbidden): pods is forbidden: User "alice" cannot list resource "pods" in API group "" at the cluster scope

This is due to the fact the the ClusterRole has been attached to a RoleBinding. Thus, permissions of a ClusterRole when referenced by a RoleBinding ( or a set of RoleBindings) are applied only to the namespaces of the RoleBindings it is attached to.

Use-Case 4: Usage resourceNames attributes within Role attached to a RoleBinding

Grant read access only to the configmap resource named prod-configmapwithin the namespace prodto the user Alicethat belongs to the developersteam.

# create a role : same process as use-case 1
kubectl create role alice-role --verb=get,list,watch --resource=pods --namespace=prod --dry-run=client -o yaml > alice-role.yaml
# Edit the output 

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: alice-role
namespace: prod
rules:
- apiGroups:
- ""
resources:
- configmaps
resourceNames: # add this line
- prod-configmap # add this line
verbs:
- get
- list
- watch
# apply the role yaml file 
kubectl apply -f alice-role.yaml

# create a roleBinding
kubectl create rolebinding alice-rolebinding --role=alice-role --namespace=prod --user=alice --group=developers --dry-run=client -o yaml > alice-rolebinding.yaml
kubectl apply -f alice-rolebinding.yaml

# OUTPUT

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alice-rolebinding
namespace: prod
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: alice-role
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: developers
# When Alice runs the following, she should see only prod-configmap 
# within the prod namespace

kubectl get configmap -n prod

6. RBAC Practical Examples for ServiceAccounts

RBAC policies allow to grant particular Roles or ClusterRoles to particular ServiceAccounts as needed.

Default RBAC policies grant scoped permissions to control-plane components, nodes, and controllers. They can be viewed through the following command:

kubectl get clusterrole

An example of a default ClusterRole is the view ClusterRole which allows to read k8s resources across all namespaces.

Use Case 1: Grant the default view ClusterRole to a ServiceAccount within a specific namespace

To grant the view ClusterRole to the appA-sa ServiceAccount within the namespace prod :

kubectl create rolebinding rb-sa \
--clusterrole=view \
--serviceaccount=prod:appA-sa \
--namespace=prod

Use Case 2: Grant the default view ClusterRole to all ServiceAccount within a specific namespace

kubectl create rolebinding rb-sa \
--clusterrole=view \
--groups=system:serviceaccounts:prod \
--namespace=prod

Use Case 3: Grant the default view ClusterRole to all ServiceAccount across all namespaces

kubectl create rolebinding rb-sa \
--clusterrole=view \
--groups=system:serviceaccounts\
--namespace=prod

Use Case 4: Grant super-user access to all service accounts cluster-wide (strongly discouraged)

kubectl create clusterrolebinding global\
--clusterrole=cluster-admin \
--group=system:serviceaccounts

Important

It is considered as a good practice to grant a role to an application-specific ServiceAccount. This requires the application to specify a serviceAccountName in its pod spec, and for the service account to be created as explained in the Managing ServiceAccounts Access to the k8s API article.

7. Aggregated ClusterRoles

ClusterRoles can be aggregated into one combined ClusterRole. This can be achieved through the usage of Labels referenced within the aggregationRule attribute.

A controller, running as part of the cluster control plane, watches for ClusterRole objects with Labels set within the aggregationRule attribute and automatically fills the rules of the Aggregated ClusterRole

Kubernetes Caution [1]

Let’s say we have two ClusterRoles : Alice-ClusterRole and Bob-ClusterRole

# alice-clusterrole.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: alice-role
labels:
aggregate-to-global: "true" # it is important to specify a label
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
# bob-clusterrole.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: bob-rules
labels:
aggregate-to-global: "true" # it is important to specify a label
rules:
- apiGroups:
- ""
resources:
- pods
- pods/logs
- pods/exec
verbs:
- get
- create
- update
- list
- watch

Let’s create an Aggregated ClusterRole that combines both of the above ClusterRoles based on their Labels

# aggregated-clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: aggragated-clusterrole
aggregationRule:
clusterRoleSelectors:
- matchLabels:
aggregate-to-global: "true"
rules: [] # The controller automatically fills in the rules
# create the aggregated clusterrole
kubectl apply -f aggregated-clusterrole.yaml
# checkout the aggregated clusterrole
kubectl get clusterrole aggragated-clusterrole -o yaml

# OUTPUT: we can see that both alice and bob clusterroles have been aggregated

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: aggragated-clusterrole
aggregationRule:
clusterRoleSelectors:
- matchLabels:
aggregate-to-global: "true"
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods
- pods/logs
- pods/exec
verbs:
- get
- create
- update
- list
- watch

Conclusion

The primary goal of this article was to give a deeper understanding of the Kubernetes Role Based Access Control (RBAC) through practical examples. This has been accomplished through giving a general overview about the k8s RBAC then getting more practical through different examples.

References

[1] Using RBAC authorization (2022) Kubernetes. Available at: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ (Accessed: 07 June 2023).

[2] Authenticating (2023) Kubernetes. Available at: https://kubernetes.io/docs/reference/access-authn-authz/authentication/ (Accessed: 07 June 2023).

--

--

Sylia CHIBOUB

Supporting Open Source and Cloud Native as a DevOps Engineer