Skip to content

Restricting Access to GUI Elements

What Are "GUI Elements" in Kubernetes?

In the Kubernetes context, "GUI elements" refers to any web-based management interface that is deployed within or provides access to a cluster. These interfaces present a significant attack surface because they often provide visual, easy-to-use access to sensitive cluster operations -- meaning a compromised GUI can lead to full cluster compromise with minimal attacker effort.

Common GUI elements include:

GUI ElementPurposeRisk Level
Kubernetes DashboardCluster management web UICritical -- can create/delete any resource
GrafanaMetrics visualizationHigh -- exposes infrastructure metrics, may have admin APIs
Prometheus UIMetrics queryingMedium -- exposes internal service discovery and metrics
Kibana / OpenSearch DashboardsLog analysisHigh -- exposes application and system logs
ArgoCD UIGitOps CD managementCritical -- can deploy arbitrary workloads
JenkinsCI/CD pipeline executionCritical -- can execute arbitrary code
Rancher / LensCluster managementCritical -- full cluster control
Weave ScopeCluster visualizationHigh -- exposes topology and can exec into containers

CKS Exam Relevance

The CKS exam may ask you to:

  • Identify and remove an insecurely deployed Kubernetes Dashboard
  • Restrict access to a web UI using NetworkPolicies
  • Audit services exposed via NodePort or LoadBalancer
  • Configure RBAC for dashboard service accounts with least privilege
  • Use kubectl proxy or port-forward for secure access to internal UIs

Why GUI Elements Are Dangerous

Real-World Incident: Tesla Cryptojacking (2018)

Attackers discovered an unprotected Kubernetes Dashboard exposed to the internet on Tesla's infrastructure. The dashboard had no authentication, allowing attackers to deploy cryptocurrency mining workloads and access AWS credentials stored in environment variables. This is one of the most cited examples of why GUI security matters.

Kubernetes Dashboard Deep Dive

The Kubernetes Dashboard is the most commonly encountered GUI element and the one most likely to appear on the CKS exam.

Default Dashboard Deployment Risks

When the Kubernetes Dashboard is deployed with default settings, several risks arise:

RiskDescription
Overly permissive ServiceAccountDefault install may bind to cluster-admin or overly broad ClusterRole
Skip login enabledSome configurations allow bypassing authentication entirely
Exposed via NodePort/LoadBalancerMakes the dashboard accessible from outside the cluster
No NetworkPolicyAny pod in the cluster can access the dashboard service
Token-based auth without expiryLong-lived tokens can be stolen and reused

Auditing the Current Dashboard Deployment

bash
# Check if the dashboard is deployed
kubectl get deployments -n kubernetes-dashboard
kubectl get deployments -A | grep -i dashboard

# Check the dashboard service type
kubectl get svc -n kubernetes-dashboard
# Look for Type: NodePort or LoadBalancer -- these are dangerous

# Check dashboard ServiceAccount and its bindings
kubectl get sa -n kubernetes-dashboard
kubectl get clusterrolebindings -o json | \
  jq -r '.items[] | select(.subjects[]? | .namespace == "kubernetes-dashboard") | 
    "\(.metadata.name) -> \(.roleRef.name)"'

# Check what permissions the dashboard service account has
kubectl auth can-i --list \
  --as=system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard \
  -n kubernetes-dashboard

# Check for skip-login argument in the dashboard deployment
kubectl get deploy kubernetes-dashboard -n kubernetes-dashboard -o yaml | \
  grep -A5 "args"

Why --enable-skip-login Is Dangerous

The --enable-skip-login flag allows users to bypass the login screen entirely. When enabled, anyone who can reach the dashboard can use it with whatever permissions the dashboard's own service account has.

bash
# Check if skip-login is enabled
kubectl get deploy kubernetes-dashboard -n kubernetes-dashboard -o yaml | \
  grep "enable-skip-login"

# If found, remove it by editing the deployment
kubectl edit deploy kubernetes-dashboard -n kubernetes-dashboard
# Remove the --enable-skip-login argument from the container args

Never Use Skip Login

The --enable-skip-login flag should never be used in any environment. If an exam question involves a dashboard with this flag, removing it is likely part of the answer.

Dashboard RBAC Configuration

Step 1: Create a Read-Only ClusterRole for Dashboard

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: dashboard-read-only
rules:
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps", "namespaces", "nodes", "events",
                "persistentvolumeclaims", "replicationcontrollers", "resourcequotas"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "daemonsets", "statefulsets", "replicasets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["batch"]
    resources: ["jobs", "cronjobs"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses", "networkpolicies"]
    verbs: ["get", "list", "watch"]
  # NOTE: Secrets are deliberately excluded
  # NOTE: No create, update, delete, or patch verbs

Step 2: Create a Dedicated Service Account

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard-viewer
  namespace: kubernetes-dashboard

Step 3: Bind the ClusterRole to the Service Account

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dashboard-viewer-binding
subjects:
  - kind: ServiceAccount
    name: dashboard-viewer
    namespace: kubernetes-dashboard
roleRef:
  kind: ClusterRole
  name: dashboard-read-only
  apiGroup: rbac.authorization.k8s.io

Step 4: Create a Token for the Service Account

bash
# Create a time-limited token (recommended)
kubectl create token dashboard-viewer \
  -n kubernetes-dashboard \
  --duration=8h

# Or create a long-lived token secret (less recommended)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: dashboard-viewer-token
  namespace: kubernetes-dashboard
  annotations:
    kubernetes.io/service-account.name: dashboard-viewer
type: kubernetes.io/service-account-token
EOF

# Retrieve the token
kubectl get secret dashboard-viewer-token \
  -n kubernetes-dashboard \
  -o jsonpath='{.data.token}' | base64 -d

Exam Tip

Use kubectl create token for time-limited tokens. This is the preferred method since Kubernetes 1.24+ as tokens are bound and expire automatically.

Step 5: Verify the Permissions

bash
# Confirm read-only access
kubectl auth can-i get pods \
  --as=system:serviceaccount:kubernetes-dashboard:dashboard-viewer
# yes

kubectl auth can-i delete pods \
  --as=system:serviceaccount:kubernetes-dashboard:dashboard-viewer
# no

kubectl auth can-i get secrets \
  --as=system:serviceaccount:kubernetes-dashboard:dashboard-viewer
# no

kubectl auth can-i create deployments \
  --as=system:serviceaccount:kubernetes-dashboard:dashboard-viewer
# no

# Full permission list
kubectl auth can-i --list \
  --as=system:serviceaccount:kubernetes-dashboard:dashboard-viewer

Removing Dangerous Dashboard RBAC

If the dashboard was deployed with overly broad permissions, you need to identify and fix them:

bash
# Find any ClusterRoleBindings granting cluster-admin to dashboard service accounts
kubectl get clusterrolebindings -o json | \
  jq -r '.items[] | select(
    (.roleRef.name == "cluster-admin") and 
    (.subjects[]? | .namespace == "kubernetes-dashboard")
  ) | .metadata.name'

# Delete the dangerous binding
kubectl delete clusterrolebinding <binding-name>

# Replace with the read-only binding created above
kubectl apply -f dashboard-viewer-binding.yaml

Secure Access Patterns

Access Methods Comparison

Access MethodSecurity LevelAuthenticationUse Case
kubectl proxyHighUses kubeconfig credentialsAdmin access from local machine
kubectl port-forwardHighUses kubeconfig credentialsAccessing a specific service temporarily
Ingress + OAuth2/OIDCMedium-HighExternal identity providerTeam access with SSO
Ingress + mTLSMedium-HighClient certificateService-to-service or trusted clients
Ingress (no auth)LowNoneNever recommended
NodePortVery LowNone (unless app enforces)Never use for management UIs
LoadBalancerVery LowNone (unless app enforces)Never use for management UIs

kubectl proxy creates a secure tunnel between your local machine and the API server. It uses your kubeconfig credentials, so authentication is already handled.

bash
# Start the proxy
kubectl proxy
# Starting to serve on 127.0.0.1:8001

# Access the dashboard at:
# http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

# The proxy:
# - Listens ONLY on localhost (127.0.0.1)
# - Uses your kubeconfig for authentication
# - Proxies requests to the API server
# - No ports are exposed to the network

Method 2: kubectl port-forward (For Specific Services)

bash
# Forward the dashboard service to a local port
kubectl port-forward svc/kubernetes-dashboard -n kubernetes-dashboard 8443:443

# Access at: https://localhost:8443

# Forward Grafana
kubectl port-forward svc/grafana -n monitoring 3000:3000

# Forward Prometheus UI
kubectl port-forward svc/prometheus-server -n monitoring 9090:9090

# Forward ArgoCD
kubectl port-forward svc/argocd-server -n argocd 8080:443

When to Use Each

  • kubectl proxy: Best for the Kubernetes Dashboard specifically, as it handles the API path automatically
  • kubectl port-forward: Best for any specific service (Grafana, Prometheus, ArgoCD, etc.)
  • Both methods require valid kubeconfig credentials and never expose ports beyond localhost

Method 3: Ingress with Authentication (For Team Access)

If a GUI must be accessible to a team (not just a single admin), use an Ingress with authentication:

yaml
# Example: Ingress with OAuth2 proxy for Grafana
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: grafana
  namespace: monitoring
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "https://oauth2-proxy.example.com/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "https://oauth2-proxy.example.com/oauth2/start?rd=$scheme://$host$request_uri"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - grafana.internal.example.com
      secretName: grafana-tls
  rules:
    - host: grafana.internal.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: grafana
                port:
                  number: 3000

NetworkPolicies for GUI Services

NetworkPolicies add a layer of defense by controlling which pods and IP addresses can reach GUI services.

Restrict Dashboard Access to Specific Namespace

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-dashboard-access
  namespace: kubernetes-dashboard
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: kubernetes-dashboard
  policyTypes:
    - Ingress
  ingress:
    # Only allow traffic from the monitoring namespace
    - from:
        - namespaceSelector:
            matchLabels:
              name: monitoring
      ports:
        - protocol: TCP
          port: 8443

Restrict Dashboard Access to Specific CIDR (Admin Network Only)

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: dashboard-admin-only
  namespace: kubernetes-dashboard
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: kubernetes-dashboard
  policyTypes:
    - Ingress
  ingress:
    # Only allow traffic from the admin network CIDR
    - from:
        - ipBlock:
            cidr: 10.0.100.0/24
      ports:
        - protocol: TCP
          port: 8443

Default Deny All Ingress for Dashboard Namespace

yaml
# Start with a default deny, then add specific allow rules
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: kubernetes-dashboard
spec:
  podSelector: {}
  policyTypes:
    - Ingress
---
# Then allow only kubectl proxy / API server traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-apiserver-only
  namespace: kubernetes-dashboard
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: kubernetes-dashboard
  policyTypes:
    - Ingress
  ingress:
    - from:
        - ipBlock:
            cidr: 10.96.0.1/32    # API server ClusterIP (adjust for your cluster)
      ports:
        - protocol: TCP
          port: 8443

Securing Other GUI Elements

Grafana

bash
# Check if Grafana is deployed
kubectl get deploy -A | grep -i grafana

# Check the service type
kubectl get svc -A | grep -i grafana
# Ensure it is ClusterIP, NOT NodePort or LoadBalancer

# Change from NodePort/LoadBalancer to ClusterIP
kubectl patch svc grafana -n monitoring -p '{"spec": {"type": "ClusterIP"}}'

Key hardening steps for Grafana:

  • Disable anonymous access in grafana.ini: [auth.anonymous] enabled = false
  • Change the default admin password from admin
  • Use OAuth2 or LDAP for authentication
  • Set the service type to ClusterIP
  • Apply a NetworkPolicy restricting ingress

Prometheus UI

bash
# Check Prometheus service
kubectl get svc -A | grep -i prometheus

# Prometheus UI has no built-in authentication
# It should NEVER be exposed externally
# Access via port-forward only:
kubectl port-forward svc/prometheus-server -n monitoring 9090:9090

ArgoCD

bash
# Check ArgoCD service
kubectl get svc -n argocd

# Ensure argocd-server is ClusterIP
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "ClusterIP"}}'

# ArgoCD has built-in authentication
# Get the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d

# Change the default password immediately after first login
# Access via port-forward
kubectl port-forward svc/argocd-server -n argocd 8080:443

General Principles for Any Web UI

Hardening Checklist for Any Web UI

  1. Service type: Always use ClusterIP -- never NodePort or LoadBalancer
  2. Authentication: Ensure login is required; disable anonymous/guest access
  3. Default credentials: Change all default passwords immediately
  4. RBAC: Apply least-privilege permissions to the UI's service account
  5. NetworkPolicy: Restrict which pods/networks can reach the UI
  6. TLS: Ensure the UI uses HTTPS, not plain HTTP
  7. Access method: Use kubectl proxy or port-forward for admin access
  8. Audit: Regularly check for newly deployed web UIs

Removing Unnecessary GUI Components

If a GUI component is not needed, the most secure option is to remove it entirely.

Audit for Web UIs in the Cluster

bash
# Find all services exposed as NodePort or LoadBalancer
kubectl get svc -A -o wide | grep -E 'NodePort|LoadBalancer'

# Find dashboard-related resources
kubectl get all -n kubernetes-dashboard

# Find deployments that might be web UIs (common names)
kubectl get deploy -A | grep -iE 'dashboard|grafana|prometheus|kibana|jenkins|argocd|weave|rancher'

# Find services on common web ports
kubectl get svc -A -o json | \
  jq -r '.items[] | select(.spec.ports[]? | .port == 80 or .port == 443 or .port == 8080 or .port == 8443 or .port == 3000 or .port == 9090) | "\(.metadata.namespace)/\(.metadata.name) port:\(.spec.ports[].port) type:\(.spec.type)"'

# Find ingresses that might expose web UIs
kubectl get ingress -A

Remove the Kubernetes Dashboard

bash
# Delete all dashboard resources
kubectl delete namespace kubernetes-dashboard

# Or if installed via Helm
helm uninstall kubernetes-dashboard -n kubernetes-dashboard

# Verify removal
kubectl get all -n kubernetes-dashboard
# No resources found

# Also check for any lingering ClusterRoles and ClusterRoleBindings
kubectl get clusterroles | grep -i dashboard
kubectl get clusterrolebindings | grep -i dashboard

# Delete any remaining cluster-scoped resources
kubectl delete clusterrole kubernetes-dashboard
kubectl delete clusterrolebinding kubernetes-dashboard

Convert NodePort/LoadBalancer Services to ClusterIP

bash
# Find all NodePort services
kubectl get svc -A --field-selector spec.type=NodePort

# Patch a service to ClusterIP
kubectl patch svc <service-name> -n <namespace> \
  -p '{"spec": {"type": "ClusterIP"}}'

# Find all LoadBalancer services
kubectl get svc -A --field-selector spec.type=LoadBalancer

# Patch to ClusterIP
kubectl patch svc <service-name> -n <namespace> \
  -p '{"spec": {"type": "ClusterIP"}}'

Exam Tip

If an exam question asks you to secure a dashboard or web UI, the steps are usually:

  1. Check the service type and change to ClusterIP if needed
  2. Check the RBAC bindings and remove cluster-admin access
  3. Remove --enable-skip-login if present
  4. Apply a NetworkPolicy to restrict access
  5. If the question says "remove" the dashboard, delete the namespace and cluster-scoped resources

Full Hardening Example

Here is a complete example of taking a poorly configured dashboard and securing it:

Before: Insecure Dashboard

bash
# Dashboard exposed as NodePort with cluster-admin access
kubectl get svc -n kubernetes-dashboard
# NAME                        TYPE       CLUSTER-IP     PORT(S)
# kubernetes-dashboard        NodePort   10.96.45.123   443:30443/TCP

kubectl get clusterrolebindings | grep dashboard
# dashboard-admin   ClusterRole/cluster-admin   ...

kubectl get deploy kubernetes-dashboard -n kubernetes-dashboard -o yaml | grep args -A5
#   args:
#     - --enable-skip-login        # DANGEROUS
#     - --namespace=kubernetes-dashboard

After: Hardened Dashboard

bash
# Step 1: Remove skip-login
kubectl patch deploy kubernetes-dashboard -n kubernetes-dashboard \
  --type='json' \
  -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/args/0"}]'
# Verify the args list no longer contains --enable-skip-login

# Step 2: Change service type to ClusterIP
kubectl patch svc kubernetes-dashboard -n kubernetes-dashboard \
  -p '{"spec": {"type": "ClusterIP"}}'

# Step 3: Remove the cluster-admin binding
kubectl delete clusterrolebinding dashboard-admin

# Step 4: Apply read-only RBAC (using the YAML from earlier sections)
kubectl apply -f dashboard-read-only-clusterrole.yaml
kubectl apply -f dashboard-viewer-sa.yaml
kubectl apply -f dashboard-viewer-binding.yaml

# Step 5: Apply NetworkPolicy
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: dashboard-restrict
  namespace: kubernetes-dashboard
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: kubernetes-dashboard
  policyTypes:
    - Ingress
  ingress: []
  # Empty ingress = deny all ingress
  # Access only via kubectl proxy (which goes through the API server)
EOF

# Step 6: Verify
kubectl get svc -n kubernetes-dashboard
# TYPE should be ClusterIP

kubectl auth can-i delete pods \
  --as=system:serviceaccount:kubernetes-dashboard:dashboard-viewer
# no

kubectl get networkpolicy -n kubernetes-dashboard
# dashboard-restrict

Quick Reference

Exam Speed Reference

bash
# Find all web UIs and dashboards
kubectl get deploy -A | grep -iE 'dashboard|grafana|prometheus|kibana|jenkins|argocd'

# Find services exposed externally
kubectl get svc -A | grep -E 'NodePort|LoadBalancer'

# Check dashboard service account permissions
kubectl auth can-i --list \
  --as=system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard

# Change service to ClusterIP
kubectl patch svc <name> -n <ns> -p '{"spec": {"type": "ClusterIP"}}'

# Delete the dashboard entirely
kubectl delete ns kubernetes-dashboard
kubectl delete clusterrole kubernetes-dashboard
kubectl delete clusterrolebinding kubernetes-dashboard

# Secure access via proxy
kubectl proxy
# http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

# Secure access via port-forward
kubectl port-forward svc/kubernetes-dashboard -n kubernetes-dashboard 8443:443

# Create a time-limited dashboard token
kubectl create token dashboard-viewer -n kubernetes-dashboard --duration=8h

# Check for skip-login flag
kubectl get deploy kubernetes-dashboard -n kubernetes-dashboard -o yaml | grep skip-login

Key Exam Takeaways

  1. Kubernetes Dashboard is the most exam-relevant GUI element -- know how to audit, secure, and remove it
  2. Never expose management UIs via NodePort or LoadBalancer -- always use ClusterIP
  3. Never use --enable-skip-login -- it bypasses all authentication
  4. Remove cluster-admin bindings from dashboard service accounts and replace with read-only roles
  5. Use kubectl proxy or kubectl port-forward for secure local access to any web UI
  6. Apply NetworkPolicies to restrict which pods and networks can reach GUI services
  7. Audit regularly for web UIs exposed on NodePort, LoadBalancer, or unauthenticated Ingresses
  8. If a GUI component is not needed, the most secure action is to remove it entirely by deleting its namespace and cluster-scoped resources
  9. Always create dedicated service accounts with minimal RBAC for any dashboard that must remain deployed

Released under the MIT License.