Kubeconfig & Kubernetes PKI Fundamentals
Comprehensive guide to understanding Kubernetes PKI (Public Key Infrastructure), X.509 certificates, and kubeconfig file structure. Learn how to work with certificates, create custom users, and manage cluster authentication.
Quick Reference: kubectl config Commands
# View configuration
kubectl config view # Show entire kubeconfig
kubectl config view --raw # Show with decoded credentials
kubectl config view --flatten # Merge all kubeconfigs
kubectl config view --minify # Show only current context
kubectl config view -o json # Output as JSON
# Set cluster
kubectl config set-cluster kubernetes \
--server=https://172.30.1.2:6443 \
--certificate-authority=/etc/kubernetes/pki/ca.crt
# Set user credentials (certificate-based)
kubectl config set-credentials kubelet \
--client-certificate=/etc/kubernetes/pki/kubelet.crt \
--client-key=/etc/kubernetes/pki/kubelet.key \
--embed-certs=true
# Set user credentials (token-based)
kubectl config set-credentials token-user \
--token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Create context
kubectl config set-context default \
--cluster=kubernetes \
--user=kubelet \
--namespace=default
# Use context
kubectl config use-context default
# Get current context
kubectl config current-context
# Get all clusters, users, contexts
kubectl config get-clusters
kubectl config get-users
kubectl config get-contexts
# Rename context
kubectl config rename-context old-name new-name
# Delete entries
kubectl config delete-cluster cluster-name
kubectl config delete-user user-name
kubectl config delete-context context-name
# Unset (remove) configuration
kubectl config unset current-context
kubectl config unset clusters.cluster-name
kubectl config unset users.user-name
kubectl config unset contexts.context-name.namespaceAdvanced: kubectl config Usage Patterns
Pattern 1: Setup Complete Cluster Access (One Command Sequence)
# 1. Add cluster configuration
kubectl config set-cluster prod-cluster \
--server=https://api.prod.example.com:6443 \
--certificate-authority=/path/to/ca.crt
# 2. Add user with certificate
kubectl config set-credentials prod-admin \
--client-certificate=/path/to/admin.crt \
--client-key=/path/to/admin.key \
--embed-certs=true
# 3. Create context binding
kubectl config set-context prod-admin \
--cluster=prod-cluster \
--user=prod-admin \
--namespace=default
# 4. Switch to use it
kubectl config use-context prod-admin
# Verify
kubectl cluster-info
kubectl get nodesPattern 2: Add Token-Based User
# For service account tokens
kubectl config set-credentials service-user \
--token=$(kubectl create token my-service-account)
# For predefined token
kubectl config set-credentials token-user \
--token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0...
# Use in context
kubectl config set-context token-context \
--cluster=kubernetes \
--user=token-userPattern 3: Multiple Kubeconfigs
# Combine multiple kubeconfig files
export KUBECONFIG=~/.kube/config:~/.kube/prod-config:~/.kube/dev-config
# View all merged
kubectl config view
# Switch between clusters easily
kubectl config use-context prod-admin
kubectl config use-context dev-admin
# Make permanent
echo 'export KUBECONFIG=~/.kube/config:~/.kube/prod-config:~/.kube/dev-config' >> ~/.zshrcPattern 4: Extract User Credentials
# Extract certificate from kubeconfig
kubectl config view --raw | \
grep 'client-certificate-data:' | \
head -1 | \
awk '{print $2}' | \
base64 -d > user.crt
# Extract key from kubeconfig
kubectl config view --raw | \
grep 'client-key-data:' | \
head -1 | \
awk '{print $2}' | \
base64 -d > user.key
# Extract CA certificate
kubectl config view --raw | \
grep 'certificate-authority-data:' | \
head -1 | \
awk '{print $2}' | \
base64 -d > ca.crtPattern 5: Context-Specific Namespace
# Create different contexts pointing to same cluster but different namespaces
kubectl config set-context dev-namespace \
--cluster=kubernetes \
--user=devuser \
--namespace=development
kubectl config set-context prod-namespace \
--cluster=kubernetes \
--user=devuser \
--namespace=production
# Switch context to change namespace automatically
kubectl config use-context dev-namespace # Now using development namespace
kubectl get pods # Shows pods from development
kubectl config use-context prod-namespace # Now using production namespace
kubectl get pods # Shows pods from productionPattern 6: Temporary Context Override
# Switch context just for one command
kubectl --context=staging-admin get nodes
# Override namespace for one command
kubectl --namespace=kube-system get pods
# Override entire cluster connection
kubectl --server=https://different-api:6443 get nodesPattern 7: Audit Kubeconfig
# Show current context
kubectl config current-context
# Show all contexts with current marker
kubectl config get-contexts
# Show which cluster/user/namespace current context uses
kubectl config view | grep -A3 "current-context"
# Check if specific context exists
kubectl config get-contexts | grep "^[*]" | awk '{print $2}'
# Get API server for current context
kubectl config view | grep -A2 "current-context" | grep cluster | awk '{print $2}'Pattern 8: Cluster Switching Script
#!/bin/bash
# Interactive cluster switcher
# Get all contexts
CONTEXTS=$(kubectl config get-contexts | awk 'NR > 1 {print $2}')
echo "Available contexts:"
select CONTEXT in $CONTEXTS; do
kubectl config use-context "$CONTEXT"
echo "Switched to: $CONTEXT"
kubectl config current-context
kubectl cluster-info | head -1
break
donePart 1: Kubernetes PKI Basics
What is PKI?
PKI (Public Key Infrastructure) is a system for creating, managing, and validating digital certificates used for authentication and encryption in Kubernetes. Every component that needs to authenticate uses certificates signed by the cluster's Certificate Authority (CA).
PKI Directory Structure
All Kubernetes certificates are stored in /etc/kubernetes/pki/:
/etc/kubernetes/pki/
├── ca.crt # Cluster CA certificate (what signed everything)
├── ca.key # Cluster CA private key (secret! signs new certs)
├── apiserver.crt # API Server certificate
├── apiserver.key # API Server private key
├── kubelet*.crt # Kubelet certificates
├── controller-manager.crt # Controller manager certificate
├── scheduler.crt # Scheduler certificate
├── front-proxy-ca.* # Front proxy CA for API aggregation
├── etcd/ # ETCD certificates (database)
│ ├── ca.crt
│ ├── server.crt
│ └── server.key
└── sa.key # Service account signing key (not X.509)Why Multiple CAs?
Kubernetes uses different CAs for security isolation:
- Cluster CA (
ca.crt/ca.key): Main CA, signs kubelet, controller-manager, scheduler certificates - API Server CA: Dedicated CA for API server communication
- Front Proxy CA: For aggregated API servers
- ETCD CA: Isolated CA for ETCD database communication
Part 2: Understanding X.509 Certificates
Certificate Structure
Every X.509 certificate contains identity information:
Certificate Structure
├── Version: v3 (X.509v3)
├── Serial Number: Unique ID (2897472356293683110)
├── Signature Algorithm: sha256WithRSAEncryption
├── Issuer: CN=kubernetes (who signed this cert)
├── Validity Period
│ ├── Not Before: Jan 1 2026 GMT
│ └── Not After: Dec 30 2035 GMT (10 year validity)
├── Subject: CN=kubernetes-admin (who this cert is for)
├── Public Key Info
│ ├── Algorithm: RSA
│ └── Size: 2048 bits
└── Extensions (critical for Kubernetes)
├── Subject Alternative Names (SAN): DNS names, IP addresses
├── Key Usage: digitalSignature, keyEncipherment
└── Extended Key Usage: serverAuth, clientAuthCertificate Components
1. CN (Common Name) - The Identity
CN identifies WHO the certificate is for:
CN=kubernetes-admin # User name "kubernetes-admin"
CN=kubelet # System component "kubelet"
CN=controller-manager # System component "controller-manager"
CN=kubernetes # API server default identity2. O (Organization) - User Groups for RBAC
Organization field maps to groups, used for Role-Based Access Control:
O=system:masters # Built-in admin group
O=kubeadm:cluster-admins # Created by kubeadm (admins)
O=system:nodes # For kubelet certificates
O=developers # Custom developer group
O=system:authenticated # Standard authenticated users groupExample Certificate Subject:
Subject: O=kubeadm:cluster-admins, CN=kubernetes-admin
↓ ↓
Groups User Name3. SAN (Subject Alternative Names) - Where the Certificate is Valid
SAN lists all valid hostnames and IPs for the certificate:
DNS Names:
- kubernetes
- kubernetes.default
- kubernetes.default.svc
- kubernetes.default.svc.cluster.local
- localhost
IP Addresses:
- 127.0.0.1 (localhost)
- 10.96.0.1 (Kubernetes service IP)
- 10.0.0.100 (Control plane node IP)
- 10.0.0.101 (Other control plane nodes)Why important? If a DNS name or IP is NOT in SAN, the certificate is invalid for that endpoint!
Part 3: Inspecting Certificates with OpenSSL
Basic Certificate Inspection
# View certificate details
openssl x509 -in /etc/kubernetes/pki/ca.crt -text -noout
# View first 30 lines
openssl x509 -in /etc/kubernetes/pki/ca.crt -text -noout | head -30
# Extract specific fields
openssl x509 -in cert.crt -noout -subject # Get CN and O
openssl x509 -in cert.crt -noout -issuer # Who signed it
openssl x509 -in cert.crt -noout -dates # Validity periodExtract Certificate from Kubeconfig
Kubeconfig stores certificates as base64-encoded data. To decode:
# Extract CA certificate from kubeconfig
kubectl config view --raw | \
grep 'certificate-authority-data:' | head -1 | \
awk '{print $2}' | \
base64 -d > ca.crt
# Inspect
openssl x509 -in ca.crt -text -noout | head -20Check Certificate Validity
# View Not Before and Not After dates
openssl x509 -in cert.crt -noout -dates
# Output:
# notBefore=Jan 1 07:08:58 2026 GMT
# notAfter=Dec 30 07:13:58 2035 GMT
# Check days until expiration
openssl x509 -in cert.crt -noout -enddate | cut -d'=' -f2 | \
xargs -I {} date -d {} +%s | \
xargs -I {} expr \( {} - $(date +%s) \) / 86400View SAN Extensions
# See all Subject Alternative Names
openssl x509 -in cert.crt -text -noout | grep -A5 "Subject Alternative Name"
# Example output:
# X509v3 Subject Alternative Name:
# DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc,
# IP Address:10.96.0.1, IP Address:10.0.0.1Part 4: Kubeconfig File Format
Complete Kubeconfig Structure
apiVersion: v1
kind: Config
preferences: {}
# SECTION 1: Clusters - How to connect to clusters
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTi... # Base64 CA cert
server: https://172.30.1.2:6443 # API server URL
name: kubernetes # Cluster name (identifier)
# SECTION 2: Users - Authentication credentials
users:
- name: kubernetes-admin
user:
# Method A: Certificate-based auth (most common)
client-certificate-data: LS0tLS1CRUdJTi... # Base64 client cert
client-key-data: LS0tLS1CRUdJTi... # Base64 client key
# SECTION 3: Contexts - Bind (cluster + user + namespace)
contexts:
- context:
cluster: kubernetes # Which cluster (from clusters[])
user: kubernetes-admin # Which user (from users[])
namespace: default # Default namespace for this context
name: kubernetes-admin@kubernetes # Context name (identifier)
# SECTION 4: Current context - Default when you run kubectl
current-context: kubernetes-admin@kubernetesSection Details
Clusters Section
Defines how to reach a Kubernetes cluster:
clusters:
- cluster:
# CA cert to verify API server certificate
certificate-authority-data: [base64 encoded]
# OR use file path:
# certificate-authority: /etc/kubernetes/pki/ca.crt
# API server URL
server: https://api.example.com:6443
# DANGEROUS: Skip cert verification (never in production)
# insecure-skip-tls-verify: true
name: production-clusterUsers Section
Defines HOW to authenticate:
users:
- name: admin
user:
# Method 1: Certificate + Key (TLS client auth)
client-certificate-data: [base64]
client-key-data: [base64]
# Method 2: Token (for service accounts)
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Method 3: Username/Password (deprecated, rarely used)
username: user
password: pass123Contexts Section
Binds together cluster + user + namespace:
contexts:
- context:
cluster: production-cluster # Reference from clusters[]
user: admin # Reference from users[]
namespace: kube-system # Default namespace for commands
name: prod-admin # Context identifierPart 5: Creating Custom Users with Certificates
Step 1: Generate Private Key
# Create a 2048-bit RSA private key for the user
openssl genrsa -out devuser.key 2048
# Result: A private key file that should be kept secretStep 2: Create Certificate Signing Request (CSR)
A CSR is a request to the CA to sign a certificate:
openssl req -new \
-key devuser.key \
-out devuser.csr \
-subj "/CN=devuser/O=developers/O=kubeadm:cluster-admins"
# /CN=devuser → Username (Common Name)
# /O=developers → Group 1
# /O=kubeadm:cluster-admins → Group 2 (RBAC admin group)Step 3: Sign CSR with Cluster CA
The cluster admin signs the CSR, creating a valid certificate:
# Sign CSR with cluster CA (valid for 365 days)
openssl x509 -req -in devuser.csr \
-CA /etc/kubernetes/pki/ca.crt \
-CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial \
-out devuser.crt \
-days 365
# After this, devuser.crt is a valid Kubernetes certificateStep 4: Add to Kubeconfig
# 1. Add cluster (if not already there)
kubectl config set-cluster kubernetes \
--server=https://api.example.com:6443 \
--certificate-authority=/etc/kubernetes/pki/ca.crt
# 2. Add user with certificate
kubectl config set-credentials devuser \
--client-certificate=devuser.crt \
--client-key=devuser.key \
--embed-certs=true # Embed cert/key data in kubeconfig file
# 3. Create context
kubectl config set-context devuser-context \
--cluster=kubernetes \
--user=devuser
# 4. Switch to use this context
kubectl config use-context devuser-context
# 5. Verify connection
kubectl get nodesComplete Automation Script
#!/bin/bash
set -e
USERNAME="devuser"
GROUP="developers"
DAYS_VALID=365
CLUSTER_IP="api.example.com:6443"
CA_CERT="/etc/kubernetes/pki/ca.crt"
CA_KEY="/etc/kubernetes/pki/ca.key"
echo "[1/5] Generating private key..."
openssl genrsa -out ${USERNAME}.key 2048
echo "[2/5] Creating certificate signing request..."
openssl req -new \
-key ${USERNAME}.key \
-out ${USERNAME}.csr \
-subj "/CN=${USERNAME}/O=${GROUP}"
echo "[3/5] Signing CSR with cluster CA..."
openssl x509 -req -in ${USERNAME}.csr \
-CA ${CA_CERT} \
-CAkey ${CA_KEY} \
-CAcreateserial \
-out ${USERNAME}.crt \
-days ${DAYS_VALID}
echo "[4/5] Adding to kubeconfig..."
kubectl config set-cluster kubernetes \
--server=https://${CLUSTER_IP} \
--certificate-authority=${CA_CERT}
kubectl config set-credentials ${USERNAME} \
--client-certificate=${USERNAME}.crt \
--client-key=${USERNAME}.key \
--embed-certs=true
kubectl config set-context ${USERNAME}-context \
--cluster=kubernetes \
--user=${USERNAME}
echo "[5/5] Verifying..."
kubectl config get-contexts | grep ${USERNAME}
echo "✅ User '${USERNAME}' created successfully!"
echo "To use: kubectl config use-context ${USERNAME}-context"
echo "To apply RBAC: kubectl create rolebinding dev-role --clusterrole=view --user=${USERNAME}"Part 7: Kubernetes API CertificateSigningRequest (CSR)
Creating CSR Through Kubernetes API (Real-World Method)
In production clusters, instead of manually signing certificates, you create a CertificateSigningRequest object and let the cluster's control plane sign it.
Practical: Generate CSR and Submit to Kubernetes
Step 1: Generate Private Key and CSR (Client-Side)
# Generate private key
openssl genrsa -out newuser.key 2048
# Create CSR (note: generate CSR with subject, no signing yet)
openssl req -new \
-key newuser.key \
-out newuser.csr \
-subj "/CN=newuser/O=developers"
# Verify CSR was created
openssl req -in newuser.csr -text -nooutStep 2: Create Kubernetes CSR Object
# Base64 encode the CSR
CSR_CONTENT=$(cat newuser.csr | base64 | tr -d '\n')
# Create CSR object
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: newuser-csr
spec:
request: $CSR_CONTENT
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 31536000 # 1 year
usages:
- client auth
EOF
# Verify CSR was created
kubectl get csr
kubectl describe csr newuser-csrStep 3: Approve the CSR
# Admin approves the request
kubectl certificate approve newuser-csr
# Verify it's approved
kubectl get csr newuser-csr
# Status should show: Approved, Issued
# View the approved certificate (base64 encoded)
kubectl get csr newuser-csr -o jsonpath='{.status.certificate}' | base64 -d > newuser.crt
# Verify the certificate
openssl x509 -in newuser.crt -text -noout | head -20Step 4: Add User to Kubeconfig
# Set cluster (if not already present)
kubectl config set-cluster kubernetes \
--server=https://api.example.com:6443 \
--certificate-authority=/path/to/ca.crt
# Add user with the issued certificate
kubectl config set-credentials newuser \
--client-certificate=newuser.crt \
--client-key=newuser.key \
--embed-certs=true
# Create context
kubectl config set-context newuser-context \
--cluster=kubernetes \
--user=newuser
# Test access
kubectl config use-context newuser-context
kubectl get podsComplete Automation Script
#!/bin/bash
set -e
USERNAME="$1"
GROUP="${2:-developers}"
CLUSTER_NAME="${3:-kubernetes}"
API_SERVER="${4:-https://api.example.com:6443}"
CA_PATH="${5:-/etc/kubernetes/pki/ca.crt}"
if [ -z "$USERNAME" ]; then
echo "Usage: $0 <username> [group] [cluster-name] [api-server] [ca-path]"
exit 1
fi
echo "📋 Creating user certificate via Kubernetes CSR API..."
echo ""
# Step 1: Generate private key
echo "[1/5] Generating private key..."
openssl genrsa -out "${USERNAME}.key" 2048 2>/dev/null
chmod 600 "${USERNAME}.key"
# Step 2: Create CSR file
echo "[2/5] Creating certificate signing request..."
openssl req -new \
-key "${USERNAME}.key" \
-out "${USERNAME}.csr" \
-subj "/CN=${USERNAME}/O=${GROUP}" 2>/dev/null
# Step 3: Submit CSR to Kubernetes
echo "[3/5] Submitting CSR to Kubernetes API..."
CSR_CONTENT=$(cat "${USERNAME}.csr" | base64 | tr -d '\n')
kubectl apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: ${USERNAME}-csr
spec:
request: $CSR_CONTENT
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 31536000
usages:
- client auth
EOF
# Step 4: Wait for approval (manual or automated)
echo "[4/5] CSR created. Waiting for approval..."
echo " Admin needs to run: kubectl certificate approve ${USERNAME}-csr"
# Optional: auto-approve for testing
# kubectl certificate approve ${USERNAME}-csr
# Wait for approval (with timeout)
TIMEOUT=300
ELAPSED=0
while [ $ELAPSED -lt $TIMEOUT ]; do
STATUS=$(kubectl get csr "${USERNAME}-csr" -o jsonpath='{.status.conditions[?(@.type=="Approved")].type}' 2>/dev/null || echo "")
if [ "$STATUS" == "Approved" ]; then
echo " ✅ CSR Approved!"
break
fi
sleep 5
ELAPSED=$((ELAPSED + 5))
done
# Step 5: Extract certificate and configure kubeconfig
echo "[5/5] Extracting certificate and configuring kubeconfig..."
kubectl get csr "${USERNAME}-csr" -o jsonpath='{.status.certificate}' | base64 -d > "${USERNAME}.crt"
# Configure kubeconfig
kubectl config set-cluster "${CLUSTER_NAME}" \
--server="${API_SERVER}" \
--certificate-authority="${CA_PATH}"
kubectl config set-credentials "${USERNAME}" \
--client-certificate="${USERNAME}.crt" \
--client-key="${USERNAME}.key" \
--embed-certs=true
kubectl config set-context "${USERNAME}-context" \
--cluster="${CLUSTER_NAME}" \
--user="${USERNAME}"
echo ""
echo "✅ User '${USERNAME}' created successfully!"
echo ""
echo "📌 Files created:"
echo " - ${USERNAME}.key (private key - KEEP SECURE)"
echo " - ${USERNAME}.csr (certificate signing request)"
echo " - ${USERNAME}.crt (signed certificate)"
echo ""
echo "📌 To use this user:"
echo " kubectl config use-context ${USERNAME}-context"
echo " kubectl get pods"
echo ""
echo "📌 To grant permissions:"
echo " kubectl create rolebinding ${USERNAME}-view --clusterrole=view --user=${USERNAME}"View and Manage CSRs
# List all CSRs
kubectl get csr
# Show detailed CSR info
kubectl describe csr <csr-name>
# Show CSR in YAML
kubectl get csr <csr-name> -o yaml
# View the original CSR content
kubectl get csr <csr-name> -o jsonpath='{.spec.request}' | base64 -d | openssl req -text -noout
# Approve CSR
kubectl certificate approve <csr-name>
# Deny CSR
kubectl certificate deny <csr-name>
# Delete CSR
kubectl delete csr <csr-name>
# Watch for pending CSRs
kubectl get csr --watchAvailable Signer Names
# Different signerName values for different certificate types:
# 1. Client certificate for connecting to API server
signerName: kubernetes.io/kube-apiserver-client
# 2. Kubelet serving certificate (for kubelet HTTPS endpoint)
signerName: kubernetes.io/kubelet-serving
# 3. Legacy (kubeadm) client certificate
signerName: kubernetes.io/kube-apiserver-client-kubelet
# Check available signers in your cluster:
kubectl get certificatesigningrequests.certificates.k8s.io -o json | \
jq '.items[].spec.signerName' | sort | uniqCSR Best Practices for Kind Clusters
# 1. For kind, approve CSRs immediately or auto-approve them
kubectl certificate approve <csr-name>
# 2. Create a helper function for approval in testing
auto-approve-csrs() {
kubectl get csr --no-headers | awk '{print $1}' | while read csr; do
if kubectl get csr "$csr" -o jsonpath='{.status.conditions}' | grep -q Approved; then
continue
fi
echo "Approving: $csr"
kubectl certificate approve "$csr"
done
}
# 3. Watch for pending CSRs
watch 'kubectl get csr'Part 8: Working with Kubeconfig - Examples
Example 1: Create Read-Only Viewer Account
# Generate keys
openssl genrsa -out viewer.key 2048
openssl req -new -key viewer.key -out viewer.csr -subj "/CN=viewer/O=viewers"
openssl x509 -req -in viewer.csr -CA /etc/kubernetes/pki/ca.crt \
-CAkey /etc/kubernetes/pki/ca.key -out viewer.crt -days 365
# Add to kubeconfig
kubectl config set-credentials viewer \
--client-certificate=viewer.crt --client-key=viewer.key
kubectl config set-context viewer-context --cluster=kubernetes --user=viewer
# Create RBAC role binding
kubectl create rolebinding viewer-role --clusterrole=view --user=viewerExample 2: Certificate Expiration Check
# Check when certs expire
for cert in /etc/kubernetes/pki/*.crt; do
echo "$cert:"
openssl x509 -in "$cert" -noout -dates
done
# List certs expiring within 30 days
# (Useful for monitoring and renewal planning)Example 3: Verify Certificate Trust Chain
# Check if cert was signed by CA
openssl verify -CAfile /etc/kubernetes/pki/ca.crt cert.crt
# Output: cert.crt: OK (if valid)Last Updated: January 9, 2026