Kubernetes Pod Security and Best Practices
Kubernetes Pod Security and Best Practices
Security in Kubernetes is a critical concern for any organization running containerized workloads in production. Pods are the smallest deployable units in Kubernetes, and securing them is essential to maintaining a robust and secure cluster. In this comprehensive guide, we will explore Pod Security Standards, Security Contexts, Network Policies, RBAC, Secrets Management, Container Security, Image Scanning, and Runtime Security.
Understanding Kubernetes Pod Security
Pod security encompasses multiple layers of protection that work together to ensure your workloads are secure. The shift from Pod Security Policies (PSPs) to Pod Security Standards (PSS) and Pod Security Admission (PSA) represents a significant evolution in how Kubernetes handles pod security.
Pod Security Standards
As of Kubernetes 1.25, Pod Security Policies (PSPs) have been removed and replaced with Pod Security Standards. These standards define three levels of security:
Privileged
The Privileged standard is unrestricted and allows for known privilege escalations. This level should only be used for system-level workloads that require elevated privileges.
Baseline
The Baseline standard prevents known privilege escalations while remaining minimally restrictive. It is suitable for most common workloads that do not require elevated privileges.
Restricted
The Restricted standard is heavily restricted and follows current pod hardening best practices. This level is recommended for security-critical applications.
Implementing Pod Security Standards
To implement Pod Security Standards at the namespace level, you can use Pod Security Admission labels:
1
2
3
4
5
6
7
8
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
The three modes available are:
- enforce: Policy violations will cause the pod to be rejected
- audit: Policy violations will trigger an audit annotation but pods will be allowed
- warn: Policy violations will trigger a user-facing warning but pods will be allowed
Security Contexts
Security Contexts define privilege and access control settings for pods and containers. They allow you to specify settings such as user and group IDs, capabilities, and SELinux options.
Pod-Level Security Context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx:1.21
resources:
limits:
memory: "128Mi"
cpu: "500m"
In this example:
runAsUser: Specifies the user ID to run the container processrunAsGroup: Specifies the primary group IDfsGroup: Specifies a special supplemental group for volume accessseccompProfile: Enables seccomp to restrict system calls
Container-Level Security Context
Container-level security contexts override pod-level settings for specific containers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: v1
kind: Pod
metadata:
name: secure-container
spec:
containers:
- name: app
image: myapp:1.0
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
volumeMounts:
- name: cache
mountPath: /cache
volumes:
- name: cache
emptyDir: {}
Key security settings:
runAsNonRoot: Ensures the container runs as a non-root userallowPrivilegeEscalation: Prevents gaining more privileges than the parent processreadOnlyRootFilesystem: Makes the root filesystem read-onlycapabilities: Drops all capabilities and only adds what is necessary
Network Policies
Network Policies control traffic flow between pods and network endpoints. By default, pods accept traffic from any source. Network Policies allow you to implement network segmentation.
Default Deny All Ingress and Egress
1
2
3
4
5
6
7
8
9
10
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
This policy denies all ingress and egress traffic to all pods in the namespace.
Allow Specific Traffic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
ports:
- protocol: TCP
port: 8080
This policy allows traffic from pods labeled app: frontend to pods labeled app: backend on port 8080.
Egress Policy for External Services
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-and-api
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
This policy allows backend pods to access DNS services and the database.
Role-Based Access Control (RBAC)
RBAC regulates access to Kubernetes resources based on the roles of individual users or service accounts.
Creating a Role
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
Creating a RoleBinding
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: ServiceAccount
name: app-service-account
namespace: production
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
ClusterRole for Cluster-Wide Permissions
1
2
3
4
5
6
7
8
9
10
11
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes"]
verbs: ["get", "list"]
Service Account for Pods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: production
automountServiceAccountToken: false
---
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: production
spec:
serviceAccountName: app-service-account
automountServiceAccountToken: false
containers:
- name: app
image: myapp:1.0
Setting automountServiceAccountToken: false prevents automatic mounting of service account tokens unless explicitly needed.
Secrets Management
Kubernetes Secrets store sensitive information such as passwords, tokens, and keys. However, default Secrets are only base64-encoded, not encrypted at rest.
Creating Secrets
1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
username: YWRtaW4=
password: cGFzc3dvcmQxMjM=
Using Secrets in Pods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
kind: Pod
metadata:
name: app-with-secret
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-credentials
Encryption at Rest
Enable encryption at rest by configuring the API server:
1
2
3
4
5
6
7
8
9
10
11
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {}
Pass this configuration to the API server using the --encryption-provider-config flag.
Using External Secrets Management
For production environments, consider using external secrets management solutions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/database
property: username
- secretKey: password
remoteRef:
key: prod/database
property: password
Container Security
Container security involves securing the container image and runtime environment.
Image Security Best Practices
- Use minimal base images
- Scan images for vulnerabilities
- Sign and verify images
- Use specific image tags, not latest
- Remove unnecessary packages and tools
Dockerfile Security Best Practices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Use minimal base image
FROM alpine:3.18 AS builder
# Create non-root user
RUN addgroup -g 1000 appgroup && \
adduser -D -u 1000 -G appgroup appuser
# Install dependencies
RUN apk add --no-cache ca-certificates
# Copy application files
WORKDIR /app
COPY --chown=appuser:appgroup . .
# Build application
RUN go build -o myapp
# Final stage
FROM alpine:3.18
# Copy user from builder
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
# Copy application
COPY --from=builder --chown=appuser:appgroup /app/myapp /app/myapp
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 8080
# Run application
CMD ["/app/myapp"]
Image Scanning
Image scanning identifies vulnerabilities in container images before deployment.
Using Trivy for Image Scanning
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Scan an image
trivy image nginx:1.21
# Scan with specific severity
trivy image --severity HIGH,CRITICAL nginx:1.21
# Output as JSON
trivy image --format json --output results.json nginx:1.21
# Scan filesystem
trivy fs --security-checks vuln,config .
Trivy Operator in Kubernetes
Deploy Trivy Operator to continuously scan images in your cluster:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: v1
kind: Namespace
metadata:
name: trivy-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: trivy-operator
namespace: trivy-system
spec:
replicas: 1
selector:
matchLabels:
app: trivy-operator
template:
metadata:
labels:
app: trivy-operator
spec:
serviceAccountName: trivy-operator
automountServiceAccountToken: true
containers:
- name: operator
image: aquasecurity/trivy-operator:0.16.0
env:
- name: OPERATOR_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: OPERATOR_TARGET_NAMESPACES
value: ""
resources:
limits:
memory: "256Mi"
cpu: "500m"
Admission Controllers for Image Validation
Use admission controllers to enforce image policies:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: image-validation
webhooks:
- name: validate-images.security.io
clientConfig:
service:
name: image-validator
namespace: security
path: /validate
caBundle: <base64-encoded-ca-cert>
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: Fail
Runtime Security
Runtime security monitors and protects containers during execution.
Falco for Runtime Security
Falco is a runtime security tool that detects anomalous behavior:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
apiVersion: v1
kind: ConfigMap
metadata:
name: falco-config
namespace: falco
data:
falco.yaml: |
rules_file:
- /etc/falco/falco_rules.yaml
- /etc/falco/falco_rules.local.yaml
- /etc/falco/k8s_audit_rules.yaml
json_output: true
json_include_output_property: true
log_stderr: true
log_syslog: true
log_level: info
priority: debug
syscall_event_drops:
threshold: 0.1
actions:
- log
- alert
falco_rules.local.yaml: |
- rule: Unauthorized Process in Container
desc: Detect process execution outside allowed list
condition: >
spawned_process and
container and
not proc.name in (allowed_processes)
output: >
Unauthorized process started
(user=%user.name command=%proc.cmdline container=%container.name)
priority: WARNING
- list: allowed_processes
items: [nginx, java, python, node]
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: falco
namespace: falco
spec:
selector:
matchLabels:
app: falco
template:
metadata:
labels:
app: falco
spec:
serviceAccountName: falco
hostNetwork: true
hostPID: true
containers:
- name: falco
image: falcosecurity/falco:0.36.0
securityContext:
privileged: true
volumeMounts:
- name: dev
mountPath: /host/dev
- name: proc
mountPath: /host/proc
readOnly: true
- name: boot
mountPath: /host/boot
readOnly: true
- name: modules
mountPath: /host/lib/modules
readOnly: true
- name: usr
mountPath: /host/usr
readOnly: true
- name: config
mountPath: /etc/falco
volumes:
- name: dev
hostPath:
path: /dev
- name: proc
hostPath:
path: /proc
- name: boot
hostPath:
path: /boot
- name: modules
hostPath:
path: /lib/modules
- name: usr
hostPath:
path: /usr
- name: config
configMap:
name: falco-config
Pod Security Best Practices Checklist
Image Security
- Use trusted base images from official sources
- Scan images for vulnerabilities before deployment
- Use specific image tags instead of latest
- Implement image signing and verification
- Regularly update base images
Container Configuration
- Run containers as non-root users
- Use read-only root filesystems where possible
- Drop all capabilities and add only what is needed
- Disable privilege escalation
- Set resource limits for CPU and memory
Network Security
- Implement Network Policies for ingress and egress
- Use default deny policies
- Segment workloads by namespace
- Encrypt traffic between services using service mesh
- Limit exposed ports and services
Access Control
- Implement least privilege RBAC policies
- Use separate service accounts for different workloads
- Disable automatic mounting of service account tokens
- Regularly audit RBAC policies
- Use namespaces for logical separation
Secrets Management
- Never store secrets in container images or environment variables
- Use Kubernetes Secrets with encryption at rest
- Consider external secrets management solutions
- Rotate secrets regularly
- Limit secret access to only necessary pods
Monitoring and Auditing
- Enable audit logging for API server
- Monitor runtime behavior with tools like Falco
- Set up alerts for security events
- Regularly review security logs
- Implement centralized logging
Example: Secure Production Deployment
Here is a complete example of a secure production deployment:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
apiVersion: v1
kind: Namespace
metadata:
name: secure-production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: secure-app-sa
namespace: secure-production
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
namespace: secure-production
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-role-binding
namespace: secure-production
subjects:
- kind: ServiceAccount
name: secure-app-sa
namespace: secure-production
roleRef:
kind: Role
name: app-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: secure-production
type: Opaque
stringData:
api-key: "your-api-key-here"
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
namespace: secure-production
spec:
podSelector:
matchLabels:
app: secure-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: ingress-controller
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
namespace: secure-production
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
serviceAccountName: secure-app-sa
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myregistry.io/secure-app:1.2.3
imagePullPolicy: Always
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
ports:
- containerPort: 8080
protocol: TCP
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: api-key
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: cache
mountPath: /cache
- name: tmp
mountPath: /tmp
volumes:
- name: cache
emptyDir: {}
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: secure-app
namespace: secure-production
spec:
selector:
app: secure-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
Conclusion
Securing Kubernetes pods requires a comprehensive approach that addresses multiple layers of security. By implementing Pod Security Standards, Security Contexts, Network Policies, RBAC, proper Secrets Management, Container Security, Image Scanning, and Runtime Security, you can significantly reduce the attack surface of your Kubernetes workloads.
Remember that security is not a one-time task but an ongoing process. Regularly review and update your security policies, stay informed about new vulnerabilities and best practices, and continuously monitor your cluster for anomalous behavior.
References
- Kubernetes Pod Security Standards: https://kubernetes.io/docs/concepts/security/pod-security-standards/
- Pod Security Admission: https://kubernetes.io/docs/concepts/security/pod-security-admission/
- Network Policies: https://kubernetes.io/docs/concepts/services-networking/network-policies/
- RBAC Authorization: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
- Secrets Management: https://kubernetes.io/docs/concepts/configuration/secret/
- Trivy Documentation: https://aquasecurity.github.io/trivy/
- Falco Documentation: https://falco.org/docs/
- CIS Kubernetes Benchmark: https://www.cisecurity.org/benchmark/kubernetes
- OWASP Kubernetes Security Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Kubernetes_Security_Cheat_Sheet.html
- NSA/CISA Kubernetes Hardening Guide: https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF