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.
# 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.keyThe certificate's Common Name (CN) becomes the username, and Organization (O) becomes the group:
# 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.
# API server flag
- --token-auth-file=/etc/kubernetes/tokens.csvToken file format:
token,user,uid,"group1,group2"OpenID Connect (OIDC)
OIDC allows integration with external identity providers (Google, Azure AD, Okta, etc.).
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.crtService Account Tokens
Service accounts are automatically created for pods. They use JWT tokens signed by the API server.
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.localWebhook Token Authentication
Delegates authentication to an external service.
- --authentication-token-webhook-config-file=/etc/kubernetes/webhook-config.yamlAuthorization Modes
Authorization determines what an authenticated user can do. The API server evaluates authorization modules in order.
Configuring Authorization Modes
spec:
containers:
- command:
- kube-apiserver
- --authorization-mode=Node,RBAC # Order matters! Evaluated left to right.| Mode | Purpose |
|---|---|
| Node | Authorizes kubelet requests based on the pods scheduled to them |
| RBAC | Role-Based Access Control using Roles, ClusterRoles, and Bindings |
| Webhook | Delegates to an external authorization service |
| ABAC | Attribute-Based Access Control (legacy, not recommended) |
| AlwaysAllow | Allows all requests (dangerous, never use in production) |
| AlwaysDeny | Denies all requests (testing only) |
Never Use AlwaysAllow
# DANGEROUS -- never use this
- --authorization-mode=AlwaysAllowThis 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
# Always include Node authorizer when using RBAC
- --authorization-mode=Node,RBACAdmission 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
| Controller | Type | Purpose |
|---|---|---|
| NodeRestriction | Validating | Limits what kubelets can modify (only their own node and pods) |
| PodSecurity | Validating | Enforces Pod Security Standards (replacement for PodSecurityPolicy) |
| NamespaceLifecycle | Validating | Prevents operations in non-existent or terminating namespaces |
| ServiceAccount | Mutating | Automatically adds ServiceAccount and token to pods |
| LimitRanger | Mutating + Validating | Enforces resource limits |
| ResourceQuota | Validating | Enforces namespace resource quotas |
| DefaultStorageClass | Mutating | Adds default storage class to PVCs |
| MutatingAdmissionWebhook | Mutating | Calls external mutating webhooks |
| ValidatingAdmissionWebhook | Validating | Calls external validating webhooks |
Configuring Admission Controllers
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 NodeRestrictionChecking Current Admission Controllers
# 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 admissionNodeRestriction 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.
spec:
containers:
- command:
- kube-apiserver
- --anonymous-auth=false # Disable anonymous authenticationWhen 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
# /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
| Level | What Is Logged |
|---|---|
| None | Nothing |
| Metadata | Request metadata (user, timestamp, resource, verb) but not request/response body |
| Request | Metadata + request body |
| RequestResponse | Metadata + request body + response body |
Step 2: Configure API Server
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: DirectoryOrCreateMount 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
- --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.localAuthorization Flags
- --authorization-mode=Node,RBAC # Never AlwaysAllowTLS Flags
- --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_SHA384etcd Communication Flags
- --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 HTTPAdmission Control Flags
- --enable-admission-plugins=NodeRestriction,PodSecurity
- --admission-control-config-file=/etc/kubernetes/admission/config.yamlEncryption Flags
- --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yamlAudit Flags
- --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=100Other Security Flags
- --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 validHardening 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
# 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 disabledAfter Editing the Manifest
After modifying /etc/kubernetes/manifests/kube-apiserver.yaml:
- The kubelet will detect the change and restart the API server automatically
- Wait 30-60 seconds for the restart
- If the API server does not come back, check
/var/log/pods/for error logs - Always back up the manifest before editing:
cp kube-apiserver.yaml /tmp/kube-apiserver.yaml.bak