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

Resources for Further Learning