Back to Blog
Kubernetes
Certificate
Role

How Kubernetes Authentication and Authorization Work in Practice

Atul Mishra
February 9, 2026

Kubernetes security can feel confusing at first, especially when working with certificates and RBAC permissions. In this blog, we will walk through the complete process step by step using a practical example.

A user named Krish creates a private key and CSR, the cluster administrator approves it and assigns the required permissions, and finally Krish logs in to verify access. This journey shows how authentication and authorization actually work inside a real Kubernetes cluster.

Prerequisites: Since I will be running Kubernetes locally using Docker and Kind, you will need:

  • Docker Desktop installed on your system.
  • Kind (Kubernetes in Docker) installed along with the kubectl client.

Step 1 — Create a fresh Kubernetes cluster

First, we create a fresh Kind cluster: kind create cluster --config cluster.yaml

This creates: 1 control plane, 3 worker nodes and a cluster with default name.

Cluster.yaml
1kind: Cluster
2apiVersion: kind.x-k8s.io/v1alpha4
3nodes:
4- role: control-plane
5  extraPortMappings:
6  - containerPort: 30009
7    hostPort: 30009
8- role: worker
9- role: worker
10- role: worker

Step 2 — Krish creates private key

Krish first generates his private RSA key locally: openssl genrsa -out private-krish.key 2048

Blog post image

Why this step?

This key proves Krish’s identity cryptographically.
Without this, Kubernetes cannot trust the user.

Step 3 — Krish creates CSR

Now Krish creates a Certificate Signing Request: openssl req -new -key private-krish.key -out csr-krish.csr -subj "/CN=krish/O=dev-team"

Blog post image

Meaning:

  • CN = krish → actual Kubernetes username
  • O = dev-team → group name

Krish now sends this CSR to the cluster admin for approval.

Step 4 — Admin creates Kubernetes CSR object

Cluster admin must:

  1. Base64 encode the CSR
  2. Create Kubernetes CSR object
  3. Approve it

Before creating a Kubernetes CertificateSigningRequest object, the cluster admin must Base64-encode the CSR file.
This is required because Kubernetes YAML and JSON manifests can only safely store text data, while the CSR file contains binary-formatted certificate content.

Base64 encoding converts this binary data into a plain text string, allowing it to be embedded inside the CSR object specification without corruption or formatting issues.


Once the CSR is approved, Kubernetes decodes the data internally and uses it to generate the signed certificate for the user.

In short, Base64 encoding ensures the CSR can be safely stored, transmitted, and processed inside Kubernetes resources.

To flatten this, we have different options. One way is: cat adam.csr | base64 | tr -d "\n" This command prints the encoded and flattened output on the screen, which you can then copy and paste directly into the YAML file.

However, we will use a cleaner approach: CSR=$(base64 -w 0 krishna.csr) This command flattens the CSR, encodes it in Base64, and stores the result in the shell variable $CSR, which we can then use directly inside the YAML file.

Blog post image


We will create the CSR object by applying the file using the command: kubectl apply -f csr-krish-object.yaml

Then, we will run kubectl get csr to view the created object, and finally use: kubectl certificate approve krishna to approve the CSR.

Blog post image

Now as a cluster CA, we have signed Krish’s certificate.

Step 5 — Admin returns signed certificate to Krish

Admin extracts the approved certificate: kubectl get csr krishna -o jsonpath='{.status.certificate}' | base64 -d > signed-csr-krish.crt

Blog post image

So far, the user krish has been created with a certificate and an approved CSR. However, if krish logs in right now, he cannot do anything because, although the context is approved, it does not have any permissions. To provide permissions, we will use a Role and RoleBinding.

Step 6 — Admin gives namespace permissions (Role & RoleBinding)

What is a Role?

A Role defines what actions are allowed inside a namespace.

Let’s create a role.yaml file to provide the required permissions, and then apply this YAML using: kubectl apply -f role.yaml to create the Role.

1apiVersion: rbac.authorization.k8s.io/v1
2kind: Role
3metadata:
4  name: reader
5  namespace: default
6rules:
7# Core API group
8- apiGroups: [""]
9  resources: ["pods", "services", "configmaps"]
10  verbs: ["get", "list", "watch"]
11
12# apps API group
13- apiGroups: ["apps"]
14  resources: ["deployments", "replicasets", "statefulsets"]
15  verbs: ["get", "list", "watch"]
16
17# batch API group
18- apiGroups: ["batch"]
19  resources: ["jobs", "cronjobs"]
20  verbs: ["get", "list", "watch"]
21
22# networking.k8s.io API group
23- apiGroups: ["networking.k8s.io"]
24  resources: ["ingresses", "networkpolicies"]
25  verbs: ["get", "list", "watch"]

What is a RoleBinding?

A RoleBinding connects user → role

Now, let’s create rolebinding.yaml and apply it using: kubectl apply -f rolebinding.yaml to create the RoleBinding.

1apiVersion: rbac.authorization.k8s.io/v1
2kind: RoleBinding
3metadata:
4  name: read-stuff
5  namespace: default
6subjects:
7- kind: User
8  name: krish  # MUST match CN exactly
9  apiGroup: rbac.authorization.k8s.io
10roleRef:
11  kind: Role
12  name: reader
13  apiGroup: rbac.authorization.k8s.io

Once this is created, you can verify the access.
Since we set the username (Common Name) in the CSR as krish and used the same name in the RoleBinding, we can use the krish context to check what actions are allowed.

Blog post image


We can get pods but not nodes, because Roles are namespace-scoped.
To access nodes as krish, we need to create a ClusterRole and ClusterRoleBinding.

Step 7 — Admin gives cluster permissions (ClusterRole)

What is a ClusterRole?

A ClusterRole gives permissions across the entire cluster,
needed for non-namespaced resources like:

  • nodes
  • namespaces
  • persistent volumes

Similar to the Role, we will now create a ClusterRole and ClusterRoleBinding, and then apply them.

1apiVersion: rbac.authorization.k8s.io/v1
2kind: ClusterRole
3metadata:
4  name: node-reader
5rules:
6- apiGroups: [""]
7  resources: ["nodes"]
8  verbs: ["get", "list", "watch"]

ClusterRoleBinding

This connects:

Krish → ClusterRole → whole cluster access

1apiVersion: rbac.authorization.k8s.io/v1
2kind: ClusterRoleBinding
3metadata:
4  name: node-reader-binding 
5subjects:
6- kind: User
7  name: krishna
8  apiGroup: rbac.authorization.k8s.io
9roleRef:
10  kind: ClusterRole
11  name: node-reader
12  apiGroup: rbac.authorization.k8s.io

Now, the user krish has permissions at both the namespace level and the cluster level, so he can access resources in both scopes.

Blog post image

Step 8 — Krish logs in

These commands configure Kubernetes to recognize krish as a valid user and allow him to log in using the approved certificate.

First, the credentials command links krish’s signed certificate and private key to a user entry in the kubeconfig file.

Next, a new context is created that connects this user to the target cluster.
Finally, switching to this context logs us in as krish, allowing us to test the permissions that were assigned through RBAC.

Add credentials

1kubectl config set-credentials krish \
2  --client-certificate=signed-csr-krish.crt \
3  --client-key=private-krish.key
4

Create context

To find out cluster name run: kubectl config get-clusters

1kubectl config set-context krish \
2  --cluster=kind-kind \
3  --user=krish

Switch to Krish

kubectl config use-context krish

Blog post image

In the final verification step, we switch to the krish context and check the accessible resources.


The output shows that krish can successfully run kubectl get pods, confirming that the Role and RoleBinding permissions are working at the namespace level.
Krish is also able to run kubectl get nodes, which proves that the ClusterRole and ClusterRoleBinding permissions are correctly applied at the cluster level.

However, when attempting to list namespaces using kubectl get ns, the request is forbidden, indicating that krish does not have permission to access that resource.


This confirms that Kubernetes RBAC is functioning as expected, granting only the specific permissions that were explicitly assigned.