Kubernetes security starts at the API server. Every request is authenticated, authorized, and then checked by admission control before anything changes in the cluster. RBAC is the part most teams touch every day, so this post keeps the focus practical: who gets access, what they can do, and how to keep that access narrow.
Kubernetes Security Model
┌─────────────────────────────────────────────────┐
│ Request Flow │
│ │
│ User/Service ──► API Server │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │Authentication│ Who are you? │
│ └──────┬───────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │Authorization │ What can you do? │
│ └──────┬───────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Admission │ Is it allowed? │
│ └──────┬───────┘ │
│ ▼ │
│ Execute │
└─────────────────────────────────────────────────┘
Authentication Methods
1. X.509 Client Certificates
# Generate user certificate
openssl genrsa -out developer.key 2048
openssl req -new -key developer.key \
-out developer.csr \
-subj "/CN=developer/O=dev-team"
# Sign with cluster CA
openssl x509 -req -in developer.csr \
-CA /etc/kubernetes/pki/ca.crt \
-CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial \
-out developer.crt \
-days 365
# Create kubeconfig
kubectl config set-credentials developer \
--client-certificate=developer.crt \
--client-key=developer.key
kubectl config set-context developer-context \
--cluster=kubernetes \
--user=developer \
--namespace=development
2. Service Accounts
# service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: webapp-sa
namespace: production
---
apiVersion: v1
kind: Secret
metadata:
name: webapp-sa-token
namespace: production
annotations:
kubernetes.io/service-account.name: webapp-sa
type: kubernetes.io/service-account-token
# pod-with-sa.yaml
apiVersion: v1
kind: Pod
metadata:
name: webapp
namespace: production
spec:
serviceAccountName: webapp-sa
automountServiceAccountToken: true
containers:
- name: webapp
image: myapp:v1.0
3. OIDC Authentication
# API server configuration
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
spec:
containers:
- command:
- kube-apiserver
- --oidc-issuer-url=https://accounts.google.com
- --oidc-client-id=my-client-id
- --oidc-username-claim=email
- --oidc-groups-claim=groups
Role-Based Access Control (RBAC)
RBAC Components
┌─────────────────────────────────────────────────┐
│ RBAC Model │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Subject │ │ Resource │ │
│ │ (User/SA) │ │ (Pod/Svc) │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ ┌──────────┐ │ │
│ └───►│RoleBinding├─────┘ │
│ └─────┬────┘ │
│ │ │
│ ┌─────▼────┐ │
│ │ Role │ │
│ │ (Verbs) │ │
│ └──────────┘ │
└─────────────────────────────────────────────────┘
Roles and ClusterRoles
# role.yaml - Namespace-scoped
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
# clusterrole.yaml - Cluster-scoped
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
RoleBindings and ClusterRoleBindings
# rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: User
name: [email protected]
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: webapp-sa
namespace: production
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
# clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-admin-binding
subjects:
- kind: User
name: [email protected]
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
Common Role Examples
# developer-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: development
name: developer
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "deployments", "services", "configmaps", "jobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods/log", "pods/exec"]
verbs: ["get", "create"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
---
# readonly-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: readonly
rules:
- apiGroups: ["", "apps", "batch", "networking.k8s.io"]
resources: ["*"]
verbs: ["get", "list", "watch"]
---
# deployer-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: deployer
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: ["apps"]
resources: ["deployments/scale"]
verbs: ["update", "patch"]
Aggregated ClusterRoles
# aggregated-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-endpoints
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
resources: ["services", "endpoints", "pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # Rules are automatically filled
Pod Security
Security Contexts
# secure-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: secure-webapp
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: webapp
image: myapp:v1.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
Pod Security Standards
# namespace-with-pss.yaml
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
apiVersion: v1
kind: Namespace
metadata:
name: development
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Network Policies
# network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: webapp-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: webapp
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: production
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
Secrets Management
Encrypted Secrets at Rest
# encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {}
External Secrets Operator
# external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: production/database
property: username
- secretKey: password
remoteRef:
key: production/database
property: password
Audit Logging
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: ""
resources: ["endpoints", "services"]
- level: None
users: ["kubelet"]
verbs: ["get"]
resources:
- group: ""
resources: ["nodes"]
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: Request
verbs: ["create", "update", "patch", "delete"]
resources:
- group: ""
resources: ["pods", "services"]
- group: "apps"
resources: ["deployments"]
- level: RequestResponse
users: ["admin"]
verbs: ["create", "delete"]
Testing RBAC
# Check permissions
kubectl auth can-i create pods --namespace production
kubectl auth can-i create pods --namespace production --as [email protected]
kubectl auth can-i '*' '*' --as system:serviceaccount:production:webapp-sa
# List permissions
kubectl auth can-i --list --namespace production
kubectl auth can-i --list --as [email protected]
# Verify role bindings
kubectl get rolebindings -n production
kubectl describe rolebinding read-pods -n production
# Check who can perform action
kubectl auth who-can create pods -n production
Security Best Practices
# secure-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-webapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
serviceAccountName: webapp-sa
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: webapp
image: myapp:v1.0@sha256:abc123...
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
limits:
cpu: "500m"
memory: "256Mi"
requests:
cpu: "100m"
memory: "128Mi"
Useful Commands
# Service Accounts
kubectl create serviceaccount webapp-sa -n production
kubectl get serviceaccounts -n production
# Roles
kubectl create role pod-reader --verb=get,list,watch --resource=pods -n production
kubectl get roles -n production
# ClusterRoles
kubectl create clusterrole secret-reader --verb=get,list --resource=secrets
kubectl get clusterroles
# RoleBindings
kubectl create rolebinding read-pods --role=pod-reader --user=developer -n production
kubectl get rolebindings -n production
# ClusterRoleBindings
kubectl create clusterrolebinding admin-binding --clusterrole=cluster-admin --user=admin
kubectl get clusterrolebindings
# Testing
kubectl auth can-i create pods -n production
kubectl auth can-i --list -n production
kubectl auth who-can delete pods -n production
What Matters in Practice
- Authentication verifies identity (certificates, tokens, OIDC)
- Authorization (RBAC) controls what authenticated users can do
- Roles are namespace-scoped; ClusterRoles are cluster-wide
- Security contexts restrict container capabilities
- Pod Security Standards enforce security baselines
- Network policies control pod-to-pod communication
- Principle of least privilege: Grant minimum required permissions