In our final article on Kubernetes RBAC, we are focusing on RBAC itself. Everything else in the series led towards this key piece. In part one we discussed authentication and authorization on a high level and in part two we focused specifically on authentication. Now let’s dive into authorization.
As usual with Kubernetes being so extensible, there are multiple mechanisms for authorization. You’ve got node authorization, ABAC, RBAC, WebHooks, as well as AlwaysDeny / AlwaysAllow. Since we are focusing on mechanisms that you’ll use in production, we’ll focus on RBAC and WebHooks in particular. But let’s take a quick look at the other options for completeness.
|Node||API Server built-in||Internal use (kubelets)|
|ABAC||Static file||Insecure, deprecated|
|RBAC||API Objects||Users and administrators|
|AlwaysDeny AlwaysAllow||API Server built-in||Testing|
Three authorization methods that we will NOT look into in detail in this article are: Node, ABAC and AlwaysDeny / AlwaysAllow. Node authorization is mainly used internally by Kubernetes components such as kubelets. Based on a static file, ABAC is considered insecure and deprecated. AlwaysDeny / AlwaysAllow are generally used for testing. So let’s move on to what really matters: production-grade authorization.
WebHook is an external service the Kubernetes API can call when it needs to decide whether a request should be allowed or not. The API for this service is well documented in the Kubernetes documentation. In fact, the Kubernetes API itself provides this API.
Extension servers and extension implementations use that mechanism when authorizing extension objects in Kubernetes based on CRD, so you may see it quite often, but most probably won’t have to interact with it directly.
Therefore we will mainly focus on the most important user-facing authorization mechanism in Kubernetes – RBAC.
From a practical standpoint, the most useful authorization method is RBAC. It is based on declarative permissions definitions and cluster API objects. The main objects are roles and cluster roles, both representing a set of permissions on certain objects in the API. These are identified by API groups, source names, and actions performed on those objects. You can have a number of rules within a role or cluster role object.
The difference between role and cluster role is that role is a namespaced object while cluster role is a global spaced object.
A role can only exist in a certain namespace and can only refer to namespaced resources. It also only defines permission to objects within that namespace.
A cluster role, on the other hand, is a global object that will provide access to global objects as well as non-resource URLs, such as API version and healthz endpoints on the API server.
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: role1 rules: - apiGroups: ['*'] resources: ['nodes', 'pods', 'pods/log'] verbs: ['get', 'list'] - apiGroups: ['*'] resources: ['configmaps'] resourceNames: ['my-configmap'] verbs: ['get', 'list']
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: namespace: default name: clusterRole1 rules: - apiGroups: ['*'] resources: ['nodes', 'pods'] verbs: ['get', 'list'] - nonResourceURLs: ['/api', '/healthz*'] verbs: ['get', 'head']
Cluster roles, unlike regular Roles, can aggregate other Cluster Roles.
You must specify an aggregation rule for your cluster role which is essentially a set of label matching rules. Kubernetes will then find all cluster roles that match those label matching rules and aggregate them into this Cluster Role dynamically. You can add and remove matching cluster roles and the aggregated cluster role will change the set of permissions accordingly — very useful for predefined cluster roles (we’ll come back to that in a bit).
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: aggregatedClusterRole1 aggregationRule: clusterRoleSelectors: - matchLabels: label1: value1 # The control plane automatically fills in the rules rules: 
Then, you’ll associate cluster roles permission with actors (service accounts, users, groups) through RoleBinding and ClusterRoleBinding.
Similarly to Roles, RoleBuiding is always created in a specific namespace and ClusterRoleBinding can be associated with either a Role in the same namespace or a ClusterRole. When associated with a Role, it provides users permission specified within that Role related to objects within that namespace. When associated with a Cluster Role it provides access to namespaced objects only defined within that Cluster Role and related to the Role’s namespace. These definitions are very convenient when dealing with predefined roles.
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: roleBinding1 namespace: default roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: role1 subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: user1 - apiGroup: rbac.authorization.k8s.io kind: Group name: group1 - kind: ServiceAccount name: sa1 namespace: default
ClusterRoleBinding, on the other hand, is a global object. It associates Cluster Roles with users, groups, and service accounts, but it can’t be associated with a namespaced Role. It is used to provide access to global objects, non-namespaced objects, or to namespaced objects in all namespaces.
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: roleBinding1 namespace: default roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: clusterRole1 subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: user1 - apiGroup: rbac.authorization.k8s.io kind: Group name: group1 - kind: ServiceAccount name: sa1 namespace: default
Every Kubernetes cluster comes with four three default cluster roles:
- cluster-admins which has full access to all cluster resources
- admin role provides full access to namespaced objects within a specific namespace
- edit role, similar to the admin role with the notable exception that it won’t allow you to alter access control and permission objects; in other words users can do anything within the namespace except granting access to other users
- view role, which provides read-only access to namespaced objects
Here’s a quick overview to help you start and make sense of the system. The easiest way to get started is with the default roles — they are more than enough for basic use cases. You may want to give your ops team cluster admin roles, and if a certain team needs access to a specific namespace, you’ll provide them with a namespace admin role. Developers can have editor roles, etc.
|cluster-admin||Full access to all cluster resources||Anything in the cluster|
|admin||Full access to all namespace resources||Anything in a namespace|
|edit||Full access to all namespace resources except Role, RoleBinding, LocalSubjectAccessReviews||Anything in a namespace except granting and checking access|
|view||RO access to all namespace resources except sensitive ones||View and list non-sensitive objects in a namespace|
New roles can be defined based on specific use cases when the default set of roles is not enough.
There are several gotchas that you need to be aware of.
Consider, for example, so-called privilege escalation via pod creation. If a person starts a pod in a namespace, through that pod, they have access to secret objects within that namespace. It doesn’t make sense to allow someone to start a pod but not allow them to read secrets in that namespace. The user can just map that secret into the pod and extract that information from there.
Another thing to keep in mind is that non-namespaced objects can be of potential interest to developers as well. Things like CRD, PriorityClass, PodSecurityPolicy may have to be created when you deploy certain frameworks (e.g. service meshes). If developers need some level of freedom to experiment with different frameworks they must be allowed to manage at least some of these namespaced objects. In many cases, it’s easier to provide them with a separate set of clusters or a space where they can create clusters to experiment than managing those non-namespaced objects one by one in a single critical cluster or a cluster shared between multiple teams.
Another thing you’ll need to think through before you start is role and role binding name conventions. You’ll need to decide how to name those roles and role bindings each time you create a new one. For example, whether you create one role binding per subject, one per role or a mix.
A few things out of scope of this summary article but worth looking into when exploring RBAC and permissions include pod security policies, network policy, limits and quotas, admission controllers, dynamic admission control, and dynamic policies (OPA). These will allow you to further expand and extend what you can do with access control and security in a Kubernetes cluster. Kubernetes is an incredibly flexible and extensible framework.
Here are objects you need to be aware of when tackling RBAC in Kubernetes. These are all available in Kubernetes documentation as well.
|API Group||API Objects|
|rbac.authorization.k8s.io/v1||ClusterRole Role ClusterRoleBinding RoleBinding|
|authorization.k8s.io/v1||LocalSubjectAccessReview SelfSubjectAccessReview SelfSubjectRulesReview SubjectAccessReview|
If you want to start experimenting, here are some useful references you should check out:
When it comes to authorization, you have a few options. Only two are widely used for production deployments however. The first one is WebHooks, an external service integration point which the Kubernetes API can use to verify if the request should be allowed. WebHooks are mainly used by extensions, plugins and integrations. The second one, and the most practical approach for cluster administrators, is RBAC.
Based on declarative permissions and cluster objects, RBAC provides a flexible framework and all tools necessary to define access to Kubernetes API objects.
If you’d like to experiment with RBAC, download Kublr and play around with its RBAC feature. The intuitive UI helps speed up the steep learning curve when dealing with RBAC YAML files.