Kubernetes Security
A comprehensive guide to Kubernetes security covering RBAC, security contexts, pod security policies, OPA/Gatekeeper, secrets management, and practical implementation strategies for securing production clusters.
Kubernetes security requires a defense-in-depth strategy with multiple layers of protection. Key security areas include:
- Authentication & Authorization: Who can access the cluster and what they can do
- Pod Security: What containers can do at runtime
- Network Security: What can communicate with what
- Data Security: Protecting secrets and sensitive data
- Policy Enforcement: Ensuring compliance with security policies
- Image Security: Ensuring only trusted images are deployed
RBAC is the primary mechanism for controlling access to Kubernetes resources. It defines who can do what to which resources in the cluster.
# Role (namespace-scoped)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# ClusterRole (cluster-scoped)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-admin-readonly
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch"]
# RoleBinding (bind role to subjects)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: developer@example.com
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: my-sa
namespace: default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
# ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: view-all
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin-readonly
apiGroup: rbac.authorization.k8s.io
# Check RBAC permissions
kubectl auth can-i get pods --namespace default
kubectl auth can-i create deployments --namespace production
kubectl auth can-i delete secrets --as=developer@example.com
# View RBAC resources
kubectl get roles --all-namespaces
kubectl get clusterroles
kubectl get rolebindings --all-namespaces
kubectl get clusterrolebindings
- Use the principle of least privilege
- Prefer namespaced Roles over ClusterRoles
- Use Groups for managing multiple users
- Regularly audit RBAC permissions
- Use ServiceAccounts for pods needing API access
- Avoid using the default ServiceAccount
Security Contexts define privilege and access control settings for pods and containers. They are essential for running containers securely.
# Pod Security Context
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsUser: 1000 # Run as non-root user
runAsGroup: 3000
fsGroup: 2000
runAsNonRoot: true # Prevent root execution
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
securityContext:
capabilities:
drop: ["ALL"] # Drop all capabilities
add: ["NET_BIND_SERVICE"] # Only add required
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
volumeMounts:
- name: data
mountPath: /data
# Security Context for Pod (user namespace)
apiVersion: v1
kind: Pod
metadata:
name: isolated-pod
spec:
securityContext:
runAsUser: 1000
seccompProfile:
type: Localhost
localhostProfile: profiles/my-seccomp.json
supplementalGroups: [2000, 3000]
# Common security settings
# - runAsNonRoot: true (prevent root)
# - readOnlyRootFilesystem: true (prevent writes)
# - allowPrivilegeEscalation: false (prevent priv escalation)
# - drop capabilities (remove unnecessary privileges)
# - seccomp profile (restrict syscalls)
runAsNonRoot: true and allowPrivilegeEscalation: false in production. Drop all unnecessary capabilities (capabilities.drop: ["ALL"]) and only add what's needed (capabilities.add: ["NET_BIND_SERVICE"]).
Pod Security Standards define three security profiles that can be enforced at the namespace level: Privileged, Baseline, and Restricted.
# Enable Pod Security Admission (built-in)
# Kubernetes v1.23+ supports Pod Security Admission controller
# Label namespace with security profile
kubectl label namespace default pod-security.kubernetes.io/enforce=baseline
kubectl label namespace default pod-security.kubernetes.io/audit=restricted
kubectl label namespace default pod-security.kubernetes.io/warn=restricted
# Enforce Restricted profile (strictest)
kubectl label namespace production pod-security.kubernetes.io/enforce=restricted
# Pod Security Standards levels:
# - Privileged: Unrestricted (default)
# - Baseline: Minimally restrictive, prevents known privilege escalations
# - Restricted: Heavily restricted, follows Pod Security best practices
# Pod that violates Restricted profile
apiVersion: v1
kind: Pod
metadata:
name: problematic-pod
spec:
containers:
- name: app
image: nginx
securityContext:
runAsUser: 0 # root user - violates Restricted
allowPrivilegeEscalation: true # violates Restricted
# Pod that complies with Restricted profile
apiVersion: v1
kind: Pod
metadata:
name: compliant-pod
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
- Privileged: No restrictions (use sparingly)
- Baseline: Prevents known privilege escalations
- Restricted: Enforces pod security best practices
Start with warn and audit modes before enforcing to avoid breaking existing workloads.
Open Policy Agent (OPA) with Gatekeeper provides policy-as-code capabilities for Kubernetes. It enables fine-grained policy enforcement beyond what's possible with built-in admission controllers.
# Install Gatekeeper
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.12/deploy/gatekeeper.yaml
# Verify Gatekeeper installation
kubectl get pods -n gatekeeper-system
# ConstraintTemplate (defines policy logic)
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the constraint parameters
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
# Constraint (enforces the policy)
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-security-labels
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "production"
- "staging"
parameters:
labels:
- "security-tier"
- "owner"
# Another ConstraintTemplate: prevent privilege escalation
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sprivilegeescalation
spec:
crd:
spec:
names:
kind: K8sPrivilegeEscalation
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sprivilegeescalation
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.allowPrivilegeEscalation == true
msg := sprintf("Container %v allows privilege escalation", [container.name])
}
# Constraint: disallow privilege escalation
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPrivilegeEscalation
metadata:
name: no-privilege-escalation
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
- Enforce custom policies beyond built-in admission controllers
- Audit existing resources for policy violations
- Dry-run mode for testing policies
- Policy-as-code with version control
- Reusable policy templates
Secrets management is critical for Kubernetes security. Secrets contain sensitive data like passwords, API keys, and certificates. Use proper secret management to protect this data.
# Create a secret
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=supersecret
# Secret from file
kubectl create secret generic tls-cert \
--from-file=cert.pem=/path/to/cert.pem \
--from-file=key.pem=/path/to/key.pem
# Secret YAML (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4= # base64 of "admin"
password: c3VwZXJzZWNyZXQ= # base64 of "supersecret"
# Use secret in pod (environment variable)
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: myapp
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: password
# Use secret as volume mount
volumes:
- name: secrets
secret:
secretName: db-credentials
mode: 0400
containers:
- name: app
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
# Sealed Secrets (GitOps safe)
# Install Sealed Secrets
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/controller.yaml
# Create sealed secret
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
# Sealed Secret YAML (safe to commit to Git)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
spec:
encryptedData:
username: AgBy... # Encrypted data
password: AgBy... # Encrypted data
# External Secrets (using HashiCorp Vault)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-store
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
auth:
kubernetes:
role: "app-role"
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
secretStoreRef:
name: vault-store
target:
name: db-credentials
data:
- secretKey: username
remoteRef:
key: app/db
property: username
- Never store raw secrets in Git
- Enable etcd encryption for secrets
- Use Sealed Secrets or SOPS for GitOps
- Use External Secrets Operator for Vault integration
- Regularly rotate secrets
- Limit secret access with RBAC
Network policies provide security at the network level, controlling which pods can communicate with each other.
# Default deny all ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
# Allow specific namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-monitoring
spec:
podSelector:
matchLabels:
app: my-app
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
policyTypes:
- Ingress
# Allow specific pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend
spec:
podSelector:
matchLabels:
app: backend
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
policyTypes:
- Ingress
# Allow egress to external database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-db
spec:
podSelector:
matchLabels:
app: my-app
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- port: 5432
protocol: TCP
policyTypes:
- Egress
- Start with default deny all policies
- Gradually allow specific traffic
- Use namespace isolation
- Implement egress policies for external access
- Test policies with network policy verification tools
- Requires a CNI that supports NetworkPolicy (Calico, Cilium, etc.)
RBAC
Pod Security
Network
Secrets
Policies
Images
runAsNonRoot: true and set a specific user ID. This reduces the attack surface and limits the impact of container compromises.Kubernetes security is a journey, not a destination. Implement defense-in-depth, regularly audit your security posture, and stay updated on emerging threats and best practices.