Assign Roles to Users and Groups in Kubernetes Cluster

|
Last Updated:
|
|
Assign Roles to Users and Groups in Kubernetes Cluster

In this tutorial, you will learn how to assign roles to users and groups in Kubernetes cluster. With Kubernetes becoming the de facto standard for container orchestration, managing access control is paramount to ensuring the security and integrity of your clusters. This guide will take you through how you control access to various resources by assigning necessary roles or permissions to users or respective groups in a Kubernetes cluster.

Assign Roles to Users and Groups in Kubernetes Cluster

Introduction to Role-Based Access Control in Kubernetes

Kubernetes manages user access to its resources through Role-Based Access Control. Check the link below to read more on Kubernetes RBAC.

Introduction to Role-Based Access Control (RBAC) in Kubernetes

Creating User Accounts in Kubernetes

While Kubernetes does not natively support management of normal user accounts, it does support the management of service accounts.

Read more about user management in Kubernetes by following the guide below.

Kubernetes User Management: Creating Users, Groups and Service Accounts

As already stated that Kubernetes does not have objects which represent normal user accounts, we have created two users, alice and bob, on the cluster that depicts the native user accounts using X509 certificate authentication strategy.

kubectl config get-users
NAME
alice
bob
kubernetes-admin

We have also created a service account called monitoring;

kubectl get serviceaccounts
NAME         SECRETS   AGE
default      0         5d21h
monitoring   0         21h

Kubernetes RBAC API Objects

There are four RBAC API objects in Kubernetes that are used to enforce access restriction in the cluster. These objects include:

  • Role: The Role API object defines the rules representing a given set of permissions within a particular namespace. When creating, you need to specify the namespace on which the role apply to, otherwise, default namespace is selected.
  • ClusterRole: The ClusterRole on the other hand defines rules that represent a given set of permissions that applies cluster-wide rather than a specific namespace.
  • RoleBinding: The RoleBinding object binds (grants permissions defined in a role) the Role to a specific subject (user, group or a service account) within a specific namespace.
  • ClusterRoleBinding: The ClusterRoleBinding similarly grants permissions defined in the ClusterRole object to a subject in a cluster-wide manner.

Creating Kubernetes Roles

Switch to Cluster Admin Context

To be able to create roles, you need to use the cluster admin context, or at least a user that has rights to create roles. To list the contexts;

kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
          alice                         kubernetes   alice              default
          bob                           kubernetes   bob                default
*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin   default

The * specifies your current context. In the above example, we are using the Cluster administrator context.

You can switch to another context using the command;

kubectl config use-context <context-name>

Create User Role

In Kubernetes, you can create user roles imperatively via the kubectl create role command, or declaratively via the manifests file using kubectl apply command.

To create a role, you need to specify;

  • the name of the role itself
  • the verb: the API request permissions like getlistcreateupdatepatchwatchdelete, and deletecollection.
  • the resource object such Pod, Deployments, Services, ReplicaSets, PersistentVolume…

To imparatively create a role, that allows users to list pods and services;

kubectl create role <name of the role> --verb=<list of permissions comma seperated> --resource=<list of resources comma seperated>

For example to create a role called ReadOnly that allows users to list or get Pods and Services resource;

kubectl create role ReadOnly --verb=list,get --resource=pods,services

In essence:

  • list permission allows you to get a quick inventory of all resources of a specific kind (e.g kubectl get pods).
  • get permission allows you to get details of a particular named resource (kubectl get pod <name of the pod>.

For more options, check;

kubectl create role --help

You can also create a role using a manifest yaml file. For example, create a YAML file to define the role, verbs and resources.

vim readonly.yml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ReadOnly
  namespace: default
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - services
  verbs:
  - list
  - get

Pods and Services are core Kubernetes resources do not have API groups associated with them, hence ” “.

To create the role using manifest yaml file, use kubectl apply -f <manifest-file>.

kubectl apply -f readonly.yaml --server-side

Read more on kubectl apply –help.

Listing Roles

You can list namespaced roles using kubectl command as follows;

kubectl get roles
NAME       CREATED AT
ReadOnly   2024-05-19T19:55:52Z

If you want to see more details;

kubectl describe roles ReadOnly
Name:         ReadOnly
Labels:       
Annotations:  
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  pods       []                 []              [list get]
  services   []                 []              [list get]

You can also get the details in YAML format;

kubectl get roles ReadOnly -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"rbac.authorization.k8s.io/v1","kind":"Role","metadata":{"name":"ReadOnly","namespace":"default"},"rules":[{"apiGroups":[""],"resources":["pods","services"],"verbs":["list","get"]}]}
  creationTimestamp: "2024-05-19T19:55:52Z"
  name: ReadOnly
  namespace: default
  resourceVersion: "1095342"
  uid: 6f391085-b7e8-4823-be99-cbfb62a932e4
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - services
  verbs:
  - list
  - get

Assigning Roles to Users/Service Accounts

Create Kubernetes RoleBindings

Now that you have roles with respective permissions in place, how can you assign them to users? To assign users to specific roles, use the command kubectl create rolebinding;

To create a rolebinding, you need:

  • name of the rolebinding
  • role to assign
  • Subject to assign the role (user (–user), group (–group) or serviceaccount (–serviceaccount))

In essence, here is the command line syntax of creating a rolebinding

kubectl create rolebinding NAME --clusterrole=NAME|--role=NAME [--user=username] [--group=groupname]
[--serviceaccount=namespace:serviceaccountname] [--dry-run=server|client|none] [options]

For example, we want to assign a user names alice to the ReadOnly role created above;

kubectl create rolebinding ReadOnlyBind --role=ReadOnly --user=alice

You can also define your rolebinding in a manifest YAML file and install using kubectl apply command;

vim readonly-bind.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ReadOnlyBind
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ReadOnly
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: bob

You can then apply the manifest;

kubectl apply -f readonly-bind.yaml

To assign a role to a group, for example;

kubectl create rolebinding <bind name> --role=<role> --group=devs

To assign a role to a service account;

kubectl create rolebinding <bind name> --role=<role> --serviceaccount=<service account name>

Listing RoleBindings

You can get a list of rolebindings using the command;

kubectl get rolebindings
NAME           ROLE            AGE
ReadOnlyBind   Role/ReadOnly   5m39s

To get more details;

kubectl describe rolebindings ReadOnlyBind
Name:         ReadOnlyBind
Labels:       
Annotations:  
Role:
  Kind:  Role
  Name:  ReadOnly
Subjects:
  Kind  Name   Namespace
  ----  ----   ---------
  User  alice  

Verify Role Assignment and Access Permissions

As you can see in the command output above, user alice is given ReadOnly permissions (get and list) on all Pods/Services resources.

To verify that the user can access what is given via RBAC, this is how we will do the test!

  1. Either create a native Linux user account on the cluster, create Kubeconfig for the user and verify access to the cluster resources as per assigned roles. This is not scalable in production environment.
  2. As a cluster admin, switch to user’s context and check access.
  3. Create human user account as a service account and assign the roles as shown above, generate the service account token and use it remotely for API access to Kubernetes cluster from another system.

To use the first method of creating user’s native Linux account and check access to cluster resources;

sudo useradd -m -s /bin/bash alice
sudo passwd alice

Next, copy the user’s credentials that were added to the Kubeconfig file. See, we had already created alice credentials based on her X-509 certificate authentication method.

kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.122.60:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    namespace: default
    user: alice
  name: alice
- context:
    cluster: kubernetes
    namespace: default
    user: bob
  name: bob
- context:
    cluster: kubernetes
    namespace: default
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: alice
  user:
    client-certificate: users/alice/alice.crt
    client-key: users/alice/alice.key
- name: bob
  user:
    client-certificate: users/bob/bob.crt
    client-key: users/bob/bob.key
- name: kubernetes-admin
  user:
    client-certificate-data: DATA+OMITTED
    client-key-data: DATA+OMITTED

So, create user’s kubeconfig directory and copy its credentials.

sudo mkdir /home/alice/.kube/

Generate a portable Kubeconfig file for the user.

kubectl config view --flatten | sudo tee  /home/alice/.kube/config

Then edit it such that it only has credentials for the specific user, in this case, alice. See my updated kubeconfig file for the user alice.

sudo cat /home/alice/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJYU14STJpaWEzMDh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRBMU1UTXlNRE0xTlRoYUZ3MHpOREExTVRFeU1EUXdOVGhhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURCK1JJUm9odVJTb0hBS2U4RXZ1TnpqODJvUEV6YzhxWGFzdlprV3FRZU9PS05zYWpWLy9CNTMxTEkKMFd0blNmaE5MRXhCU2dYdmRxYXI2NGJUWWgzbzV1MExNaVBXTzFWZ25SM0xGcVpuWkNpMUdRUnJwZndSaU45cwpUczdwQWNnMFBISy9zdWF1RUprbnJZeGUwcjZtMHlLSFdBZ2tSN1lHUmIzYmI3YWNTNlRiYWF5RTFlS0NUblhECkpzTlZGU2lzckd5LzJCaVE2NHNDSGRtL21SYm94dnpCZ1EvckszckdQSEdxVWNQd0YwVG9qaVB6UVJkZ242N0gKbXJ3NHRESFo3QzQveU5PZTNWY0hvd0dJZTEybHN2TU5PVkJwZFlhTVNTcUZjL24yNWQ3R2dYcWdGbDdjc3JsdQpkbnBZeENHdUg3c29YZHZMRDZHS2VPNlBlS2piQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJSODd2ZHhLV0Q4RWhMK3ozei9CUmdENWVJbEN6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ2JZN0hwUVRNdwpFTVNnOWdWbjErTmJVRzUxWmlwUmNuQ2JDRXN3OFhBUFZVV2REallQekI4QitCdE9NU2NBY2l5QU5pZllMU01iClNOb2ZBeG95NnJUT0FtTDBHM2w2VUtvUEZUTExSSWxIR1hqdi9rQllHbTA1MWk0ZUVJdHBmblhuU3BCM0NSVnQKcWN5UFhqQmJVNFhIS2pVTzBibXNsMkVoZ1BPQm1oZ2k0VXRWYXk0eU9jcWN4ZS9MTFJjaE9DcS9DaEhYcmU5dQo1WEthRGVpUTZtZ081SDNYbFBqeGhtaThQcWUyTzJWdWQ3ZmMxeTg2d2lrRWhWU2ZNRTE4dk1ZV0VvLzhhckxmCmFuSGwveUJDY2c1dkg0RTdXYVJRajNPTTJkUTZjY0JiVlphd2w5MGVmTllZNFJ1OUFnbWNDd09qWVVFQWViK04KZlRzTnJDdytFV1lFCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    server: https://192.168.122.60:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    namespace: default
    user: alice
  name: alice
current-context: alice
kind: Config
preferences: {}
users:
- name: alice
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNyRENDQVpRQ0ZIa2FOdkZCczEvUzJTSDhsMWJWUk9OSW05eEJNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1CVXgKRXpBUkJnTlZCQU1UQ210MVltVnlibVYwWlhNd0hoY05NalF3TlRFNE1UY3pNVFUyV2hjTk1qVXdOVEU0TVRjegpNVFUyV2pBUU1RNHdEQVlEVlFRRERBVmhiR2xqWlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDCkFRb0NnZ0VCQUxMMGFqRk0yRWVuRGhEY3NhQlA4eDBaRDBpV2VqSG1WUk1veEQzejZqa0VKMDZCVnlUZTZlNmIKYmZPVEtRS0daUnh0Q1BsZXFSR3Y5a2tPUXhpYXpZSnpKeGlMaUh5bndvNVZpcFg1STBkNmUzNC92d2NhLzJ3cAorNmM1TDlpZThLWFhNZWcycW5ZT3NLWnZJcmFIbk16SWIvTVFwWmlHM0x6TTdJQUpJeHAzZGllSE1HMU02VWxCClc0YWxNd3FkZTZNY2xuWHdsUm52RFZEdjBta1pZQ3lCQTVTNVVZZkhOL1Nybk0rcVhmYk5qVlQxRG9FNzBEemwKRUowb2dtV1NTcENqR2krQWhqMktWenBacGo2NlhzUkg4dFE5eTdOTG91aGs3TGF4UFduQ2FvbDZaRFhPOUtVawp1eUF1SHRlWHROckhvQVVlNlZzWTJ2eFBPSitBamlrQ0F3RUFBVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBCnFBTlBDMkU4eWlBY2toZ2pzZTJlbzJVMXlJRGoxem1Yak9uZnpkdVVjUzVaeXNtU3llUGtqb2JMK2VsVmEzZmUKVktWSnh1Nkp0NWQ5eWtXLzRpWWxKWWpESFAxK3RZN1ZtMmU3Yi9vRkZSQXVVOFIwellHRVVzV2pveTI1a3BQMwpWMzIyYVhQL3hPNTlOTDRVN1ZMRnkyeTZPRWUvbXlUbjhHdHZUVnNOaTZ2WlBMcmEvcXA4S1B6cmZRdWJFYjRjCnpEWmNaOTV3aE95a1FtVEh5aGltaUROVWdnMDMwY2pFNzI1QytRaFhaRXRCd3RhQ2RGYmpzZmJHc1p1WldBREUKbUUzRVZtUE1PTzNjT3cwUEpoUTZuK2lhcml3ak9Ia1NGZThZZi9ldW5YMHhyeHBWUS9xL0ZkWTJQYStlTkEvUQo4U2Z2NUZlSVRwbUpUakc3akc5Vlh3PT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
    client-key-data: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ3k5R294VE5oSHB3NFEKM0xHZ1QvTWRHUTlJbG5veDVsVVRLTVE5OCtvNUJDZE9nVmNrM3VudW0yM3preWtDaG1VY2JRajVYcWtSci9aSgpEa01ZbXMyQ2N5Y1lpNGg4cDhLT1ZZcVYrU05IZW50K1A3OEhHdjlzS2Z1bk9TL1ludkNsMXpIb05xcDJEckNtCmJ5SzJoNXpNeUcvekVLV1lodHk4ek95QUNTTWFkM1luaHpCdFRPbEpRVnVHcFRNS25YdWpISloxOEpVWjd3MVEKNzlKcEdXQXNnUU9VdVZHSHh6ZjBxNXpQcWwzMnpZMVU5UTZCTzlBODVSQ2RLSUpsa2txUW94b3ZnSVk5aWxjNgpXYVkrdWw3RVIvTFVQY3V6UzZMb1pPeTJzVDFwd21xSmVtUTF6dlNsSkxzZ0xoN1hsN1RheDZBRkh1bGJHTnI4ClR6aWZnSTRwQWdNQkFBRUNnZ0VBQWNYS2lQcis4UndEZzBpUGpzaGR5TWpqZWdLOGZPWnkrNE93RUFGRWZnWDYKQmdxQ1YveTV2WkkySFNZMDZMcmswS1luWTBLU0VXcVZnYzlaZUhPakVwWFBwbzdLbHBuNUdDaVZSaE0wSDZpSQpnZExza1hYZzZsZTl1QWxGK3RQZ04zTnBlWVBkT09qelhyZTAzOVY1bVVHeC9Ta1lrVG5tWEd4bmRNNEZHN1hZCjFkNStsaGxYcitCcUxNN2drVjZiSFVUL0hQcURTN3RlZUg3UDh0OTVrUVA0cHlXOVdTNk0zejhvZEJDUXlrYTkKNDhlYWxoVU9LaDRmTWFmZ2lQb056VGlJZS9RQmhRVHQvQ0hQdGh3S2Y0KzVWN2ZycHVzUFNGdC9zTEVCdWpFUgp1WDM3alBkVjhKSk1VeHJMallOcHBVV3BRUld4bmxSVTZDQTJqOVFKYlFLQmdRRHlsV1ZRVnJUcUNmclNxbW9jCnJ1aUVjS09LY3FRV0hyQVc0dDFxZlV0Yms3bzIrTC8vMEF1MytONVNYVlprL3B0bjdsblVPV2RjbXJUODhyMWcKcDJqUUlubENDZ3VEN2dBakRYckZrYWtmZEhSSDA5NzBsOEhMRWhYWUFoR1RBVWEyMFo0S01CZERBTjZpcVhMMApwTnlob0RSQ2Jzdm01ZXFIWTZBY2ZDUWNJd0tCZ1FDODJpSmZMQ3ovczVvamc4NzNZNWZMdUxBZ201TWxML3YzCnBOcUptVU9OV3VrdW1QclROUy9FT3N1MUxxWjRvY0pKMERMWERRNDJBMXJuZGw3R2dNaDgreVFGMjFRNkVxMlUKdGNkYmVVSG9LT2JGRG9ySVNZM3BBMEsvUlZ0SE5vOWN3enRvR3BoaXpCbHduZFpMd1FDK25QbHJGWUZkNmp0cQpldEx3UlN5YlF3S0JnRFluRDVUZHBrbFFyUU4yTTNYdnZjeEM4TjhwTkdRVHVhK0NPWGRhUFFaV2RnMXJma0QzCkNvYXBNY2dsT2ZJVnZFOTVMK2htWUNLV0RxMGc2eEcyalhsWkdNU2JSWExRSUl1eXFLT09Icmo4NERCZ3BiYm8KWWNTWlp2THZrMGpEMGl0aG8rd1dURHNTNktCYlAyUkpvVThiV2s4eU9LWjAwT1FrWTB1NGtyOE5Bb0dBQmtncgpSSWN2cUFITmFza0RwVzhHcVp3bkg2Nk5JbnVLSWg2MXRrWUczVGpjOE5QZDVCQ3MyaFlxbUloSXVWS0lKL1JvCi9JWk9wclZOM00wdk1lTXV5Qm1DaFQ5YWVlUU5LaGt4M0hVWUlDVGNLRW5uaStvR2NtM05WcGQwQmRabXhtc28KR3JwbnYwR1N4eEE1QktRUzVrUktkNmxyZURoR2FiQlVPL0hSSGdrQ2dZRUF4T0RzMEdQNWFUL29kalJWQlZjcgo1QUhBRHQ5a0NNUHhIekJHRm5WM3JBMU9PZnJEeUtkNVNXQ1hBNGpkTmdwWm11QjNmRDYybjFzd2hZYWU1elJ1ClYzdDNVeFZ6cWxrZlF2MDlCZU1kVkxJcThQVGt5WnRhT0J0UVRFdzd4dW16S2ROMUFnS2xQQndWTmRHUmdsTkkKdFdwcUtrMFBna0tVZ2hBQmtYZ2ZzQXc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K

Update ownership of user’s files/directories;

sudo chown -R alice: /home/alice/.kube/

Switch to the user and check access to resources as defined in the roles;

sudo -u alice -i
alice@master:~$ pwd
/home/alice

Confirm your credentials are in place;

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

To check if you can access the resources defined in the role, use the kubectl auth can-i command;

alice@master:~$ kubectl auth can-i get pods
yes
alice@master:~$ kubectl auth can-i list pods
yes
alice@master:~$ kubectl auth can-i list services
yes
alice@master:~$ kubectl auth can-i get services
yes

As you can, yes confirms access to a resource. if no, then no access. See;

alice@master:~$ kubectl auth can-i '*' '*'
no

Read more on kubectl auth can-i –help.

alice@master:~$ kubectl get pods --all-namespaces 
Error from server (Forbidden): pods is forbidden: User "alice" cannot list resource "pods" in API group "" at the cluster scope

As you can see, if you have multiple users, managing this will be cumbersome!

You can also switch to the user’s context as Kubernetes cluster administrator and verify access;

kubectl config use-context <context-name>

E.g

kubectl config use-context alice
kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         alice                         kubernetes   alice              default
          bob                           kubernetes   bob                default
          kubernetes-admin@kubernetes   kubernetes   kubernetes-admin   default

Switch to user’s context;

kubectl config use-context alice
kubectl config current-context
$ kubectl auth can-i '*' '*'
no
$ kubectl auth can-i get '*'
no
$ kubectl auth can-i list '*'
no
$ kubectl auth can-i get 'pods'
yes
$ kubectl auth can-i get 'services'
yes
$ kubectl auth can-i list 'pods'
yes
$ kubectl auth can-i list 'services'
yes

Kubernetes Service Accounts and API Access (via Secrets and Tokens)

Similarly, if you want to access the cluster using API calls from another system, you can actually create the human user account as a service account as shown above. See our example;

kubectl get serviceaccounts
NAME         SECRETS   AGE
alice-svc    0         5h
kubectl describe roles alice-svc
Name:         alice-svc
Labels:       
Annotations:  
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  pods       []                 []              [get list]
  services   []                 []              [get list]

Next, create a service account secret. A secret is used to store confidential information, such as authentication credentials, tokens, API keys, or TLS certificates, that applications or users need to access securely.

Recent versions of Kubernetes do not automatically generate secrets for a service account when created. You can confirm with the command:

kubectl describe sa alice-svc
Name:                alice-svc
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>

Therefore, if you have a service account you need to use for API access, you can generate a secret and token for it. Note that there are two types of tokens based on their lifespan.

  • Long-lived tokens are associated with service accounts and have a prolonged lifespan, ideal for persistent processes needing continuous access to the Kubernetes API. These tokens persist until manually revoked, requiring careful management to mitigate security risks if compromised.
  • Time-limited/short-lived tokens are automatically generated with a restricted lifespan. Typically used for temporary access, they’re suitable for short-lived tasks or processes. Once expired, these tokens become invalid, necessitating the generation of new tokens for further access.

For our case, we will generate long-lived token for demo purposes.

You can generate a secret imperatively using a command line or declarative via the manifest file.

To create the secret using a manifest yaml file, simply run the command like below;

kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: alice-svc-secret
  annotations:
    kubernetes.io/service-account.name: alice-svc
type: kubernetes.io/service-account-token
EOF

This will create a Kubernetes secret named alice-svc-secret, which contains a token associated with the service account named alice-svc. This secret can be used for authentication purposes, allowing the service account alice-svc to access the Kubernetes API securely using the token stored in this secret.

To achieve the same using command line, get a token and generate a secret using it.

kubectl create secret generic <name> --from-literal=token=<your-token>

For testing purposes, let’s use a random base64-encoded string of 32 characters;

kubectl create secret generic alice-svc --from-literal=token=$(openssl rand -base64 32)

Note that there are different types of Kubernetes secrets:

  • Opaque: This is the most common type of secret in Kubernetes and is used hold arbitrary data, such as usernames, passwords, tokens, or any other sensitive information. Opaque secrets are stored as base64-encoded strings. When generting using kubectl command, you need to specify the generic option.
  • Service Account Tokens: These are used to store tokens that identifies a service account. They are used mostly to generate long-lived token, which is no longer recommended as from Kubernetes v1.22, which recommends use of short-live tokens. Note that also from K8S v1.22, these tokens are not automatically generated when you create a service account.
  • Docker config: These are the secrets that used to access Docker container image registry. When using kubectl command to generate the Docker config secret, you specify the command option, docker-registry.
  • TLS Secrets: These are used to store TLS certificates and private keys.

There are other types of secrets such as basic authentication and ssh authentication secrets.

Read more on kubectl create secret –help.

Once you have created the secret, annotate with the service account name. This basically means to associate with the secret with the service account.

kubectl annotate secret alice-svc-secret kubernetes.io/service-account.name=alice-svc

Check more details about the secret;

kubectl describe secrets alice-svc-secret
Name:         alice-svc-secret
Namespace:    default
Labels:       kubernetes.io/legacy-token-last-used=2024-05-24
Annotations:  kubernetes.io/service-account.name: alice-svc
              kubernetes.io/service-account.uid: b3ee12b4-175b-460b-8afe-b3bbb884e334

Type:  kubernetes.io/service-account-token

Data
====
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6InoyeTB4MmZuN0ZnM1Y4a043NXhVWGw0djNaX1VmZDBfN0xXWFVjMFh0em8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImFsaWNlLXN2Yy1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWxpY2Utc3ZjIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiYjNlZTEyYjQtMTc1Yi00NjBiLThhZmUtYjNiYmI4ODRlMzM0Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6YWxpY2Utc3ZjIn0.TnG9j6BVza-WdDxYh4g9sU3AbcMjSdv3x5Cbpug-dd5n04G7B7KtiilcUgpdB6pwrjPvN8QbF0uTDRtAK7p06Cn7eb0NBvRDYoh40Viv504F5MC2cb6nMoCZzlh6iOFiiMtccwfW4QgwM9IP1kSAP042CVn53HgXto8h11n8k6rcymEyniHwL-BmR64RmbEYOsiyiZQB-y9MqwXt8zVU6S_3w80a49PNxWrs-AjS9vtnyFTtaIEj4bC91V3W-7w1aerwY0x6mYjkWodz8dGqzKHWV0hPOra1fN7ult6JjZFkSf2r1_QMGky5myS_mQbViYhqxz302hZsfsGv9eqy1w
ca.crt:     1107 bytes
namespace:  7 bytes

You can now verify access to the cluster resources from another system via API.

For this, you need to generate the user’s token. You can simply copy the value of the token in the command output above. However, you can also run this command to extract it.

kubectl get secret alice-svc-secret -o=jsonpath='{.data.token}' | base64 --decode
eyJhbGciOiJSUzI1NiIsImtpZCI6InoyeTB4MmZuN0ZnM1Y4a043NXhVWGw0djNaX1VmZDBfN0xXWFVjMFh0em8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImFsaWNlLXN2Yy1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWxpY2Utc3ZjIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiYjNlZTEyYjQtMTc1Yi00NjBiLThhZmUtYjNiYmI4ODRlMzM0Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6YWxpY2Utc3ZjIn0.TnG9j6BVza-WdDxYh4g9sU3AbcMjSdv3x5Cbpug-dd5n04G7B7KtiilcUgpdB6pwrjPvN8QbF0uTDRtAK7p06Cn7eb0NBvRDYoh40Viv504F5MC2cb6nMoCZzlh6iOFiiMtccwfW4QgwM9IP1kSAP042CVn53HgXto8h11n8k6rcymEyniHwL-BmR64RmbEYOsiyiZQB-y9MqwXt8zVU6S_3w80a49PNxWrs-AjS9vtnyFTtaIEj4bC91V3W-7w1aerwY0x6mYjkWodz8dGqzKHWV0hPOra1fN7ult6JjZFkSf2r1_QMGky5myS_mQbViYhqxz302hZsfsGv9eqy1w

Once you have the token, you can use it to authenticate your API calls. For example, you can use curl to make API calls to the Kubernetes API server.

curl -H "Authorization: Bearer $TOKEN" <RESOURCE URI>

For example, to check access from another system using the user’s secret above. Set the TOKEN variable to the token value above.

TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6InoyeTB4MmZuN0ZnM1Y4a043NXhVWGw0djNaX1VmZDBfN0xXWFVjMFh0em8ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImFsaWNlLXN2Yy1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWxpY2Utc3ZjIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiYjNlZTEyYjQtMTc1Yi00NjBiLThhZmUtYjNiYmI4ODRlMzM0Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6YWxpY2Utc3ZjIn0.TnG9j6BVza-WdDxYh4g9sU3AbcMjSdv3x5Cbpug-dd5n04G7B7KtiilcUgpdB6pwrjPvN8QbF0uTDRtAK7p06Cn7eb0NBvRDYoh40Viv504F5MC2cb6nMoCZzlh6iOFiiMtccwfW4QgwM9IP1kSAP042CVn53HgXto8h11n8k6rcymEyniHwL-BmR64RmbEYOsiyiZQB-y9MqwXt8zVU6S_3w80a49PNxWrs-AjS9vtnyFTtaIEj4bC91V3W-7w1aerwY0x6mYjkWodz8dGqzKHWV0hPOra1fN7ult6JjZFkSf2r1_QMGky5myS_mQbViYhqxz302hZsfsGv9eqy1w
curl -H "Authorization: Bearer $TOKEN" https://192.168.122.60:6443/api/v1/pods -k

The command tries to list pods on all namespaces in the Kubernetes cluster. Remeber Alice permissions revolves only around listing Pods and Services in the default namespace.

The command above will be denied! See;

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "pods is forbidden: User \"system:serviceaccount:default:alice-svc\" cannot list resource \"pods\" in API group \"\" at the cluster scope",
  "reason": "Forbidden",
  "details": {
    "kind": "pods"
  },
  "code": 403

List pods in the default namespace;

curl -H "Authorization: Bearer $TOKEN" https://192.168.122.60:6443/api/v1/namespaces/default/pods -k
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "resourceVersion": "1910734"
  },
  "items": []
curl -H "Authorization: Bearer $TOKEN" https://192.168.122.60:6443/api/v1/namespaces/default/services -k
{
  "kind": "ServiceList",
  "apiVersion": "v1",
  "metadata": {
    "resourceVersion": "1910774"
  },
  "items": [
    {
      "metadata": {
        "name": "kubernetes",
        "namespace": "default",
        "uid": "70ed9dfa-c4ea-46b4-9874-5a8e170efb16",
        "resourceVersion": "234",
        "creationTimestamp": "2024-05-13T20:41:04Z",
        "labels": {
          "component": "apiserver",
          "provider": "kubernetes"
        },
        "managedFields": [
          {
            "manager": "kube-apiserver",
            "operation": "Update",
            "apiVersion": "v1",
            "time": "2024-05-13T20:41:04Z",
            "fieldsType": "FieldsV1",
            "fieldsV1": {
              "f:metadata": {
                "f:labels": {
                  ".": {},
                  "f:component": {},
                  "f:provider": {}
                }
              },
              "f:spec": {
                "f:clusterIP": {},
                "f:internalTrafficPolicy": {},
                "f:ipFamilyPolicy": {},
                "f:ports": {
                  ".": {},
                  "k:{\"port\":443,\"protocol\":\"TCP\"}": {
                    ".": {},
                    "f:name": {},
                    "f:port": {},
                    "f:protocol": {},
                    "f:targetPort": {}
                  }
                },
                "f:sessionAffinity": {},
                "f:type": {}
              }
            }
          }
        ]
      },
      "spec": {
        "ports": [
          {
            "name": "https",
            "protocol": "TCP",
            "port": 443,
            "targetPort": 6443
          }
        ],
        "clusterIP": "10.96.0.1",
        "clusterIPs": [
          "10.96.0.1"
        ],
        "type": "ClusterIP",
        "sessionAffinity": "None",
        "ipFamilies": [
          "IPv4"
        ],
        "ipFamilyPolicy": "SingleStack",
        "internalTrafficPolicy": "Cluster"
      },
      "status": {
        "loadBalancer": {}
      }
    }
  ]
curl -H "Authorization: Bearer $TOKEN" https://192.168.122.60:6443/api/v1/namespaces/default/services -sk | jq -r '["NAME", "TYPE", "CLUSTER-IP", "EXTERNAL-IP", "PORT(S)", "AGE"], (.items[] | [.metadata.name, .spec.type, .spec.clusterIP, "<none>", (.spec.ports[0].port | tostring) + "/" + .spec.ports[0].protocol, .metadata.creationTimestamp]) | @tsv'
NAME	TYPE	CLUSTER-IP	EXTERNAL-IP	PORT(S)	AGE
kubernetes	ClusterIP	10.96.0.1		443/TCP	2024-05-13T20:41:04Z

And that is it!

Assigning Roles to Groups in Kubernetes

In our previous guide, we discussed how to create groups in Kubernetes.

While there is no native way of creating groups in a Kubernetes cluster itself, we used an example of creating users with X509 certificate authentication.

openssl x509 -in ~/.kube/users/dave/dave.crt -subject -noout

Output;

subject=CN = dave, O = devops-int
openssl x509 -in ~/.kube/users/carol/carol.crt -subject -noout
subject=CN = carol, O = devops-int

As you can see, both users belong to devops-int group.

Create a view-only role on Pods, Services and Deployments for devops-int group;

kubectl create role view-only --verb=get,list,watch --resource=pods,deployments,services

Assign the role to the group via role binding in the default namespace.

kubectl create rolebinding devops-view-only --group=devops-int --role=view-only

Verify that you can list or get pods, services, and deployments in the specified namespace.

Switch to dave context!

kubectl config use-context dave

Verify permissions;

kubectl auth can-i '*' '*'
no
kubectl auth can-i get '*'
no
kubectl auth can-i list '*'
no
kubectl auth can-i list pods
yes
kubectl auth can-i get pods
yes
kubectl auth can-i list services
yes
kubectl auth can-i get services
yes
kubectl auth can-i list deployments
yes
kubectl auth can-i get deployments
yes

Create and Assign ClusterRoles to Users and Groups

If you want to create roles that applies cluster-wide, you simply have to replace role option with clusterrole option in the kubectl create command as shown above.

kubectl create clusterrole <name> --verb=<permissions> --resource=<resources>

Before you can create your custom cluster role, you can view existing roles;

kubectl get clusterroles
NAME                                                                   CREATED AT
admin                                                                  2024-05-13T20:41:04Z
api-clusterrole                                                        2024-05-17T04:45:14Z
calico-cni-plugin                                                      2024-05-13T21:05:22Z
calico-kube-controllers                                                2024-05-13T21:05:22Z
calico-node                                                            2024-05-13T21:05:22Z
calico-typha                                                           2024-05-13T21:05:22Z
cluster-admin                                                          2024-05-13T20:41:04Z
edit                                                                   2024-05-13T20:41:04Z
kubeadm:get-nodes                                                      2024-05-13T20:41:05Z
system:aggregate-to-admin                                              2024-05-13T20:41:04Z
system:aggregate-to-edit                                               2024-05-13T20:41:04Z
system:aggregate-to-view                                               2024-05-13T20:41:04Z
system:auth-delegator                                                  2024-05-13T20:41:04Z
system:basic-user                                                      2024-05-13T20:41:04Z
system:certificates.k8s.io:certificatesigningrequests:nodeclient       2024-05-13T20:41:04Z
system:certificates.k8s.io:certificatesigningrequests:selfnodeclient   2024-05-13T20:41:04Z
system:certificates.k8s.io:kube-apiserver-client-approver              2024-05-13T20:41:04Z
system:certificates.k8s.io:kube-apiserver-client-kubelet-approver      2024-05-13T20:41:04Z
system:certificates.k8s.io:kubelet-serving-approver                    2024-05-13T20:41:04Z
system:certificates.k8s.io:legacy-unknown-approver                     2024-05-13T20:41:04Z
system:controller:attachdetach-controller                              2024-05-13T20:41:04Z
system:controller:certificate-controller                               2024-05-13T20:41:04Z
system:controller:clusterrole-aggregation-controller                   2024-05-13T20:41:04Z
system:controller:cronjob-controller                                   2024-05-13T20:41:04Z
system:controller:daemon-set-controller                                2024-05-13T20:41:04Z
system:controller:deployment-controller                                2024-05-13T20:41:04Z
system:controller:disruption-controller                                2024-05-13T20:41:04Z
system:controller:endpoint-controller                                  2024-05-13T20:41:04Z
system:controller:endpointslice-controller                             2024-05-13T20:41:04Z
system:controller:endpointslicemirroring-controller                    2024-05-13T20:41:04Z
system:controller:ephemeral-volume-controller                          2024-05-13T20:41:04Z
system:controller:expand-controller                                    2024-05-13T20:41:04Z
system:controller:generic-garbage-collector                            2024-05-13T20:41:04Z
system:controller:horizontal-pod-autoscaler                            2024-05-13T20:41:04Z
system:controller:job-controller                                       2024-05-13T20:41:04Z
system:controller:legacy-service-account-token-cleaner                 2024-05-13T20:41:04Z
system:controller:namespace-controller                                 2024-05-13T20:41:04Z
system:controller:node-controller                                      2024-05-13T20:41:04Z
system:controller:persistent-volume-binder                             2024-05-13T20:41:04Z
system:controller:pod-garbage-collector                                2024-05-13T20:41:04Z
system:controller:pv-protection-controller                             2024-05-13T20:41:04Z
system:controller:pvc-protection-controller                            2024-05-13T20:41:04Z
system:controller:replicaset-controller                                2024-05-13T20:41:04Z
system:controller:replication-controller                               2024-05-13T20:41:04Z
system:controller:resourcequota-controller                             2024-05-13T20:41:04Z
system:controller:root-ca-cert-publisher                               2024-05-13T20:41:04Z
system:controller:route-controller                                     2024-05-13T20:41:04Z
system:controller:service-account-controller                           2024-05-13T20:41:04Z
system:controller:service-controller                                   2024-05-13T20:41:04Z
system:controller:statefulset-controller                               2024-05-13T20:41:04Z
system:controller:ttl-after-finished-controller                        2024-05-13T20:41:04Z
system:controller:ttl-controller                                       2024-05-13T20:41:04Z
system:controller:validatingadmissionpolicy-status-controller          2024-05-13T20:41:04Z
system:coredns                                                         2024-05-13T20:41:05Z
system:discovery                                                       2024-05-13T20:41:04Z
system:heapster                                                        2024-05-13T20:41:04Z
system:kube-aggregator                                                 2024-05-13T20:41:04Z
system:kube-controller-manager                                         2024-05-13T20:41:04Z
system:kube-dns                                                        2024-05-13T20:41:04Z
system:kube-scheduler                                                  2024-05-13T20:41:04Z
system:kubelet-api-admin                                               2024-05-13T20:41:04Z
system:monitoring                                                      2024-05-13T20:41:04Z
system:node                                                            2024-05-13T20:41:04Z
system:node-bootstrapper                                               2024-05-13T20:41:04Z
system:node-problem-detector                                           2024-05-13T20:41:04Z
system:node-proxier                                                    2024-05-13T20:41:04Z
system:persistent-volume-provisioner                                   2024-05-13T20:41:04Z
system:public-info-viewer                                              2024-05-13T20:41:04Z
system:service-account-issuer-discovery                                2024-05-13T20:41:04Z
system:volume-scheduler                                                2024-05-13T20:41:04Z
tigera-operator                                                        2024-05-13T20:53:51Z
view                                                                   2024-05-13T20:41:04Z

To check permissions associated with view role;

kubectl describe clusterrole view
Name:         view
Labels:       kubernetes.io/bootstrapping=rbac-defaults
              rbac.authorization.k8s.io/aggregate-to-edit=true
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources                                    Non-Resource URLs  Resource Names  Verbs
  ---------                                    -----------------  --------------  -----
  bindings                                     []                 []              [get list watch]
  configmaps                                   []                 []              [get list watch]
  endpoints                                    []                 []              [get list watch]
  events                                       []                 []              [get list watch]
  limitranges                                  []                 []              [get list watch]
  namespaces/status                            []                 []              [get list watch]
  namespaces                                   []                 []              [get list watch]
  persistentvolumeclaims/status                []                 []              [get list watch]
  persistentvolumeclaims                       []                 []              [get list watch]
  pods/log                                     []                 []              [get list watch]
  pods/status                                  []                 []              [get list watch]
  pods                                         []                 []              [get list watch]
  replicationcontrollers/scale                 []                 []              [get list watch]
  replicationcontrollers/status                []                 []              [get list watch]
  replicationcontrollers                       []                 []              [get list watch]
  resourcequotas/status                        []                 []              [get list watch]
  resourcequotas                               []                 []              [get list watch]
  serviceaccounts                              []                 []              [get list watch]
  services/status                              []                 []              [get list watch]
  services                                     []                 []              [get list watch]
  controllerrevisions.apps                     []                 []              [get list watch]
  daemonsets.apps/status                       []                 []              [get list watch]
  daemonsets.apps                              []                 []              [get list watch]
  deployments.apps/scale                       []                 []              [get list watch]
  deployments.apps/status                      []                 []              [get list watch]
  deployments.apps                             []                 []              [get list watch]
  replicasets.apps/scale                       []                 []              [get list watch]
  replicasets.apps/status                      []                 []              [get list watch]
  replicasets.apps                             []                 []              [get list watch]
  statefulsets.apps/scale                      []                 []              [get list watch]
  statefulsets.apps/status                     []                 []              [get list watch]
  statefulsets.apps                            []                 []              [get list watch]
  horizontalpodautoscalers.autoscaling/status  []                 []              [get list watch]
  horizontalpodautoscalers.autoscaling         []                 []              [get list watch]
  cronjobs.batch/status                        []                 []              [get list watch]
  cronjobs.batch                               []                 []              [get list watch]
  jobs.batch/status                            []                 []              [get list watch]
  jobs.batch                                   []                 []              [get list watch]
  endpointslices.discovery.k8s.io              []                 []              [get list watch]
  daemonsets.extensions/status                 []                 []              [get list watch]
  daemonsets.extensions                        []                 []              [get list watch]
  deployments.extensions/scale                 []                 []              [get list watch]
  deployments.extensions/status                []                 []              [get list watch]
  deployments.extensions                       []                 []              [get list watch]
  ingresses.extensions/status                  []                 []              [get list watch]
  ingresses.extensions                         []                 []              [get list watch]
  networkpolicies.extensions                   []                 []              [get list watch]
  replicasets.extensions/scale                 []                 []              [get list watch]
  replicasets.extensions/status                []                 []              [get list watch]
  replicasets.extensions                       []                 []              [get list watch]
  replicationcontrollers.extensions/scale      []                 []              [get list watch]
  ingresses.networking.k8s.io/status           []                 []              [get list watch]
  ingresses.networking.k8s.io                  []                 []              [get list watch]
  networkpolicies.networking.k8s.io            []                 []              [get list watch]
  poddisruptionbudgets.policy/status           []                 []              [get list watch]
  poddisruptionbudgets.policy                  []                 []              [get list watch]

If you want to create your custom role, here is an example that allows you to view all resources.

kubectl create clusterrole view-only --verb=get,list,watch --resource=*

To assign a roles to user/service account or a group, you use clusterrolebinding.

kubectl create clusterrolebinding bob-view-cluster --clusterrole=view --user=bob

For a service account, replace –user with –serviceaccount and –group for group.

To confirm, either switch to user’s context or try API access with tokens.

kubectl config use-context bob
/sbin/ipkubectl auth can-i '*' '*'
no
/sbin/ipkubectl auth can-i list '*'
no
/sbin/ipkubectl auth can-i list services
yes
/sbin/ipkubectl auth can-i list deployments
yes

And that is it on how to create Kubernetes roles, assign them to the users/groups and verify.

Read more on Kubernetes API Access Control.

SUPPORT US VIA A VIRTUAL CUP OF COFFEE

We're passionate about sharing our knowledge and experiences with you through our blog. If you appreciate our efforts, consider buying us a virtual coffee. Your support keeps us motivated and enables us to continually improve, ensuring that we can provide you with the best content possible. Thank you for being a coffee-fueled champion of our work!

Photo of author
Kifarunix
Linux Certified Engineer, with a passion for open-source technology and a strong understanding of Linux systems. With experience in system administration, troubleshooting, and automation, I am skilled in maintaining and optimizing Linux infrastructure.

Leave a Comment