Kubernetes: accessing the Kubernetes Dashboard with least privilege

The Kubernetes Dashboard provides a convenient web interface for viewing cluster resources.  However, if you are logged using a token tied to the ‘cluster-admin’ role, you will have privileges beyond what are typically necessary.

In this article, I will show you how to create a ServiceAccount and ClusterRole with limited privileges that can be used to access the Kubernetes Dashboard.

Prerequisite Kubernetes Dashboard

I will assume you have already installed the Kubernetes Dashboard.   If not, you can run the command below, and it will install the service and deployment into the “kubernetes-dashboard” namespace.

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.0/aio/deploy/recommended.yaml

Exposing Dashboard

Although you could expose the Dashboard service via NodePort or something like an NGINX Ingress, for simplicity and security we will use ‘kubectl port-forward’ to surface it only at the localhost:8443 port.

kubectl port-forward -n kubernetes-dashboard service/kubernetes-dashboard --address 0.0.0.0 8443:443

Create Service Account with limited access

Overview

We want to create a service account that has limited privileges within the Dashboard interface.  Specifically, we will allow it to get+list+watch: namespaces, pods, configmaps, services, and pod logs in any namespace.  Additionally, it will be able to view secrets, but only in the default namepace (i.e. not kube-system or any other namespaces).

The manifest files used in the sections below are also accessible on my github.

Create service account

This account will be provided a limited role in the Dashboard interface.

# limited-user-sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: limited-user
  namespace: kubernetes-dashboard

Apply to the cluster.

kubectl apply -f limited-user-sa.yaml

Create cluster level role

At the cluster level, we want to create a role that only allows access to namespaces, pods, configmap, services, and pod logs.  Resources not listed here will NOT be available, such as secrets, nodes, storage classes, Daemonset, ReplicaSet, etc.

# limited-clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
  name: limited-clusterrole
  namespace: default
rules:
- apiGroups:
  - ""
  resources: ["namespaces","pods", "configmaps", "services", "pods/log"]
  verbs:
  - get
  - list
  - watch

Apply it to the cluster.

kubectl apply -f limited-clusterrole.yaml

Create namespace level role

But we do want to grant the ability to view secrets only in the ‘default’ namespace.  For this we create a Role (not ClusterRole like the section above), and grant access.

# limited-ns-role.yaml 
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
  name: limited-ns-role
  namespace: default
rules:
- apiGroups:
  - ""
  resources: ["secrets"]
  verbs:
  - get
  - list
  - watch

Apply it to the cluster.

kubectl apply -f limited-ns-role.yaml

Bind service account to roles

With the ClusterRole and Role defined, we now need to bind these to the service account.  We use the ClusterRoleBinding and RoleBinding to associate the service account with these roles.

# limited-binding.yaml 
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: limited-binding
roleRef:
  kind: ClusterRole
  name: limited-clusterrole
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: limited-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: limited-ns-binding
roleRef:
  kind: Role
  name: limited-ns-role
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: limited-user
  namespace: kubernetes-dashboard

Apply it to the cluster.

kubectl apply -f limited-binding.yaml

Decode token for Dashboard login

Use the secret name from the ‘limited-user’ service account to retrieve the token necessary to login to the Dashboard web UI.

service_acct=limited-user
kubectl describe sa -n kubernetes-dashboard $service_acct
secret_name=$(kubectl get sa -n kubernetes-dashboard $service_acct -o=jsonpath="{.secrets[*].name}")

kubectl get secret -n kubernetes-dashboard $secret_name
limited_token=$(kubectl get secret -n kubernetes-dashboard $secret_name -o=jsonpath="{.data.token}" | base64 -d)

# use this long token at the Dashboard web UI
echo $limited_token

Validate access from Dashboard UI

Pull up the Dashboard UI at https://localhost:8443, which is how it has been port-forward in the earlier section.

Use the token from the section above, and press “Sign in”.

Notice that you can view the pods and secrets from the default namespace as shown below.

But if you select the “kube-system” namespace from the top dropdown, the list of secrets is empty.

This is because we only granted access to the secrets in the default namespace.  And if you view the logs of the dashboard, you will see that it attempted to list the secrets from the kube-system namespace, but did not have the privileges.

$ kubectl logs -n kubernetes-dashboard deployment/kubernetes-dashboard

...
2022/08/05 12:40:24 Non-critical error occurred during resource retrieval: secrets is forbidden: User "system:serviceaccount:kubernetes-dashboard:limited-user" cannot list resource "secrets" in API group "" in the namespace "kube-system
...

 

REFERENCES

kubernetes.io, deploying the Kubernetes dashboard

phoenixnap, kubectl port-forward

bobcares.com, kubernetes dashboard with NGINX ingress

ibm.com, serviceaccount and clusterrolebinding for dashboard access

upcloud.com, shows limited access ClusterRole for accessing dashboard

Carlos Jimenco, example of limited Role

NOTES

View name, kind, version of resources on cluster

kubectl api-resources

Create temporary Service Account with admin level access

So we can compare it with limited access, we will temporarily create a service account that has full ‘cluster-admin’ level access.

# dashboard-admin.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard-admin
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: dashboard-admin
  namespace: kube-system

Apply it to the cluster.  This will be deleted in a later step.

# temporary full admin user
kubectl apply -f dashboard-admin.yaml

Retrieve the login token for the admin role

service_acct=dashboard-admin
kubectl describe sa -n kubernetes-dashboard $service_acct
secret_name=$(kubectl get sa -n kubernetes-dashboard $service_acct -o=jsonpath="{.secrets[*].name}")

kubectl get secret -n kubernetes-dashboard $secret_name
admin_token=$(kubectl get secret -n kubernetes-dashboard $secret_name -o=jsonpath="{.data.token}" | base64 -d)
echo $admin_token

You can use this token to login to the dashboard.  When done, be sure to delete this service account and cluster role.

kubectl delete -f dashboard-admin.yaml