CKS — GUI Access Control
Exam Context
| Property | Value |
|---|---|
| Domain | Domain 1 — Cluster Setup |
| Weight | 10% |
| Objective | Minimize use of, and access to, GUI elements |
The Kubernetes Dashboard is the primary GUI element in a cluster. When misconfigured, it becomes one of the most dangerous attack vectors — providing unauthenticated access to create, delete, and modify any resource in the cluster.
The Threat: Why GUI Access Must Be Controlled
The Tesla Cryptomining Incident
In 2018, attackers discovered Tesla’s Kubernetes Dashboard was publicly accessible without authentication. They:
- Accessed the Dashboard over the internet (exposed via LoadBalancer with no auth)
- Used the Dashboard’s built-in terminal to deploy cryptomining containers
- Found AWS credentials in the Pod environment variables
- Accessed Tesla’s AWS S3 buckets containing proprietary telemetry data
- Operated undetected for months, consuming GPU resources for mining
Root cause: The Dashboard was deployed with a ClusterAdmin ServiceAccount and exposed without authentication.
Dashboard Attack Surface
| Risk | Description |
|---|---|
| Unauthenticated access | Default Dashboard install allows skip-login in older versions |
| ClusterAdmin binding | Dashboard ServiceAccount bound to cluster-admin ClusterRole |
| External exposure | Dashboard exposed via NodePort or LoadBalancer without authentication |
| RBAC bypass | Dashboard running with overprivileged ServiceAccount bypasses intended RBAC |
| Exec into Pods | Dashboard provides a built-in terminal to exec into any Pod |
| Secret viewing | Dashboard can display Secrets in plaintext |
Kubernetes Dashboard Security Configuration
Default Dashboard Installation
The standard Dashboard install:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
This installs the Dashboard in the kubernetes-dashboard namespace with a minimal ServiceAccount — but older versions and common tutorials bind it to cluster-admin, creating a massive security hole.
Check Current Dashboard Access
# Check if Dashboard is installed
kubectl get pods -n kubernetes-dashboard
# Check the ServiceAccount
kubectl get serviceaccount -n kubernetes-dashboard
# Check RBAC bindings for the Dashboard
kubectl get clusterrolebinding | grep dashboard
kubectl get rolebinding -n kubernetes-dashboard
Verify Dashboard Exposure
# Check how the Dashboard Service is exposed
kubectl get svc -n kubernetes-dashboard
Expected (secure) output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard ClusterIP 10.96.45.123 <none> 443/TCP 30m
ClusterIP is secure — it’s only accessible from within the cluster or via kubectl proxy.
Dangerous outputs:
# NodePort — accessible on every node's IP
kubernetes-dashboard NodePort 10.96.45.123 <none> 443:30443/TCP 30m
# LoadBalancer — accessible from the internet
kubernetes-dashboard LoadBalancer 10.96.45.123 203.0.113.50 443:30443/TCP 30m
Restricting Dashboard Access with RBAC
Step 1: Create a Read-Only ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dashboard-viewer
rules:
- apiGroups: [""]
resources: ["pods", "services", "namespaces", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "daemonsets", "statefulsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
What’s NOT included:
- No
create,update,delete,patchverbs — read only - No
secretsresource — cannot view Secret contents - No
execsub-resource — cannot exec into Pods - No
nodesresource — cannot view node details
Step 2: Create a Dedicated ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: dashboard-readonly
namespace: kubernetes-dashboard
Step 3: Bind the ClusterRole to the ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-readonly-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dashboard-viewer
subjects:
- kind: ServiceAccount
name: dashboard-readonly
namespace: kubernetes-dashboard
Step 4: Generate a Token for the ServiceAccount
# Create a token (Kubernetes 1.24+)
kubectl create token dashboard-readonly -n kubernetes-dashboard --duration=8h
This token is used to log into the Dashboard. It expires after 8 hours and has only read-only permissions.
Apply All RBAC Resources
kubectl apply -f dashboard-viewer-role.yaml
kubectl apply -f dashboard-readonly-sa.yaml
kubectl apply -f dashboard-readonly-binding.yaml
# Verify
kubectl auth can-i list pods --as=system:serviceaccount:kubernetes-dashboard:dashboard-readonly
# Expected: yes
kubectl auth can-i delete pods --as=system:serviceaccount:kubernetes-dashboard:dashboard-readonly
# Expected: no
kubectl auth can-i get secrets --as=system:serviceaccount:kubernetes-dashboard:dashboard-readonly
# Expected: no
kubectl auth can-i create pods/exec --as=system:serviceaccount:kubernetes-dashboard:dashboard-readonly
# Expected: no
Removing Dangerous Bindings
If the Dashboard was installed with excessive permissions, fix it:
Find and Remove cluster-admin Bindings
# Find any ClusterRoleBinding giving Dashboard cluster-admin
kubectl get clusterrolebinding -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") |
select(.subjects[]?.name | contains("dashboard")) | .metadata.name'
# Delete the dangerous binding
kubectl delete clusterrolebinding kubernetes-dashboard-admin
Verify No Excessive Permissions Remain
# List all bindings for the Dashboard ServiceAccount
kubectl get clusterrolebinding -o json | \
jq '.items[] | select(.subjects[]?.namespace=="kubernetes-dashboard") |
{name: .metadata.name, role: .roleRef.name}'
Disabling Public Dashboard Exposure
Convert LoadBalancer/NodePort to ClusterIP
# Patch the service to ClusterIP
kubectl patch svc kubernetes-dashboard -n kubernetes-dashboard \
-p '{"spec": {"type": "ClusterIP"}}'
Restrict with NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-dashboard-access
namespace: kubernetes-dashboard
spec:
podSelector:
matchLabels:
k8s-app: kubernetes-dashboard
policyTypes:
- Ingress
ingress:
# Only allow access from specific admin namespace
- from:
- namespaceSelector:
matchLabels:
purpose: admin
ports:
- protocol: TCP
port: 8443
This NetworkPolicy restricts Dashboard access to only Pods in namespaces labeled purpose: admin.
Access Dashboard Securely via kubectl proxy
# Start kubectl proxy (local access only)
kubectl proxy --port=8001
# Access the Dashboard at:
# http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
kubectl proxy authenticates through your kubeconfig and only listens on localhost. This is the safest way to access the Dashboard.
Access via Port-Forward
# Port-forward the Dashboard
kubectl port-forward -n kubernetes-dashboard svc/kubernetes-dashboard 8443:443
# Access at https://localhost:8443
Secure Alternatives to the Kubernetes Dashboard
| Tool | Type | Security Advantage |
|---|---|---|
| kubectl | CLI | No GUI attack surface; uses kubeconfig authentication |
| k9s | Terminal UI | Local terminal, reads kubeconfig, no network exposure |
| Lens | Desktop app | Local application, RBAC-aware, no cluster-side deployment |
| Headlamp | Web UI | OIDC/token auth built-in, more secure default configuration |
| Octant | Desktop app | Local rendering, no cluster-side components |
CKS recommendation: For production clusters, use kubectl or k9s over the Dashboard. The Dashboard adds attack surface without significant operational benefit for experienced operators.
When to Completely Remove the Dashboard
Remove the Dashboard entirely if:
- Your organization uses
kubectlor CLI tools exclusively - The Dashboard has been involved in a security incident
- You can’t justify the attack surface for the limited convenience it provides
- Compliance requirements restrict GUI-based cluster management
Removing the Dashboard
# Delete all Dashboard resources
kubectl delete namespace kubernetes-dashboard
# Verify removal
kubectl get namespace kubernetes-dashboard
# Expected: Error from server (NotFound): namespaces "kubernetes-dashboard" not found
# Check for lingering ClusterRoles and ClusterRoleBindings
kubectl get clusterrole | grep dashboard
kubectl get clusterrolebinding | grep dashboard
# Delete any remaining cluster-scoped resources
kubectl delete clusterrole kubernetes-dashboard 2>/dev/null
kubectl delete clusterrolebinding kubernetes-dashboard 2>/dev/null
Security Hardening Checklist
- Dashboard ServiceAccount uses minimal RBAC (never
cluster-admin) - Dashboard Service type is
ClusterIP(notNodePortorLoadBalancer) - Skip-login option is disabled
- Token authentication is enforced
- Tokens are short-lived (8h or less)
- NetworkPolicy restricts which Pods can access the Dashboard
cluster-adminClusterRoleBindings for Dashboard are removed- Dashboard namespace is labeled for NetworkPolicy selectors
- Regular audits check for Dashboard permission drift
Exam Tips
| Tip | Details |
|---|---|
| Know the risks | Be able to explain why an exposed Dashboard is dangerous |
| RBAC is the key control | Bind Dashboard SA to minimal ClusterRole, never cluster-admin |
| kubectl auth can-i | Use this to verify RBAC restrictions quickly |
| Service type matters | ClusterIP = safe, NodePort/LoadBalancer = exposed |
| kubectl proxy | The secure way to access ClusterIP services from your workstation |
| Don’t forget NetworkPolicy | Restrict ingress to Dashboard Pods from admin namespaces only |
Practice Questions
Q1: The Kubernetes Dashboard is deployed with a ClusterRoleBinding to cluster-admin. Explain the security risk and fix it.
Show Answer
Risk: A Dashboard bound to cluster-admin can:
- Create, modify, or delete any resource in any namespace
- Read all Secrets (including ServiceAccount tokens and TLS keys)
- Exec into any Pod (arbitrary code execution)
- Create Pods with hostPID, hostNetwork, or privileged security context
- Effectively grants anyone with Dashboard access full cluster control
If the Dashboard is exposed without authentication, this is equivalent to giving anonymous users root access to the entire cluster.
Fix:
# 1. Find and delete the dangerous binding
kubectl get clusterrolebinding | grep dashboard
kubectl delete clusterrolebinding kubernetes-dashboard-admin
# 2. Create a restricted ClusterRole
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dashboard-restricted
rules:
- apiGroups: [""]
resources: ["pods", "services", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
EOF
# 3. Bind it to the Dashboard ServiceAccount
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-restricted-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: dashboard-restricted
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
EOF
Q2: How would you securely access the Kubernetes Dashboard without exposing it outside the cluster?
Show Answer
Method 1: kubectl proxy
kubectl proxy --port=8001
# Access: http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
- Authenticates via kubeconfig
- Only listens on localhost
- No cluster-side network exposure
Method 2: kubectl port-forward
kubectl port-forward -n kubernetes-dashboard svc/kubernetes-dashboard 8443:443
# Access: https://localhost:8443
- Direct tunnel to the Dashboard service
- Only accessible locally
- Requires a valid kubeconfig
Both methods:
- Ensure the Service remains
ClusterIP - No Ingress, NodePort, or LoadBalancer needed
- Traffic is authenticated by kubectl before reaching the Dashboard
Q3: Write a NetworkPolicy that restricts Kubernetes Dashboard access to only Pods with label role: admin.
Show Answer
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: dashboard-admin-only
namespace: kubernetes-dashboard
spec:
podSelector:
matchLabels:
k8s-app: kubernetes-dashboard
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: admin
namespaceSelector: {}
ports:
- protocol: TCP
port: 8443
Notes:
podSelectortargets the Dashboard Pod (labelk8s-app: kubernetes-dashboard)ingress.fromallows only Pods labeledrole: adminfrom any namespace- Port 8443 is the default Dashboard HTTPS port
- All other ingress to the Dashboard is denied
namespaceSelector: {}combined withpodSelectormeans “Pods withrole: adminin any namespace”
Q4: What is the difference between exposing the Dashboard via NodePort vs. ClusterIP + kubectl proxy?
Show Answer
| Aspect | NodePort | ClusterIP + kubectl proxy |
|---|---|---|
| Network exposure | Accessible on every node’s IP at port 30000-32767 | Only accessible via localhost |
| Authentication | Depends on Dashboard config (may allow skip-login) | kubectl authenticates via kubeconfig first |
| Firewall | Must be blocked by external firewall | No external port opened |
| Attack surface | Anyone who can reach node IP:port | Requires kubectl access + valid kubeconfig |
| Network scanning | Discoverable via port scan | Invisible to external scanners |
| Production use | Never appropriate | Recommended for ad-hoc access |
NodePort risks:
- Every node exposes the same port → larger attack surface
- Network scanners find it easily
- If no authentication is configured, it’s wide open
- Even with firewall rules, accidental misconfiguration can expose it
Q5: An organization uses the Kubernetes Dashboard in production. What security controls should be in place?
Show Answer
Minimum required controls:
-
RBAC: Bind the Dashboard ServiceAccount to a read-only ClusterRole. Never use
cluster-admin. -
Service Type: Use
ClusterIPonly. NeverNodePortorLoadBalancer. - Authentication: Require token-based login. Disable skip-login:
```yaml
In the Dashboard Deployment args
args:
- –enable-skip-login=false
- –token-ttl=28800 # 8 hours ```
-
NetworkPolicy: Restrict ingress to Dashboard Pods from authorized sources only.
- Short-lived tokens: Generate tokens with
--durationflag:kubectl create token dashboard-user -n kubernetes-dashboard --duration=8h -
Audit logging: Ensure API audit policy captures Dashboard ServiceAccount requests.
- Regular review: Periodically audit Dashboard RBAC bindings:
kubectl auth can-i --list --as=system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard - Consider removal: If the Dashboard isn’t actively used, remove it entirely — it’s attack surface with no benefit.
| ← Node Metadata Protection | Binary Verification → |