Skip to content

บทที่ 16 — Security (mTLS + Zero Trust + JWT Propagation + Broker Auth)

← บทที่ 15: Tracing | สารบัญ | บทที่ 17: Service Mesh →

TL;DR: บทนี้ตอบ "ความปลอดภัยใน microservices ที่ทุก call = network call"

  • (1) Identity & Encryption: Zero Trust (อย่าเชื่อใคร — ตรวจทุกครั้ง), mTLS (TLS สองทาง — ทั้ง 2 ฝั่งยืนยันตัวตน) + PKI (ระบบจัดการ certificate) + rotation (เปลี่ยน cert เป็นระยะ), SPIFFE/SPIRE (workload identity = "บัตรประชาชน" ของ service), Workload Identity Federation
  • (2) Authorization: JWT propagation, broker auth (SASL/SCRAM/ACL), Zanzibar/OpenFGA/Cedar (fine-grained authz — สิทธิ์ละเอียดระดับ resource)
  • (3) Supply Chain: Sigstore/Cosign/SLSA (sign image + บัญชี dependency)

ข้ามได้ถ้า: ใช้ mesh ทำ mTLS อัตโนมัติ + JWT propagation + Zero Trust ครบอยู่แล้ว

ใน monolith — security ที่ entry เพียงพอ ใน microservices — ทุก service-to-service call = network call = ต้อง secure

อัพเดต 2026:

  • SPIFFE/SPIRE = CNCF graduated 2022 — standard ของ workload identity ข้าม cloud/cluster
  • 🆕 Workload Identity Federation (GCP/AWS/Azure) — federate K8s SA → cloud IAM โดยไม่ต้องใช้ key/secret
  • 🆕 Zanzibar / OpenFGA / Keto — fine-grained authorization (Google-scale RBAC/ReBAC)
  • 🆕 Sigstore + Cosign + SLSA — supply chain security (sign image, attest provenance)
  • OPA ยังเป็น standard ของ policy-as-code แต่ Cedar (AWS) เป็นทางเลือกใหม่
  • mTLS auto ใน service mesh (Istio Ambient, Cilium SPIFFE) — โปรแกรมเมอร์ไม่ต้องทำเอง

📖 กางศัพท์ย่อในหัวข้อ "อัพเดต 2026" ข้างบน (ของใหม่เยอะ — ไม่ต้องจำหมดในรอบแรก เดี๋ยวเจอในเนื้อบท):

  • PKI (พี-เค-ไอ = Public Key Infrastructure) = ระบบจัดการ certificate — มี Root CA, Intermediate CA, cert ของแต่ละ entity
  • JWT (เจ-ดับ-บลิว-ที = JSON Web Token) = โทเคน 3 ส่วน header.payload.signature คั่นด้วยจุด; claim = ฟิลด์ใน payload (เช่น sub=subject, exp=expiry, iss=issuer/ผู้ออก, aud=audience/ผู้รับ)
  • OAuth2 (โอ-อ๊อธ-ทู) = protocol authorization — ขอสิทธิ์เข้าใช้ resource
  • OIDC (OpenID Connect) = protocol authentication ที่ build บน OAuth2 — ใช้ระบุตัวตน user
  • OPA (โอ-พา = Open Policy Agent) = engine policy-as-code ของ CNCF; เขียน policy ด้วยภาษา Rego
  • Cedar (ซี-ดาร์) = ภาษา policy ของ AWS — ทางเลือกใหม่ของ Rego/OPA
  • Zanzibar (แซน-ซิ-บาร์) = ระบบ authorization ของ Google ที่ scale ทั่วโลก (paper 2019); open-source clone: OpenFGA, SpiceDB, Keto
  • RBAC = Role-Based Access Control (ตาม role); ABAC = Attribute-Based (ตาม attribute); ReBAC (รี-แบค หรือ อาร์-อี-แบค — ไม่มีการออกเสียงมาตรฐาน) = Relationship-Based (ตามความสัมพันธ์, แบบ Zanzibar)
  • Sigstore (sig-สโตร์) = ระบบ signing แบบ keyless (CNCF graduated 2023); Cosign (โค-ไซน์) = container signing tool — เซ็น Docker image ด้วย key/identity
  • SLSA (สาล-ซา = Supply chain Levels for Software Artifacts) = framework ของ Google กำหนดระดับ supply chain security 4 ระดับ
  • SBOM (เอส-บอม = Software Bill of Materials) = บัญชีรายการ dependency ทั้งหมดของซอฟต์แวร์ — ใช้ตรวจว่ามี vulnerable component ไหม

ใช้เวลา 4-5 ชั่วโมง

📖 กางศัพท์หลักของบทนี้ (เจอบ่อย — จำไว้ก่อน):

  • mTLS (mutual TLS) = "TLS แบบสองทาง" คือทั้ง client และ server ต่างยืนยันตัวตนซึ่งกันและกันด้วย certificate (TLS ปกติยืนยันแค่ฝั่ง server)
  • Zero Trust = "ไม่เชื่อใครโดยปริยาย" หลักการที่ไม่ถือว่า network ภายในปลอดภัย ทุก call ต้องพิสูจน์ตัวตนใหม่หมด
  • JWT (JSON Web Token) = โทเคนรูปแบบหนึ่งที่ใส่ข้อมูลตัวตน/สิทธิ์ไว้ในตัวและเซ็นกำกับ; JWT propagation = การส่งต่อโทเคนนี้ไปตาม service ต่าง ๆ ในสาย request
  • Broker Auth = การยืนยันตัวตน/สิทธิ์ก่อนเข้าใช้ message broker (เช่น SASL/SCRAM, ACL)
  • คำย่อในรายการ "อัพเดต 2026" ข้างบน (SPIFFE, OPA, Cedar ฯลฯ) เป็นเครื่องมือเฉพาะ — ยังไม่ต้องจำ เดี๋ยวเจอในเนื้อบท

Part 1: Threat Model

1.1 Attack Surface ใหม่

การแตก monolith เป็น microservices เพิ่มพื้นที่โจมตี (attack surface) อย่างมาก — เดิมการเรียกฟังก์ชันในกระบวนการเดียว (in-process function call) กลายเป็น network call ระหว่าง service ที่ดักฟัง/ปลอมแปลงได้

ผลคือมี attack vector ใหม่ ๆ ที่ monolith ไม่มี:

  • Lateral movement (ขยายพื้นที่โจมตีข้าม service)
  • Stolen credential (ขโมย credential)
  • Eavesdropping (ดักฟัง network)

📖 คำแปล vector ใน diagram:

  • Lateral movement (ขยายพื้นที่โจมตี — attacker เจาะเข้ามาแล้วเดินจาก service หนึ่งไปยังอีก service อื่นในระบบ)
  • Stolen credential (ขโมย credential) — ได้ token/password ของ service หนึ่งไปใช้ปลอมเป็นอีก service
  • Eavesdropping (ดักฟัง network) — แอบอ่าน traffic ที่ไม่ได้เข้ารหัส
  • Privilege escalation (ขยายสิทธิ์) — service ที่มีสิทธิ์น้อย หา bug ยกตัวเองเป็น admin
  • Supply chain (โจมตีโซ่อุปทาน) — attacker แทรก malicious code เข้า npm/Maven package ที่ใช้ทั่วโลก พอ victim pull dependency มาก็โดน

1.2 Zero Trust Model

แนวคิดความปลอดภัยสมัยใหม่คือ Zero Trust — เลิกเชื่อว่า "อยู่ใน network ภายในแล้วปลอดภัย" เพราะถ้า attacker เจาะเข้ามาได้ก็เดินทะลุได้หมด แทนที่ด้วยการ authenticate + authorize ทุก call, ให้สิทธิ์น้อยที่สุด, และเข้ารหัสทุกที่:

Principle: "Never trust, always verify" (อย่าเชื่อใคร — ตรวจทุกครั้ง)

  • ไม่มี "trusted internal network"
  • ทุก call = authenticate + authorize
  • Least privilege (สิทธิ์น้อยที่สุด — ให้สิทธิ์แค่เท่าที่จำเป็น ไม่เกิน)
  • Encrypt everywhere (in-transit + at-rest)

Part 2: mTLS (Mutual TLS)

2.1 TLS ปกติ vs mTLS

→ ใน mTLS, ทั้ง 2 ฝั่งยืนยันตัวตน ก่อนสื่อสาร

2.2 PKI Setup

mTLS ต้องมีระบบจัดการ certificate (PKI) — มี Root CA เป็นรากความเชื่อใจ ออก cert ผ่าน Intermediate CA ให้แต่ละ service service ที่ถือ cert ที่ลงนามโดย CA เดียวกันจึงเชื่อใจกันได้:

แต่ละ service มี cert + private key

2.3 Cert Rotation (สำคัญ)

certificate มีอายุและต้องต่ออายุ (rotate) — ถ้าทำ manual จะพลาดง่าย (cert หมดอายุกลางดึก = ระบบล่ม) ทางที่ดีคือใช้ cert อายุสั้นแล้ว auto-rotate ด้วย cert-manager หรือ Vault PKI:

text
Manual rotation = ปัญหา
Automatic = cert-manager + Let's Encrypt / Vault PKI

Recommended: cert TTL 1-24h ขึ้นกับ tooling (SPIRE: 1h default, Istio: ค่า default แตกต่างกันไปตาม version/config — ตรวจสอบเอกสาร Istio release ที่ใช้จริงก่อน อย่ายึด 24h เป็นค่าตายตัว) + auto-rotate เสมอ

💡 เทียบกับ cert traditional ที่ใช้ 1-2 ปี — cert 24 ชม. = ถ้ารั่ว effect window สั้นมาก (วงเสียหายแคบ). แต่ถ้าระบบไม่มี automation ที่ดี cert อายุสั้น = operational burden สูง (คนต้องเฝ้าตลอด) จึงต้องคู่กับ auto-rotate เท่านั้น

2.4 Service Mesh ทำให้ง่าย

ทำ mTLS เองทุก service ยุ่งยากมาก — service mesh (Istio/Linkerd) ทำให้อัตโนมัติทั้งหมด: ออก cert ต่อ pod, rotate ให้, เข้ารหัส traffic ภายในทั้งหมด โดยไม่ต้องแก้โค้ด app เลย นี่คือเหตุผลหลักที่คนใช้ mesh:

Istio / Linkerd auto-mTLS:

  • Auto-provision cert per pod
  • Auto-rotate (24 hr default)
  • Encrypt all internal traffic
  • No code change

ดูบท 17

2.5 ใน Spring Boot Manual

ถ้าไม่มี service mesh ก็ตั้ง mTLS เองใน Spring ได้ — ฝั่ง server เปิด SSL + client-auth: need (บังคับ client ต้องมี cert) ส่วน client โหลด keystore (cert ตัวเอง) + truststore (CA ที่เชื่อ) แต่จะเห็นว่ายุ่งและต้องทำซ้ำทุก service:

Lab — สร้าง self-signed cert ก่อน (5 นาที)

ถ้าคุณยังไม่เคยทำ TLS cert manual — ทำ lab นี้ก่อน. ใช้ keytool ที่มาพร้อม JDK สร้าง self-signed cert + truststore ในเครื่องตัวเอง:

💡 keytool มากับ JDK — ถ้าพิมพ์ keytool แล้ว shell บอก command not found ให้เช็คว่า JDK อยู่ใน PATH หรือยัง (เช่นใน Windows: where keytool, ใน macOS/Linux: which keytool)

bash
# 1. สร้าง CA root (เครื่อง dev เท่านั้น — production ใช้ Let's Encrypt / Vault PKI)
#    -ext BC=ca,pathlen:1 + KU=keyCertSign,cRLSign = บอกว่า cert นี้เป็น CA จริง
#    (sign cert อื่นได้) ถ้าขาด ext นี้ keytool จะออก end-entity cert ที่ใช้เป็น CA ไม่ได้
#    ⚠️ ตรวจ syntax กับ `keytool -ext help` ของ JDK ของคุณ — รูปแบบ shorthand เปลี่ยนตาม JDK version
keytool -genkeypair -alias ca -keyalg RSA -keysize 2048 -validity 365 \
  -dname "CN=DevCA,OU=Dev,O=MyOrg,C=TH" \
  -ext BC=ca,pathlen:1 -ext KU=keyCertSign,cRLSign \
  -keystore ca.p12 -storetype PKCS12 -storepass changeit

# 2. Export CA cert (ไว้ใส่ใน truststore ของทุก service)
keytool -exportcert -alias ca -keystore ca.p12 -storepass changeit \
  -file ca.crt

# 3. สร้าง cert ของ Server (orders service)
keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -validity 365 \
  -dname "CN=orders.local,OU=Dev,O=MyOrg,C=TH" \
  -keystore server.p12 -storetype PKCS12 -storepass changeit

# 4. สร้าง truststore (เก็บ CA cert ที่เราเชื่อ)
keytool -importcert -alias ca -file ca.crt -storepass changeit -noprompt \
  -keystore truststore.p12 -storetype PKCS12

# 5. ตรวจสอบ
keytool -list -v -keystore server.p12 -storepass changeit
keytool -list -v -keystore truststore.p12 -storepass changeit

หลังจากนี้:

  • server.p12 = key + cert ของ server → ใช้ในฝั่ง server.ssl.key-store
  • truststore.p12 = รายการ cert ที่เราเชื่อ → ใช้ในฝั่ง server.ssl.trust-store + client ทั้งคู่
  • ca.crt = ไฟล์ public ของ CA → distribute ไปทุก service (ปกติเก็บใน K8s ConfigMap หรือ Vault)

⚠️ Production ห้ามใช้ self-signed — ใช้ Let's Encrypt (public) หรือ Vault PKI / cert-manager (internal). lab นี้แค่ทำให้คุณเห็นว่า "cert" หน้าตาเป็นยังไง


yaml
server:
  ssl:
    enabled: true
    key-store: /etc/certs/server.p12
    key-store-password: ${KEYSTORE_PASSWORD}
    key-store-type: PKCS12
    client-auth: need                          # require client cert
    trust-store: /etc/certs/truststore.p12
    trust-store-password: ${TRUSTSTORE_PASSWORD}
    enabled-protocols: TLSv1.3,TLSv1.2         # ปี 2026: ห้าม TLS 1.0/1.1 (insecure)

⚠️ ถ้าไม่ระบุ enabled-protocols Spring Boot/Tomcat อาจ enable TLS 1.0/1.1 ตาม default ของ JVM เก่า — ใน 2026 ถือว่าไม่ปลอดภัย (browser/client ทุกตัว deprecate แล้ว) บังคับเป็น TLSv1.3,TLSv1.2 เสมอ

💡 สำหรับ internal mTLS deployment ใหม่ (2026): แนะนำ TLS 1.3-only (enabled-protocols: TLSv1.3) — เก็บ TLS 1.2 fallback ไว้เฉพาะตอนต้อง interop กับ legacy client ที่อัปไม่ได้ เพราะ browser และ major client เริ่ม deprecate TLS 1.2 แล้ว

Client (full example — load keystore + truststore จากไฟล์):

java
import java.io.FileInputStream;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.ssl.SSLContextBuilder;

char[] password = System.getenv("KEYSTORE_PASSWORD").toCharArray();

// 1. โหลด keystore (cert + private key ของ client เอง)
KeyStore keystore = KeyStore.getInstance("PKCS12");
try (FileInputStream in = new FileInputStream("/etc/certs/client.p12")) {
    keystore.load(in, password);
}

// 2. โหลด truststore (รายการ CA ที่เราเชื่อ)
KeyStore truststore = KeyStore.getInstance("PKCS12");
try (FileInputStream in = new FileInputStream("/etc/certs/truststore.p12")) {
    truststore.load(in, password);
}

// 3. Build SSLContext — บังคับ TLS 1.3/1.2 เท่านั้น
SSLContext ctx = SSLContextBuilder.create()
    .setProtocol("TLSv1.3")
    .loadKeyMaterial(keystore, password)
    .loadTrustMaterial(truststore, null)
    .build();

CloseableHttpClient client = HttpClients.custom()
    .setSSLContext(ctx)
    .build();

→ ทำเองยุ่งยาก — ใช้ service mesh ดีกว่า


Part 3: Service Identity (SPIFFE / SPIRE)

SPIFFE (สปิ๊ฟ — Secure Production Identity Framework For Everyone) = CNCF spec ของ workload identity (กรอบมาตรฐาน "บัตรประชาชน" ของ workload), graduated 2022

3.1 SPIFFE ID

SPIFFE ID คือ "บัตรประชาชน" ของ workload ในรูปแบบมาตรฐาน (URI) — ระบุ trust domain + path ทำให้ service มี identity ที่ใช้ได้เหมือนกันข้าม cluster/cloud/VM แทนที่จะผูกกับ IP หรือ hostname ที่เปลี่ยนได้:

text
SPIFFE ID format:  spiffe://<trust-domain>/<path>

ตัวอย่าง (ใช้ pattern ตาม SPIRE k8s_psat / aws_iid plugin):
  spiffe://acme.com/ns/production/sa/orders          # K8s: namespace + service account
  spiffe://acme.com/ns/production/sa/orders-v2       # version แยกผ่าน SA ไม่ใช่ path
  spiffe://aws.acme.com/account/123/role/orders     # AWS: account + IAM role

→ Identity ที่ standardize ข้าม cluster / cloud / VM / serverless

3.2 SVID (SPIFFE Verifiable Identity Document)

2 รูปแบบ:

  • X.509-SVIDTLS cert ที่มี SPIFFE ID ใน SAN
  • JWT-SVIDJWT ที่มี SPIFFE ID ใน sub claim

ทุก workload ได้ SVID เป็น identity แทน "username/password" หรือ "API key"

3.3 SPIRE = SPIFFE Runtime Environment

Workload attestation = SPIRE Agent ยืนยันว่า workload คือใคร โดยดูจาก:

  • K8s: pod label + service account + namespace
  • AWS: EC2 instance ID + IAM role
  • Linux: process UID/GID + binary path + container ID

→ ไม่ต้องเก็บ secret/credential ใน workload — agent issue cert/JWT ให้แบบ runtime

3.4 ทำไมต้องใช้ SPIFFE

Problem แบบเดิมSPIFFE solution
Hard-code API key per serviceAuto-issued SVID, ไม่ต้องเก็บ secret
Cert rotation manualAuto-rotate ทุก 1-24 ชม.
Identity ต่างกันต่อ cloudStandard format ข้าม cloud
K8s SA + AWS IAM แยกกันFederation ผ่าน SPIFFE bridge
Zero Trust อยากตรวจตัวตนmTLS + SVID = strong identity

3.5 Integration

SPIFFE ไม่ใช่แค่ spec ลอย ๆ — เครื่องมือ infra ยอดนิยมรองรับแล้ว (Istio, Cilium, Vault, Consul, AWS App Mesh) ดังนั้นถ้าใช้ stack เหล่านี้ก็ได้ workload identity มาตรฐานมาเลยโดยไม่ต้องสร้างเอง:

text
Istio Ambient        →  ztunnel ใช้ SPIFFE ID (cluster.local trust domain)
Cilium Mesh          →  เปิด SPIFFE mode + SPIRE
Vault auth           →  cert auth ผ่าน SPIRE
HashiCorp Consul     →  Connect intentions ใช้ SPIFFE ID
AWS App Mesh         →  Native SPIFFE support

3.6 ตัวอย่าง — Spring Boot workload ใช้ SVID

java
// pom: io.spiffe:java-spiffe-core:0.8.x (java-spiffe project @ github.com/spiffe/java-spiffe)
// ⚠️ ตรวจ API + class names กับ release ของ version ที่ใช้ — แต่ละ minor มี breaking changes
//   (ก่อน 0.7 ใช้ artifactId อื่น; pseudo-API ข้างล่างอ้างอิงเอกสาร 0.8.x — ตรวจ Javadoc ของจริง)

DefaultWorkloadApiClient client = DefaultWorkloadApiClient.newClient(
    WorkloadApiClient.ClientOptions.builder()
        .spiffeSocketPath("/run/spire/sockets/agent.sock")
        .build());

X509Source source = DefaultX509Source.newSource(
    DefaultX509Source.X509SourceOptions.builder()
        .workloadApiClient(client)
        .build());

// Get fresh SVID
X509Svid svid = source.getX509Svid();
String spiffeId = svid.getSpiffeId().toString();
// "spiffe://acme.com/ns/production/sa/orders"

// Build mTLS context — auto-rotated
// ⚠️ SpiffeSslContextFactory.getSslContext() ไม่มีใน public java-spiffe API (0.8.x)
// Pattern ที่ถูกต้องสำหรับ java-spiffe 0.8.x:
//   X509Source source = DefaultX509Source.newSource(options);
//   TrustManager trustManager = new SpiffeTrustManager(source);
//   KeyManager keyManager = new SpiffeKeyManager(source);
//   SSLContext ctx = SSLContext.getInstance("TLS");
//   ctx.init(new KeyManager[]{keyManager}, new TrustManager[]{trustManager}, null);
// ตรวจ Javadoc ของ release จริงที่ https://github.com/spiffe/java-spiffe ก่อนใช้งาน
SSLContext ctx = buildSslContextFromSpiffeSource(source); // implement ตาม pattern ข้างบน

⚠️ K8s manifest ที่ต้องคู่กัน — workload ต้อง mount SPIRE agent socket เข้า pod:

yaml
spec:
  containers:
    - name: orders
      volumeMounts:
        - name: spire-agent-socket
          mountPath: /run/spire/sockets
          readOnly: true
  volumes:
    - name: spire-agent-socket
      hostPath:
        path: /run/spire/sockets       # SPIRE agent (DaemonSet) เปิด socket ที่ node
        type: Directory

💡 ใช้กับ service mesh = ไม่ต้องเขียน code นี้ — sidecar/ztunnel ทำให้


Part 3.5: Workload Identity Federation (cloud-native, no secrets)

ปัญหาเดิม: pod ต้องเรียก AWS S3 → ต้องเก็บ AWS access key ใน K8s secret → leak risk

Workload Identity Federation = K8s ServiceAccount → ⇄ → Cloud IAM Role โดย exchange JWT

3.5.1 GCP Workload Identity

yaml
# 1. K8s SA + IAM binding
apiVersion: v1
kind: ServiceAccount
metadata:
  name: orders-sa
  annotations:
    iam.gke.io/gcp-service-account: orders@my-project.iam.gserviceaccount.com
bash
# 2. Bind GCP IAM
gcloud iam service-accounts add-iam-policy-binding \
  orders@my-project.iam.gserviceaccount.com \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:my-project.svc.id.goog[default/orders-sa]"

→ Pod ใช้ Google client library, library ขอ token จาก K8s metadata → exchange เป็น GCP token อัตโนมัติ → ไม่ต้องเก็บ key file ใน secret

3.5.2 AWS IRSA (IAM Roles for Service Accounts)

ฝั่ง AWS ใช้ IRSA — ผูก K8s ServiceAccount เข้ากับ IAM role ผ่าน annotation แล้ว AWS SDK จะแลก OIDC token ของ pod เป็น STS credential ให้เอง ผลคือ pod เรียก S3/DynamoDB ได้โดยไม่ต้องเก็บ access key ใน secret:

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: orders-sa
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123:role/OrdersRole

AWS SDK auto-detect → ใช้ OIDC token จาก K8s → exchange เป็น STS credential

3.5.3 Azure Workload Identity (AKS 2023+)

yaml
metadata:
  annotations:
    azure.workload.identity/client-id: <managed-identity-client-id>

→ pattern เดียวกัน — federated OIDC token exchange

💡 2026 rule: ห้ามเก็บ cloud access key ใน K8s Secret. ใช้ Workload Identity Federation เสมอ


Part 4: JWT Propagation

4.1 ภาพรวม

วิธีพา "ตัวตนของ user" ไปกับ request ข้ามทุก service คือ JWT — user login ที่ Auth Service ได้ token, gateway verify แล้วส่ง JWT ต่อไปทุก service ที่ chain เดียวกัน แต่ละ service จึงรู้ว่าใครเป็นคนเรียกโดยไม่ต้องถาม Auth Service ซ้ำ:

JWT carry user identity ผ่านทุก service

4.2 Validation Strategy

Validate at Gateway only

text
Gateway: verify JWT + extract claims + inject X-User-Id header
Downstream: trust X-User-Id (because behind gateway)

ข้อดี: downstream เร็ว ข้อเสีย: ไม่ Zero Trust — ถ้า attacker access internal = fake header ได้

Validate at every service

text
Every service: verify JWT signature

→ True Zero Trust

trade-off: latency + JWKs caching

4.3 JWKs (JSON Web Key Set)

service verify ลายเซ็น JWT ได้โดยไม่ต้องถาม Auth Server ทุกครั้ง — Auth Server publish public key ที่ /.well-known/jwks.json, service ดึงมา cache แล้วใช้ verify เอง เมื่อ key หมุน (rotate) ก็มี kid ระบุว่าใช้ key ไหน + invalidate cache ตาม TTL:

Auth Server publish public key:

json
GET /.well-known/jwks.json
{
  "keys": [
    { "kid": "...", "kty": "RSA", "n": "...", "e": "AQAB", "alg": "RS256" }
  ]
}

Service cache JWKs + verify signature Key rotation: auth server publish new key → cache invalidate after TTL

4.4 Spring Security Resource Server

Spring ทำให้ verify JWT ง่ายมาก — เพิ่ม starter oauth2-resource-server แล้วชี้ issuer-uri/jwk-set-uri Spring จะดึง JWKS + verify ลายเซ็น + เช็ค expiry ให้อัตโนมัติ เหลือแค่กำหนด authorization rule และ map claim เป็น role:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
yaml
spring.security.oauth2.resourceserver.jwt:
  issuer-uri: https://auth.acme.com
  # หรือ
  jwk-set-uri: https://auth.acme.com/.well-known/jwks.json
java
@Bean
public SecurityFilterChain chain(HttpSecurity http) throws Exception {
    return http
        .authorizeHttpRequests(a -> a
            .requestMatchers("/actuator/**").permitAll()
            .anyRequest().authenticated())
        .oauth2ResourceServer(o -> o.jwt(j -> j.jwtAuthenticationConverter(jwtConverter())))
        .build();
}

@Bean
public JwtAuthenticationConverter jwtConverter() {
    JwtGrantedAuthoritiesConverter g = new JwtGrantedAuthoritiesConverter();
    g.setAuthoritiesClaimName("roles");
    g.setAuthorityPrefix("ROLE_");
    JwtAuthenticationConverter c = new JwtAuthenticationConverter();
    c.setJwtGrantedAuthoritiesConverter(g);
    return c;
}

4.5 Forward JWT

เพื่อให้ตัวตนของ user ไปถึง service ปลายทาง ต้อง forward JWT ต่อทุก outgoing call — ใช้ interceptor ดึง token จาก context ปัจจุบันแล้วแนบเป็น Authorization header อัตโนมัติ ไม่ต้องเขียนซ้ำทุกที่ที่เรียก downstream:

java
@Component
public class JwtPropagationInterceptor implements ExchangeFilterFunction {
    @Override
    public Mono<ClientResponse> filter(ClientRequest req, ExchangeFunction next) {
        return Mono.deferContextual(ctx -> {
            String token = ctx.getOrEmpty(JWT_TOKEN_KEY).map(String::valueOf).orElse(null);
            // ❌ ห้ามทำ: req = ClientRequest.from(req)... — req เป็น effectively final ใน lambda
            //    Java จะ compile error: "Variable used in lambda expression should be final or effectively final"
            ClientRequest outgoing = token != null
                ? ClientRequest.from(req)
                    .header("Authorization", "Bearer " + token)
                    .build()
                : req;
            return next.exchange(outgoing);
        });
    }
}

4.6 Token Exchange (RFC 8693)

บางครั้ง forward token ตรง ๆ ไม่เหมาะ (เช่นอยากจำกัด scope ที่ B ทำได้) — Token Exchange ให้ A แลก token ของ user เป็น token ใหม่ที่แคบลงสำหรับเรียก B โดยเฉพาะ ปลอดภัยกว่าและ audit ได้ว่าใครทำในนามใคร:

ใน complex flow — service A เรียก service B ในนาม user, แต่ B trust A:


Part 5: Authorization Patterns

5.1 Coarse-grained at Gateway

ชั้นแรกของ authorization คือกรองหยาบ ๆ ที่ gateway — เช็คตาม path/role กว้าง ๆ (เช่น /admin/** ต้องเป็น ADMIN) เพื่อตัด request ที่ไม่มีสิทธิ์ตั้งแต่ขอบ ก่อนจะปล่อยให้ service ภายในเช็คละเอียดต่อ:

Gateway:

  • /admin/** requires role=ADMIN
  • /api/** requires authenticated

5.2 Fine-grained at Service

gateway เช็คได้แค่หยาบ — การตัดสินใจที่ต้องรู้บริบทธุรกิจ (เช่น "ยกเลิก order ได้เฉพาะเจ้าของหรือ admin") ต้องทำที่ service ที่เป็นเจ้าของข้อมูล ใช้ @PreAuthorize ตรวจสิทธิ์ตามเงื่อนไขจริงรายคำสั่ง:

Service-level:

  • order.create requires user
  • order.cancel requires (owner || admin)
  • order.refund requires admin
java
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.userId")
public void cancelOrder(String userId, String orderId) { ... }

5.3 🆕 Fine-Grained Authorization — Zanzibar / OpenFGA / Keto

📖 กางศัพท์ที่จะเจอ:

  • RBAC (Role-Based Access Control) = "สิทธิ์ตามบทบาท" — user X มี role Admin → ทำได้ทุก action ที่ admin ทำได้ (กรณีคลาสสิก แต่ไม่ flex)
  • ABAC (Attribute-Based) = "สิทธิ์ตาม attribute" — user.country == doc.country AND user.dept == doc.dept → ตอบ allow
  • ReBAC (Relationship-Based) = "สิทธิ์ตามความสัมพันธ์" — user A เห็น doc X เพราะ user B (เจ้าของ) share ให้ A. ติดตาม relationship เป็น graph
  • Zanzibar = ชื่อระบบของ Google (paper 2019) ที่จัดการ ReBAC ระดับ planet-scale สำหรับ Drive/YouTube/Photos — "OpenFGA / SpiceDB / Keto" คือ open-source implementations ของแนวคิดนี้

ปัญหา: RBAC แบบเดิม (role-based) ไม่พอสำหรับ "user A เห็น document X เพราะ user B share ให้"

Google Zanzibar (2019 paper) = pattern ReBAC (Relationship-Based Access Control) ที่ Google ใช้กับ Drive/YouTube/Photos

Open source implementations:

  • OpenFGA (CNCF Sandbox, ทำโดย Auth0/Okta) — 2026 popular choice
  • SpiceDB (Authzed) — production-grade Zanzibar clone
  • Ory Keto — Go-native

ตัวอย่าง OpenFGA model

python
# DSL — define relationship + permission
model
  schema 1.1

type user

type document
  relations
    define owner: [user]
    define editor: [user] or owner
    define viewer: [user] or editor
    define can_share: owner
    define can_edit: editor
    define can_view: viewer

Insert relationships

bash
# user:alice owns document:report-2026
fga tuple write user:alice owner document:report-2026

# user:bob is editor of document:report-2026
fga tuple write user:bob editor document:report-2026

Check permission

java
// Spring Boot client
boolean canEdit = openFga.check(CheckRequest.builder()
    .user("user:bob")
    .relation("can_edit")
    .object("document:report-2026")
    .build())
    .isAllowed();

ทำไมต้องใช้

ปัญหาRBACReBAC (OpenFGA)
"user A share doc to user B"hard-codetuple insert
"Folder permission inherits"manualparent relation
"Team-based access"role explosiongroup relation
"Audit who can access X"full table scanreverse query (list-users)
Check latencyขึ้นอยู่กับ design< 10ms (consistent hash, replicated)

💡 ใช้เมื่อ: SaaS product ที่ user share resource (Google Docs, Notion, Linear). ไม่ใช้เมื่อ: backend internal ที่ role static (ใช้ OPA/Cedar พอ)

5.4 Policy as Code — OPA (Open Policy Agent)

แทนที่จะกระจาย authorization logic ปนในโค้ดทุก service OPA ให้เขียน policy แยกเป็นไฟล์ (ภาษา Rego) ที่ service ถามว่า "request นี้อนุญาตไหม" — ได้ policy รวมศูนย์ที่ test/audit/แก้ได้โดยไม่ต้อง deploy service ใหม่:

rego
# policy.rego
package authz

default allow = false

allow {
    input.method == "GET"
    input.path == "/orders"
    input.user.role == "USER"
}

allow {
    input.method == "DELETE"
    parts := split(input.path, "/")   # เช่น "/orders/42" → ["", "orders", "42"]
    parts[1] == "orders"
    order_id := parts[2]
    input.user.id == data.orders[order_id].owner_id
}

allow {
    input.user.role == "ADMIN"
}

App:

java
boolean allowed = opa.evaluate("authz/allow", input);
if (!allowed) throw new AccessDeniedException();

→ policy แยกจาก code, version + audit ได้


Part 6: Broker Authentication

6.1 Kafka SASL/SCRAM

broker ก็ต้องป้องกันไม่ให้ใครก็ได้เชื่อมต่อ — Kafka รองรับ SASL/SCRAM (username/password ที่ hash) คู่กับ SSL (SASL_SSL) เพื่อทั้งพิสูจน์ตัวตน client และเข้ารหัส traffic:

yaml
spring.kafka:
  properties:
    security.protocol: SASL_SSL
    sasl.mechanism: SCRAM-SHA-512
    sasl.jaas.config: org.apache.kafka.common.security.scram.ScramLoginModule required username="user" password="${KAFKA_PASSWORD}";
    ssl.truststore.location: /etc/kafka/truststore.jks
    ssl.truststore.password: ${TRUSTSTORE_PASSWORD}

💡 ใน production 2026 พิจารณา SASL/OAUTHBEARER (token-based, short-lived) หรือ mTLS แทน SCRAM ที่ใช้ static password — token-based ดีกว่าเพราะ rotate ง่ายและไม่ต้องเก็บ password ใน config (SCRAM password ที่อยู่ใน config = secret leak risk ถ้า config rendered ผิด/log หลุด)

6.2 Kafka mTLS

อีกทางเลือกแทน SASL คือใช้ mTLS — client พิสูจน์ตัวตนด้วย certificate แทน password (ตั้ง keystore ของ client + truststore ของ CA) เหมาะกับ environment ที่ใช้ cert-based identity อยู่แล้ว:

yaml
spring.kafka.properties:
  security.protocol: SSL
  ssl.keystore.location: /etc/kafka/keystore.jks
  ssl.keystore.password: ${KEYSTORE_PASSWORD}
  ssl.truststore.location: /etc/kafka/truststore.jks
  ssl.truststore.password: ${TRUSTSTORE_PASSWORD}

6.3 Kafka ACL

หลังรู้ว่า client เป็นใคร (authn) ต้องคุมว่าทำอะไรได้ (authz) ด้วย ACL — กำหนดว่า service ไหน produce/consume topic หรือ group ใดได้บ้าง ยึดหลัก least privilege ให้สิทธิ์เท่าที่ใช้จริง:

bash
kafka-acls.sh --bootstrap-server kafka:9092 \
  --add --allow-principal User:order-service \
  --producer --topic orders

kafka-acls.sh --bootstrap-server kafka:9092 \
  --add --allow-principal User:billing-service \
  --consumer --topic orders --group billing

→ ใครเขียน topic ไหน, ใครอ่าน group ไหน

💡 --consumer = shortcut ที่ grant 3 สิทธิ์รวด: Read on topic + Read on group + Describe on topic. ถ้าอยาก fine-grained ให้ใส่ --operation Read --topic ... แยกเอง

Kafka 4.0+ (KRaft only) ต้องใช้ StandardAuthorizer (built-in, ไม่ต้องพึ่ง ZooKeeper) — AclAuthorizer (class เก่า) ยังอยู่ใน codebase แต่ ใช้ไม่ได้แล้วใน 4.0 เพราะมัน depend on ZK (ZooKeeper — ระบบ coordinate เก่าของ Kafka ที่เก็บ metadata/config แบบ external service) และ ZK mode ถูก remove ออกจาก 4.0 แล้ว (Kafka เปลี่ยนมาใช้ KRaft — โหมดใหม่ที่ broker จัดการ metadata กันเองไม่ต้องพึ่ง ZooKeeper อีกต่อไป; ดูบท 08) ตั้ง authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer ใน server.properties

⚠️ Migration gotcha: ถ้า cluster เดิมเก็บ ACL ผ่าน ZooKeeper-based AclAuthorizer อยู่แล้ว การย้ายไป StandardAuthorizer ไม่ใช่แค่เปลี่ยนชื่อ config — ต้องรัน migration tool (kafka-storage.sh + ZK-to-KRaft migration path ตามเอกสารทางการ) เพื่อย้าย ACL เดิมเข้า KRaft metadata ก่อน ไม่งั้น ACL ที่เคยตั้งไว้จะหายไปเงียบ ๆ

6.4 RabbitMQ

RabbitMQ ก็มีระบบเดียวกัน — เปิด SSL/TLS เข้ารหัส traffic และตั้ง user + vhost + permission ที่กำหนดสิทธิ์เป็น 3 ส่วน (configure/write/read) ด้วย regex ของชื่อ resource ควบคุมได้ละเอียดว่าใครแตะ queue/exchange ไหนได้:

yaml
spring.rabbitmq:
  ssl:
    enabled: true
    key-store: /etc/rabbit/client.p12
    key-store-password: ${RABBIT_KEYSTORE_PASSWORD}
    trust-store: /etc/rabbit/truststore.p12
    trust-store-password: ${RABBIT_TRUSTSTORE_PASSWORD}

User + vhost + permissions:

bash
rabbitmqctl add_user order-svc <password>
rabbitmqctl set_permissions -p / order-svc \
  '^orders\.|^outbox\.'  '^orders\.'  '^orders\.'
# ⚠️ ใช้ single quote ไม่ใช่ double quote เพื่อให้ regex ส่งถึง rabbitmqctl โดยไม่ถูก shell ตีความ

format: (configure regex) (write regex) (read regex)


Part 7: Secret Management

ทบทวนจากบท 05 + observability บท 05:

  • Vault — dynamic secret, rotation, audit
  • AWS Secrets Manager / Parameter Store
  • Kubernetes Secrets + Sealed Secrets (GitOps)
  • External Secrets Operator (sync external → K8s Secret)

→ ห้ามใส่ secret ใน:

  • Docker image
  • Git repo (แม้ .env)
  • Logs / traces

7.1 Dynamic Database Credentials

จุดเด่นของ Vault ที่เหนือกว่า secret store ทั่วไปคือ dynamic secret — แทนที่จะมี DB password ถาวร (ที่ leak แล้วใช้ได้ตลอด) Vault สร้าง credential ชั่วคราวให้ทุกครั้งที่ app ขอ ใช้ได้แค่ช่วงสั้น ๆ แล้ว auto-revoke ลดความเสียหายถ้า leak มาก:

Vault plugin → app เรียก:

text
GET /v1/database/creds/order-app-role
→ { username: "v-token-1234", password: "...", lease_duration: 3600 }

→ Credential ใช้ 1 ชม. + auto-revoke


Part 8: Encryption at Rest

text
DB: TDE (Transparent Data Encryption) หรือ disk-level
Backup: encrypt before upload S3
S3 / Object: server-side encryption (SSE-KMS)
PII: column-level encryption + tokenization

8.1 PII Tokenization

วิธีปกป้องข้อมูลส่วนตัว (PII) แบบลึกคือ tokenization — แทนค่าจริง (อีเมล) ด้วย token ไร้ความหมายแล้วเก็บคู่จริงไว้ใน Vault ที่เดียว service ทั่วไปทำงานกับ token ได้ (แสดงแบบ mask) ส่วนคนที่ต้องการค่าจริงต้องขอจาก Vault ซึ่ง audit ทุกครั้ง:

text
Email "alice@example.com" → token "TOK-abc123" (stored)
Vault stores: TOK-abc123 → "alice@example.com"

Service can show: "ali***@***.com" without decrypt
Need real email → ask Vault (audited)

Part 9: Threat Mitigations

9.1 OWASP API Top 10 (2023)

ทบทวนจาก api-design/04 §Input Validation (ดูรายละเอียดการ validate input จากภายนอกที่นั่น):

  1. Broken Object Level Authorization (BOLA)/orders/123 user A เห็น order ของ user B
  2. Broken Authentication — weak token, no refresh rotation
  3. Broken Object Property Level Authorization — user เห็น field ที่ไม่ควรเห็น
  4. Unrestricted Resource Consumption — DoS, no rate limit
  5. Broken Function Level Authorization — endpoint admin user เรียกได้
  6. Unrestricted Access to Sensitive Business Flow — bot abuse (เช่น scalping)
  7. Server Side Request Forgery (SSRF) — service A fetch URL ที่ user supply
  8. Security Misconfiguration
  9. Improper Inventory ManagementAPI เก่า / staging exposed
  10. Unsafe Consumption of APIs

9.2 Mitigation per Service

text
Input validation (Bean Validation + Zod-equivalent)
Output encoding (anti-XSS)
SQL parameter binding (กัน injection)
File upload restrictions
URL allowlist for SSRF (Server-Side Request Forgery = หลอกให้ server ส่ง request ออกแทน)
Rate limit per endpoint
Audit log

Part 10: Audit Log

ทุก action สำคัญ:

json
{
  "timestamp": "...",
  "actor": { "id": "...", "type": "user" },
  "action": "order.cancel",
  "resource": { "type": "order", "id": "ord-123" },
  "result": "success",
  "ip": "1.2.3.4",
  "user_agent": "...",
  "trace_id": "..."
}

→ ส่งไป Kafka → store in audit warehouse

Use case:

  • Compliance (SOX, PCI, GDPR)
  • Forensics ตอนถูก hack
  • User dispute

Part 11: Supply Chain Security

ดู security/06:

  • SBOM (Software Bill of Materials) per service
  • Dependency scanning (Snyk, Dependabot)
  • Image signing (Cosign, Sigstore)
  • Image scanning (Trivy, Grype)
  • Provenance (SLSA framework)

Part 12: Network Policy

K8s NetworkPolicy:

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-service-policy
spec:
  podSelector:
    matchLabels: { app: order-service }
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api-gateway }
        - podSelector:
            matchLabels: { app: bff-mobile }
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels: { app: postgres }
      ports: [ { port: 5432 } ]
    - to:
        - podSelector:
            matchLabels: { app: kafka }
      ports: [ { port: 9092 } ]

pod-level firewall — least privilege network


Part 13: Production Checklist

text
☐ mTLS ทุก service-to-service (มัก ผ่าน service mesh)
☐ JWT validation ทุก service (Zero Trust)
☐ JWKs rotation handled
☐ Token Exchange สำหรับ service identity
☐ Authorization: coarse (gateway) + fine (service) + policy (OPA)
☐ Broker auth: SASL/SCRAM + ACL
☐ Secret management (Vault / Secrets Manager)
☐ No secret in code, image, git, log
☐ Encryption at rest (DB, backup, S3)
☐ PII tokenization for sensitive field
☐ Audit log → centralized
☐ NetworkPolicy least privilege
☐ Vulnerability scan: deps + image
☐ Image signing (provenance)
☐ Rate limit at gateway + service
☐ Input validation everywhere
☐ Penetration test quarterly
☐ Security review for new service
☐ Incident response runbook

Part 13.5: Supply Chain Security Deep — Sigstore + SLSA

หลัง SolarWinds (2020), Log4Shell (2021), xz backdoor (2024) — supply chain เป็น attack vector อันดับ 1

13.5.1 Sigstore (CNCF graduated 2023)

stack ของ tool ที่ทำให้ "sign software ฟรี + verifiable":

Toolทำอะไร
Cosignsign container image / blob
Fulcioissue short-lived cert ผูกกับ OIDC identity
Rekortransparent log (เหมือน Certificate Transparency)
Gitsignsign git commit ด้วย Sigstore

Sign + verify ใน CI/CD

bash
# CI: build + sign (keyless — ผูกกับ GitHub OIDC token)
docker build -t ghcr.io/acme/orders:v1.2.3 .
docker push ghcr.io/acme/orders:v1.2.3

cosign sign --yes ghcr.io/acme/orders:v1.2.3
# → uses Fulcio (cert from OIDC) → log to Rekor
bash
# K8s admission controller: verify
cosign verify ghcr.io/acme/orders:v1.2.3 \
  --certificate-identity-regexp '^https://github.com/acme/orders/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

→ ถ้า admission controller (Kyverno/OPA Gatekeeper) verify ไม่ผ่าน = block deploy

13.5.2 SLSA Framework (Supply-chain Levels for Software Artifacts)

Google open standard — 4 levels ของ supply chain hardening:

LevelRequirements
SLSA 1Build is scripted + provenance generated
SLSA 2Hosted build service + version controlled + signed provenance
SLSA 3Source + build platform meet "non-falsifiable" criteria
SLSA 4Two-person review + hermetic + reproducible builds

Provenance attestation (in-toto format)

bash
# Generate SLSA provenance during build
slsa-github-generator: pulled by GitHub Actions reusable workflow

# Attestation embeds:
{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [{ "name": "ghcr.io/acme/orders", "digest": { "sha256": "..." }}],
  "predicate": {
    "buildType": "https://github.com/Attestations/GitHubActionsWorkflow@v1",
    "builder": { "id": "https://github.com/actions/runner" },
    "invocation": { "configSource": { "uri": "git+https://github.com/acme/orders@refs/heads/main" }},
    "materials": [{ "uri": "git+https://github.com/acme/orders", "digest": { "sha1": "abc..." }}]
  }
}

→ ลูกค้า/auditor verify "image นี้ build จาก git commit นี้ บน CI นี้ จริง"

13.5.3 SBOM (Software Bill of Materials)

bash
# Syft — generate SPDX or CycloneDX SBOM
syft ghcr.io/acme/orders:v1.2.3 -o spdx-json > sbom.json

# Attach SBOM to image
cosign attach sbom --sbom sbom.json ghcr.io/acme/orders:v1.2.3

# Vulnerability scan against SBOM
grype sbom:sbom.json

→ เมื่อ CVE ใหม่ออก (Log4Shell) — query SBOM ทุก image → รู้ทันทีว่าโดน


Part 13.6: Production War Stories

🔥 Capital One (2019): SSRF + IAM = $80M fine + 100M record leak

WAF vulnerability → SSRF → ดึง IAM credential จาก EC2 metadata service v1 → S3 dump

Lesson:

  • Enforce IMDSv2-only mode (HttpTokens=required) + disable IMDSv1 entirely — ตั้งแต่ Nov 2023 AWS ทำให้ IMDSv2 เป็น default ของ new instance แต่ instance เก่ายังเปิด v1 ค้าง; ใน AWS Organizations ใช้ SCP บังคับทั้ง org
  • ใช้ Workload Identity Federation + IRSA — ไม่มี IAM credential persistent
  • URL allowlist สำหรับ outbound HTTP (กัน SSRF)
  • WAF + rate limit + alerting on internal metadata access

🔥 Twilio (2022): JWT signing key leak → 1700 customer affected

  1. Attacker phishing เอา engineer credential ไปได้ 2) ใช้ credential นั้นเข้า internal panel 3) ดึง (dump) signing key ออกมาจาก panel 4) ใช้ signing key ปลอม (forge) JWT ในนามของ user คนไหนก็ได้

Lesson:

  • Hardware Security Module (HSM) หรือ KMS-backed key — ไม่มี plain key file
  • Key rotation อัตโนมัติ + revocation list
  • 2FA + WebAuthn for admin
  • Audit log access ทุก secret read

🔥 SolarWinds (2020): build pipeline compromise affect 18K customer

Attacker compromise build server → inject backdoor ใน Orion update → ลูกค้า pull update = run malware

Lesson:

  • SLSA Level 3+ — non-falsifiable provenance
  • Sigstore signing + admission controller verify
  • Build environment hermetic + reproducible
  • Two-person review for build pipeline changes

🔥 xz utils backdoor (Mar 2024): Linux ecosystem 2 ปี attack ใกล้สำเร็จ

Maintainer ที่ trust 2 ปี → inject backdoor ใน xz-utils library → Linux distro รับ → ใกล้ระบาด ssh ทั่วโลก แต่ Microsoft engineer สังเกตเห็น

Lesson:

  • OpenSSF Best Practices — multiple maintainer, 2-person review
  • ระวัง dependency ที่ maintainer คนเดียว
  • Audit binary changes (ไม่ใช่แค่ source diff)
  • Reproducible build — ผู้ verify build จาก source แล้ว match binary

🔥 บริษัท SaaS (2023): IDOR (BOLA) ใน mobile API → ผู้ใช้เห็น order ของกัน

API /orders/{id} ไม่ check ownership → user A enumerate id 1-1M → ดู order ทุกคน

Lesson:

  • Authorization ต้องทำใน service (gateway แค่ authenticate)
  • Use OpenFGA/SpiceDB สำหรับ ReBAC complex
  • Object reference = UUID (กัน enumeration)
  • Pentest annual + bug bounty

Part 13.7: 📋 Cheat Sheet

Zero Trust baseline

text
1. mTLS ทุก service-to-service           → service mesh (Istio Ambient / Cilium)
2. Workload identity                     → SPIFFE/SPIRE หรือ K8s SA
3. JWT validation ทุก service            → resource server (Spring Security OAuth2)
4. Authorization layered                 → gateway (coarse) + service (fine) + OPA/OpenFGA
5. Cloud IAM ผ่าน federation             → ไม่เก็บ access key ใน K8s secret
6. NetworkPolicy default-deny            → ขึ้นทีละ rule
7. Secret ใน external manager            → External Secrets Operator
8. Audit log centralized                 → Kafka → audit DW
9. Supply chain hardening                → SBOM + Cosign + SLSA L3
10. Annual pentest + bug bounty

Authorization tool selection

text
Simple role/permission (admin, user)     → Spring Security @PreAuthorize
Policy as code (declarative rules)        → OPA / Cedar (AWS)
Resource-level + sharing (Drive-like)    → OpenFGA / SpiceDB (Zanzibar-style)
Cross-service consistent policy           → OPA sidecar pattern หรือ Cedar in-proc

Common mistake & fix

❌ Anti-pattern✅ Fix
Validate JWT แค่ที่ gatewayValidate ทุก service (Zero Trust)
X-User-Id header trustTrust signed JWT, ไม่ใช่ header
Hard-coded API keyWorkload Identity / SPIFFE
Long-lived cert (1 year)Short-lived (1-24h) + auto-rotate
K8s Secret ใน git plainSOPS+Age หรือ ESO + Vault
latest image tagImage digest (@sha256:...) + Cosign verify
No NetworkPolicyDefault-deny per namespace
Same DB credential ทุก servicePer-service credential (Vault dynamic)
BOLA (no ownership check)Authorization ใน service + use UUID id
No audit logCentralized audit → tamper-proof store

Secret rotation playbook

text
1. Rotation event (90 days, suspect leak, employee left)
2. Generate new credential ใน Vault
3. ESO sync to K8s Secret (1h)
4. Rolling restart pod (pickup new env)
5. Verify all instance ใช้ใหม่
6. Revoke old credential ที่ source
7. Audit log: "Rotated <secret> at <ts>, reason: <reason>"
8. Alert if rotation > 90 days overdue

Incident response runbook (ย่อ)

text
P0: Active breach
  1. Page security on-call
  2. Isolate affected service (NetworkPolicy block)
  3. Revoke credential + rotate keys
  4. Snapshot for forensics (don't reboot!)
  5. Notify legal + compliance (GDPR 72hr clock)
  6. Block traffic at edge (CloudFlare/WAF)
  7. Forensics → identify scope + IoC
  8. Patch + rotate + redeploy
  9. Post-mortem + customer notification

Part 14: Checkpoint

  1. Zero Trust หมายความว่ายังไง?
  2. mTLS ต่าง TLS ยังไง?
  3. JWKs ทำงานยังไง?
  4. Validate JWT ที่ gateway vs ทุก service?
  5. Token Exchange ใช้ตอนไหน?
  6. OPA ทำอะไร?
  7. Kafka SASL/SCRAM vs mTLS?
  8. Vault dynamic credentials ทำอะไร?
  9. BOLA vulnerability คืออะไร?
  10. NetworkPolicy ใน K8s ป้องกันอะไร?

Part 15: สรุปบทนี้

  • Zero Trust — verify everywhere, no trusted network
  • mTLS = ทั้ง 2 ฝั่ง auth + encrypted (ใช้ service mesh — Istio Ambient / Cilium)
  • 🆕 SPIFFE/SPIRE (CNCF graduated) = standard workload identity ข้าม cloud
  • 🆕 Workload Identity Federation (GCP/AWS IRSA/Azure) = ไม่เก็บ key ใน K8s secret
  • JWT propagation — validate ทุก service + JWKs cache + rotation
  • Authorization layered — gateway (coarse) + service (fine) + OPA / OpenFGA / Cedar
  • 🆕 OpenFGA / SpiceDB (Zanzibar-style) = ReBAC สำหรับ resource sharing (SaaS)
  • Broker auth: SASL/SCRAM + ACL per topic
  • 🆕 Supply chain: Sigstore (Cosign + Fulcio + Rekor) + SLSA L3+ + SBOM
  • Secret: External Secrets Operator + Vault + auto-rotation
  • Lessons: Capital One (IMDSv2), Twilio (HSM), SolarWinds (SLSA), xz (dependency audit)
  • Secret: Vault / Secrets Manager — never in code
  • Audit log = centralized
  • NetworkPolicy = pod firewall

บทถัดไป — Service Mesh (Istio, Linkerd)


← บทที่ 15 | สารบัญ | บทที่ 17: Service Mesh →