Skip to content

Securing the API Server

Overview

The Kubernetes API server (kube-apiserver) is the central management entity of the entire cluster. Every request -- from kubectl commands, controller operations, kubelet updates, and external integrations -- flows through the API server. Securing it is the single most important step in cluster hardening.

CKS Exam Relevance

You should be comfortable editing the API server static pod manifest at /etc/kubernetes/manifests/kube-apiserver.yaml, understanding the effects of each security-relevant flag, and configuring authentication, authorization, and admission controllers.

API Request Flow

Every request to the API server passes through three sequential stages: Authentication, Authorization, and Admission Control.

Authentication Mechanisms

Authentication determines who is making the request. Kubernetes supports multiple authentication methods, and the API server can use several simultaneously.

Client Certificates (x509)

The most common authentication method. Clients present a certificate signed by the cluster CA.

yaml
# API server flags for certificate authentication
spec:
  containers:
    - command:
        - kube-apiserver
        - --client-ca-file=/etc/kubernetes/pki/ca.crt
        - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
        - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key

The certificate's Common Name (CN) becomes the username, and Organization (O) becomes the group:

bash
# Create a certificate for user "jane" in group "developers"
openssl req -new -key jane.key -out jane.csr \
  -subj "/CN=jane/O=developers"

Static Token File

Not Recommended for Production

Static token files store bearer tokens in plain text. They require an API server restart to update and provide no token expiration.

yaml
# API server flag
- --token-auth-file=/etc/kubernetes/tokens.csv

Token file format:

token,user,uid,"group1,group2"

OpenID Connect (OIDC)

OIDC allows integration with external identity providers (Google, Azure AD, Okta, etc.).

yaml
spec:
  containers:
    - command:
        - kube-apiserver
        - --oidc-issuer-url=https://accounts.google.com
        - --oidc-client-id=my-kubernetes-client
        - --oidc-username-claim=email
        - --oidc-groups-claim=groups
        - --oidc-ca-file=/etc/kubernetes/pki/oidc-ca.crt

Service Account Tokens

Service accounts are automatically created for pods. They use JWT tokens signed by the API server.

yaml
spec:
  containers:
    - command:
        - kube-apiserver
        - --service-account-key-file=/etc/kubernetes/pki/sa.pub
        - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
        - --service-account-issuer=https://kubernetes.default.svc.cluster.local

Webhook Token Authentication

Delegates authentication to an external service.

yaml
- --authentication-token-webhook-config-file=/etc/kubernetes/webhook-config.yaml

Authorization Modes

Authorization determines what an authenticated user can do. The API server evaluates authorization modules in order.

Configuring Authorization Modes

yaml
spec:
  containers:
    - command:
        - kube-apiserver
        - --authorization-mode=Node,RBAC    # Order matters! Evaluated left to right.
ModePurpose
NodeAuthorizes kubelet requests based on the pods scheduled to them
RBACRole-Based Access Control using Roles, ClusterRoles, and Bindings
WebhookDelegates to an external authorization service
ABACAttribute-Based Access Control (legacy, not recommended)
AlwaysAllowAllows all requests (dangerous, never use in production)
AlwaysDenyDenies all requests (testing only)

Never Use AlwaysAllow

yaml
# DANGEROUS -- never use this
- --authorization-mode=AlwaysAllow

This bypasses all authorization checks. Any authenticated user (or anonymous user if anonymous auth is enabled) can perform any action.

Node Authorization

The Node authorizer grants kubelets the permissions they need to function:

  • Read: Services, Endpoints, Nodes, Pods
  • Write: Node status, Pod status, Events
  • Auth: Create/update/delete CertificateSigningRequests
yaml
# Always include Node authorizer when using RBAC
- --authorization-mode=Node,RBAC

Admission Controllers

Admission controllers intercept requests after authentication and authorization but before the object is persisted to etcd. They can mutate (modify) or validate (accept/reject) requests.

Critical Admission Controllers for CKS

ControllerTypePurpose
NodeRestrictionValidatingLimits what kubelets can modify (only their own node and pods)
PodSecurityValidatingEnforces Pod Security Standards (replacement for PodSecurityPolicy)
NamespaceLifecycleValidatingPrevents operations in non-existent or terminating namespaces
ServiceAccountMutatingAutomatically adds ServiceAccount and token to pods
LimitRangerMutating + ValidatingEnforces resource limits
ResourceQuotaValidatingEnforces namespace resource quotas
DefaultStorageClassMutatingAdds default storage class to PVCs
MutatingAdmissionWebhookMutatingCalls external mutating webhooks
ValidatingAdmissionWebhookValidatingCalls external validating webhooks

Configuring Admission Controllers

yaml
spec:
  containers:
    - command:
        - kube-apiserver
        - --enable-admission-plugins=NodeRestriction,PodSecurity,NamespaceLifecycle,ServiceAccount,LimitRanger,ResourceQuota
        # Do NOT disable these security-critical controllers:
        # --disable-admission-plugins should NOT include NodeRestriction

Checking Current Admission Controllers

bash
# View enabled admission plugins
ps aux | grep kube-apiserver | tr ' ' '\n' | grep admission

# Or check the manifest
cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep admission

NodeRestriction Is Essential

The NodeRestriction admission controller prevents kubelets from modifying objects they should not have access to. Without it, a compromised kubelet could modify any node or pod in the cluster. Always ensure it is enabled.

Disabling Anonymous Authentication

By default, the API server allows anonymous requests. Anonymous users are assigned the username system:anonymous and group system:unauthenticated.

yaml
spec:
  containers:
    - command:
        - kube-apiserver
        - --anonymous-auth=false    # Disable anonymous authentication

When Anonymous Auth Is Needed

Some components (like certain health check probes) use anonymous authentication to access /healthz and /readyz. If you disable anonymous auth, ensure these components authenticate properly or configure appropriate RBAC rules. In the exam, follow the question's instructions precisely.

Enabling Audit Logging

Audit logging records all API requests for security monitoring and forensics.

Step 1: Create an Audit Policy

yaml
# /etc/kubernetes/audit/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  # Log no requests to the following
  - level: None
    resources:
      - group: ""
        resources: ["endpoints", "services", "services/status"]

  # Log pod changes at RequestResponse level
  - level: RequestResponse
    resources:
      - group: ""
        resources: ["pods"]

  # Log secrets at Metadata level (do NOT log request/response bodies for secrets)
  - level: Metadata
    resources:
      - group: ""
        resources: ["secrets", "configmaps"]

  # Log all other resources at Request level
  - level: Request
    resources:
      - group: ""
        resources: ["*"]

  # Default: log everything else at Metadata level
  - level: Metadata
    omitStages:
      - "RequestReceived"

Audit Levels

LevelWhat Is Logged
NoneNothing
MetadataRequest metadata (user, timestamp, resource, verb) but not request/response body
RequestMetadata + request body
RequestResponseMetadata + request body + response body

Step 2: Configure API Server

yaml
spec:
  containers:
    - command:
        - kube-apiserver
        - --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
        - --audit-log-path=/var/log/kubernetes/audit/audit.log
        - --audit-log-maxage=30       # Retain logs for 30 days
        - --audit-log-maxbackup=10    # Keep 10 backup files
        - --audit-log-maxsize=100     # Max 100 MB per file
      volumeMounts:
        - name: audit-policy
          mountPath: /etc/kubernetes/audit
          readOnly: true
        - name: audit-log
          mountPath: /var/log/kubernetes/audit
  volumes:
    - name: audit-policy
      hostPath:
        path: /etc/kubernetes/audit
        type: DirectoryOrCreate
    - name: audit-log
      hostPath:
        path: /var/log/kubernetes/audit
        type: DirectoryOrCreate

Mount Volumes

If you add --audit-policy-file or --audit-log-path, you must also add the corresponding volumeMounts and volumes to the API server manifest. Missing volume mounts will cause the API server to fail to start.

API Server Flags That Matter for Security

Here is a comprehensive reference of security-critical API server flags:

Authentication Flags

yaml
- --anonymous-auth=false                             # Disable anonymous access
- --client-ca-file=/etc/kubernetes/pki/ca.crt       # CA for client certificate auth
- --token-auth-file=                                  # Remove if present (static tokens are insecure)
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
- --service-account-issuer=https://kubernetes.default.svc.cluster.local

Authorization Flags

yaml
- --authorization-mode=Node,RBAC                     # Never AlwaysAllow

TLS Flags

yaml
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
- --tls-min-version=VersionTLS12                     # Minimum TLS 1.2
- --tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

etcd Communication Flags

yaml
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379             # Use HTTPS, never HTTP

Admission Control Flags

yaml
- --enable-admission-plugins=NodeRestriction,PodSecurity
- --admission-control-config-file=/etc/kubernetes/admission/config.yaml

Encryption Flags

yaml
- --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml

Audit Flags

yaml
- --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100

Other Security Flags

yaml
- --profiling=false                                   # Disable profiling endpoint
- --insecure-port=0                                   # Disable insecure port (deprecated, removed in 1.24+)
- --kubelet-certificate-authority=/etc/kubernetes/pki/ca.crt
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --request-timeout=300s                              # Reasonable timeout
- --service-account-lookup=true                       # Verify SA tokens are valid

Hardening Checklist

Complete API Server Hardening Checklist

  • [ ] Anonymous authentication disabled (--anonymous-auth=false)
  • [ ] Authorization mode set to Node,RBAC
  • [ ] NodeRestriction admission controller enabled
  • [ ] PodSecurity admission controller enabled
  • [ ] Audit logging enabled with appropriate policy
  • [ ] TLS minimum version set to 1.2
  • [ ] etcd communication encrypted with TLS
  • [ ] Profiling disabled (--profiling=false)
  • [ ] Insecure port disabled (--insecure-port=0)
  • [ ] Service account lookup enabled
  • [ ] Encryption at rest configured
  • [ ] Strong cipher suites configured

Verifying API Server Configuration

bash
# Check the current API server process flags
ps aux | grep kube-apiserver | tr ' ' '\n' | sort

# Check the API server manifest
cat /etc/kubernetes/manifests/kube-apiserver.yaml

# Verify API server is accessible only via HTTPS
curl -k https://localhost:6443/healthz
# Should return "ok"

curl http://localhost:8080/healthz
# Should fail (insecure port should be disabled)

# Check enabled admission plugins
kubectl -n kube-system describe pod kube-apiserver | grep admission

# Test if anonymous auth is disabled
curl -k https://localhost:6443/api/v1/pods
# Should return 401 Unauthorized if anonymous auth is disabled

After Editing the Manifest

After modifying /etc/kubernetes/manifests/kube-apiserver.yaml:

  1. The kubelet will detect the change and restart the API server automatically
  2. Wait 30-60 seconds for the restart
  3. If the API server does not come back, check /var/log/pods/ for error logs
  4. Always back up the manifest before editing: cp kube-apiserver.yaml /tmp/kube-apiserver.yaml.bak

Released under the MIT License.