Implementing RBAC in Kubernetes: A Real-World Example
Implementing RBAC in Kubernetes: A Real-World Example In this article, we'll dive into how to implement Role-Based Access Control (RBAC) in Kubernetes through a practical, real-world example. While many tutorials cover RBAC concepts, they often remain abstract or overly simplified. By walking through an actual use case, you'll gain a better understanding of how to apply RBAC effectively in a Kubernetes cluster. What is RBAC? RBAC stands for Role-Based Access Control. It's a method for regulating access to resources in a system based on the roles of individual users within an organization. Essentially, RBAC is a policy-neutral access control mechanism that assigns permissions to users according to their roles. The key components of RBAC in Kubernetes include: Roles: Define what actions are allowed on which resources. RoleBindings: Link roles to users, granting them the associated permissions. ServiceAccounts: Represent identities for processes running inside pods. RBAC simplifies user assignments and ensures that access control is clear, consistent, and manageable. What is a Role? In Kubernetes, a Role is a collection of permissions that define what actions are allowed on certain resources. Roles can be applied to users, groups, or even other roles (creating hierarchies). For example: An "Admin" role might have permissions to create, read, update, and delete resources. A "Developer" role might only have permissions to create, read, and update resources. A "Viewer" role might only have permissions to read resources. Roles are a key part of RBAC as they encapsulate the actions users or processes can perform within Kubernetes. What is a RoleBinding? A RoleBinding is the mechanism that binds a Role to a user, a group of users, or even other roles. A RoleBinding effectively grants the permissions defined in a Role to whoever is bound to it. For example: You can bind the "Admin" role to a user, which grants them full access to Kubernetes resources. You can also create a RoleBinding that binds the "Viewer" role to a group of users, allowing them to only read the resources. RoleBindings are essential for controlling access to Kubernetes resources, ensuring that users have the correct permissions according to their roles. Real-World Example: A Python Server and Client in Kubernetes In this example, we will implement RBAC in a Kubernetes environment to control access between two applications: a server and a client. Server: This application generates a table of random numbers. Client: This application consumes the random number data from the server. We will containerize both applications using Docker and then deploy them to a Kubernetes cluster. Finally, we'll create a RoleBinding to allow the client to access the server. Prerequisites Before we begin, ensure you have the following installed on your machine: Docker (e.g., Docker Desktop) Kubernetes (e.g., Minikube, kind or a local Kubernetes setup) Python (preferably version 3.x) pip (Python package manager) virtualenv (for managing Python environments) kubectl (Kubernetes command-line tool) netcat (for network testing) Clone the Git Repository to Follow Along Access the source code on GitHub. Server Code The server code is responsible for generating random numbers and exposing them over a network. It is located under: kubernetes_rbac_real_example/randomNumberServer/server.py Create the Docker Image for the Server First, we need to create a Dockerfile for the server: kubernetes_rbac_real_example /randomNumberServer/Dockerfile FROM python:3.7 COPY . /app WORKDIR /app CMD ["python", "server.py"] To build the Docker image: export dockerhub_username= docker build -t ${dockerhub_username}/random-number-server . docker push ${dockerhub_username}/random-number-server Client Code The client code connects to the server and retrieves the random numbers. It is located under: kubernetes_rbac_real_example/randomNumberClient/client.py Create the Docker Image for the Client Create a Dockerfile for the client: kubernetes_rbac_real_example /randomNumberClient/Dockerfile FROM python:3.7 COPY . /app WORKDIR /app CMD ["python", "client.py"] To build the Docker image: export dockerhub_username= docker build -t ${dockerhub_username}/random-number-client . docker push ${dockerhub_username}/random-number-client Running the Docker Containers Next, we'll set up a Docker network to allow communication between the server and client containers: docker network create -d bridge random-net Now, run the server container: docker run -it -d --name random-number-server -p 3215 --network=random-net --hostname random01 random-number-server Run the client container: docker run -it -d --name random-number-client -p 3216:3216 --network=random-net -e RANDOM_SERVER=random01 --hostname
Implementing RBAC in Kubernetes: A Real-World Example
In this article, we'll dive into how to implement Role-Based Access Control (RBAC) in Kubernetes through a practical, real-world example. While many tutorials cover RBAC concepts, they often remain abstract or overly simplified. By walking through an actual use case, you'll gain a better understanding of how to apply RBAC effectively in a Kubernetes cluster.
What is RBAC?
RBAC stands for Role-Based Access Control. It's a method for regulating access to resources in a system based on the roles of individual users within an organization. Essentially, RBAC is a policy-neutral access control mechanism that assigns permissions to users according to their roles. The key components of RBAC in Kubernetes include:
- Roles: Define what actions are allowed on which resources.
- RoleBindings: Link roles to users, granting them the associated permissions.
- ServiceAccounts: Represent identities for processes running inside pods.
RBAC simplifies user assignments and ensures that access control is clear, consistent, and manageable.
What is a Role?
In Kubernetes, a Role is a collection of permissions that define what actions are allowed on certain resources. Roles can be applied to users, groups, or even other roles (creating hierarchies).
For example:
- An "Admin" role might have permissions to create, read, update, and delete resources.
- A "Developer" role might only have permissions to create, read, and update resources.
- A "Viewer" role might only have permissions to read resources.
Roles are a key part of RBAC as they encapsulate the actions users or processes can perform within Kubernetes.
What is a RoleBinding?
A RoleBinding is the mechanism that binds a Role to a user, a group of users, or even other roles. A RoleBinding effectively grants the permissions defined in a Role to whoever is bound to it.
For example:
- You can bind the "Admin" role to a user, which grants them full access to Kubernetes resources.
- You can also create a RoleBinding that binds the "Viewer" role to a group of users, allowing them to only read the resources.
RoleBindings are essential for controlling access to Kubernetes resources, ensuring that users have the correct permissions according to their roles.
Real-World Example: A Python Server and Client in Kubernetes
In this example, we will implement RBAC in a Kubernetes environment to control access between two applications: a server and a client.
- Server: This application generates a table of random numbers.
- Client: This application consumes the random number data from the server.
We will containerize both applications using Docker and then deploy them to a Kubernetes cluster. Finally, we'll create a RoleBinding to allow the client to access the server.
Prerequisites
Before we begin, ensure you have the following installed on your machine:
-
Docker
(e.g., Docker Desktop) -
Kubernetes
(e.g., Minikube, kind or a local Kubernetes setup) -
Python
(preferably version 3.x) -
pip
(Python package manager) -
virtualenv
(for managing Python environments) -
kubectl
(Kubernetes command-line tool) -
netcat
(for network testing)
Clone the Git Repository to Follow Along
Access the source code on GitHub.
Server Code
The server code is responsible for generating random numbers and exposing them over a network. It is located under: kubernetes_rbac_real_example/randomNumberServer/server.py
Create the Docker Image for the Server
First, we need to create a Dockerfile for the server: kubernetes_rbac_real_example
/randomNumberServer/Dockerfile
FROM python:3.7
COPY . /app
WORKDIR /app
CMD ["python", "server.py"]
To build the Docker image:
export dockerhub_username=
docker build -t ${dockerhub_username}/random-number-server .
docker push ${dockerhub_username}/random-number-server
Client Code
The client code connects to the server and retrieves the random numbers. It is located under: kubernetes_rbac_real_example/randomNumberClient/client.py
Create the Docker Image for the Client
Create a Dockerfile for the client: kubernetes_rbac_real_example
/randomNumberClient/Dockerfile
FROM python:3.7
COPY . /app
WORKDIR /app
CMD ["python", "client.py"]
To build the Docker image:
export dockerhub_username=
docker build -t ${dockerhub_username}/random-number-client .
docker push ${dockerhub_username}/random-number-client
Running the Docker Containers
Next, we'll set up a Docker network to allow communication between the server and client containers:
docker network create -d bridge random-net
Now, run the server container:
docker run -it -d --name random-number-server -p 3215 --network=random-net --hostname random01 random-number-server
Run the client container:
docker run -it -d --name random-number-client -p 3216:3216 --network=random-net -e RANDOM_SERVER=random01 --hostname=random02 random-number-client
Testing the Setup with netcat
In a new shell, use netcat to connect to the client container's port and view the random numbers it receives:
nc localhost 3216
You'll be prompted to enter parameters for the number generation. Here's an example of output you should see:
Enter 3 numbers min,max,cols separated by commas: 1,900,12
565 636 362 538 483 103 898 188 81 432 245 519
120 644 866 487 407 534 156 870 630 418 581 231
174 43 675 9 380 60 555 127 505 471 764 191
...
Kubernetes Environment using Kind
Visit the Kind documentation for detailed instructions on setting up a Kubernetes cluster using Kind.
Once installed and your cluster set up, check the nodes in your cluster:
kind get nodes
kind-worker2
kind-control-plane
kind-worker
kind-worker3
Deploying RBAC to Kubernetes
Once we've confirmed that the Docker containers work correctly, we can deploy both the server and client to a Kubernetes cluster.
Create a pod file to deploy the server and client. If you wish change the image to use your own dockerhub account. E.g. if your account name is johnmcollins
, then the image is set to johnmcollins/random-number-server
and johnmcollins/random-number-client
.
First, create the namespace:
kubectl create namespace random-numbers
Server
cat <<EOF> random-number-server.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: random-number-server
name: random-number-server
namespace: random-numbers
spec:
containers:
- name: random-number-server
image: contactnkm/random-number-server:latest
ports:
- containerPort: 3215
EOF
Create the service for this pod:
cat <<EOF> random-number-service.yaml
apiVersion: v1
kind: Service
metadata:
name: random-number-service
namespace: random-numbers
spec:
selector:
app: random-number-server
ports:
- protocol: TCP
port: 3215
targetPort: 3215
EOF
Run the following commands to deploy the random number server and random number service:
kubectl apply -f random-number-server.yaml
kubectl apply -f random-number-service.yaml
We need to ensure our service DNS is working correctly. We can do this by creating a pod that will run a nslookup
command to the service.
cat <<EOF> dnsutils.yaml
apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
- name: dnsutils
image: gcr.io/kubernetes-e2e-test-images/dnsutils:1.3
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always
EOF
kubectl apply -f dnsutils.yaml
Run the nslookup
command to verify the service:
kubectl exec -ti dnsutils -n random-numbers -- nslookup random-number-service
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: random-number-service.random-numbers.svc.cluster.local
Address: 10.96.35.141
So we now know for sure our DNS is working correctly, and our FQDN is random-number-service.random-numbers.svc.cluster.local
.
In the client pod, set the environment variable RANDOM_NUMBER_SERVER
to the FQDN of the server service.
Client
cat <<EOF> random-number-client.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
app: random-number-client
name: random-number-client
namespace: random-numbers
spec:
containers:
- name: random-number-client
image: contactnkm/random-number-client:latest
ports:
- containerPort: 3216
env:
- name: RANDOM_SERVER
value: "random-number-service.random-numbers.svc.cluster.local"
EOF
Create a random number client service:
cat <<EOF> random-number-client-service.yaml
apiVersion: v1
kind: Service
metadata:
name: random-number-client-service
namespace: random-numbers
spec:
selector:
app: random-number-client
ports:
- protocol: TCP
port: 3216
targetPort: 3216
EOF
Deploy the client and random number client service to the cluster:
kubectl apply -f random-number-client.yaml
kubectl apply -f random-number-client-service.yaml
Check that all the service endpoints are created; it should not show none
:
kubectl get ep -n random-numbers
NAME ENDPOINTS AGE
random-number-client-service 10.244.2.6:3216 12s
random-number-service 10.244.1.12:3215 87s
Testing the Client and Server
Connect to the host using the dnsutils pod and test the client and server:
kubectl exec -ti dnsutils -n random-numbers -- sh
Test the application and use Ctrl-C
to exit:
nc random-number-client-service 3216
Enter 3 numbers min,max,cols separated by commas: 10,36,6
16 19 17 30 31 25
28 14 35 32 29 34
20 18 12 33 27 24
36 23 22 26 21 10
15 11 13
Using RBAC in Kubernetes
The next step is to set up RBAC to allow the client to interact with the server.
Create a Service Account.
kubectl create sa random-numbers-sa -n random-numbers
Step 1: Create a Role for Access
First, we create a Role that defines the permissions for accessing the server. This role grants read access to the server's resources.
For example, the role could be defined in a YAML file:
cat < roles.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: random-numbers
name: client-access-role
rules:
- apiGroups: [""]
resources:
- configmaps
- services # Add other resources as needed
verbs:
- get
- list # Add other verbs as needed
EOF
Step 2: Create a RoleBinding
Now, we create a RoleBinding to bind the "client-access-role" to the client pod. This will grant the client the necessary permissions to interact with the server pod.
For example, the RoleBinding might look like this:
cat < role-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: client-access-role-binding
namespace: random-numbers
subjects:
- kind: ServiceAccount
name: random-numbers-sa
namespace: random-numbers
roleRef:
kind: Role
name: client-access-role
apiGroup: rbac.authorization.k8s.io
EOF
Testing in Kubernetes
After deploying the server and client containers to Kubernetes, you can test the connection between them and verify that the RoleBinding is working. We will do this with a simple curl application.
Create the curl application:
cat <<EOF> curlapp.yaml
apiVersion: v1
kind: Pod
metadata:
name: curlo
namespace: random-numbers
labels:
app: curlo
spec:
serviceAccountName: random-numbers-sa
containers:
- name: curlo
image: curlimages/curl
command: ["sleep","999999"]
EOF
kubectl apply -f curlapp.yaml
Connect to the host using the following command
kubectl exec -it curlo -n random-numbers -- /bin/sh
In order to use curl with https we will need to use the certificate file ca.crt
file and token
in the curl
command.
cat /var/run/secrets/kubernetes.io/serviceaccount/token > TOKEN
export TOKEN=$(cat TOKEN)
curl -k --header "Authorization: Bearer $TOKEN" --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes.default.svc
For various reasons which I will let you discover, you will notice how this can be shortened to:
export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes.default.svc
With the following command you should be able to see the service we had created earlier
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes.default.svc/api/v1/namespaces/random-numbers/services/random-number-service
Result:
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "random-number-service",
"namespace": "random-numbers",
"uid": "504cd44b-7e10-4ea2-9ee8-a912909f6dee",
"resourceVersion": "183093",
"creationTimestamp": "2025-01-19T19:52:28Z",
"managedFields": [
...
"status": {
"loadBalancer": {}
}
Try to list the other pods that are running in the cluster within the same namespace using the following curl
command. You should get a 403 Forbidden error
.
curl -k --header "Authorization: Bearer $TOKEN" https://kubernetes.default.svc/api/v1/namespaces/random-numbers/pods
Result
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:random-numbers:random-numbers-sa\" cannot list resource \"pods\" in API group \"\" in the namespace \"random-numbers\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}
Great! Now we have achieved the exact level of role-based access control needed to limit access exclusively to services
and configmaps
within the random-numbers
namespace.
Next, create a ConfigMap
to store the min
, max
, and table -size
values for our random number generator. These values will be used by the client pod to generate the random number table.
kubectl create configmap random-number-config --from-literal=min=1 --from-literal=max=100 --from-literal=table-size=10 -n random-numbers
Here is the yaml file version of the same command:
cat < random-number-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: random-number-config
namespace: random-numbers
data:
min: "1"
max: "100"
table-size: "10"
table-name: "random-numbers"
EOF
kubectl apply -f random-number-configmap.yaml
Python Kubernetes API Client
We need to modify the client pod to use the Kubernetes API to get the configmap
values and use them to generate the random number table.
We will use the Kubernetes API client for Python to get the configmap values.
The source code can be found here: kubernetes_rbac_real_example/randomNumberClientCM/client-cm.py
We will need to add the kubernetes library to our Docker image. Use pip
to install the Kubernetes API client for Python in your Dockerfile ( note we have already done this for you ).
pip install kubernetes
Here is the code required to get the configmap values:
import os
from kubernetes import client, config
def get_configmap_values():
config.load_incluster_config()
v1 = client.CoreV1Api()
configmap = v1.read_namespaced_config_map(name="random-number-config", namespace="random-numbers")
min_value = int(configmap.data["min"])
max_value = int(configmap.data["max"])
table_size = int(configmap.data["table-size"])
return min_value, max_value, table_size
Build a new docker image called {your-username}/random-number-client-cm
and push it to docker hub.
docker build -t {your-username}/random-number-client-cm .
docker push {your-username}/random-number-client-cm
Lets double check we have our service account added the client pod definition and the lastest docker image for the client pod. Remember, the service account is used to restrict access to the Kubernetes API using RBAC.
apiVersion: v1
kind: Pod
metadata:
labels:
app: random-number-client
name: random-number-client
namespace: random-numbers
spec:
serviceAccountName: random-numbers-sa
containers:
- name: random-number-client
image: {your-docker-username}/random-number-client-cm:latest
ports:
- containerPort: 3216
env:
- name: RANDOM_SERVER
value: "random-number-service.random-numbers.svc.cluster.local"
kubectl delete -f random-number-client.yaml
kubectl apply -f random-number-client.yaml
If everything is set up correctly, the client should be able to access the server and receive the random number table, just like it did in the Docker environment but can't do very much else. Let's test it out:
kubectl exec -ti dnsutils -n random-numbers -- sh
/ # nc random-number-client-service 3216
Press [Enter] to get a new set of values using the kubernetes config map:
Will send these values to server: 1,100,10
Waiting for server response...
74 80 40 26 17 72 87 38 64 79
97 96 63 86 7 58 75 52 76 32
47 29 5 83 68 90 60 100 16 70
84 9 44 37 48 49 98 81 27 54
45 61 51 25 43 57 65 89 92 19
22 24 55 28 53 15 13 8 10 39
20 77 31 93 91 12 62 94 34 42
35 1 67 85 78 59 14 99 11 33
21 30 95 2 6 82 4 3 56 66
50 69 18 23 46 71 36 88 41 73
Press [Enter] to get a new set of values using the kubernetes config map:
Now try changing the min
, max
and table-size
values in the config map and see if the client can pick up the new values.
apiVersion: v1
kind: ConfigMap
metadata:
name: random-number-config
namespace: random-numbers
data:
min: "64"
max: "256"
table-size: "8"
table-name: "random-numbers"
...
Save the file apply the changes.
kubectl apply -f random-number-configmap.yaml
Visit the window where you are running the dnsutils pod and hit enter again in the terminal. This should now fetch a new table using the updated min
,max
and table-size
values.
Press [Enter] to get a new set of values using the kubernetes config map:
Will send these values to server: 64,256,8
Waiting for server response...
219 195 91 88 104 93 134 144
246 131 241 239 128 210 190 114
163 183 191 161 115 145 160 70
136 218 171 67 98 237 248 256
227 173 80 202 206 71 129 126
249 154 245 216 193 204 92 66
75 169 106 189 155 185 238 179
103 230 99 221 149 137 152 138
117 174 100 247 215 170 105 68
120 74 180 141 231 186 139 250
79 122 209 121 127 123 83 157
94 235 199 211 201 125 188 95
229 205 220 198 147 236 217 176
97 167 213 234 203 225 84 172
208 142 254 150 175 148 87 228
158 253 135 64 242 81 233 212
133 72 118 73 243 143 226 159
146 140 178 77 124 151 112 109
164 184 251 153 96 197 65 240
78 132 166 252 165 194 102 244
86 187 116 232 222 196 110 111
119 90 168 255 101 76 82 85
162 89 108 69 223 200 214 182
207 192 113 177 130 156 107 181
224
Conclusion
In this article, we've walked through a real-world example of using RBAC in a Kubernetes environment. We demonstrated how to set up roles and RoleBindings to control access between two applications—one acting as a server and the other as a client. By building the applications with Docker, deploying them to Kubernetes, and using RBAC, you now have a solid understanding of how to implement access control in a Kubernetes cluster.
Remember, RBAC is a powerful feature that helps you ensure that users and services in your cluster have the appropriate permissions based on their roles. With RBAC, you can implement fine-grained access control to safeguard your resources.
What's Your Reaction?