Kubernetes ได้กลายเป็นมาตรฐานในการ Deploy และจัดการ Container Applications ในระดับ Production แต่ด้วยความซับซ้อนของ K8s ทำให้พื้นผิวการโจมตี (Attack Surface) กว้างขึ้นอย่างมาก ในปี 2026 เราเห็นเหตุการณ์ด้านความปลอดภัยที่เกี่ยวข้องกับ Kubernetes เพิ่มขึ้นอย่างต่อเนื่อง ตั้งแต่การเข้าถึง Cluster โดยไม่ได้รับอนุญาต การรั่วไหลของ Secrets ไปจนถึง Supply Chain Attacks ผ่าน Container Images
บทความนี้จะครอบคลุมทุกด้านของ Kubernetes Security ตั้งแต่การทำความเข้าใจ Attack Surface การตั้งค่า RBAC Pod Security Standards Network Policies Secrets Management Image Security Admission Controllers Runtime Security จนถึง Supply Chain Security และ Incident Response พร้อม Best Practices ที่ใช้ได้จริงในสภาพแวดล้อม Production
K8s Attack Surface — พื้นผิวการโจมตี
การรักษาความปลอดภัย Kubernetes ต้องเริ่มจากการเข้าใจว่ามีจุดใดบ้างที่อาจถูกโจมตี Kubernetes มี Component หลายตัวที่ทำงานร่วมกัน และแต่ละตัวมีช่องทางที่อาจถูกเจาะได้
| Component | ความเสี่ยง | การป้องกัน |
|---|---|---|
| API Server | เข้าถึงโดยไม่ได้รับอนุญาต | RBAC, Authentication, Audit Log |
| kubelet | Node-level access, container escape | ปิด anonymous auth, ใช้ certificate |
| etcd | ข้อมูลทั้งหมดของ Cluster อยู่ที่นี่ | Encryption at rest, mTLS, network isolation |
| Container Runtime | Container escape, privilege escalation | Pod Security Standards, seccomp, AppArmor |
| Network | Pod-to-Pod ไม่มี restriction | Network Policies, service mesh |
| Container Images | Vulnerabilities, malware | Image scanning, signing, minimal base images |
| Supply Chain | Compromised dependencies | SBOM, SLSA, verified publishers |
# ตรวจสอบ API Server เบื้องต้น
# ดูว่า anonymous access เปิดอยู่หรือไม่
kubectl auth can-i --list --as=system:anonymous
# ดู RBAC bindings ทั้งหมด
kubectl get clusterrolebindings -o wide
kubectl get rolebindings --all-namespaces -o wide
# ตรวจสอบ kubelet configuration
ssh node1 "cat /var/lib/kubelet/config.yaml | grep -A5 authentication"
# ดู etcd encryption status
kubectl -n kube-system get cm kubeadm-config -o yaml | grep -A10 encryption
RBAC Deep Dive — Role-Based Access Control
RBAC เป็นกลไกหลักในการควบคุมสิทธิ์การเข้าถึง Kubernetes API ทำงานบนหลักการ Least Privilege คือให้สิทธิ์น้อยที่สุดเท่าที่จำเป็น RBAC มี 4 Resources หลัก ได้แก่ Role ClusterRole RoleBinding และ ClusterRoleBinding
Role และ ClusterRole
# Role — สิทธิ์ภายใน Namespace เดียว
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""] # core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
# ClusterRole — สิทธิ์ทั้ง Cluster
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-viewer
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "watch", "list"]
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
verbs: ["get", "list"]
---
# ClusterRole สำหรับ Developer — จำกัดสิทธิ์ชัดเจน
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: developer
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "deployments", "services", "configmaps", "jobs", "cronjobs"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"] # อ่านได้ แต่แก้ไขไม่ได้
resourceNames: ["app-config"] # จำกัดเฉพาะ Secret ที่ระบุ
- apiGroups: [""]
resources: ["pods/exec", "pods/portforward"]
verbs: ["create"] # debug ได้
RoleBinding และ ClusterRoleBinding
# RoleBinding — ผูก Role กับ User ใน Namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods-binding
namespace: production
subjects:
- kind: User
name: developer@company.com
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
# Service Account — สำหรับ Application
apiVersion: v1
kind: ServiceAccount
metadata:
name: monitoring-sa
namespace: monitoring
automountServiceAccountToken: false # ปิด auto-mount token
---
# ผูก ClusterRole กับ ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: monitoring-binding
subjects:
- kind: ServiceAccount
name: monitoring-sa
namespace: monitoring
roleRef:
kind: ClusterRole
name: node-viewer
apiGroup: rbac.authorization.k8s.io
cluster-admin นอกจากจำเป็นจริงๆ 2) ใช้ Service Account แยกต่างหากสำหรับแต่ละ Application 3) ตั้ง automountServiceAccountToken: false เป็น Default 4) ตรวจสอบ RBAC Permissions อย่างสม่ำเสมอ 5) ใช้ Group แทน User สำหรับทีม
# คำสั่งตรวจสอบ RBAC
# ดูว่า User มีสิทธิ์อะไรบ้าง
kubectl auth can-i --list --as=developer@company.com
kubectl auth can-i create pods --as=developer@company.com -n production
# ดู ServiceAccount ที่มี cluster-admin
kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects'
# ตรวจจับ RBAC ที่ให้สิทธิ์มากเกินไป
# ดู wildcards (*) ใน verbs หรือ resources
kubectl get clusterroles -o json | jq '.items[] | select(.rules[]?.verbs[]? == "*") | .metadata.name'
Pod Security Standards — มาตรฐานความปลอดภัย Pod
Pod Security Standards (PSS) เป็นมาตรฐานที่ Kubernetes กำหนดไว้ 3 ระดับ เพื่อควบคุมความปลอดภัยของ Pod ตั้งแต่ Kubernetes 1.25 ใช้ Pod Security Admission (PSA) แทน PodSecurityPolicy (PSP) ที่ถูก Deprecate ไปแล้ว
| ระดับ | ความเข้มงวด | เหมาะกับ |
|---|---|---|
| Privileged | ไม่มีข้อจำกัด | System-level workloads (CNI, storage drivers) |
| Baseline | ป้องกัน Privilege Escalation พื้นฐาน | Application ทั่วไป |
| Restricted | เข้มงวดสูงสุด ตามมาตรฐาน Hardening | Security-critical workloads |
# เปิดใช้ Pod Security Standards ผ่าน Namespace Labels
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# enforce = บังคับ (reject Pod ที่ไม่ผ่าน)
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# warn = แสดงคำเตือน (แต่ยังอนุญาต)
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
# audit = บันทึกลง Audit Log
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
---
# Namespace สำหรับ system workloads
apiVersion: v1
kind: Namespace
metadata:
name: kube-system
labels:
pod-security.kubernetes.io/enforce: privileged
SecurityContext — ตั้งค่าความปลอดภัย Pod/Container
# Pod ที่ผ่าน Restricted level
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
securityContext:
runAsNonRoot: true # ห้ามรันเป็น root
runAsUser: 1000 # ระบุ UID
runAsGroup: 1000 # ระบุ GID
fsGroup: 1000 # Group สำหรับ Volume
seccompProfile:
type: RuntimeDefault # เปิด seccomp
automountServiceAccountToken: false # ปิด SA token auto-mount
containers:
- name: app
image: myregistry.com/app:v1.2.3@sha256:abc123... # ใช้ digest
securityContext:
allowPrivilegeEscalation: false # ห้าม escalate
readOnlyRootFilesystem: true # Filesystem อ่านอย่างเดียว
capabilities:
drop: ["ALL"] # ลบ capabilities ทั้งหมด
runAsNonRoot: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
ports:
- containerPort: 8080
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {} # writable /tmp
- name: cache
emptyDir:
sizeLimit: 100Mi
readOnlyRootFilesystem: true บังคับให้ Container ไม่สามารถเขียนไฟล์บน Root Filesystem ได้ ถ้า Application ต้องเขียนไฟล์ ให้ Mount emptyDir volume เฉพาะ Directory ที่ต้องการ เช่น /tmp หรือ /app/cache
Network Policies — ควบคุมการสื่อสาร Pod
โดย Default Kubernetes อนุญาตให้ Pod ทุกตัวสื่อสารกันได้หมด ซึ่งอันตรายมากใน Production เพราะถ้า Attacker เจาะ Pod หนึ่งได้ จะสามารถเข้าถึง Pod อื่นได้ทั้งหมด Network Policies ช่วยจำกัดการสื่อสารตามหลัก Zero Trust
# Default Deny All — กฎข้อแรกที่ต้องตั้ง
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # เลือกทุก Pod ใน Namespace
policyTypes:
- Ingress
- Egress
---
# อนุญาต DNS (จำเป็นสำหรับ Service Discovery)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# Frontend -> Backend: อนุญาตเฉพาะที่จำเป็น
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-allow-from-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
---
# Backend -> Database: จำกัดเฉพาะ port 5432
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-allow-from-backend
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
---
# อนุญาต Backend เข้าถึง External API เฉพาะ IP ที่ระบุ
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-egress
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8 # Internal services
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to:
- ipBlock:
cidr: 203.0.113.0/24 # External API IP range
ports:
- protocol: TCP
port: 443
Secrets Management — จัดการข้อมูลลับ
Kubernetes Secrets โดย Default เก็บข้อมูลแบบ Base64 encoded (ไม่ได้เข้ารหัส) ใน etcd ซึ่งไม่ปลอดภัยเพียงพอสำหรับ Production ต้องใช้วิธีเพิ่มเติมในการปกป้อง Secrets
etcd Encryption at Rest
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
providers:
- aescbc:
keys:
- name: key1
secret:
- identity: {} # fallback สำหรับอ่าน secrets เก่า
# เพิ่มใน API Server flags:
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
# ตรวจสอบว่า Encryption ทำงาน
kubectl get secret test-secret -o yaml
# ถ้า annotated ว่า encrypted = ทำงานถูกต้อง
# Re-encrypt secrets ที่มีอยู่
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
External Secrets Operator
# ใช้ External Secrets Operator ดึง Secrets จาก Vault/AWS/GCP
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.company.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "production-app"
serviceAccountRef:
name: vault-auth-sa
---
# ExternalSecret — ดึง Secret จาก Vault อัตโนมัติ
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h # Sync ทุก 1 ชั่วโมง
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-secrets # K8s Secret ที่จะสร้าง
creationPolicy: Owner
data:
- secretKey: database-url
remoteRef:
key: production/database
property: url
- secretKey: api-key
remoteRef:
key: production/api
property: key
---
# AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
spec:
provider:
aws:
service: SecretsManager
region: ap-southeast-1
auth:
jwt:
serviceAccountRef:
name: aws-sa
SOPS — Encrypted Secrets ใน Git
# ใช้ Mozilla SOPS เข้ารหัส Secrets ก่อนเก็บใน Git
# ติดตั้ง: brew install sops
# สร้าง .sops.yaml ใน root ของ repo
creation_rules:
- path_regex: .*secrets.*\.yaml$
encrypted_regex: ^(data|stringData)$
kms: arn:aws:kms:ap-southeast-1:123456:key/abc-def
# หรือใช้ age: age1...
# เข้ารหัส
sops --encrypt secrets.yaml > secrets.enc.yaml
# แก้ไข (จะ decrypt ให้อัตโนมัติใน editor)
sops secrets.enc.yaml
# ใช้กับ Flux CD / ArgoCD
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app
spec:
decryption:
provider: sops
secretRef:
name: sops-age-key
Image Security — ความปลอดภัยของ Container Image
Vulnerability Scanning ด้วย Trivy
# Scan Image เพื่อหา Vulnerabilities
trivy image myapp:latest
# Scan เฉพาะ HIGH และ CRITICAL
trivy image --severity HIGH,CRITICAL myapp:latest
# Scan ใน CI/CD Pipeline — Fail ถ้าพบ CRITICAL
trivy image --exit-code 1 --severity CRITICAL myapp:latest
# Scan Kubernetes Cluster ทั้งหมด
trivy k8s --report summary cluster
# Scan SBOM (Software Bill of Materials)
trivy image --format spdx-json -o sbom.json myapp:latest
# ใช้ Trivy Operator — Scan อัตโนมัติใน Cluster
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/trivy-operator/main/deploy/static/trivy-operator.yaml
# ดูผล VulnerabilityReport
kubectl get vulnerabilityreports -A -o wide
Image Signing ด้วย cosign/Sigstore
# ติดตั้ง cosign
# brew install cosign
# สร้าง Keypair
cosign generate-key-pair
# Sign Image
cosign sign --key cosign.key myregistry.com/app:v1.0.0
# Verify Image
cosign verify --key cosign.pub myregistry.com/app:v1.0.0
# Keyless Signing (ใช้ OIDC identity - แนะนำ)
cosign sign myregistry.com/app:v1.0.0
# จะเปิด Browser ให้ Login ด้วย Google/GitHub
# Verify Keyless
cosign verify \
--certificate-identity=ci@company.com \
--certificate-oidc-issuer=https://accounts.google.com \
myregistry.com/app:v1.0.0
Dockerfile Best Practices สำหรับ Security
# ใช้ Minimal Base Image
FROM cgr.dev/chainguard/python:latest-dev AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Multi-stage: Final image ไม่มี build tools
FROM cgr.dev/chainguard/python:latest
WORKDIR /app
COPY --from=builder /app /app
COPY . .
# รันเป็น Non-root user
USER 65534:65534
# ใช้ ENTRYPOINT แทน CMD
ENTRYPOINT ["python", "app.py"]
# อย่า COPY . . ก่อน RUN pip install (cache layer)
# อย่าเก็บ secrets ใน image
# อย่าใช้ :latest tag ใน production
# ใช้ image digest แทน tag สำหรับ deterministic builds
Admission Controllers — Policy Enforcement
Admission Controllers ทำหน้าที่ตรวจสอบและบังคับ Policy ก่อนที่ Resource จะถูกสร้างใน Cluster เปรียบเหมือน Gatekeeper ที่ยืนหน้าประตู ตรวจสอบว่า Resource ที่จะเข้ามาเป็นไปตามกฎหรือไม่
Kyverno — Policy Engine ที่ใช้งานง่าย
# ติดตั้ง Kyverno
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
---
# Policy: บังคับให้ทุก Pod ต้องไม่รันเป็น root
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-run-as-nonroot
spec:
validationFailureAction: Enforce # Enforce = block, Audit = log only
rules:
- name: run-as-non-root
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Running as root is not allowed."
pattern:
spec:
securityContext:
runAsNonRoot: true
containers:
- securityContext:
runAsNonRoot: true
---
# Policy: บังคับ Resource Limits
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
spec:
validationFailureAction: Enforce
rules:
- name: check-limits
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "CPU and memory limits are required."
pattern:
spec:
containers:
- resources:
limits:
cpu: "?*"
memory: "?*"
---
# Policy: ห้ามใช้ :latest tag
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
rules:
- name: no-latest
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Using ':latest' tag is not allowed."
pattern:
spec:
containers:
- image: "!*:latest"
---
# Policy: Auto-add labels ให้ทุก Pod
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-labels
spec:
rules:
- name: add-labels
match:
any:
- resources:
kinds: ["Pod"]
mutate:
patchStrategicMerge:
metadata:
labels:
managed-by: kyverno
security-scan: required
OPA/Gatekeeper
# ConstraintTemplate — กำหนดรูปแบบ Policy
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}
---
# Constraint — ใช้ Template บังคับ labels
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["team", "environment", "cost-center"]
Runtime Security — ตรวจจับภัยคุกคามขณะรัน
Falco — Runtime Threat Detection
# ติดตั้ง Falco ด้วย Helm
helm install falco falcosecurity/falco \
--namespace falco --create-namespace \
--set falcosidekick.enabled=true \
--set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/..."
# Falco Rules ตัวอย่าง
- rule: Terminal shell in container
desc: ตรวจจับการเปิด shell ใน Container
condition: >
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint
output: >
Shell opened in container
(user=%user.name container=%container.name
shell=%proc.name parent=%proc.pname)
priority: WARNING
tags: [container, shell, mitre_execution]
- rule: Read sensitive file in container
desc: ตรวจจับการอ่านไฟล์ sensitive
condition: >
open_read and container
and (fd.name startswith /etc/shadow or
fd.name startswith /etc/passwd or
fd.name contains private_key)
output: >
Sensitive file read (file=%fd.name container=%container.name)
priority: CRITICAL
- rule: Outbound connection to suspicious IP
desc: ตรวจจับ Connection ไปยัง IP ที่น่าสงสัย
condition: >
outbound and container
and fd.sip.name in (crypto_mining_pools)
output: >
Suspicious outbound connection
(container=%container.name ip=%fd.sip)
priority: CRITICAL
Tetragon — eBPF-based Security Observability
# ติดตั้ง Tetragon
helm install tetragon cilium/tetragon -n kube-system
# TracingPolicy — Monitor file access
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: monitor-sensitive-files
spec:
kprobes:
- call: "security_file_open"
syscall: false
args:
- index: 0
type: "file"
selectors:
- matchArgs:
- index: 0
operator: "Prefix"
values:
- "/etc/shadow"
- "/etc/passwd"
- "/root/.ssh"
# ดู Events
kubectl logs -n kube-system ds/tetragon -f | tetra getevents
Supply Chain Security — ความปลอดภัย Software Supply Chain
SBOM — Software Bill of Materials
# สร้าง SBOM ด้วย Syft
syft myregistry.com/app:v1.0.0 -o spdx-json > sbom.json
# Scan SBOM เพื่อหา Vulnerabilities
grype sbom:sbom.json
# Attach SBOM กับ Image ด้วย cosign
cosign attach sbom --sbom sbom.json myregistry.com/app:v1.0.0
SLSA — Supply-chain Levels for Software Artifacts
# GitHub Actions workflow สำหรับ SLSA Level 3
name: Build and Attest
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write # สำหรับ keyless signing
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Build Image
run: docker build -t myregistry.com/app:${GITHUB_REF_NAME} .
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myregistry.com/app:${GITHUB_REF_NAME}
- name: Sign Image (Keyless)
run: cosign sign myregistry.com/app:${GITHUB_REF_NAME}
- name: Generate SLSA Provenance
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1
with:
image: myregistry.com/app
digest: ${steps.build.outputs.digest}
Audit Logging — บันทึกการเข้าถึง
# Audit Policy — กำหนดว่าจะ Log อะไรบ้าง
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log ทุกการเปลี่ยนแปลง Secrets (RequestResponse level)
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
# Log การสร้าง/ลบ resources ที่สำคัญ
- level: RequestResponse
resources:
- group: ""
resources: ["pods", "services"]
- group: "apps"
resources: ["deployments", "statefulsets"]
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
# Log kubectl exec/attach (ใครเข้าไปใน Pod)
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# Skip logging health checks
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
# Default: log metadata ของทุกอย่าง
- level: Metadata
# ใช้กับ API Server:
# --audit-policy-file=/etc/kubernetes/audit-policy.yaml
# --audit-log-path=/var/log/kubernetes/audit.log
# --audit-log-maxage=30
# --audit-log-maxbackup=10
# --audit-log-maxsize=100
CIS Kubernetes Benchmark
CIS (Center for Internet Security) มี Benchmark สำหรับ Kubernetes ที่เป็นมาตรฐานอุตสาหกรรม ครอบคลุมการตั้งค่าทุกส่วนของ Cluster
# ใช้ kube-bench ตรวจสอบ CIS Benchmark
# ติดตั้งและรัน
docker run --pid=host -v /etc:/etc:ro -v /var:/var:ro \
-t aquasec/kube-bench:latest run --targets=master
# หรือรันเป็น Job ใน Cluster
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs job/kube-bench
# ตัวอย่าง Output:
# [PASS] 1.1.1 Ensure API server pod specification file permissions
# [FAIL] 1.1.2 Ensure API server pod specification file ownership
# [WARN] 1.1.3 Ensure controller manager pod specification
# ...
# == Summary master ==
# 42 checks PASS
# 8 checks FAIL
# 11 checks WARN
# Checklist สำคัญ:
# - API Server: เปิด RBAC, ปิด anonymous auth, ตั้ง audit logging
# - etcd: เข้ารหัส at rest, ใช้ TLS, จำกัด access
# - kubelet: ปิด anonymous auth, เปิด certificate rotation
# - Network: ตั้ง Network Policies, ใช้ encrypted communication
Multi-tenancy Security — หลายทีมใช้ Cluster เดียว
# Namespace Isolation สำหรับแต่ละทีม
apiVersion: v1
kind: Namespace
metadata:
name: team-alpha
labels:
team: alpha
pod-security.kubernetes.io/enforce: restricted
---
# ResourceQuota — จำกัด Resources ต่อ Namespace
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-alpha-quota
namespace: team-alpha
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
services: "20"
secrets: "30"
persistentvolumeclaims: "10"
---
# LimitRange — กำหนด Default Limits
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-alpha
spec:
limits:
- default:
cpu: 500m
memory: 256Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "2"
memory: 2Gi
min:
cpu: 50m
memory: 64Mi
type: Container
---
# Network Policy — Isolation ระหว่าง Namespaces
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-from-other-namespaces
namespace: team-alpha
spec:
podSelector: {}
ingress:
- from:
- podSelector: {} # อนุญาตเฉพาะภายใน Namespace เดียวกัน
Zero Trust ใน Kubernetes
Zero Trust หมายความว่า "ไม่ไว้ใจใครเลย แม้แต่ภายใน Cluster" ทุกการสื่อสารต้องผ่านการตรวจสอบและเข้ารหัส
# Service Mesh (Istio) — mTLS ระหว่าง Services ทั้งหมด
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: strict-mtls
namespace: production
spec:
mtls:
mode: STRICT # บังคับ mTLS ทุก connection
---
# Authorization Policy — ใครเข้าถึง Service ใดได้
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: backend-policy
namespace: production
spec:
selector:
matchLabels:
app: backend
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/production/sa/frontend-sa"]
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/*"]
---
# Deny ทั้งหมดที่ไม่ match กฎ
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: production
spec:
{} # Empty spec = deny all
Security Monitoring และ Incident Response
# Prometheus Alerts สำหรับ Security Events
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: security-alerts
spec:
groups:
- name: kubernetes-security
rules:
- alert: PrivilegedContainerDetected
expr: |
kube_pod_container_status_running{} * on(pod, namespace)
group_left() kube_pod_spec_containers_security_context_privileged{} == 1
for: 1m
labels:
severity: critical
annotations:
summary: "Privileged container detected: {{ $labels.pod }}"
- alert: PodRunningAsRoot
expr: |
kube_pod_container_status_running{} * on(pod, namespace)
group_left() (kube_pod_spec_containers_security_context_run_as_user{} == 0)
for: 1m
labels:
severity: warning
annotations:
summary: "Container running as root: {{ $labels.pod }}"
- alert: UnauthorizedAPIAccess
expr: |
sum(rate(apiserver_audit_event_total{verb=~"create|update|delete",
user_agent!~".*kube.*"}[5m])) by (user) > 100
for: 5m
labels:
severity: warning
# Incident Response Checklist:
# 1. ตรวจจับ: Falco alert, Audit log, Prometheus alert
# 2. กักกัน: NetworkPolicy deny-all, cordon node
# 3. สืบสวน: kubectl logs, audit logs, Falco events
# 4. กู้คืน: Redeploy from clean image, rotate secrets
# 5. ป้องกัน: Update policies, patch vulnerabilities
# Incident Response Commands
# 1. กักกัน Pod ที่ถูกเจาะ (ตัด Network ทันที)
kubectl label pod compromised-pod quarantine=true
# + NetworkPolicy ที่ deny traffic จาก label quarantine=true
# 2. เก็บ Evidence ก่อนลบ
kubectl logs compromised-pod > /evidence/pod-logs.txt
kubectl describe pod compromised-pod > /evidence/pod-describe.txt
kubectl get pod compromised-pod -o yaml > /evidence/pod-yaml.txt
# 3. ดู Audit Log ว่าใครทำอะไร
cat /var/log/kubernetes/audit.log | jq 'select(.user.username=="suspicious-user")'
# 4. Cordon Node ที่มีปัญหา
kubectl cordon affected-node
kubectl drain affected-node --ignore-daemonsets
# 5. Rotate Secrets ที่อาจถูกเข้าถึง
kubectl delete secret compromised-secret -n production
kubectl create secret generic compromised-secret --from-literal=key=NEW_VALUE
# 6. ตรวจสอบ Image ที่ใช้
trivy image affected-image:tag --severity CRITICAL,HIGH
Security Checklist สำหรับ Production K8s
| หมวด | รายการตรวจสอบ | สถานะ |
|---|---|---|
| RBAC | ไม่มี User/SA ที่มี cluster-admin โดยไม่จำเป็น | ตรวจสอบ |
| RBAC | automountServiceAccountToken: false เป็น Default | ตรวจสอบ |
| Pod Security | Namespace ทุกอันมี PSS Label (Restricted) | ตรวจสอบ |
| Pod Security | runAsNonRoot: true ทุก Container | ตรวจสอบ |
| Pod Security | readOnlyRootFilesystem: true | ตรวจสอบ |
| Pod Security | drop ALL capabilities | ตรวจสอบ |
| Network | Default Deny NetworkPolicy ทุก Namespace | ตรวจสอบ |
| Secrets | etcd Encryption at Rest เปิด | ตรวจสอบ |
| Secrets | ใช้ External Secrets (Vault/AWS SM) | ตรวจสอบ |
| Images | Scan ทุก Image ใน CI/CD | ตรวจสอบ |
| Images | ใช้ Image Digest แทน Tag | ตรวจสอบ |
| Images | Sign Image ด้วย cosign | ตรวจสอบ |
| Admission | Kyverno/OPA Gatekeeper ติดตั้งแล้ว | ตรวจสอบ |
| Runtime | Falco/Tetragon สำหรับ Runtime Detection | ตรวจสอบ |
| Audit | Audit Logging เปิดและส่งไป SIEM | ตรวจสอบ |
| CIS | kube-bench ผ่านทุกข้อ CRITICAL | ตรวจสอบ |
สรุป
Kubernetes Security ไม่ใช่สิ่งที่ตั้งค่าครั้งเดียวแล้วจบ แต่เป็นกระบวนการต่อเนื่องที่ต้องดูแลและปรับปรุงอยู่เสมอ การรักษาความปลอดภัย K8s ที่ดีต้องครอบคลุมทุกชั้น ตั้งแต่ Infrastructure ไปจนถึง Application และ Supply Chain
เริ่มต้นจากสิ่งที่สำคัญที่สุดก่อน ได้แก่ RBAC ที่เข้มงวด Pod Security Standards ระดับ Restricted Network Policies แบบ Default Deny และ Secrets ที่เข้ารหัส จากนั้นค่อยเพิ่ม Admission Controllers Image Scanning Runtime Security และ Audit Logging ทีละขั้น อย่าลืมรัน CIS Benchmark เป็นประจำเพื่อตรวจสอบว่า Cluster ของคุณเป็นไปตามมาตรฐาน และเตรียม Incident Response Plan ไว้เสมอ เพราะคำถามไม่ใช่ว่า "จะถูกโจมตีหรือไม่" แต่คือ "จะถูกโจมตีเมื่อไหร่" การเตรียมพร้อมจะทำให้คุณรับมือได้อย่างมีประสิทธิภาพ
