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
