Container & Kubernetes Security
Kubernetes Security Essentials
4 min read
Kubernetes orchestrates containers at scale but introduces a complex attack surface. Securing the control plane, workloads, and network requires understanding RBAC, Pod Security Standards, and network policies.
Kubernetes Attack Surface
Critical Components
┌─────────────────────────────────────────────────────┐
│ Control Plane │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ API │ │ etcd │ │ Controller/ │ │
│ │ Server │ │ (secrets)│ │ Scheduler │ │
│ └──────────┘ └──────────┘ └───────────────────┘ │
├─────────────────────────────────────────────────────┤
│ Worker Nodes │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ kubelet │ │ kube- │ │ Container │ │
│ │ │ │ proxy │ │ Runtime │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
| Component | Risk | Protection |
|---|---|---|
| API Server | Unauthorized access | RBAC, authentication |
| etcd | Secrets exposure | Encryption at rest |
| kubelet | Node compromise | Disable anonymous auth |
| Container runtime | Container escape | Pod security policies |
| Network | Lateral movement | Network policies |
Role-Based Access Control (RBAC)
RBAC Components
# Role - defines permissions within a namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
# RoleBinding - grants Role to users/groups
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: User
name: developer@example.com
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
ClusterRole for Cluster-Wide Access
# ClusterRole - cluster-wide permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
---
# ClusterRoleBinding - grants ClusterRole cluster-wide
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: ServiceAccount
name: security-scanner
namespace: security
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
RBAC Best Practices
| Practice | Implementation |
|---|---|
| Least privilege | Only grant required permissions |
| Namespace isolation | Use Roles, not ClusterRoles when possible |
| Service account scoping | Dedicated SA per application |
| No default SA | Create specific service accounts |
| Audit RBAC | Regular permission reviews |
Pod Security Standards
Security Context Configuration
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "128Mi"
cpu: "500m"
requests:
memory: "64Mi"
cpu: "250m"
Pod Security Standards (PSS) Levels
| Level | Description | Use Case |
|---|---|---|
| Privileged | Unrestricted | System workloads |
| Baseline | Minimally restrictive | Standard workloads |
| Restricted | Heavily restricted | Security-sensitive |
Enforce via namespace labels:
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
Network Policies
Default Deny Policy
# Deny all ingress and egress by default
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Allow Specific Traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-api
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
---
# Allow API to access database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 5432
Secrets Management
Kubernetes Secrets Security
# Create secret
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4= # base64 encoded
password: cGFzc3dvcmQxMjM=
Secrets are base64 encoded, not encrypted!
Enable Encryption at Rest
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <32-byte-base64-key>
- identity: {}
External Secrets Management
# Using External Secrets Operator with AWS
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: production/db
property: password
Managed Kubernetes Security
Cloud Provider Controls
| Feature | EKS | AKS | GKE |
|---|---|---|---|
| Control plane security | AWS managed | Azure managed | Google managed |
| Node security | Managed node groups | Node pools | Autopilot |
| RBAC integration | IAM roles for SA | AAD integration | Workload Identity |
| Network policies | Calico | Azure CNI | GKE Dataplane V2 |
| Secrets | Secrets Manager | Key Vault | Secret Manager |
Next, we'll explore cloud-native security tools like Falco, OPA, and admission controllers. :::