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