Skip to content

Module 1.5: GUI Security (Kubernetes Dashboard)

Hands-On Lab Available
K8s Cluster advanced 30 min
Launch Lab ↗

Opens in Killercoda in a new tab

Complexity: [MEDIUM] - Common attack surface

Time to Complete: 30-35 minutes

Prerequisites: RBAC knowledge from CKA, Module 1.1 (Network Policies)


After completing this module, you will be able to:

  1. Configure Kubernetes Dashboard with authentication and least-privilege RBAC
  2. Audit dashboard deployments for exposed services and overly permissive ServiceAccounts
  3. Implement network-level restrictions to limit dashboard access to authorized users
  4. Evaluate whether to deploy, restrict, or remove GUI components in production clusters

The Kubernetes Dashboard has been a notorious attack vector. In 2018, Tesla’s Kubernetes cluster was compromised through an exposed dashboard—attackers used it to mine cryptocurrency. A misconfigured dashboard gives attackers full cluster control with a nice GUI.

CKS tests your ability to secure or restrict web-based cluster access.


┌─────────────────────────────────────────────────────────────┐
│ DASHBOARD ATTACK SCENARIO │
├─────────────────────────────────────────────────────────────┤
│ │
│ Common Misconfiguration: │
│ │
│ Internet ────► Dashboard (exposed) ────► Full cluster │
│ access! │
│ │
│ What goes wrong: │
│ ───────────────────────────────────────────────────────── │
│ 1. Dashboard exposed without authentication │
│ 2. Dashboard uses cluster-admin ServiceAccount │
│ 3. Skip button allows anonymous access │
│ 4. No NetworkPolicy restricting access │
│ │
│ Result: │
│ ⚠️ Anyone can view secrets │
│ ⚠️ Anyone can deploy pods (cryptominers!) │
│ ⚠️ Anyone can delete resources │
│ ⚠️ Full cluster compromise │
│ │
│ Real incident: Tesla (2018) │
│ └── Attackers mined crypto using exposed dashboard │
│ │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ DASHBOARD ACCESS MODES │
├─────────────────────────────────────────────────────────────┤
│ │
│ Option 1: Don't Install It │
│ ───────────────────────────────────────────────────────── │
│ Most secure. Use kubectl instead. │
│ CLI is more secure than GUI. │
│ │
│ Option 2: Read-Only Access │
│ ───────────────────────────────────────────────────────── │
│ Dashboard can view but not modify. │
│ Use minimal RBAC permissions. │
│ │
│ Option 3: Authenticated Access Only │
│ ───────────────────────────────────────────────────────── │
│ Require token or kubeconfig login. │
│ No skip button. │
│ │
│ Option 4: Internal Access Only │
│ ───────────────────────────────────────────────────────── │
│ kubectl proxy or port-forward required. │
│ No external exposure. │
│ │
└─────────────────────────────────────────────────────────────┘

Stop and think: The Tesla breach happened because their dashboard was exposed without authentication. But the dashboard needed a ServiceAccount with permissions to read secrets. Why would anyone give a dashboard cluster-admin? Think about the convenience-vs-security trade-off that leads to this misconfiguration.

Terminal window
# Official dashboard installation
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
# Verify deployment
kubectl get pods -n kubernetes-dashboard
kubectl get svc -n kubernetes-dashboard
# Read-only dashboard service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: dashboard-readonly
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dashboard-readonly
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "daemonsets", "replicasets", "statefulsets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-readonly
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dashboard-readonly
subjects:
- kind: ServiceAccount
name: dashboard-readonly
namespace: kubernetes-dashboard
Terminal window
# Create token for the service account
kubectl create token dashboard-readonly -n kubernetes-dashboard
# Or create a long-lived secret (older method)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: dashboard-readonly-token
namespace: kubernetes-dashboard
annotations:
kubernetes.io/service-account.name: dashboard-readonly
type: kubernetes.io/service-account-token
EOF
# Get the token
kubectl get secret dashboard-readonly-token -n kubernetes-dashboard -o jsonpath='{.data.token}' | base64 -d

Terminal window
# Start proxy (only accessible from localhost)
kubectl proxy
# Access dashboard at:
# http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
Terminal window
# Forward dashboard port
kubectl port-forward -n kubernetes-dashboard svc/kubernetes-dashboard 8443:443
# Access at https://localhost:8443
# Use token to authenticate
# Expose dashboard as NodePort
apiVersion: v1
kind: Service
metadata:
name: kubernetes-dashboard-nodeport
namespace: kubernetes-dashboard
spec:
type: NodePort
selector:
k8s-app: kubernetes-dashboard
ports:
- port: 443
targetPort: 8443
nodePort: 30443

Warning: NodePort exposes on all nodes. Use NetworkPolicy to restrict access!


What would happen if: You deploy the dashboard with a read-only ServiceAccount, but a user logs in with a token from a different ServiceAccount that has cluster-admin. Does the dashboard’s ServiceAccount RBAC protect you? (Hint: the dashboard acts on behalf of the logged-in user.)

Pause and predict: Your team exposes the dashboard via a LoadBalancer Service for convenience. What’s the attack surface compared to kubectl proxy? List at least 3 additional risks.

# Only allow access from specific namespace/pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: dashboard-access
namespace: kubernetes-dashboard
spec:
podSelector:
matchLabels:
k8s-app: kubernetes-dashboard
policyTypes:
- Ingress
ingress:
# Only from admin namespace
- from:
- namespaceSelector:
matchLabels:
name: admin-access
ports:
- port: 8443

The dashboard has a “Skip” button that allows anonymous access. Disable it:

# In dashboard deployment, add argument
spec:
containers:
- name: kubernetes-dashboard
args:
- --auto-generate-certificates
- --namespace=kubernetes-dashboard
- --enable-skip-login=false # Disable skip button

Or patch existing deployment:

Terminal window
kubectl patch deployment kubernetes-dashboard -n kubernetes-dashboard \
--type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--enable-skip-login=false"}]'

If you must expose dashboard externally:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kubernetes-dashboard
namespace: kubernetes-dashboard
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# Client certificate authentication
nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
nginx.ingress.kubernetes.io/auth-tls-secret: "kubernetes-dashboard/ca-secret"
spec:
ingressClassName: nginx
tls:
- hosts:
- dashboard.example.com
secretName: dashboard-tls
rules:
- host: dashboard.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kubernetes-dashboard
port:
number: 443

┌─────────────────────────────────────────────────────────────┐
│ DASHBOARD SECURITY CHECKLIST │
├─────────────────────────────────────────────────────────────┤
│ │
│ □ Do you really need the dashboard? │
│ └── Consider kubectl or Lens instead │
│ │
│ □ Minimal RBAC permissions │
│ └── Never use cluster-admin │
│ └── Read-only if possible │
│ │
│ □ Skip button disabled │
│ └── --enable-skip-login=false │
│ │
│ □ Access restricted │
│ └── kubectl proxy or port-forward │
│ └── NetworkPolicy limiting source │
│ │
│ □ If exposed externally │
│ └── TLS required │
│ └── mTLS client certificates │
│ └── VPN access only │
│ │
│ □ Token-based authentication only │
│ └── Short-lived tokens preferred │
│ └── No basic auth │
│ │
└─────────────────────────────────────────────────────────────┘

Terminal window
# Check current dashboard permissions
kubectl get clusterrolebinding | grep dashboard
kubectl describe clusterrolebinding kubernetes-dashboard
# If using cluster-admin, create restricted role instead
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dashboard-viewer
rules:
- apiGroups: [""]
resources: ["pods", "services", "nodes"]
verbs: ["get", "list"]
EOF
# Update binding
kubectl delete clusterrolebinding kubernetes-dashboard
kubectl create clusterrolebinding kubernetes-dashboard \
--clusterrole=dashboard-viewer \
--serviceaccount=kubernetes-dashboard:kubernetes-dashboard
Terminal window
# Patch dashboard to disable skip
kubectl patch deployment kubernetes-dashboard -n kubernetes-dashboard \
--type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--enable-skip-login=false"}]'
# Verify
kubectl get deployment kubernetes-dashboard -n kubernetes-dashboard -o yaml | grep skip
Terminal window
# Create NetworkPolicy to restrict access
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: dashboard-restrict
namespace: kubernetes-dashboard
spec:
podSelector:
matchLabels:
k8s-app: kubernetes-dashboard
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
dashboard-access: "true"
EOF

┌─────────────────────────────────────────────────────────────┐
│ DASHBOARD ALTERNATIVES │
├─────────────────────────────────────────────────────────────┤
│ │
│ kubectl (CLI) │
│ ───────────────────────────────────────────────────────── │
│ • Most secure - uses kubeconfig │
│ • Full functionality │
│ • Scriptable │
│ │
│ Lens (Desktop App) │
│ ───────────────────────────────────────────────────────── │
│ • Local GUI application │
│ • Uses your kubeconfig │
│ • No cluster-side components │
│ │
│ K9s (Terminal UI) │
│ ───────────────────────────────────────────────────────── │
│ • Terminal-based GUI │
│ • Uses your kubeconfig │
│ • Very efficient for operations │
│ │
│ Rancher/OpenShift Console │
│ ───────────────────────────────────────────────────────── │
│ • Enterprise-grade │
│ • Built-in authentication │
│ • More secure by design │
│ │
└─────────────────────────────────────────────────────────────┘

  • The Tesla breach in 2018 happened because their Kubernetes dashboard was exposed without password protection. Attackers deployed crypto-mining containers.

  • Dashboard v2.0+ disabled the skip button by default. Older versions had it enabled, making anonymous access trivially easy.

  • The dashboard pods themselves need RBAC permissions to read cluster resources. Limiting the dashboard’s ServiceAccount limits what users can see.

  • kubectl proxy is secure because it only binds to localhost and uses your kubeconfig credentials. The dashboard sees your permissions, not elevated ones.


MistakeWhy It HurtsSolution
Using cluster-admin for dashboardFull cluster access for attackersCreate minimal RBAC
Exposing via LoadBalancerPublic internet accessUse kubectl proxy
Leaving skip button enabledAnonymous access possible—enable-skip-login=false
No NetworkPolicyAny pod can reach dashboardRestrict ingress sources
Not updating dashboardKnown vulnerabilitiesKeep updated

  1. Your SOC team discovers an unknown IP address accessing the Kubernetes dashboard at 3 AM. The dashboard is exposed via a LoadBalancer Service, and the attacker is browsing secrets across all namespaces. When you check the dashboard’s ServiceAccount, it’s bound to cluster-admin. What immediate steps do you take, and how should the dashboard have been deployed to prevent this?

    Answer Immediate response: delete or scale down the dashboard deployment to stop the breach, then rotate any secrets the attacker viewed. Long-term fix: never bind the dashboard to `cluster-admin` -- create a read-only ClusterRole with minimal permissions (get/list on specific resources only). Access should be through `kubectl proxy` (binds to localhost only, uses your kubeconfig credentials), not a LoadBalancer. Add a NetworkPolicy to restrict ingress sources, and disable the skip button with `--enable-skip-login=false`. The dashboard should inherit the logged-in user's RBAC permissions, not have its own elevated access.
  2. A developer reports they can access the Kubernetes dashboard without entering a token — they just click “Skip” and get full visibility into the cluster. The security team is alarmed. What dashboard argument prevents this, and why is the skip button dangerous even if the dashboard’s ServiceAccount has read-only permissions?

    Answer Add `--enable-skip-login=false` to the dashboard container arguments to remove the skip button. Even with read-only permissions, the skip button is dangerous because it allows completely unauthenticated access -- anyone who can reach the dashboard URL can view pod logs, ConfigMaps, environment variables, and service configurations. This reconnaissance data helps attackers plan further attacks. Additionally, if someone later escalates the ServiceAccount permissions (intentionally or accidentally), all anonymous users inherit those elevated permissions. Authentication should always be required.
  3. During a penetration test, the tester discovers the Kubernetes dashboard is exposed via NodePort 30443. They can reach it from any machine on the corporate network. The dashboard requires a token, but the tester finds a ServiceAccount token in a ConfigMap in the default namespace. They use it to log in and see workloads. What chain of security failures led to this compromise?

    Answer Multiple failures combined: (1) The dashboard was exposed via NodePort instead of using `kubectl proxy` or port-forward, making it accessible from the network. (2) No NetworkPolicy restricted which sources could reach the dashboard pods. (3) A ServiceAccount token was stored in a ConfigMap -- tokens should never be stored in ConfigMaps as they're not encrypted. (4) The token had sufficient permissions to view workloads. The fix requires defense in depth: switch to `kubectl proxy` access, add a NetworkPolicy limiting ingress to the dashboard, remove the token from the ConfigMap, use short-lived tokens via `kubectl create token`, and apply RBAC least privilege.
  4. Your organization is debating whether to install the Kubernetes dashboard in production. The ops team wants it for convenience; the security team wants to ban it. A compromise is proposed: install it but restrict access. Design a security configuration that makes the dashboard acceptable — cover access method, RBAC, authentication, and network controls.

    Answer A secure dashboard deployment requires four layers: (1) Access method: use `kubectl proxy` only -- this binds to localhost and requires kubeconfig authentication, eliminating network exposure entirely. Never use LoadBalancer, NodePort, or Ingress. (2) RBAC: create a custom ClusterRole with only `get` and `list` verbs on specific resources (pods, services, deployments) -- never use `cluster-admin`. Exclude secrets from viewable resources. (3) Authentication: disable the skip button with `--enable-skip-login=false` and require token-based login with short-lived tokens from `kubectl create token`. (4) Network: apply a NetworkPolicy with `ingress: []` (deny all ingress) so only `kubectl proxy` works. If the ops team needs more than this allows, consider Lens or K9s as alternatives that use local kubeconfig without cluster-side components.

Task: Secure a Kubernetes dashboard installation.

Terminal window
# Step 1: Install dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
# Step 2: Wait for deployment
kubectl wait --for=condition=available deployment/kubernetes-dashboard -n kubernetes-dashboard --timeout=120s
# Step 3: Create restricted ServiceAccount
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: dashboard-readonly
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dashboard-readonly
rules:
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-readonly
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dashboard-readonly
subjects:
- kind: ServiceAccount
name: dashboard-readonly
namespace: kubernetes-dashboard
EOF
# Step 4: Disable skip button
kubectl patch deployment kubernetes-dashboard -n kubernetes-dashboard \
--type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--enable-skip-login=false"}]'
# Step 5: Create NetworkPolicy
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: dashboard-ingress
namespace: kubernetes-dashboard
spec:
podSelector:
matchLabels:
k8s-app: kubernetes-dashboard
policyTypes:
- Ingress
ingress: [] # Deny all ingress - only kubectl proxy works
EOF
# Step 6: Get token for readonly user
kubectl create token dashboard-readonly -n kubernetes-dashboard
# Step 7: Access via proxy
kubectl proxy &
echo "Access dashboard at: http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/"
# Cleanup
kubectl delete namespace kubernetes-dashboard

Success criteria: Dashboard requires token, skip is disabled, NetworkPolicy restricts access.


Dashboard Risks:

  • Full cluster access if misconfigured
  • Skip button allows anonymous access
  • Public exposure invites attacks

Security Measures:

  • Minimal RBAC (never cluster-admin)
  • Disable skip button
  • Use kubectl proxy for access
  • NetworkPolicy restrictions

Best Practices:

  • Consider not installing dashboard
  • Use kubectl, Lens, or K9s instead
  • If needed, restrict access heavily
  • Token authentication only

Exam Tips:

  • Know how to create minimal ServiceAccount
  • Know the skip button argument
  • Understand kubectl proxy is most secure

You’ve finished Cluster Setup (10% of CKS). You now understand:

  • Network Policies for segmentation
  • CIS Benchmarks with kube-bench
  • Ingress TLS and security headers
  • Metadata service protection
  • Dashboard security hardening

Next Part: Part 2: Cluster Hardening - RBAC, ServiceAccounts, and API security.