Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- .woodpecker.yaml: image paths -> library/autojanet-{agent,dispatcher}
- .woodpecker.yaml: secret names RS_HARBOR_USER / RS_HARBOR_PASS (global)
- container/Dockerfile: restore COPY skills/, skills/ populated from opencode config
- skills/: 84 opencode skills bundled into image
- k8s/manifests: update image refs to library/
5 KiB
5 KiB
| name | description |
|---|---|
| securing-k8s-service | 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.
apiVersion: v1
kind: ServiceAccount
metadata:
name: <app>
namespace: <app>
automountServiceAccountToken: false # disable unless app calls k8s API
If app needs k8s API access, create a minimal Role and bind it:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: <app>-role
namespace: <app>
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: <app>-rolebinding
namespace: <app>
subjects:
- kind: ServiceAccount
name: <app>
roleRef:
kind: Role
name: <app>-role
apiGroup: rbac.authorization.k8s.io
2. Pod Security Context
Set on spec.template.spec (pod-level) AND each container.
# 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:
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/<user>/.config
3. Resource Limits
Always set both requests AND limits.
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:
kubectl top pods -n <namespace>
4. NetworkPolicy (Cilium)
Default deny all, then add exceptions.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: <app>-netpol
namespace: <app>
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: <app>
policyTypes: [Ingress, Egress]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik
ports:
- port: <app-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: <db-namespace>
ports:
- port: 5432
5. Secret Handling
- Never put secrets in
values.yamlor ConfigMaps — use ExternalSecret → OpenBao - Set
argocd.argoproj.io/sync-wave: "-1"on ExternalSecrets so they exist before the Deployment - Prefer
secretKeyRefin env vars over mounted files (unless app requires file format)
6. Image Security
- Pin image tags:
image:v1.2.3notimage:latest - Check registry.ctz.fyi Harbor scans for vulnerability reports
- For custom images: use minimal base (distroless, alpine, scratch)
- Set
imagePullPolicy: IfNotPresent(notAlways, unless actively testing)
Quick Audit (Existing Deployments)
# Security contexts
kubectl get deploy <name> -n <ns> -o jsonpath='{.spec.template.spec.securityContext}'
kubectl get deploy <name> -n <ns> -o jsonpath='{.spec.template.spec.containers[0].securityContext}'
# Resource limits
kubectl get deploy <name> -n <ns> -o jsonpath='{.spec.template.spec.containers[0].resources}'
# ServiceAccount
kubectl get deploy <name> -n <ns> -o jsonpath='{.spec.template.spec.serviceAccountName}'
# Default SA automount (should be false or unset)
kubectl get sa default -n <ns> -o jsonpath='{.automountServiceAccountToken}'
# NetworkPolicies
kubectl get networkpolicy -n <ns>
Priority Order
If you can only do some of these, start here:
- Non-root + no privilege escalation — most impactful, easy to add
- Resource limits — prevents noisy-neighbor and OOM cascade
- Dedicated ServiceAccount + no automount — limits blast radius
- NetworkPolicy — isolates a compromised pod
- readOnlyRootFilesystem — hardens against post-compromise persistence