Skip to main content

Security

RBAC, Security Contexts, Pod Security Standards, and security best practices for Kubernetes clusters and workloads.

Role-Based Access Control (RBAC)

Roles and ClusterRoles

Role (Namespace-scoped)

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: ['']
resources: ['pods']
verbs: ['get', 'watch', 'list']
- apiGroups: ['']
resources: ['pods/log']
verbs: ['get']

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: deployment-manager
rules:
- apiGroups: ['apps']
resources: ['deployments']
verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete']
- apiGroups: ['']
resources: ['pods']
verbs: ['get', 'list', 'watch']

ClusterRole (Cluster-wide)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: ['']
resources: ['nodes']
verbs: ['get', 'watch', 'list']

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-admin-custom
rules:
- apiGroups: ['*']
resources: ['*']
verbs: ['*']
- nonResourceURLs: ['*']
verbs: ['*']

RoleBindings and ClusterRoleBindings

RoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: pod-reader
namespace: default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployment-manager-binding
namespace: production
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: deployment-manager
apiGroup: rbac.authorization.k8s.io

ClusterRoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cluster-admin-binding
subjects:
- kind: User
name: admin
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: system:masters
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io

Service Accounts

ServiceAccount Creation

apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
namespace: default
automountServiceAccountToken: false

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: pod-reader-sa
namespace: default
annotations:
kubernetes.io/enforce-mountable-secrets: 'true'
secrets:
- name: pod-reader-token

Using ServiceAccount in Pods

apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
serviceAccountName: my-service-account
automountServiceAccountToken: false
containers:
- name: app
image: nginx
volumeMounts:
- name: token-volume
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
readOnly: true
volumes:
- name: token-volume
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
- downwardAPI:
items:
- path: namespace
fieldRef:
fieldPath: metadata.namespace

RBAC Operations

# Get roles and rolebindings
kubectl get roles
kubectl get rolebindings
kubectl get clusterroles
kubectl get clusterrolebindings

# Describe RBAC resources
kubectl describe role role-name
kubectl describe rolebinding binding-name
kubectl describe clusterrole cluster-role-name

# Check permissions
kubectl auth can-i create pods
kubectl auth can-i create pods --as=user
kubectl auth can-i create pods --as=system:serviceaccount:default:my-sa

# Create service account
kubectl create serviceaccount my-service-account

# Create role
kubectl create role pod-reader --verb=get,list,watch --resource=pods

# Create rolebinding
kubectl create rolebinding pod-reader-binding --role=pod-reader --user=jane

# Create clusterrole
kubectl create clusterrole node-reader --verb=get,list,watch --resource=nodes

# Create clusterrolebinding
kubectl create clusterrolebinding node-reader-binding --clusterrole=node-reader --user=jane

Security Contexts

Pod Security Context

apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsUser: 1000
runAsGroup: 2000
runAsNonRoot: true
fsGroup: 3000
seccompProfile:
type: RuntimeDefault
seLinuxOptions:
level: 's0:c123,c456'
containers:
- name: app
image: nginx
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 1001

Container Security Context

apiVersion: v1
kind: Pod
metadata:
name: container-security
spec:
containers:
- name: secure-container
image: nginx
securityContext:
runAsUser: 1000
runAsGroup: 2000
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp-volume
mountPath: /tmp
- name: cache-volume
mountPath: /var/cache/nginx
volumes:
- name: tmp-volume
emptyDir: {}
- name: cache-volume
emptyDir: {}

Privileged and Host Access

# Privileged container (avoid in production)
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: privileged-container
image: busybox
securityContext:
privileged: true
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
runAsUser: 0
volumeMounts:
- name: host-root
mountPath: /host
volumes:
- name: host-root
hostPath:
path: /

# Host network and PID
---
apiVersion: v1
kind: Pod
metadata:
name: host-access-pod
spec:
hostNetwork: true
hostPID: true
hostIPC: true
containers:
- name: host-container
image: busybox
command: ['sleep', '3600']

Pod Security Standards

Pod Security Policy (Deprecated)

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted-psp
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'

Pod Security Standards (Current)

# Namespace with Pod Security Standards
apiVersion: v1
kind: Namespace
metadata:
name: secure-namespace
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted

---
# Baseline security
apiVersion: v1
kind: Namespace
metadata:
name: baseline-namespace
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/audit: baseline
pod-security.kubernetes.io/warn: baseline

Security Context Constraints (OpenShift)

apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
name: restricted-scc
allowHostDirVolumePlugin: false
allowHostIPC: false
allowHostNetwork: false
allowHostPID: false
allowHostPorts: false
allowPrivilegedContainer: false
allowedCapabilities: null
defaultAddCapabilities: null
requiredDropCapabilities:
- KILL
- MKNOD
- SETUID
- SETGID
runAsUser:
type: MustRunAsRange
uidRangeMin: 1000000000
uidRangeMax: 2000000000
seLinuxContext:
type: MustRunAs
volumes:
- configMap
- downwardAPI
- emptyDir
- persistentVolumeClaim
- projected
- secret

Network Security

Network Policies

# Default deny all traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

---
# Allow specific communication
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
- namespaceSelector:
matchLabels:
name: frontend-namespace
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432

Secure Communication with TLS

apiVersion: v1
kind: Secret
metadata:
name: tls-secret
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTi... # Base64 encoded certificate
tls.key: LS0tLS1CRUdJTi... # Base64 encoded private key

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: secure-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
spec:
tls:
- hosts:
- secure.example.com
secretName: tls-secret
rules:
- host: secure.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: secure-service
port:
number: 443

Secrets Management

Encrypted Secrets at Rest

apiVersion: apiserver.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- identity: {}

External Secrets Operator

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: 'https://vault.example.com'
path: 'secret'
version: 'v2'
auth:
kubernetes:
mountPath: 'kubernetes'
role: 'my-role'

---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: vault-secret
spec:
refreshInterval: 15s
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: example-secret
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: secret/data/database
property: password

Sealed Secrets

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: sealed-secret-example
spec:
encryptedData:
password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEQAx...
template:
metadata:
name: secret-example
type: Opaque

Image Security

Image Scanning

# Scan image with trivy
trivy image nginx:latest

# Scan image in cluster
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: image-scanner
spec:
containers:
- name: trivy
image: aquasec/trivy:latest
command: ["trivy"]
args: ["image", "nginx:latest"]
restartPolicy: Never
EOF

Image Policy Webhook

apiVersion: v1
kind: ConfigMap
metadata:
name: image-policy-webhook
namespace: kube-system
data:
admission_config.json: |
{
"imagePolicy": {
"kubeConfigFile": "/etc/kubernetes/image-policy-webhook.kubeconfig",
"allowTTL": 50,
"denyTTL": 50,
"retryBackoff": 500,
"defaultAllow": false
}
}

Admission Controllers

# ValidatingAdmissionWebhook
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionWebhook
metadata:
name: security-validator
webhooks:
- name: security.example.com
clientConfig:
service:
name: security-webhook
namespace: default
path: '/validate'
rules:
- operations: ['CREATE', 'UPDATE']
apiGroups: ['']
apiVersions: ['v1']
resources: ['pods']
admissionReviewVersions: ['v1', 'v1beta1']
sideEffects: None
failurePolicy: Fail

Runtime Security

Falco Security Monitoring

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: falco
namespace: falco
spec:
selector:
matchLabels:
app: falco
template:
metadata:
labels:
app: falco
spec:
serviceAccount: falco
hostNetwork: true
hostPID: true
containers:
- name: falco
image: falcosecurity/falco:latest
args:
- /usr/bin/falco
- --cri=/run/containerd/containerd.sock
- --k8s-api=https://kubernetes.default.svc.cluster.local
- --k8s-api-cert=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- --k8s-api-key=/var/run/secrets/kubernetes.io/serviceaccount/token
securityContext:
privileged: true
volumeMounts:
- mountPath: /host/var/run/docker.sock
name: docker-sock
- mountPath: /host/dev
name: dev-fs
- mountPath: /host/proc
name: proc-fs
readOnly: true
- mountPath: /host/boot
name: boot-fs
readOnly: true
- mountPath: /host/lib/modules
name: lib-modules
readOnly: true
- mountPath: /host/usr
name: usr-fs
readOnly: true
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
- name: dev-fs
hostPath:
path: /dev
- name: proc-fs
hostPath:
path: /proc
- name: boot-fs
hostPath:
path: /boot
- name: lib-modules
hostPath:
path: /lib/modules
- name: usr-fs
hostPath:
path: /usr

OPA Gatekeeper

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredsecuritycontext
spec:
crd:
spec:
names:
kind: K8sRequiredSecurityContext
validation:
type: object
properties:
runAsNonRoot:
type: boolean
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredsecuritycontext

violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := "Container must run as non-root user"
}

---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredSecurityContext
metadata:
name: must-run-as-nonroot
spec:
match:
kinds:
- apiGroups: ['']
kinds: ['Pod']
parameters:
runAsNonRoot: true

Security Best Practices

Secure Pod Configuration

apiVersion: v1
kind: Pod
metadata:
name: secure-pod-example
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 2000
fsGroup: 3000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx:1.20-alpine
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: '256Mi'
cpu: '200m'
requests:
memory: '128Mi'
cpu: '100m'
volumeMounts:
- name: tmp-volume
mountPath: /tmp
- name: nginx-cache
mountPath: /var/cache/nginx
volumes:
- name: tmp-volume
emptyDir: {}
- name: nginx-cache
emptyDir: {}

Security Scanning Commands

# Check pod security standards compliance
kubectl label --dry-run=server --overwrite ns default \
pod-security.kubernetes.io/enforce=restricted

# Audit security context
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.securityContext}{"\n"}{end}'

# Check for privileged containers
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].securityContext.privileged}{"\n"}{end}'

# Check service accounts
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}'

# Check capabilities
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].securityContext.capabilities}{"\n"}{end}'

Quick Reference

RBAC Commands

  • kubectl auth can-i verb resource - Check permissions
  • kubectl create role name --verb=get --resource=pods - Create role
  • kubectl create rolebinding name --role=role --user=user - Bind role
  • kubectl get rolebindings - List role bindings

Security Context

  • runAsNonRoot: true - Prevent root execution
  • readOnlyRootFilesystem: true - Read-only filesystem
  • allowPrivilegeEscalation: false - Prevent privilege escalation
  • capabilities.drop: [ALL] - Drop all capabilities

Pod Security Standards

  • Privileged - Unrestricted policy (allow all)
  • Baseline - Minimally restrictive (block known privilege escalations)
  • Restricted - Heavily restricted (current Pod hardening best practices)

Security Best Practices

  • Use non-root containers
  • Implement RBAC with least privilege
  • Scan images for vulnerabilities
  • Use network policies for traffic isolation
  • Enable audit logging
  • Rotate secrets regularly
  • Use Pod Security Standards
  • Implement runtime security monitoring