Skip to content

mTLS & SPIFFE/SVID — Zero-Trust Service Identity

Tại sao mTLS quan trọng?

Trong Kubernetes không có service mesh, traffic giữa các Pods mặc định là plain text, không authenticated. Bất kỳ Pod nào cũng có thể nói chuyện với Pod khác (trừ khi có NetworkPolicy). Một attacker compromise được một Pod có thể:

  • Sniff traffic giữa các services
  • Impersonate service khác
  • Lateral movement qua toàn bộ cluster

Mutual TLS (mTLS) giải quyết cả ba vấn đề:

  1. Mã hóa: Traffic được encrypt, không thể sniff
  2. Authentication: Cả hai phía verify identity lẫn nhau (mutual = hai chiều)
  3. Authorization: Sau khi verify identity, có thể enforce "Service A được phép gọi Service B"

SPIFFE: Framework định danh service

SPIFFE là gì?

SPIFFE (Secure Production Identity Framework for Everyone) là standard mở cho service identity trong dynamic infrastructure. Core concept:

  • Mỗi workload nhận một SVID (SPIFFE Verifiable Identity Document)
  • SVID là một X.509 certificate với SPIFFE ID trong Subject Alternative Name (SAN)
  • SPIFFE ID có format: spiffe://trust-domain/path

Trong Istio/GKE:

  • Trust domain: cluster.local
  • Path: /ns/<namespace>/sa/<service-account>
  • Ví dụ: spiffe://cluster.local/ns/production/sa/payment-service

SPIFFE ID vs Kubernetes Identity

SPIFFE/SVIDKubernetes
Identityspiffe://cluster.local/ns/NS/sa/SAServiceAccount object
FormatX.509 certificateJWT token
Use casemTLS service-to-serviceAPI Server authentication
Lifespan24h (auto-rotated)1h (TokenRequest)
IssuerIstiod CAKubernetes CA

Certificate Lifecycle trong Istio

Luồng cấp certificate

1. Envoy proxy start trong Pod

   │ Generate private key (locally trong memory)

2. Envoy gửi CSR (Certificate Signing Request) đến Istiod
   qua gRPC SDS (Secret Discovery Service)

   │ CSR payload:
   │  - Public key
   │  - Requested SPIFFE ID: spiffe://cluster.local/ns/NS/sa/SA
   │  - Identity proof (Kubernetes ServiceAccount token)

3. Istiod verify CSR
   - Verify Kubernetes SA token (gọi TokenReview API)
   - Confirm Pod đang chạy với SA đó
   - Verify namespace/SA tồn tại

   │ Sign với Istiod CA

4. Istiod trả về signed SVID (X.509 certificate)
   - Subject: empty (SPIFFE không dùng Subject)
   - SAN: spiffe://cluster.local/ns/NS/sa/SA
   - Validity: 24h (default)
   - Signed bởi Istiod intermediate CA

   │ Push qua SDS stream

5. Envoy store certificate trong memory
   - Sử dụng ngay cho mTLS connections
   - Monitor expiry, request renewal tại 50% lifetime (12h)
   - Auto-rotate trước khi expire

Certificate hierarchy

┌─────────────────────────────────────┐
│         Root CA                      │
│  (Self-signed, trong Istiod secret)  │
│  Validity: 10 years                  │
└──────────────────┬──────────────────┘
                   │ signs

┌─────────────────────────────────────┐
│      Istiod Intermediate CA         │
│  Validity: 1 year                    │
│  cacerts secret trong istio-system  │
└──────────────────┬──────────────────┘
                   │ signs

┌─────────────────────────────────────┐
│         Workload SVID               │
│  spiffe://cluster.local/ns/NS/sa/SA │
│  Validity: 24h                       │
│  Used cho mTLS connections          │
└─────────────────────────────────────┘

Xem certificates

bash
# Inspect Envoy certificate
kubectl exec my-pod -c istio-proxy -- \
  openssl s_client -connect other-service:8080 2>/dev/null | \
  openssl x509 -text -noout

# Hoặc dùng istioctl
istioctl proxy-config secret my-pod -n production

# Output:
# RESOURCE NAME  TYPE           STATUS     VALID CERT     SERIAL NUMBER     NOT AFTER                NOT BEFORE
# default        Cert Chain     ACTIVE     true           xxxxxxxx          2026-06-26T10:00:00Z    2026-06-25T10:00:00Z
# ROOTCA         CA             ACTIVE     true           xxxxxxxx          2036-06-25T10:00:00Z    2026-06-25T10:00:00Z

# Xem chi tiết certificate
istioctl proxy-config secret my-pod -n production -o json | \
  jq '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' -r | \
  base64 -d | openssl x509 -text -noout | grep -A5 "Subject Alternative Name"

# Output:
# X509v3 Subject Alternative Name: critical
#     URI:spiffe://cluster.local/ns/production/sa/payment-service

PeerAuthentication: PERMISSIVE vs STRICT

PeerAuthentication resource

PeerAuthentication policy kiểm soát mTLS mode cho inbound connections:

yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT  # Chỉ chấp nhận mTLS connections

Ba modes

1. STRICT: Chỉ chấp nhận mTLS connections

yaml
mtls:
  mode: STRICT
  • Tất cả inbound traffic PHẢI có valid mTLS certificate
  • Plain text connections bị từ chối
  • Production target: mọi namespace đều nên STRICT

2. PERMISSIVE (default sau khi install CSM): Chấp nhận cả mTLS và plain text

yaml
mtls:
  mode: PERMISSIVE
  • mTLS connection vẫn được verify nếu có certificate
  • Plain text connection được chấp nhận
  • Migration period: Khi chưa tất cả services đều có sidecar

3. DISABLE: Không dùng mTLS

yaml
mtls:
  mode: DISABLE
  • Plain text only
  • Ít khi dùng trong production

Scope của PeerAuthentication

yaml
# Mesh-wide default (trong istio-system namespace)
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

---
# Namespace-level override
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: legacy-services
spec:
  mtls:
    mode: PERMISSIVE  # Legacy services chưa có sidecar

---
# Workload-level override (Port-specific)
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: payment-service-pa
  namespace: production
spec:
  selector:
    matchLabels:
      app: payment-service
  mtls:
    mode: STRICT
  portLevelMtls:
    9090:  # Prometheus metrics port không cần mTLS
      mode: DISABLE
    8080:  # API port cần mTLS
      mode: STRICT

Migration từ PERMISSIVE đến STRICT

bash
# Bước 1: Set toàn bộ mesh sang PERMISSIVE (default)
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: PERMISSIVE
EOF

# Bước 2: Inject sidecar vào tất cả namespaces
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
  kubectl label namespace $ns istio-injection=enabled --overwrite
  kubectl rollout restart deployment -n $ns
done

# Bước 3: Verify mTLS connections
istioctl x describe service payment-service -n production
# Output sẽ show mTLS status

# Bước 4: Check còn plain text connections không
# Dùng Istio metrics: connection_security_policy
kubectl exec -n istio-system deployment/prometheus -- \
  curl -s 'localhost:9090/api/v1/query?query=istio_requests_total{connection_security_policy="none",destination_service_namespace="production"}' | \
  jq '.data.result'

# Bước 5: Switch từng namespace sang STRICT
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT
EOF

# Bước 6: Monitor errors (5xx) sau khi switch
kubectl exec -n istio-system deployment/prometheus -- \
  curl -s 'localhost:9090/api/v1/query?query=sum(rate(istio_requests_total{response_code=~"5.*",destination_service_namespace="production"}[5m]))' | \
  jq '.data.result'

AuthorizationPolicy: Kiểm soát Service-to-Service Access

Sau khi có mTLS identity, bạn có thể enforce authorization:

yaml
# Deny tất cả traffic vào payment-service
# trừ order-service
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: payment-service-authz
  namespace: production
spec:
  selector:
    matchLabels:
      app: payment-service
  action: ALLOW
  rules:
  - from:
    - source:
        principals:
        - "cluster.local/ns/production/sa/order-service"
        - "cluster.local/ns/production/sa/reconciliation-service"
    to:
    - operation:
        methods: ["GET", "POST"]
        paths: ["/api/v1/*"]
    when:
    - key: source.namespace
      values: ["production"]

---
# Deny all (implicit deny sau khi có ALLOW policy)
# Không cần explicit DENY, khi có ALLOW rule thì
# tất cả traffic không match sẽ bị từ chối

AuthorizationPolicy Conditions

yaml
# Dùng JWT claims cho external users
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: api-gateway-authz
spec:
  selector:
    matchLabels:
      app: api-gateway
  rules:
  - from:
    - source:
        requestPrincipals: ["*"]  # Yêu cầu JWT
    when:
    - key: request.auth.claims[iss]
      values: ["https://accounts.google.com"]
    - key: request.auth.claims[email_verified]
      values: ["true"]

Debugging mTLS Issues

1. Connection bị reject với PEER_CERT_VERIFY_FAILED

bash
# Triệu chứng: 503 errors với flag "PEER_CERT_VERIFY_FAILED"
kubectl logs my-pod -c istio-proxy | grep "PEER_CERT"

# Nguyên nhân thường gặp:
# a. Certificate hết hạn
istioctl proxy-config secret my-pod | grep "NOT AFTER"

# b. Trust domain mismatch
# Service A và Service B có different trust domains

# c. CA root không match
# Kiểm tra root CA
istioctl proxy-config secret my-pod -o json | \
  jq '.dynamicActiveSecrets[] | select(.name == "ROOTCA") | 
  .secret.validationContext.trustedCa.inlineBytes' -r | \
  base64 -d | openssl x509 -text -noout | grep -A2 "Issuer"

2. 503 với "upstream connect error"

bash
# Check PeerAuthentication mode mismatch
# Bên gửi (caller): STRICT mTLS
# Bên nhận (server): DISABLE → reject mTLS

kubectl get peerauthentication -A
kubectl get destinationrule -A

# Kiểm tra Envoy access log
kubectl logs my-pod -c istio-proxy | grep "upstream_peer"

3. Plain text bị reject trong STRICT mode

bash
# Triệu chứng: Service không có sidecar gọi service có STRICT PA
# Error: "Connection reset by peer" hoặc "SSL: wrong version number"

# Giải pháp: Thêm exception trong PeerAuthentication
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: allow-legacy
  namespace: production
spec:
  selector:
    matchLabels:
      app: payment-service
  mtls:
    mode: STRICT
  portLevelMtls:
    8080:
      mode: PERMISSIVE  # Tạm thời cho legacy callers

4. Certificate không được rotate

bash
# Check certificate expiry
istioctl proxy-config secret my-pod

# Nếu certificate expiry sắp tới và không được rotate:
# 1. Check Istiod logs
kubectl logs -n istio-system deployment/istiod | grep "cert"

# 2. Check SDS gRPC connection từ Envoy đến Istiod
kubectl exec my-pod -c istio-proxy -- curl localhost:15000/clusters | \
  grep xds-grpc

# 3. Force renew bằng restart Pod
kubectl rollout restart deployment my-deployment

Sử dụng istioctl để diagnose

bash
# Analyze mesh configuration
istioctl analyze

# Output: List của warnings/errors, ví dụ:
# Warning [IST0108] (Namespace production) The namespace is not 
#   labeled for Istio injection
# Warning [IST0110] (VirtualService payment-vs) The weight sum 
#   for all routes is not 100

# Describe specific service
istioctl x describe service payment-service -n production
# Output: mTLS status, policies applied, routes

# Check proxy sync status
istioctl proxy-status
# NAME                   CDS        LDS        EDS        RDS    ISTIOD  VERSION
# payment-svc-7db...     SYNCED     SYNCED     SYNCED     SYNCED  istiod-xxx  1.20.0

Tích hợp với Cloud Service Mesh Dashboard

CSM Dashboard trong Google Cloud Console hiển thị:

Cloud Console → Anthos → Service Mesh → Services → [service-name]

  ├── SLOs (error rate, latency P50/P99)
  ├── Topology (service graph)
  ├── mTLS status (percentage secured traffic)
  └── Security policies applied

Để hiển thị security context trong CSM Dashboard:

yaml
# Cần telemetry API enabled
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
  name: default
  namespace: istio-system
spec:
  accessLogging:
  - providers:
    - name: envoy
  metrics:
  - providers:
    - name: prometheus
  tracing:
  - providers:
    - name: stackdriver
    randomSamplingPercentage: 1.0

Production Checklist cho mTLS

✅ Mesh-wide PeerAuthentication STRICT (hoặc PERMISSIVE trong migration)
✅ Tất cả namespaces production đều có STRICT PeerAuthentication
✅ AuthorizationPolicy enforce principle of least privilege
✅ Certificate expiry monitored (Envoy metric: pilot_xds_push_errors)
✅ Root CA backed up (cacerts secret trong istio-system)
✅ mTLS percentage metric > 99.9% trong production
✅ No plain text connections trong Cloud Monitoring / CSM dashboard

References