~/home/study/discovering-kubernetes-api-server

Discovering the Kubernetes API Server Endpoint - An Intermediate Guide

Learn how to locate, interrogate, and safely interact with the Kubernetes API server endpoint. The guide walks through local cluster setup, URL discovery, TLS handling, proxy vs direct access, OpenAPI enumeration, and anonymous endpoint detection.

Introduction

The Kubernetes API server is the heart of every cluster - it is the only component that validates and persists the desired state of the entire control plane. Whether you are a security tester trying to map attack surfaces or an operator automating workloads, knowing exactly where the API endpoint lives and how it behaves is essential.

In practice, the API server can be reached via multiple network paths (cluster internal, external load balancer, DNS names) and can expose a rich OpenAPI specification. Mis-configurations such as anonymous access, expired certificates, or unintentionally exposed ports are common footholds for attackers.

This guide equips you with the hands-on skills to discover the endpoint in a controlled lab, validate TLS, enumerate the full API surface, and spot insecure exposure.

Prerequisites

  • Comfortable with Linux command line (bash, grep, jq, curl)
  • Basic Docker usage (pulling images, running containers)
  • Understanding of Kubernetes architecture - pods, services, nodes, and the control plane components
  • Access to a workstation where you can install kind or minikube

Core Concepts

Kubernetes clusters expose a single HTTPS endpoint – this endpoint is defined in the kubeconfig file under the clusters[].cluster.server field. The API server validates client certificates, bearer tokens, or anonymous requests based on the --anonymous-auth flag.

From a security perspective, the following concepts matter:

  1. Discovery surface: How an attacker can learn the address (DNS, service IP, node port).
  2. Transport security: Self-signed certs vs public CAs, and how to trust them.
  3. Authentication paths: ServiceAccount tokens, client certs, and the dangerous "no-auth" path.
  4. API enumeration: The OpenAPI v2/v3 spec reveals every verb, resource, and sub-resource.

Understanding these fundamentals lets you both test for weaknesses and harden the cluster.

Lab environment setup with kind/minikube

We will spin up a single-node cluster using kind (Kubernetes IN Docker). The steps are identical for minikube - replace kind commands with their minikube equivalents.

# Install kind (if not already installed)
GO111MODULE="on" go get sigs.k8s.io/[email protected]

# Verify installation
kind version

# Create a cluster named "apidiscovery"
cat > kind-config.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes: - role: control-plane extraPortMappings: - containerPort: 6443 # expose the API server on the host hostPort: 6443 protocol: TCP
EOF

kind create cluster --name apidiscovery --config kind-config.yaml

# Export kubeconfig for the new cluster
export KUBECONFIG=$(kind get kubeconfig-path --name="apidiscovery")

# Verify connectivity
kubectl version --short
kubectl get nodes
EOF

The extraPortMappings stanza forwards the control-plane’s port 6443 to the host, allowing us to reach the API server via localhost:6443. This mirrors a typical cloud load-balancer exposure.

Identifying the API server URL via kubeconfig and DNS

Once the cluster is up, the address lives in the kubeconfig file. You can extract it with kubectl config view or yq for scripting.

# Show the raw kubeconfig (YAML)
kubectl config view --minify --output yaml

# Extract the server URL only
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

# Example output
# https://127.0.0.1:6443

If you are in a cloud environment, the server will be a DNS name such as api.my-cluster.example.com. You can resolve it with dig or nslookup to confirm the IP address matches the load-balancer:

dig +short api.my-cluster.example.com
# 35.226.123.45

For internal clusters, the API server is often reachable at the kubernetes service IP (default 10.96.0.1) inside the pod network. You can discover it from a running pod:

kubectl run -i --tty dns-test --image=busybox --restart=Never -- sh
# Inside the pod
nslookup kubernetes.default.svc.cluster.local
# Server: 10.96.0.10
# Address: 10.96.0.10#53
# Name: kubernetes.default.svc.cluster.local
# Address: 10.96.0.1

These three methods (kubeconfig, DNS, and service IP) cover the majority of discovery vectors an attacker would try.

Handling self-signed TLS certificates

Kind clusters generate a self-signed certificate for the API server. Most tools (kubectl, curl) will reject it unless you explicitly trust the CA.

Extract the CA bundle from the kubeconfig and store it locally:

# Extract the base64-encoded CA data
CA_DATA=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
# Decode and write to a file
echo $CA_DATA | base64 -d > ca.crt

# Verify with openssl
openssl x509 -in ca.crt -noout -text | grep Subject

Now you can call the API with curl while trusting the CA:

curl --cacert ca.crt https://127.0.0.1:6443/version
# Expected JSON output showing server version

If you skip --cacert, curl will abort with SSL certificate problem: self signed certificate. In penetration tests, you can also use the -k/--insecure flag to ignore verification, but that masks TLS mis-configuration that you might want to report.

Using kubectl proxy vs direct HTTP requests

kubectl proxy runs a local HTTP server that forwards requests to the API server, automatically handling authentication (via the current context) and TLS. This is handy for browsers or scripts that cannot easily embed client certificates.

# Start the proxy in the background
kubectl proxy --port=8001 &
# Verify the proxy is alive
curl http://127.0.0.1:8001/healthz
# => ok

With the proxy running, you can query the API without worrying about certs:

curl http://127.0.0.1:8001/api/v1/namespaces/default/pods

Direct HTTP requests give you raw visibility of how the API behaves when presented with no authentication. This is essential for testing anonymous access:

curl -k https://127.0.0.1:6443/api
# If the server allows unauthenticated GET, you will see the API root JSON.

When conducting a security assessment, start with the proxy to confirm you have legitimate credentials, then switch to raw requests to see what an unauthenticated attacker would see.

Enumerating open API paths with OpenAPI spec

Kubernetes publishes its OpenAPI (Swagger) definition at /openapi/v2 (v2) or /openapi/v3. The spec lists every resource, sub-resource, verb, and required parameters.

# Pull the spec (using the trusted CA)
curl --silent --cacert ca.crt https://127.0.0.1:6443/openapi/v2 > openapi.json

# Use jq to extract top-level paths
jq -r '.paths | keys[]' openapi.json | sort | uniq -c | sort -nr | head -n 20

Typical output includes:

/api/v1
/api/v1/namespaces/{namespace}/pods
/apis/apps/v1/deployments
/apis/rbac.authorization.k8s.io/v1/roles

For a quick visual, you can feed the JSON to swagger-ui or redoc locally:

docker run -p 8080:8080 -v $(pwd)/openapi.json:/usr/share/nginx/html/openapi.json:ro redocly/redoc
# Open http://localhost:8080 in a browser.

Enumerating the spec is a powerful reconnaissance step - it tells you which resources are enabled (e.g., PodSecurityPolicy, TokenReview) and which verbs are allowed. Attackers often look for create on pods/exec or clusterrolebindings to achieve privilege escalation.

Detecting anonymous access and unauthenticated endpoints

Kubernetes can be configured to allow anonymous requests. The flag --anonymous-auth=true (default) means that if no credentials are presented, the request is mapped to the system:anonymous user, which may have limited RBAC permissions.

To test the real exposure, perform a series of unauthenticated calls and observe the HTTP status codes:

# 1. API root - should always be reachable (no auth needed for discovery)
curl -k -s -o /dev/null -w "%{http_code}
" https://127.0.0.1:6443/api
# Expected: 200

# 2. List namespaces - usually requires auth
curl -k -s -o /dev/null -w "%{http_code}
" https://127.0.0.1:6443/api/v1/namespaces
# Expected: 401 (Unauthorized) if RBAC blocks anonymous

# 3. Attempt a privileged endpoint (clusterrolebindings)
curl -k -s -o /dev/null -w "%{http_code}
" https://127.0.0.1:6443/apis/rbac.authorization.k8s.io/v1/clusterrolebindings
# Expected: 403 (Forbidden) or 401 - if you see 200, the cluster is dangerously open.

If any of the privileged resources return 200 without a token, you have found an unauthenticated exposure. Document the exact path, method, and response for reporting.

Another subtle vector is the /metrics endpoint (exposed by the API server when --enable-aggregator-routing is on). Test it similarly:

curl -k -s https://127.0.0.1:6443/metrics | head -n 5

Metrics can leak internal IPs, version numbers, and even token hashes. Include this in your checklist.

Practical Examples

Example 1 - Enumerating all Ingress resources without credentials

curl -k -s https://127.0.0.1:6443/apis/networking.k8s.io/v1/ingresses | jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, hosts: (.spec.rules[].host)}'

This request works because the API server permits GET on the ingresses resource for the system:anonymous user in many default clusters. An attacker could harvest hostnames for phishing or DNS-recon.

Example 2 - Using a ServiceAccount token to bypass network policies

# Create a low-priv ServiceAccount
kubectl create serviceaccount recon-sa -n default
# Bind it to a role that can list pods
cat > role.yaml <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: name: pod-reader namespace: default
rules:
- apiGroups: [""] resources: ["pods"] verbs: ["get", "list"]
EOF
kubectl apply -f role.yaml
kubectl create rolebinding recon-sa-binding --role=pod-reader --serviceaccount=default:recon-sa -n default

# Extract the token
SECRET=$(kubectl get sa recon-sa -n default -o jsonpath='{.secrets[0].name}')
TOKEN=$(kubectl get secret $SECRET -n default -o jsonpath='{.data.token}' | base64 -d)

# Use the token directly against the API
curl -k -H "Authorization: Bearer $TOKEN" https://127.0.0.1:6443/api/v1/namespaces/default/pods

The above demonstrates that even a modest ServiceAccount can be leveraged to enumerate workloads, a stepping stone for privilege escalation.

Tools & Commands

  • kind - creates local clusters in Docker.
  • kubectl - primary client; kubectl config view, kubectl proxy, kubectl run.
  • curl - raw HTTP interaction; --cacert, -k, -H for bearer tokens.
  • jq - JSON parsing; useful for extracting paths from OpenAPI.
  • openssl - inspect certificates.
  • dig / nslookup - DNS discovery of the API server’s name.
  • swagger-ui / redoc - visual rendering of OpenAPI spec.

Defense & Mitigation

  • Disable anonymous access: start the API server with --anonymous-auth=false and ensure system:anonymous has no RBAC bindings.
  • Enforce client certificate authentication for any external load-balancer access; rotate the CA regularly.
  • Network segmentation: expose the API server only on internal subnets or via a bastion host.
  • Audit API server logs for unauthenticated requests; set up alerts for 200 responses on privileged paths.
  • Apply least-privilege RBAC: audit roles that grant create on clusterrolebindings or pods/exec.
  • Secure the OpenAPI endpoint: optionally enable --requestheader-client-ca-file and restrict access via API aggregation layer.

Common Mistakes

  • Assuming the API server is only reachable via kubectl. Direct HTTP calls often reveal mis-configurations.
  • Neglecting to trust the self-signed CA, leading to false-negative TLS failures.
  • Over-relying on kubectl proxy in production - the proxy bypasses authentication checks and should never be exposed publicly.
  • Skipping the enumeration of the OpenAPI spec - many hidden resources (e.g., tokenreviews) are missed.
  • Forgetting to disable --anonymous-auth on clusters that are internet-facing.

Real-World Impact

In 2022, a major SaaS provider exposed its Kubernetes API publicly without authentication. Attackers enumerated pods/exec and clusterrolebindings, leading to a full cluster takeover. The root cause was a default --anonymous-auth=true combined with a permissive network firewall.

Today, supply-chain attacks often start with an unauthenticated API scrape to locate secrets-holding pods. Hardened clusters that block anonymous discovery force attackers to first compromise a node or a ServiceAccount, raising the bar significantly.

My experience shows that the quickest way to gain “foothold” is to query /api/v1/namespaces without auth. If you get a 200, you have a serious issue. Regularly schedule automated scans (e.g., with kube-hunter) that include anonymous checks.

Practice Exercises

  1. Setup Lab: Install kind, create a cluster, and verify the API server is reachable on localhost:6443.
  2. Extract the API URL: Use kubectl config view and resolve the DNS name (if any). Document both the IP and DNS forms.
  3. TLS Validation: Export the CA from the kubeconfig, then make a curl request with --cacert. Record the output.
  4. Anonymous Test: Perform unauthenticated GET requests to /api, /api/v1/namespaces, and /apis/rbac.authorization.k8s.io/v1/clusterrolebindings. Note the status codes.
  5. OpenAPI Enumeration: Download /openapi/v2, parse with jq, and list all resources that support the create verb.
  6. Privilege Escalation: Create a ServiceAccount with minimal rights, bind it to a Role that can list pods, then use its token to query the API directly.
  7. Mitigation Test: Modify the API server arguments (if using a custom kind config) to disable anonymous auth, restart the cluster, and re-run the anonymous tests to confirm they now return 401.

Complete each step, capture screenshots or command output, and compare your findings against the expected results.

Further Reading

  • Official Kubernetes API Server docs - authentication and authorization.
  • “Kubernetes Hardening Guide” - CIS Benchmarks.
  • “Kube-Hunter” - automated attack surface scanner.
  • “Understanding the OpenAPI Spec for Kubernetes” - blog series by CNCF.
  • “ServiceAccount Token Projection” - Kubernetes 1.24+ security improvements.

Summary

  • The API server endpoint is discoverable via kubeconfig, DNS, and internal service IPs.
  • Self-signed certificates require explicit trust; extract the CA from kubeconfig.
  • kubectl proxy simplifies authenticated calls, but raw HTTP reveals unauthenticated behavior.
  • Downloading the OpenAPI spec provides a complete map of reachable resources and verbs.
  • Always test for anonymous access - 200 on privileged paths indicates a critical mis-configuration.
  • Mitigate by disabling anonymous auth, tightening RBAC, enforcing TLS, and segmenting network access.