Kubernetes Series: Securing Access to the k8s API through RBAC
Introduction
This article covers the k8s Role-based access control (RBAC). It is a continuation of two previous articles of the k8s series:
- Kubernetes Series: Managing Normal Users Access to the k8s API
- Managing ServiceAccounts Access to the k8s API
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]
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 theroleRef
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 resourceNames
is 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"]
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: ["*"]
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
orjane@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
Grant read access to pods in the namespace prod
to user Alice
within the developer
team.
# 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
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
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-configmap
within the namespace prod
to the user Alice
that belongs to the developers
team.
# 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
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).