# Istio Mutual TLS * Istio 是一種服務網格,也就是一種現代化的服務網路層,提供透明化且與程式設計語言種類無關的平台,能讓您以有彈性又簡單的方式將應用程式網路功能自動化。這是一項熱門的解決方案,廣泛用於管理各種不同的微服務 (組成起來即形成一個雲端原生應用程式)。不僅如此,Istio 服務網格也提供相應的支援,讓這些微服務能夠彼此通訊和共用資料。 ## rancher 安裝 istio * 使用 rancher cluster tools 安裝 istio,如果要 enable Kiali 需要先裝 Monitoring ,需要使用他的 crd。 ## Mutual TLS ![](https://hackmd.io/_uploads/By1F4TZ32.png) * Kubernetes Cluster 是由好幾個 Node 作爲運算單元所組成,這些 Node 可能放在同個機房也可能放在不同地區,而同一個 Microservices 元件不一定會部署到同個 Node 上,所以元件之間的流量可能會經過 Internet ,若是沒對封包內容進行加密,裡面又有使用者的敏感資訊時,駭客就能擷取封包獲取這些敏感資料。 * Mutual TLS 可以用來避免在 cluster 內的 man-in-the-middle attack(中間人攻擊),讓 pod 跟 pod 之間做溝通時可以做到流量的加密,以及在服務設置訪問策略以防未授權的流量進入系統等等,藉此保障服務的安全性。 ## Mutual TLS 實作 * 環境準備 ``` $ mkdir istio;cd istio $ wget https://raw.githubusercontent.com/istio/istio/release-1.18/samples/httpbin/httpbin.yaml $ wget https://raw.githubusercontent.com/istio/istio/release-1.18/samples/sleep/sleep.yaml $ wget https://github.com/istio/istio/releases/download/1.18.2/istio-1.18.2-linux-amd64.tar.gz $ tar -zxvf istio-1.18.2-linux-amd64.tar.gz $ sudo mv istio-1.18.2/bin/istioctl /usr/local/bin/ $ ls -l total 27292 -rw-r--r-- 1 rancher users 1506 Aug 9 17:36 httpbin.yaml drwxr-x--- 6 rancher users 115 Jul 22 07:40 istio-1.18.2 -rw-r--r-- 1 rancher users 27936557 Jul 26 00:37 istio-1.18.2-linux-amd64.tar.gz -rw-r--r-- 1 rancher users 1669 Aug 9 17:42 sleep.yaml ``` * 建立 foo、bar namespace 並且都攜帶 Envoy sidecar。 ``` $ kubectl create ns foo $ kubectl label namespace foo istio-injection=enabled namespace/foo labeled $ kubectl apply -f httpbin.yaml -n foo serviceaccount/httpbin created service/httpbin created deployment.apps/httpbin created $ kubectl apply -f sleep.yaml -n foo serviceaccount/sleep created service/sleep created deployment.apps/sleep created ``` ``` $ kubectl create ns bar $ kubectl label namespace bar istio-injection=enabled namespace/bar labeled $ kubectl apply -f httpbin.yaml -n bar serviceaccount/httpbin created service/httpbin created deployment.apps/httpbin created $ kubectl apply -f sleep.yaml -n bar serviceaccount/sleep created service/sleep created deployment.apps/sleep created ``` * 建立 legacy namespace 並且不帶 Envoy sidecar ``` $ kubectl create ns legacy namespace/legacy created $ kubectl apply -f sleep.yaml -n legacy serviceaccount/sleep created service/sleep created deployment.apps/sleep created ``` * 測試所有連線都是正常回應 ``` $ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done sleep.foo to httpbin.foo: 200 sleep.foo to httpbin.bar: 200 sleep.bar to httpbin.foo: 200 sleep.bar to httpbin.bar: 200 sleep.legacy to httpbin.foo: 200 sleep.legacy to httpbin.bar: 200 ``` * 鎖定 foo namespace 中的工作負載以僅接受相互 TLS 流量。 ``` $ kubectl apply -n foo -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default spec: mtls: mode: STRICT EOF ``` ``` $ kubectl get peerauthentication -n foo default NAME MODE AGE default STRICT 10s ``` * 因為沒有加密,所以無法從 legacy 的 sleep curl 到 foo 的 httpbin。 ``` $ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done sleep.foo to httpbin.foo: 200 sleep.foo to httpbin.bar: 200 sleep.bar to httpbin.foo: 200 sleep.bar to httpbin.bar: 200 sleep.legacy to httpbin.foo: 000 command terminated with exit code 56 sleep.legacy to httpbin.bar: 200 ``` ## 鎖定所有 namespace 互相 TLS * 將策略放入 Istio 安裝的 namespace 中來鎖定所有 namespace 中的工作負載,使其僅接受相互 TLS 流量。 * 只針對有加入 Envoy sidecar 的 namespace 有效。 ``` $ kubectl apply -n istio-system -f - <<EOF apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default spec: mtls: mode: STRICT EOF ``` ``` $ kubectl get peerauthentication -n istio-system default NAME MODE AGE default STRICT 50m ``` * legacy 都無法連線到 foo 與 bar 的服務 ``` $ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done sleep.foo to httpbin.foo: 200 sleep.foo to httpbin.bar: 200 sleep.bar to httpbin.foo: 200 sleep.bar to httpbin.bar: 200 sleep.legacy to httpbin.foo: 000 command terminated with exit code 56 sleep.legacy to httpbin.bar: 000 command terminated with exit code 56 ``` ## 驗證是否加密 * 使用 debug 的方式進入到 pod ``` $ kubectl -n foo debug httpbin-5c5944c58c-j5s9b -it --image=quay.io/cooloo9871/alpine --share-processes --target=httpbin ``` * 安裝 tcpdump ``` / # apk add tcpdump (1/2) Installing libpcap (1.10.0-r0) (2/2) Installing tcpdump (4.99.0-r0) Executing busybox-1.32.1-r6.trigger OK: 8 MiB in 16 packages # 可以觀察到 sleep.legacy 發送請求時,會在輸出中看到明文文本,sleep.foo 發送請求時,會在輸出中看到加密文本 / # tcpdump dst port 80 -A tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes 06:00:37.537018 IP 10-42-80-216.sleep.foo.svc.cluster.local.39808 > httpbin-5c5944c58c-j5s9b.80: Flags [S], seq 613509745, win 64860, options [mss 1410,sackOK,TS val 1047613846 ecr 0,nop,wscale 7], length 0 E..<}.@.?... *P. *P....P$.jq.......\.1......... ``` ### 從 istio kiali 觀察 * 經過加密的流量會有一個鎖頭 ![](https://hackmd.io/_uploads/ByyYqWfhn.png) * 取消規則後,沒有經過加密的流量就沒有鎖頭 ![](https://hackmd.io/_uploads/Sk1ys-Mh3.png) ### 使用 istioctl 驗證是否有加密 ``` $ istioctl experimental authz check <pod-name>.<namespace> ``` * 已檢查確定有加密 ``` $ istioctl experimental authz check httpbin-5c5944c58c-gq4bn.bar ACTION AuthorizationPolicy RULES ``` * legacy namespace 的 pod 就沒有進行加密 ``` $ istioctl experimental authz check sleep-bc9998558-vnc8w.legacy 2023-08-10T03:01:51.712516Z error klog an error occurred forwarding 36449 -> 15000: error forwarding port 15000 to pod 9d1eee775d91ef52f654c476c7ebade8179de2c2506eb61f48c61417374f0d2e, uid : failed to execute portforward in network namespace "/var/run/netns/cni-e16b2108-c140-3e2a-c482-05505153cea0": failed to connect to localhost:15000 inside namespace "9d1eee775d91ef52f654c476c7ebade8179de2c2506eb61f48c61417374f0d2e", IPv4: dial tcp4 127.0.0.1:15000: connect: connection refused IPv6 dial tcp6 [::1]:15000: connect: connection refused Error: failed to get config dump from pod sleep-bc9998558-vnc8w in legacy ``` ``` $ istioctl -n mtls x describe pod <pod-name> Pod: mtls-test Pod Revision: 1-27-1 Pod Ports: 15090 (istio-proxy) Warning: No Kubernetes Services select pod mtls-test (see https://istio.io/docs/setup/kubernetes/additional-setup/requirements/ ) -------------------- -------------------- Effective PeerAuthentication: Workload mTLS mode: STRICT Applied PeerAuthentication: default.mtls ``` ### 測試直接連接服務 ``` $ SVC_IP=$(kubectl -n bar get svc httpbin -o jsonpath='{.spec.clusterIP}') ``` * 在未開啟 mtls 的情況下可以直接連到服務 ``` $ curl $SVC_IP:8000/headers -s -o /dev/null -w "%{http_code}\n" 200 $ echo $? 0 ``` * 開啟後就無法直接連到服務 ``` $ curl $SVC_IP:8000/headers -s -o /dev/null -w "%{http_code}\n" 000 $ echo $? 56 ``` ## 環境清理 ``` $ kubectl delete peerauthentication -n foo default $ kubectl delete peerauthentication -n istio-system default $ kubectl delete ns foo bar legacy ``` ### 文件連結 https://istio.io/latest/docs/tasks/security/authentication/mtls-migration/ https://godleon.github.io/blog/ServiceMesh/istio-introdution/ https://istio.io/latest/zh/blog/2023/secure-apps-with-istio/ ###### tags: `work`