1. Overview

In a Kubernetes environment, securing and controlling access to the cluster is crucial for efficient and secure management. User management is a key element. As in any well-structured system, authentication is the first step in any request to the API Server. Whether it’s normal users, applications inside pods, or kubelets, they all go through an authentication and authorization process when interacting with the cluster. Kubernetes provides several methods for managing these processes.

In this tutorial, we’ll start by discussing the concepts of authentication and certificates in Kubernetes. Then, we’ll explore managing permissions using the RBAC (Role-Based Access Control) method, reviewing the steps necessary to add users in Kubernetes using kubectl.

2. Understand Authentication in Kubernetes

When a request arrives at the API server, the first step is authentication, which validates the identity of the person or object that submitted the API request. Once the request is authenticated, it passes through authorization, which determines whether the requester is allowed to perform the requested action, before being intercepted by admission controllers.

2.1. Users

Kubernetes clusters have two types of users: Kubernetes-managed service accounts and normal users.

Normal users, who are typically real people requiring access to the cluster via kubectl, have their login names and IDs managed by external systems, independently of Kubernetes. External systems manage users, so Kubernetes API lacks user API objects. Usernames come from these external systems, such as certificates, tokens, password files, or external directory services.

In contrast, service accounts are users managed by the Kubernetes API. They are associated with credentials stored as secrets, mounted in pods, allowing cluster processes to communicate with the Kubernetes API.

2.2. Certificates

Kubernetes provides configurable authentication. Thus, we can define various methods for the API server, with the certificate plugin being one of the most commonly used.

Although Kubernetes doesn’t have a user API, the cluster’s certificate authority (CA) considers any user presenting a valid certificate as authenticated. Kubernetes determines the username from the common name field in the certificate’s “subject” (for example, “/CN=baeldung”).

We use certificates to authenticate to the cluster, and we store the user’s certificate information in the default kubeconfig file, $HOME/.kube/config. For example, when we work with kubectl, it loads this configuration file to read the certificate information and locate the cluster to submit the API request to:

$ kubectl get nodes -v 6
I0715 14:05:07.282529 2050251 loader.go:395] Config loaded from file:  /home/vagrant/.kube/config
I0715 14:05:07.308604 2050251 round_trippers.go:553] GET https://192.168.121.130:6443/api/v1/nodes?limit=500 200 OK in 10 milliseconds
NAME             STATUS     ROLES           AGE   VERSION
control-plane1   Ready      control-plane   80d   v1.29.1
node1            Ready      <none>          80d   v1.29.1
node2            NotReady   <none>          80d   v1.29.1
node3            NotReady   <none>          80d   v1.29.1

This shows the kubeconfig file loading in the first line.

We can use the kubectl command to display a well-formatted output of this file:

$ kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.121.130:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: DATA+OMITTED
    client-key-data: DATA+OMITTED

In the users section, we find the name kubernetes-admin.

Next, the configuration file reveals the client certificate data and the client key data. Specifically, the client certificate data (client-certificate-data) represents the certificate used in the API request to authenticate the user to the API server. Additionally, the client key data refers to the private key associated with this certificate. These data are masked for output purposes. But we can read these details by adding the –raw option to obtain the Base64 encoded strings associated with these certificates:

$ kubectl config view --raw

kubectl stores this information in the kubeconfig file.

3. Creating a Certificate-Based User

Now that we know a user needs a certificate issued by the Kubernetes cluster to authenticate and invoke an API, let’s see how to create a valid certificate for a user we’ll call baeldung for this example.

3.1. Generating a CSR

In Kubernetes, the API Server provides a certificate API for submitting a certificate signing request to create and sign X.509 certificates. Once signed, the certificate can be uploaded for user authentication in the cluster.

First, we create a private key using openssl:

$ openssl genrsa -out baeldung.key 2048

This writes a 2,048-bit private key to the file named baeldung.key. Next, with this private key, we can generate a Certificate Signing Request:

$ openssl req -new -key baeldung.key -out baeldung.csr -subj "/CN=baeldung"

It’s important to specify that the CN (Common Name) of the subject is the actual username we want to create, and O (Organization) represents the group when necessary.

We Base64 encode the baeldung.csr file before submitting it to the API server:

$ cat baeldung.csr | base64 | tr -d "\n" > baeldung-base64.csr

The baeldung-base64.csr file now contains the Base64 encoded CSR.

3.2. Submitting the CSR to the API Server

Now we can create a CertificateSigningRequest object and submit it to the Kubernetes cluster:

$ cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: baeldung
spec:
  groups:
  - system:authenticated  
  request: $(cat baeldung-base64.csr)
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 864000  # ten days
  usages:
  - client auth
EOF

In the metadata, we define baeldung as the name of the CertificateSigningRequest object. This is not the username that is part of the certificate but simply the name of this particular object.

Among the key elements in the specifications, we use the contents of the recently Base64 encoded baeldung-base64.csr file as the request value. The expirationSeconds field specifies the validity period of the requested certificate, measured in seconds from the approval of the CSR. For example, (864000 seconds) equates to a validity of 10 days from the issuance of the certificate.

Kubernetes v1.22 introduced spec.expirationSeconds; earlier versions and API servers before v1.22 don’t support this field and ignore it.

We can verify the CSR to see its current state:

$ kubectl get certificatesigningrequests
NAME       AGE   SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
baeldung   34s   kubernetes.io/kube-apiserver-client   kubernetes-admin   10d                 Pending

This request is not automatically approved, so the CSR is pending administrative approval.

We can manually approve the CSR using the kubectl certificate approve command:

$ kubectl certificate approve baeldung
certificatesigningrequest.certificates.k8s.io/baeldung approved

The CSR is updated and we can see that its status is now Approved,Issued:

$ kubectl get certificatesigningrequests baeldung
NAME       AGE   SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
baeldung   22m   kubernetes.io/kube-apiserver-client   kubernetes-admin   10d                 Approved,Issued

We have one hour to export the certificate after approving the CSR because Kubernetes periodically deletes CertificateSigningRequests that haven’t changed state for a certain period.

3.3. Retrieving the CSR Object Certificate

We can export the issued certificate from the CertificateSigningRequest:

$ kubectl get certificatesigningrequests baeldung -o jsonpath='{ .status.certificate }' | base64 --decode > baeldung.crt

The base64 –decode command decodes the certificate located in the status.certificate field, which is in Base64 format. We then redirect the output to the baeldung.crt file.

At this point, we have a private key baeldung.key and the baeldung.crt certificate we exported. We can use them together to create a kubeconfig file

3.4. Creating a kubeconfig File for the User

This step involves creating a kubeconfig file for the new user, allowing kubectl to authenticate to the Kubernetes cluster. First, we define the cluster details:

$ kubectl config set-cluster kubernetes-baeldung --server=https://192.168.121.130:6443 --certificate-authority=/etc/kubernetes/pki/ca.crt --embed-certs=true --kubeconfig=baeldung.conf

Here the cluster name is kubernetes-baeldung. This configures kubectl to connect to the Kubernetes server at https://192.168.121.130:6443, using the CA certificate located at /etc/kubernetes/pki/ca.crt and embedding the certificates in the kubeconfig baeldung.conf file.

Next, we configure the authentication information for the user:

$ kubectl config set-credentials baeldung --client-key=baeldung.key --client-certificate=baeldung.crt --embed-certs=true --kubeconfig=baeldung.conf

This command associates the client certificate and private key of the baeldung user with the kubeconfig baeldung.conf file.

To group the cluster and authentication information into a usable context, we define the context e.g. baeldung-context:

$ kubectl config set-context baeldung@kubernetes-baeldung --cluster=kubernetes-baeldung --user=baeldung --kubeconfig=baeldung.conf

The context specifies which cluster and user to use for kubectl operations.

To interact with the cluster, we set the active context in the kubeconfig file:

$ kubectl config use-context baeldung@kubernetes-baeldung --kubeconfig=baeldung.conf

This command configures the context by specifying the cluster information (–cluster=kubernetes-baeldung) and user information for kubectl operations.

4. Using RBAC to Control Access

Authorization allows a user to perform actions on API resources and happens after authentication. Kubernetes offers several authorization plugins, each implementing authorization differently.

In Kubeadm-based clusters, the default authorization modules are RBAC and Node. Specifically, RBAC controls access to resources by defining user roles and their associated functions.

First of all, we define a role that can act on the pods resources in the default namespace for example:

$ kubectl create role baeldung-role --verb=create,get,list --resource=pods --namespace default

Then we bind the user to this role so that he can perform the functions defined in the role. We use a RoleBinding for this, defining which user can access the resources defined in the role baeldung-role:

$ kubectl create rolebinding baeldung-rolebinding --role=baeldung-role --user=baeldung --namespace default

This allows the baeldung user to perform actions authorized by the baeldung-role on the resources specified in this namespace.

5. Testing the Configuration

To ensure the user configuration and RBAC roles are correctly set up, we test the configuration using the kubectl auth can-i command.

To begin with, let’s verify that the baeldung user can create pods in the default namespace:

$ kubectl auth can-i create pods --namespace=default --kubeconfig=baeldung.conf -v 6
I0718 17:05:01.839042  462870 loader.go:395] Config loaded from file:  baeldung.conf
I0718 17:05:01.860406  462870 round_trippers.go:553] GET https://192.168.121.130:6443/api/v1 200 OK in 9 milliseconds
I0718 17:05:01.864164  462870 round_trippers.go:553] POST https://192.168.121.130:6443/apis/authorization.k8s.io/v1/selfsubjectaccessreviews 201 Created in 3 milliseconds
yes

This command checks if the user has permission to perform specific actions on the cluster. The output shows that the baeldung user can create pods in the default namespace.

Following that**,** let’s check if the baeldung user can create deployments in the default namespace:

$ kubectl auth can-i create deployments --namespace=default --kubeconfig=baeldung.conf
no

The output indicates that the baeldung user doesn’t have permission to create deployments in the default namespace, which aligns with the permissions defined in the baeldung-role.

Alternatively, placing the baeldung.conf file in the $HOME/.kube directory of a Linux user loads it by default. Consequently, this eliminates the need to specify the —kubeconfig option for every command.

6. Conclusion

In this article, we explored the process of managing user authentication and permissions within a Kubernetes cluster, focusing on certificate-based user creation, kubeconfig configuration, and RBAC for access control.

Proper user management and access control are key to a secure Kubernetes environment. By following the steps to generate certificates, configure kubeconfig files, and set RBAC roles, we can ensure users have the right access and protect the cluster from unauthorized use.