Securing the Cluster With Role-Based Access Control
Starting with Kubernetes version 1.6.0, cluster security was ramped up considerably. In earlier versions, if you managed to acquire the authentication token from one of the pods, you could use it to do anything you want in the cluster.
But in version 1.8.0, the RBAC authorization plugin graduated to GA (General Availability) and is now enabled by default on many clusters. RBAC prevents unauthorized users from viewing or modifying the cluster state. The default Service-Account isn’t allowed to view cluster state, let alone modify it in any way, unless you grant it additional privileges. To write apps that communicate with the Kubernetes API server, you need to understand how to manage authorization through RBAC-specific resources.
In addition to RBAC, Kubernetes also includes other authorization plugins, such as the Attribute-based access control (ABAC) plugin, a Web-Hook plugin and custom plugin implementations. RBAC is the standard, though.
Introducing the RBAC authorization plugin
The Kubernetes API server can be configured to use an authorization plugin to check whether an action is allowed to be performed by the user requesting the action. Because the API server exposes a REST interface, users perform actions by sending HTTP requests to the server. Users authenticate themselves by including credentials in the request (an authentication token, username and password, or a client certificate).
Understanding actions
REST clients send GET, POST, PUT, DELETE, and other types of HTTP requests to specific URL paths, which represent specific REST resources. In Kubernetes, those resources are Pods, Services, Secrets, and so on. Here are a few examples of actions in Kubernetes:
- Get Pods
- Create Services
- Update Secrets
Introducing RBAC resources
The RBAC authorization rules are configured through four resources, which can be grouped into two groups:
- Roles and ClusterRoles, which specify which verbs can be performed on which resources.
- RoleBindings and ClusterRoleBindings, which bind the above roles to specific users, groups, or ServiceAccounts.
Roles define what can be done, while bindings define who can do it.

Roles grant permissions, whereas RoleBindings bind Roles to subjects.
The distinction between a Role and a ClusterRole, or between a RoleBinding and a ClusterRoleBinding, is that the Role and RoleBinding are namespaced resources, whereas the ClusterRole and ClusterRoleBinding are cluster-level resources (not namespaced).

As you can see from the figure, multiple RoleBindings can exist in a single name-space (this is also true for Roles). Likewise, multiple ClusterRoleBindings and Cluster-Roles can be created. Another thing shown in the figure is that although RoleBindings are namespaced, they can also reference ClusterRoles, which aren’t.
To try out RBAC, you’ll run two pods in different namespaces to see how per-namespace security behaves.
Creating the namespaces and running the pods
You’re going to create one pod in namespace foo and the other one in namespace bar
kubectl create ns foo
namespace/foo created
kubectl run test --image=luksa/kubectl-proxy -n foo
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/test created
kubectl create ns bar
namespace/bar created
kubectl run test --image=luksa/kubectl-proxy -n bar
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/test created
Now open two terminals and use kubectl exec to run a shell inside each of the two pods (one in each terminal). For example, to run the shell in the pod in namespace foo, first get the name of the pod:
Terminal 1
kubectl get pod -n foo
NAME READY STATUS RESTARTS AGE
test-7fcbd9fbbc-pzzzc 1/1 Running 0 63s
Then use the name in the kubectl exec command:
kubectl exec -it test-7fcbd9fbbc-pzzzc -n foo sh
/ #
Do the same in the other terminal, but for the pod in the bar namespace.
Terminal 2
kubectl get pod -n bar
NAME READY STATUS RESTARTS AGE
test-7fcbd9fbbc-p6r9h 1/1 Running 0 63s
kubectl exec -it test-7fcbd9fbbc-p6r9h -n bar sh
/ #
Listing Services from your pods
To verify that RBAC is enabled and preventing the pod from reading cluster state, use curl to list Services in the foo namespace:
Terminal 1
curl localhost:8001/api/v1/namespaces/foo/services
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "services is forbidden: User \"system:serviceaccount:foo:default\" cannot list resource \"services\" in API group \"\" in the namespace \"foo\"",
"reason": "Forbidden",
"details": {
"kind": "services"
},
"code": 403
}
You’re connecting to localhost:8001, which is where the kubectl proxy process is listening. The process received your request and sent it to the API server while authenticating as the default ServiceAccount in the foo namespace (as evident from the API server’s response).
The API server responded that the ServiceAccount isn’t allowed to list Services in the foo namespace, even though the pod is running in that same namespace. You’re seeing RBAC in action. The default permissions for a ServiceAccount don’t allow it to list or modify any resources. Now, let’s learn how to allow the ServiceAccount to do that. First, you’ll need to create a Role resource.
Using Roles and RoleBindings
A Role resource defines what actions can be taken on which resources. The following listing defines a Role, which allows users to get and list Services in the foo namespace.
Terminal 3
cat << EOF > service-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: foo #1
name: service-reader
rules:
- apiGroups: [""] #2
verbs: ["get", "list"] #3
resources: ["services"] #4
EOF
- Roles are namespaced (if namespace is omitted, the current namespace is used).
- Services are resources in the core
apiGroup, which has no name – hence the “”. - Getting individual Services (by name) and listing all of them is allowed.
- This rule pertains to services (plural name must be used).
In the example, you’re allowing access to all Service resources, but you could also limit access only to specific Service instances by specifying their names through an additional
resourceNamesfield.

Create the abole Role in the foo namespace now:
kubectl create -f service-reader.yaml -n foo
role.rbac.authorization.k8s.io/service-reader created
Instead of creating the service-reader Role from a YAML file, you could also create it with the special kubectl create role command. Let’s use this method to create the Role in the bar namespace:
kubectl create role service-reader --verb=get --verb=list \
--resource=services -n bar
role.rbac.authorization.k8s.io/service-reader created
These two Roles will allow you to list Services in the foo and bar namespaces from within your two pods (running in the foo and bar namespace, respectively). But creating the two Roles isn’t enough (you can check by executing the curl command again). You need to bind each of the Roles to the ServiceAccounts in their respective namespaces.
Terminal 1
curl localhost:8001/api/v1/namespaces/foo/services
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "services is forbidden: User \"system:serviceaccount:foo:default\" cannot list services in the namespace \"foo\"",
"reason": "Forbidden",
"details": {
"kind": "services"
},
"code": 403
}
Binding a Role to a ServiceAccount
A Role defines what actions can be performed, but it doesn’t specify who can perform them. To do that, you must bind the Role to a subject, which can be a user, a Service-Account, or a group (of users or ServiceAccounts).
Binding Roles to subjects is achieved by creating a RoleBinding resource. To bind the Role to the default ServiceAccount, run the following command:
Terminal 3
kubectl create rolebinding test --role=service-reader \
--serviceaccount=foo:default -n foo
rolebinding.rbac.authorization.k8s.io/test created
The command should be self-explanatory. You’re creating a RoleBinding, which binds the service-reader Role to the default Service-Account in namespace foo. You’re creating the RoleBinding in namespace foo.

To bind a Role to a user instead of a ServiceAccount, use the
--userargument to specify the username. To bind it to a group, use--group.
The following listing shows the YAML of the RoleBinding you created.
kubectl get rolebinding test -n foo -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: "2019-09-27T07:46:29Z"
name: test
namespace: foo
resourceVersion: "10303"
selfLink: /apis/rbac.authorization.k8s.io/v1/namespaces/foo/rolebindings/test
uid: ead18111-e0fa-11e9-a0cb-42010a800218
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role #1
name: service-reader #1
subjects:
- kind: ServiceAccount #2
name: default #2
namespace: foo #2
- This RoleBinding references the
service-readerRole. - And binds it to the
defaultServiceAccount in thefoonamespace.
As you can see, a RoleBinding always references a single Role (as evident from the roleRef property), but can bind the Role to multiple subjects (for example, one or more Service-Accounts and any number of users or groups). Because this RoleBinding binds the Role to the ServiceAccount the pod in namespace foo is running under, you can now list Services from within that pod.
Terminal 1
curl localhost:8001/api/v1/namespaces/foo/services
{
"kind": "ServiceList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/foo/services",
"resourceVersion": "10426"
},
"items": []
}
The list of items is empty, because no Services exist.
Terminal 2
curl localhost:8001/api/v1/namespaces/bar/services
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "services is forbidden: User \"system:serviceaccount:bar:default\" cannot list resource \"services\" in API group \"\" in the namespace \"bar\"",
"reason": "Forbidden",
"details": {
"kind": "services"
},
"code": 403
}
The pod in namespace bar can’t list the Services in its own namespace, and obviously also not those in the foo namespace. But you can edit your RoleBinding in the foo namespace and add the other pod’s ServiceAccount, even though it’s in a different namespace. Run the following command:
Terminal 3
kubectl edit rolebinding test -n foo
Then change the following lines as the list of subjects
subjects:
- kind: ServiceAccount
name: default
namespace: foo
- kind: ServiceAccount
name: default #1
namespace: bar #1
- You’re referencing the
defaultServiceAccount in thebarnamespace.
rolebinding.rbac.authorization.k8s.io/test edited
Now you can also list Services in the foo namespace from inside the pod running in the bar namespace.
Terminal 2
curl localhost:8001/api/v1/namespaces/foo/services
{
"kind": "ServiceList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/foo/services",
"resourceVersion": "10759"
},
"items": []
}
You have a RoleBinding in namespace foo, which references the service-reader Role (also in the foo namespace) and binds the default ServiceAccounts in both the foo and the bar namespaces, as depicted in the figure

A RoleBinding binding ServiceAccounts from different namespaces to the same Role.