Aller au contenu principal

:::caution Not deployed on this cluster kube-bench has not been run as a standing deployment. The commands below are valid but have not been executed against this cluster. Run them manually to generate a CIS benchmark report. :::

Compliance β€” CIS Benchmarks, Audit Logs & Hardening

Compliance is the proof layer: evidence that the cluster meets industry standards. This page covers CIS Kubernetes benchmark scanning with kube-bench, Kubernetes API audit logging, and a hardening checklist for k3s.


CIS Kubernetes Benchmark with kube-bench​

kube-bench runs the official CIS (Center for Internet Security) Kubernetes benchmark against your cluster and scores each control as PASS / FAIL / WARN.

Run kube-bench as a Job​

# kube-bench-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: kube-bench
namespace: kube-system
spec:
template:
spec:
hostPID: true
nodeSelector:
node-role.kubernetes.io/control-plane: "true"
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
restartPolicy: Never
containers:
- name: kube-bench
image: aquasec/kube-bench:latest
command: ["kube-bench"]
args:
- "--benchmark"
- "k3s-cis-1.7" # use k3s-specific benchmark
- "--json"
volumeMounts:
- name: var-lib-etcd
mountPath: /var/lib/etcd
readOnly: true
- name: etc-kubernetes
mountPath: /etc/kubernetes
readOnly: true
volumes:
- name: var-lib-etcd
hostPath:
path: /var/lib/rancher/k3s/server/db
- name: etc-kubernetes
hostPath:
path: /etc/rancher/k3s
kubectl apply -f kube-bench-job.yaml

# Wait for completion
kubectl wait -n kube-system job/kube-bench --for=condition=complete --timeout=120s

# Get the full report
kubectl logs -n kube-system job/kube-bench | jq .

# Get summary (pass/fail/warn counts)
kubectl logs -n kube-system job/kube-bench | jq '.Totals'

Understanding the Score​

== Summary ==
34 checks PASS
10 checks FAIL
8 checks WARN

Target score: PASS β‰₯ 80%, FAIL = 0 critical controls

Critical Controls to Fix First​

CheckDescriptionRemediation
1.2.1Anonymous auth disabled--anonymous-auth=false in API server args
1.2.6Audit logging enabledSee audit log section below
3.2.1Logging at request/response levelAudit policy configured
4.2.6Pod security policiesOPA Gatekeeper (Phase 15)
5.1.1RBAC enabledAlready enabled in k3s
5.2.2No privileged podsOPA policy enforced
5.7.4No default SA token automountSet automountServiceAccountToken: false

k3s Hardening Configuration​

Apply to /etc/rancher/k3s/config.yaml on the control plane:

# /etc/rancher/k3s/config.yaml (on set-hog)
# API Server hardening
kube-apiserver-arg:
- "anonymous-auth=false"
- "audit-log-path=/var/log/kubernetes/audit.log"
- "audit-policy-file=/etc/rancher/k3s/audit-policy.yaml"
- "audit-log-maxage=30"
- "audit-log-maxbackup=10"
- "audit-log-maxsize=100"
- "request-timeout=300s"
- "service-account-lookup=true"
- "tls-min-version=VersionTLS12"
- "tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"

# Controller Manager hardening
kube-controller-manager-arg:
- "terminated-pod-gc-threshold=10"
- "use-service-account-credentials=true"
- "service-account-private-key-file=/var/lib/rancher/k3s/server/tls/service.key"

# Scheduler hardening
kube-scheduler-arg:
- "bind-address=127.0.0.1"

# etcd hardening
etcd-arg:
- "cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
# Apply and restart k3s
sudo systemctl restart k3s

# Verify on workers too
sudo tee -a /etc/rancher/k3s/config.yaml << 'EOF'
kubelet-arg:
- "anonymous-auth=false"
- "authorization-mode=Webhook"
- "read-only-port=0"
- "protect-kernel-defaults=true"
- "event-qps=0"
- "rotate-certificates=true"
- "tls-min-version=VersionTLS12"
EOF
sudo systemctl restart k3s-agent

API Audit Logging​

Full audit policy (in addition to what's set in k3s config):

# /etc/rancher/k3s/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- RequestReceived

rules:
# Always log exec/attach/portforward at RequestResponse level
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]

# Log secret access at Metadata level
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
verbs: ["get", "list", "watch"]

# Log RBAC changes at RequestResponse
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources:
- clusterroles
- clusterrolebindings
- roles
- rolebindings

# Log all deletes
- level: RequestResponse
verbs: ["delete", "deletecollection"]

# Log privileged pod creation
- level: RequestResponse
resources:
- group: ""
resources: ["pods"]
verbs: ["create", "update", "patch"]

# Don't log routine read operations for noise reduction
- level: None
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["events", "nodes", "endpoints"]

# Default: log metadata for everything else
- level: Metadata

Ship Audit Logs to Loki​

# promtail ConfigMap addition β€” scrape audit log file from host
- job_name: k8s_audit
static_configs:
- targets:
- localhost
labels:
job: k8s_audit
__path__: /var/log/kubernetes/audit.log
pipeline_stages:
- json:
expressions:
verb: verb
user: user.username
resource: objectRef.resource
namespace: objectRef.namespace
- labels:
verb:
user:
resource:

Query in Grafana:

{job="k8s_audit"} | json | verb="delete" | resource="secrets"

Disable Default Service Account Token Auto-Mount​

Most pods don't need Kubernetes API access β€” but they get a token by default:

# Patch default service account in each namespace
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: myteam-staging
automountServiceAccountToken: false

Apply across all namespaces:

for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
kubectl patch serviceaccount default \
-n $ns \
-p '{"automountServiceAccountToken": false}'
done

Scheduled Compliance Scan (CronJob)​

Run kube-bench weekly and store results:

apiVersion: batch/v1
kind: CronJob
metadata:
name: kube-bench-weekly
namespace: kube-system
spec:
schedule: "0 2 * * 0" # Sunday 02:00
jobTemplate:
spec:
template:
spec:
hostPID: true
restartPolicy: OnFailure
containers:
- name: kube-bench
image: aquasec/kube-bench:latest
command:
- sh
- -c
- |
kube-bench --benchmark k3s-cis-1.7 --json \
> /reports/kube-bench-$(date +%Y%m%d).json
# Push to MinIO (Velero bucket)
mc alias set minio $MINIO_URL $MINIO_ACCESS_KEY $MINIO_SECRET_KEY
mc cp /reports/kube-bench-$(date +%Y%m%d).json \
minio/compliance/kube-bench/

Compliance Dashboard Checklist​

Track posture over time. Target state:

ControlTargetOwner
Anonymous auth disabledβœ…Platform
Audit logging enabledβœ…Platform
No privileged podsβœ…OPA Gatekeeper
All images signedβœ…Cosign Policy Controller
No root containersβœ…OPA Gatekeeper
RBAC least privilegeβœ…Namespace owners
Network policies in placeβœ…Cilium
Secrets in Vault (not Secrets)βœ…Platform
MFA for adminsβœ…Authentik
kube-bench score β‰₯ 80%TargetPlatform
Falco active on all nodesβœ…Security
SBOM for all prod imagesβœ…CI/CD pipeline

Done When​

βœ” kube-bench runs clean with FAIL < 5 (all critical controls passing)
βœ” API audit logs shipping to Loki, queryable in Grafana
βœ” k3s hardening config applied on all nodes
βœ” Default SA token automount disabled in all namespaces
βœ” Weekly kube-bench CronJob stores reports to MinIO
βœ” Compliance dashboard shows green across all controls