SiamCafe.net Blog
Cybersecurity

mTLS Service Mesh Edge Deployment — ระบบเข้ารหัสและยืนยันตัวตนสำหรับ Edge Computing

mtls service mesh edge deployment
mTLS Service Mesh Edge Deployment | SiamCafe Blog
2026-04-13· อ. บอม — SiamCafe.net· 9,312 คำ

mTLS คืออะไรและทำไมถึงสำคัญสำหรับ Edge Deployment

Mutual TLS (mTLS) เป็นโปรโตคอลที่ทั้งฝั่ง client และ server ต้องแสดง certificate เพื่อยืนยันตัวตนซึ่งกันและกัน ต่างจาก TLS ปกติที่เฉพาะ server เท่านั้นที่ต้องแสดง certificate ทำให้ mTLS ปลอดภัยกว่าเพราะป้องกันทั้ง man-in-the-middle attack และ unauthorized client access

Edge Deployment คือการ deploy workload ไปยังตำแหน่งที่อยู่ใกล้กับผู้ใช้เช่น Edge Data Center, Branch Office หรือ IoT Gateway เพื่อลด latency และเพิ่มประสิทธิภาพ ปัญหาหลักของ Edge Deployment คือ network ระหว่าง edge กับ central cluster มักไม่น่าเชื่อถือและอาจถูกดักฟังได้ mTLS จึงจำเป็นสำหรับการเข้ารหัสและยืนยันตัวตนของทุก connection

Service Mesh เช่น Istio หรือ Linkerd ทำให้การ implement mTLS ง่ายขึ้นมากเพราะจัดการ certificate issuance, rotation และ enforcement ให้อัตโนมัติผ่าน sidecar proxy โดยที่แอปพลิเคชันไม่ต้องเปลี่ยน code เลย

สถาปัตยกรรมของระบบประกอบด้วย Central Cluster ที่เป็น control plane หลัก, Edge Clusters ที่กระจายอยู่ตามตำแหน่งต่างๆ และ Certificate Authority (CA) ที่ออก certificate ให้ทุก workload ในทุก cluster

ติดตั้ง Istio พร้อม mTLS Strict Mode

ติดตั้ง Istio ด้วย IstioOperator Configuration ที่เปิด mTLS Strict Mode ทั่วทั้ง mesh

# ดาวน์โหลดและติดตั้ง istioctl
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.22.0 sh -
export PATH=$PWD/istio-1.22.0/bin:$PATH

# istio-config.yaml — Istio Configuration สำหรับ Edge Deployment
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-edge
spec:
  profile: default
  meshConfig:
    accessLogFile: /dev/stdout
    accessLogEncoding: JSON
    enableAutoMtls: true
    defaultConfig:
      holdApplicationUntilProxyStarts: true
      proxyMetadata:
        ISTIO_META_DNS_CAPTURE: "true"
        ISTIO_META_DNS_AUTO_ALLOCATE: "true"
    outboundTrafficPolicy:
      mode: REGISTRY_ONLY
  components:
    pilot:
      k8s:
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
        hpaSpec:
          minReplicas: 2
          maxReplicas: 5
    ingressGateways:
      - name: istio-ingressgateway
        enabled: true
        k8s:
          service:
            type: LoadBalancer
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
  values:
    global:
      proxy:
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            cpu: 200m
            memory: 256Mi
    pilot:
      env:
        PILOT_ENABLE_CROSS_CLUSTER_WORKLOAD_ENTRY: "true"

# ติดตั้ง Istio
istioctl install -f istio-config.yaml -y

# เปิด sidecar injection สำหรับ namespace
kubectl label namespace default istio-injection=enabled
kubectl label namespace production istio-injection=enabled

# ตรวจสอบการติดตั้ง
istioctl analyze
# Output: No validation issues found when analyzing namespace: default

# ตรวจสอบ mTLS status
istioctl x describe pod 
# Output: Mutual TLS: STRICT

ตั้งค่า PeerAuthentication เพื่อ enforce mTLS Strict Mode ทั่วทั้ง mesh

# peer-auth-strict.yaml — Enforce mTLS Strict ทั่ว mesh
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
---
# DestinationRule สำหรับ enforce mTLS
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: default
  namespace: istio-system
spec:
  host: "*.local"
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
---
# AuthorizationPolicy — อนุญาตเฉพาะ authenticated traffic
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: require-mtls
  namespace: production
spec:
  action: ALLOW
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/production/sa/*"]
      to:
        - operation:
            methods: ["GET", "POST", "PUT", "DELETE"]
---
# Deny all traffic ที่ไม่ผ่าน mTLS
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-non-mtls
  namespace: production
spec:
  action: DENY
  rules:
    - from:
        - source:
            notPrincipals: ["cluster.local/*"]

# kubectl apply -f peer-auth-strict.yaml

ตั้งค่า Certificate Management ด้วย cert-manager

ใช้ cert-manager จัดการ certificate สำหรับ Istio CA เพื่อรองรับ cross-cluster mTLS

# ติดตั้ง cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true \
  --set global.leaderElection.namespace=cert-manager

# ตรวจสอบการติดตั้ง
kubectl get pods -n cert-manager
# NAME                                       READY   STATUS
# cert-manager-xxx                            1/1     Running
# cert-manager-cainjector-xxx                 1/1     Running
# cert-manager-webhook-xxx                    1/1     Running

# สร้าง Root CA สำหรับ multi-cluster mTLS
# root-ca.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: mesh-root-ca
  namespace: cert-manager
spec:
  isCA: true
  commonName: mesh-root-ca
  duration: 87600h  # 10 years
  renewBefore: 8760h  # 1 year
  secretName: mesh-root-ca-secret
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer
    group: cert-manager.io
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: mesh-ca-issuer
spec:
  ca:
    secretName: mesh-root-ca-secret
---
# Intermediate CA สำหรับ Istio
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: istio-ca
  namespace: istio-system
spec:
  isCA: true
  commonName: istio-ca
  duration: 8760h  # 1 year
  renewBefore: 720h  # 30 days
  secretName: cacerts
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: mesh-ca-issuer
    kind: ClusterIssuer
    group: cert-manager.io
  usages:
    - digital signature
    - key encipherment
    - cert sign

# kubectl apply -f root-ca.yaml

Deploy Workload บน Edge Location พร้อม mTLS

ตั้งค่า Edge Cluster ให้ join กับ Central Mesh และ deploy workload พร้อม mTLS

# สร้าง Remote Secret สำหรับ cross-cluster access
# บน Central Cluster
istioctl x create-remote-secret \
  --name=edge-cluster-01 \
  --context=edge-cluster-01 | \
  kubectl apply -f - --context=central-cluster

# ตรวจสอบว่า cluster เชื่อมต่อกัน
kubectl get secrets -n istio-system --context=central-cluster | grep istio-remote
# istio-remote-secret-edge-cluster-01

# edge-workload.yaml — Deploy workload บน Edge
apiVersion: apps/v1
kind: Deployment
metadata:
  name: edge-api-gateway
  namespace: production
  labels:
    app: edge-api-gateway
    version: v1
    location: edge-bangkok
spec:
  replicas: 3
  selector:
    matchLabels:
      app: edge-api-gateway
  template:
    metadata:
      labels:
        app: edge-api-gateway
        version: v1
      annotations:
        sidecar.istio.io/inject: "true"
        proxy.istio.io/config: |
          holdApplicationUntilProxyStarts: true
          terminationDrainDuration: 30s
    spec:
      serviceAccountName: edge-api-sa
      containers:
        - name: api-gateway
          image: myregistry/edge-api:v2.1.0
          ports:
            - containerPort: 8080
          env:
            - name: UPSTREAM_SERVICE
              value: "central-api.production.svc.cluster.local"
            - name: CACHE_TTL
              value: "300"
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: edge-api-gateway
  namespace: production
spec:
  selector:
    app: edge-api-gateway
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: edge-api-sa
  namespace: production

# kubectl apply -f edge-workload.yaml --context=edge-cluster-01

ตั้งค่า Gateway และ VirtualService สำหรับ Edge Ingress

# edge-gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: edge-gateway
  namespace: production
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 443
        name: https
        protocol: HTTPS
      tls:
        mode: SIMPLE
        credentialName: edge-tls-cert
      hosts:
        - "edge-bkk.example.com"
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: edge-api-vs
  namespace: production
spec:
  hosts:
    - "edge-bkk.example.com"
  gateways:
    - edge-gateway
  http:
    - match:
        - uri:
            prefix: /api/
      route:
        - destination:
            host: edge-api-gateway
            port:
              number: 80
      timeout: 10s
      retries:
        attempts: 3
        perTryTimeout: 3s
        retryOn: gateway-error, connect-failure, refused-stream

ตั้งค่า Cross-Cluster mTLS ระหว่าง Edge กับ Central

ตั้งค่า ServiceEntry และ DestinationRule เพื่อให้ Edge Workload สื่อสารกับ Central Cluster ผ่าน mTLS

# cross-cluster-config.yaml
# ServiceEntry สำหรับเข้าถึง Central API จาก Edge
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: central-api-entry
  namespace: production
spec:
  hosts:
    - central-api.production.global
  location: MESH_INTERNAL
  ports:
    - number: 443
      name: https
      protocol: TLS
    - number: 80
      name: http
      protocol: HTTP
  resolution: DNS
  endpoints:
    - address: central-api.example.com
      ports:
        https: 15443
---
# DestinationRule สำหรับ mTLS ไปยัง Central
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: central-api-mtls
  namespace: production
spec:
  host: central-api.production.global
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
      sni: central-api.production.global
    connectionPool:
      tcp:
        maxConnections: 100
        connectTimeout: 10s
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 50
        http2MaxRequests: 100
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50

# kubectl apply -f cross-cluster-config.yaml --context=edge-cluster-01

ทดสอบ mTLS connectivity ระหว่าง clusters

# ทดสอบจาก Edge Pod ไปยัง Central API
kubectl exec -it deploy/edge-api-gateway -c api-gateway \
  --context=edge-cluster-01 -- \
  curl -sv https://central-api.production.global/health 2>&1 | grep -E "SSL|subject|issuer"

# Output:
# * SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
# * subject: O=cluster.local
# * issuer: O=cluster.local; CN=istio-ca

# ตรวจสอบ certificate chain
istioctl proxy-config secret deploy/edge-api-gateway --context=edge-cluster-01 -o json | \
  python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
for s in data.get('dynamicActiveSecrets', []):
    name = s.get('name', '')
    if 'ROOTCA' in name or 'default' in name:
        cert = s.get('secret', {}).get('tlsCertificate', {}).get('certificateChain', {}).get('inlineBytes', '')
        if cert:
            print(f'{name}: {len(base64.b64decode(cert))} bytes')
"

# ตรวจสอบ mTLS status ของทุก service
istioctl x check-inject --context=edge-cluster-01
istioctl proxy-status --context=edge-cluster-01

Monitoring และ Troubleshooting mTLS Issues

สร้าง monitoring dashboard สำหรับ mTLS status และ certificate health

#!/usr/bin/env python3
# mtls_health_check.py — ตรวจสอบ mTLS health ทั่ว mesh
import subprocess
import json
import sys
from datetime import datetime, timedelta

def run_kubectl(cmd, context=""):
    full_cmd = f"kubectl {cmd}"
    if context:
        full_cmd += f" --context={context}"
    result = subprocess.run(full_cmd.split(), capture_output=True, text=True)
    return result.stdout

def check_mtls_status(context):
    """ตรวจสอบว่าทุก pod ใช้ mTLS"""
    output = run_kubectl("get pods -A -o json", context)
    pods = json.loads(output)
    issues = []
    
    for pod in pods["items"]:
        ns = pod["metadata"]["namespace"]
        name = pod["metadata"]["name"]
        containers = [c["name"] for c in pod["spec"].get("containers", [])]
        
        # ตรวจสอบว่ามี istio-proxy sidecar
        if "istio-proxy" not in containers and ns not in ["kube-system", "istio-system", "cert-manager"]:
            issues.append(f"  NO SIDECAR: {ns}/{name}")
    
    return issues

def check_certificate_expiry(context):
    """ตรวจสอบวันหมดอายุ certificate"""
    output = run_kubectl(
        "get certificates -A -o json", context
    )
    certs = json.loads(output)
    expiring_soon = []
    
    for cert in certs.get("items", []):
        name = cert["metadata"]["name"]
        ns = cert["metadata"]["namespace"]
        status = cert.get("status", {})
        not_after = status.get("notAfter", "")
        
        if not_after:
            expiry = datetime.fromisoformat(not_after.replace("Z", "+00:00"))
            days_left = (expiry - datetime.now(expiry.tzinfo)).days
            
            if days_left < 30:
                expiring_soon.append(f"  {ns}/{name}: expires in {days_left} days")
    
    return expiring_soon

def check_peer_authentication(context):
    """ตรวจสอบ PeerAuthentication policies"""
    output = run_kubectl(
        "get peerauthentication -A -o json", context
    )
    policies = json.loads(output)
    non_strict = []
    
    for policy in policies.get("items", []):
        name = policy["metadata"]["name"]
        ns = policy["metadata"]["namespace"]
        mode = policy.get("spec", {}).get("mtls", {}).get("mode", "UNSET")
        
        if mode != "STRICT":
            non_strict.append(f"  {ns}/{name}: mode={mode}")
    
    return non_strict

if __name__ == "__main__":
    contexts = sys.argv[1:] if len(sys.argv) > 1 else ["central-cluster", "edge-cluster-01"]
    
    for ctx in contexts:
        print(f"\n=== Cluster: {ctx} ===")
        
        print("\n[mTLS Sidecar Check]")
        issues = check_mtls_status(ctx)
        if issues:
            print(f"  WARNING: {len(issues)} pods without sidecar")
            for i in issues[:10]:
                print(i)
        else:
            print("  OK: All pods have istio-proxy sidecar")
        
        print("\n[Certificate Expiry Check]")
        expiring = check_certificate_expiry(ctx)
        if expiring:
            print(f"  WARNING: {len(expiring)} certificates expiring soon")
            for e in expiring:
                print(e)
        else:
            print("  OK: All certificates valid")
        
        print("\n[PeerAuthentication Check]")
        non_strict = check_peer_authentication(ctx)
        if non_strict:
            print(f"  WARNING: {len(non_strict)} non-STRICT policies")
            for p in non_strict:
                print(p)
        else:
            print("  OK: All policies are STRICT")

Prometheus Alert Rules สำหรับ mTLS monitoring

# mTLS Prometheus Alert Rules
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: mtls-alerts
  namespace: istio-system
spec:
  groups:
    - name: mtls-health
      rules:
        - alert: MTLSCertificateExpiringSoon
          expr: |
            (certmanager_certificate_expiration_timestamp_seconds - time()) / 86400 < 14
          for: 1h
          labels:
            severity: warning
          annotations:
            summary: "Certificate {{ $labels.name }} expires in {{ $value | humanizeDuration }}"

        - alert: MTLSConnectionFailure
          expr: |
            sum(rate(istio_tcp_connections_closed_total{connection_security_policy!="mutual_tls"}[5m])) by (destination_service_name) > 0
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "Non-mTLS connections detected for {{ $labels.destination_service_name }}"

        - alert: IstioCertificateRotationFailed
          expr: |
            sum(rate(istio_agent_pilot_xds_push_errors_total[5m])) > 0
          for: 10m
          labels:
            severity: critical
          annotations:
            summary: "Istio certificate rotation errors detected"

FAQ คำถามที่พบบ่อย

Q: mTLS กับ TLS ปกติต่างกันอย่างไร?

A: TLS ปกติแค่ server แสดง certificate ให้ client ตรวจสอบ ส่วน mTLS ทั้ง client และ server ต้องแสดง certificate ซึ่งกันและกัน ทำให้ server รู้ว่า client เป็นใครและ client รู้ว่า server เป็นใคร ใน Service Mesh mTLS ถูกจัดการโดย sidecar proxy อัตโนมัติโดยแอปไม่ต้องเปลี่ยน code

Q: mTLS STRICT Mode กับ PERMISSIVE Mode ต่างกันอย่างไร?

A: STRICT Mode บังคับให้ทุก connection ต้องใช้ mTLS ถ้า client ไม่มี certificate จะถูกปฏิเสธ ส่วน PERMISSIVE Mode ยอมรับทั้ง mTLS และ plain-text connection เหมาะสำหรับช่วง migration ที่ยังมี workload บางตัวไม่มี sidecar แนะนำให้ใช้ PERMISSIVE ก่อนแล้วค่อยเปลี่ยนเป็น STRICT เมื่อทุก workload พร้อม

Q: Certificate Rotation ทำงานอย่างไรใน Istio?

A: Istio ออก workload certificate ที่มีอายุ 24 ชั่วโมงโดย default แล้ว rotate อัตโนมัติก่อนหมดอายุ กระบวนการนี้จัดการโดย istio-agent ที่อยู่ใน sidecar container ส่ง CSR (Certificate Signing Request) ไปยัง istiod เพื่อขอ certificate ใหม่โดยไม่ต้อง restart pod

Q: Edge Cluster จำเป็นต้องมี istiod เป็นของตัวเองไหม?

A: ขึ้นอยู่กับ architecture ถ้าใช้ Primary-Remote model edge cluster ไม่ต้องมี istiod จะใช้ istiod จาก central cluster แต่ถ้า network ระหว่าง edge กับ central ไม่เสถียร แนะนำใช้ Multi-Primary model ที่ edge มี istiod ของตัวเองเพื่อให้ทำงานได้แม้ขาดการเชื่อมต่อกับ central

📖 บทความที่เกี่ยวข้อง

mTLS Service Mesh Best Practices ที่ต้องรู้อ่านบทความ → OPA Gatekeeper Service Mesh Setupอ่านบทความ → mTLS Service Mesh Disaster Recovery Planอ่านบทความ → PostgreSQL JSONB Edge Deploymentอ่านบทความ → Flatcar Container Linux Service Mesh Setupอ่านบทความ →

📚 ดูบทความทั้งหมด →