--- name: securing-k8s-service description: Use when finishing a new Kubernetes service deployment or auditing an existing one for security hardening on homelab k3s or cloud EKS clusters. --- # Securing a Kubernetes Service ## Overview Work through each section in order. If you can only do some: follow the **priority order** at the bottom — the top items give the most security improvement per minute of effort. --- ## 1. ServiceAccount (RBAC) Every app gets its own ServiceAccount. Never use `default`. ```yaml apiVersion: v1 kind: ServiceAccount metadata: name: namespace: automountServiceAccountToken: false # disable unless app calls k8s API ``` If app needs k8s API access, create a minimal Role and bind it: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: -role namespace: rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: -rolebinding namespace: subjects: - kind: ServiceAccount name: roleRef: kind: Role name: -role apiGroup: rbac.authorization.k8s.io ``` --- ## 2. Pod Security Context Set on `spec.template.spec` (pod-level) AND each container. ```yaml # Pod-level (spec.template.spec.securityContext) securityContext: runAsNonRoot: true runAsUser: 1000 # check image docs for correct UID runAsGroup: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault # Container-level (each container's securityContext) securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] add: [] # only add if explicitly required (e.g., NET_BIND_SERVICE for :80) ``` `readOnlyRootFilesystem: true` — if the app writes to disk, mount writable paths via `emptyDir`: ```yaml volumes: - name: tmp emptyDir: {} - name: cache emptyDir: {} volumeMounts: - name: tmp mountPath: /tmp - name: cache mountPath: /var/cache ``` Common paths that need emptyDir: `/tmp`, `/var/cache`, `/var/run`, `/home//.config` --- ## 3. Resource Limits Always set both requests AND limits. ```yaml resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" # throttles (not kills) — set generously or omit if unsure memory: "512Mi" # OOMKill if exceeded — set to ~2-3x steady-state usage ``` Check actual usage after deploy: ```bash kubectl top pods -n ``` --- ## 4. NetworkPolicy (Cilium) Default deny all, then add exceptions. ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: -netpol namespace: spec: podSelector: matchLabels: app.kubernetes.io/name: policyTypes: [Ingress, Egress] ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: traefik ports: - port: egress: # DNS — almost always needed - to: [] ports: - port: 53 protocol: UDP # External HTTPS — add if app calls external APIs - to: [] ports: - port: 443 # Specific namespace (e.g., database) - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: ports: - port: 5432 ``` --- ## 5. Secret Handling - Never put secrets in `values.yaml` or ConfigMaps — use ExternalSecret → OpenBao - Set `argocd.argoproj.io/sync-wave: "-1"` on ExternalSecrets so they exist before the Deployment - Prefer `secretKeyRef` in env vars over mounted files (unless app requires file format) --- ## 6. Image Security - Pin image tags: `image:v1.2.3` not `image:latest` - Check registry.ctz.fyi Harbor scans for vulnerability reports - For custom images: use minimal base (distroless, alpine, scratch) - Set `imagePullPolicy: IfNotPresent` (not `Always`, unless actively testing) --- ## Quick Audit (Existing Deployments) ```bash # Security contexts kubectl get deploy -n -o jsonpath='{.spec.template.spec.securityContext}' kubectl get deploy -n -o jsonpath='{.spec.template.spec.containers[0].securityContext}' # Resource limits kubectl get deploy -n -o jsonpath='{.spec.template.spec.containers[0].resources}' # ServiceAccount kubectl get deploy -n -o jsonpath='{.spec.template.spec.serviceAccountName}' # Default SA automount (should be false or unset) kubectl get sa default -n -o jsonpath='{.automountServiceAccountToken}' # NetworkPolicies kubectl get networkpolicy -n ``` --- ## Priority Order If you can only do some of these, start here: 1. **Non-root + no privilege escalation** — most impactful, easy to add 2. **Resource limits** — prevents noisy-neighbor and OOM cascade 3. **Dedicated ServiceAccount + no automount** — limits blast radius 4. **NetworkPolicy** — isolates a compromised pod 5. **readOnlyRootFilesystem** — hardens against post-compromise persistence