# Istio Mutual TLS
* Istio 是一種服務網格,也就是一種現代化的服務網路層,提供透明化且與程式設計語言種類無關的平台,能讓您以有彈性又簡單的方式將應用程式網路功能自動化。這是一項熱門的解決方案,廣泛用於管理各種不同的微服務 (組成起來即形成一個雲端原生應用程式)。不僅如此,Istio 服務網格也提供相應的支援,讓這些微服務能夠彼此通訊和共用資料。
## rancher 安裝 istio
* 使用 rancher cluster tools 安裝 istio,如果要 enable Kiali 需要先裝 Monitoring ,需要使用他的 crd。
## Mutual TLS

* 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 觀察
* 經過加密的流量會有一個鎖頭

* 取消規則後,沒有經過加密的流量就沒有鎖頭

### 使用 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`