In September 2016 I was contracted to an organisation that was adopting a new technology called Kubernetes. At that point I had never heard of it but I had played with Docker and looked at the security associated with it. Little did I know what a huge technological impact Kubernetes would have and how fortunate I was to be exposed to it early. Back then Kubernetes didn’t have RBAC and I remember quite the stir in the Spring of 2017 when they announced its availability in beta. Over time I’ve become more and more accustomed to Kubernetes RBAC being able to exploit it during penetration tests as well provide advice to customers of how to apply least privilege principles and lock it down for Kubernetes based Capture the Flag (CTF) events.
During KubeCon + CloudNativeCon North America 2023, I was surprised at the number of questions I was asked surrounding it and the lack of depth of knowledge in the area so I’ve decided to provide a few tips and tricks I’ve found during my journey with Kubernetes RBAC.
This post assumes you have a good grasp of Kubernetes and the basics of RBAC. If you want to know more about the fundamentals, I recommend starting at the Official Kubernetes Site
RBAC is bound to service accounts either via a role or clusterrole binding. When you obtain a service account token you want to know what you can do with it, that is, what permissions it has against the Kubernetes API. kubectl
has a magical command which will allow you to enumerate permissions called auth can-i
. It essentially determines whether an action is allowed or not. For example, I could do the following to determine whether I can list pods within my current namespace.
$ kubectl auth can-i list pods
yes
If I wanted to know if I could list pods for all namespaces I could append it with --all-namespaces
or in shorthand -A
.
$ kubectl auth can-i list pods -A
yes
Although this is useful, it would take a considerable time to determine what resources within the cluster you have access to. This is where the --list
function comes in. It allows you to list all allowed actions by a namespace.
$ kubectl auth can-i --list -n kube-system
Resources Non-Resource URLs Resource Names Verbs
*.* [] [] [*]
[*] [] [*]
selfsubjectreviews.authentication.k8s.io [] [] [create]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]
[/api/*] [] [get]
[/api] [] [get]
[/apis/*] [] [get]
[/apis] [] [get]
[/healthz] [] [get]
[/healthz] [] [get]
[/livez] [] [get]
[/livez] [] [get]
[/openapi/*] [] [get]
[/openapi] [] [get]
[/readyz] [] [get]
[/readyz] [] [get]
[/version/] [] [get]
[/version/] [] [get]
[/version] [] [get]
[/version] [] [get]
If you are able to identify all namespaces and combine it with this auth can-i
command, you’ll soon have a complete picture of what the current service account has access to in the cluster.
$ available_ns=$(kubectl get namespaces -o name | cut -d "/" -f 2) && for i in $available_ns; do echo "namespace: $i" && kubectl auth can-i --list -n $i; done
If you are required to configure service accounts in a Kubernetes cluster, you’ll need to delve into creating roles, cluster roles and their associated bindings. Depending on how much time you are given and your understanding of the resource you are configuring, creating the RBAC manifests could be relatively simple or hugely frustrating. It can be tempting to make use of the ‘*’
parameter when you believe that a service account requires access to all resources or be able to perform all operations.
The primary issue of doing this is that you may overlook the impact of assigning a permission set or not realize that an additional permission is granted. For example, you may want to allow a service account to have access to all resources associated with the Core API (v1). A quick look at the Core API, returns the following resource kinds.
$ kubectl api-resources --api-group=’’
NAME SHORTNAMES APIVERSION NAMESPACED KIND
bindings v1 true Binding
componentstatuses cs v1 false ComponentStatus
configmaps cm v1 true ConfigMap
endpoints ep v1 true Endpoints
events ev v1 true Event
limitranges limits v1 true LimitRange
namespaces ns v1 false Namespace
nodes no v1 false Node
persistentvolumeclaims pvc v1 true PersistentVolumeClaim
persistentvolumes pv v1 false PersistentVolume
pods po v1 true Pod
podtemplates v1 true PodTemplate
replicationcontrollers rc v1 true ReplicationController
resourcequotas quota v1 true ResourceQuota
secrets v1 true Secret
serviceaccounts sa v1 true ServiceAccount
services svc v1 true Service
Defining a manifest to access these resources would look something like this:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: clusterrole-core-star
rules:
- apiGroups:
- ""
resources:
- "*"
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
In this instance, we are not applying ’*’
to all verbs so we can assume that the role only has the operational verbs to the kinds we discovered previously. Although this is true, it is not the complete picture. Kubernetes has sub-resources which reside under the primary resources listed. A good example of this is serviceaccounts/token
sub-resource which allows a user to issue tokens for existing service accounts if granted the create
permission. As you can imagine this permission allows a user to escalate privileges within the cluster.
This demonstrates the power of the ’*’
parameter for a resource kind but what damage can be done with verbs? If applied to a specific resource, a lot of damage. Some Kubernetes resources have special verbs associated with them. Examples include Role
and ClusterRole
which has escalate
and bind
(verbs which allow a service account to perform privileged operations) and User
, Group
, ServiceAccounts
which has impersonate
(allows users gain the rights of other users in the cluster). If you want to know more about the impact of these verbs I recommend looking at the Kubernetes RBAC Good Practices and Privilege Escalation Prevention.
The point of all this is…Be Explicit.
If you believe you need all the permissions to a specific resource, list out all the verbs that you need. Do not assume that ’*’
is safe. Also consider what would happen if a new verb is introduced by the Kubernetes maintainers. Rather than be caught out by granting that permission unknowingly, you know that your manifest will not have that verb unless a user has explicitly put it in.
One last point on this topic. It is becoming more common to leverage third party tooling within Kubernetes. Whilst you may trust the publisher of the tool, it has been found that the RBAC permissions that are applied to these products are aimed at ensuring it will work in any environment. As such they can be over privileged and in some cases for Kubernetes Operators I’ve seen service accounts granted cluster-admin
. Like any other third party tooling, check the permissions it requires and ensure it is not granted anything beyond what it needs.
A common mistake I see with Kubernetes RBAC is the belief that a ClusterRole
must be used to access resources in another namespace. This is untrue and if a service account only requires access to specific namespaces, it can be defined in a Role
and RoleBinding
. Let’s look through an example.
Let’s say I have two namespaces, public
and private
. I have a pod deployed into both namespaces and for the public pod, I attach a service account called user
. If I wanted the user
service account to be able to get
and list
a pod in the private
namespace I can specify the following Role
and RoleBinding
.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: private-pod-access
namespace: private
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: private-pod-access
namespace: private
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: private-pod-access
subjects:
- kind: ServiceAccount
name: user
namespace: public
The important part of the RBAC manifests is the namespace to which they are applied. Both are applied to the private
namespace but the service account, associated with Role
by the RoleBinding
, is bound to the public
namespace. With this in place, I can now exec
into the public pod and be able to get
pods in the private
namespace.
Another feature I’ve rarely seen used by a customer is the ability to restrict access to a specific resource. This is useful if a service account requires access to a resource, such as a secret, but you don’t want it to have access to all secrets. This is achieved with the resourceNames
rule within a Role
manifest.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-user-role
namespace: private
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
resourceNames:
- user-secret
If I were to attempt to get
secrets for the private
namespace, I would receive a warning from API server that I do not have permissions to do that, however if I were to get the specific resource in this instance (user-secret), it will return with a success.
The following is a list of tools I’ve found useful for numerating or exploiting Kubernetes RBAC.
To help understand useful and dangerous Kubernetes RBAC permissions I’ve created a repository (https://github.com/wakeward/kubernetes-rbac-examples) with examples to review and test. A reminder the dangerous manifests are for educational means and please don’t run them in a production environment.