Skip to content

人家都在抓神奇寶貝,而我在抓 Kubernetes 封包

Wireshark: Packet Don't Lie.

文章 永續性軟體工程: 遠端抓封包實錄 提到如何遠端抓單體 VM 封包,那同樣的招數可不可以去抓 Azure Kubernetes Service 特定 Pod 裡面的封包? 答案是可以,搭配 ksniff 來做就好

來抓 Kubernetes 封包

架構圖

  1. 因為我不能直接從家裡連線到 Azure Kuberentes Service,所有操作都需要透過 Bastion VM 進行操作連線,所以需要先用 SSH 連線到 Bastion VM 才能做事
  2. kubectl 一定要能對 Private AKS 操作,不然 ksniff 或程式你都無法部署下去
  3. 整張圖我沒提的部分就是 Private AKS 和 VM 的 UDR 設定,但這個太 Azure Networking 先跳過
  4. 一如既往,我是強烈建議人人都要有一個 debug container 好去做一些觀落陰的事情,詳見 當遇到 Distroless Container 除錯要什麼沒什麼該怎麼辦? 你的好朋友 kubectl debug,這邊不贅述

0. 安裝 ksniff

請直接參考 eldadru/ksniff 文件,另外它的前置作業是要裝 krew,務必要把 krew PATH 設定好

1. 部署 httpbin-re 服務

# 確定 kubectl 運作正常且可以連線到 Private AKS
$ kubectl cluster-info
Kubernetes control plane is running at https://dns-aoai.private.eastus2.azmk8s.io:443
CoreDNS is running at https://dns-aoai.private.eastus2.azmk8s.io:443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://dns-aoai.private.eastus2.azmk8s.io:443/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy

# 跑一個 http server 服務起來
$ kubectl apply -f https://raw.githubusercontent.com/pichuang/httpbin-re/master/k8s/httpbin-re.yaml
deployment.apps/httpbin-re created
service/httpbin-re created

# 確認服務有揭露出來
$ kubectl get svc httpbin-rek
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
httpbin-re   ClusterIP   10.0.133.240   <none>        8080/TCP   7h26m

主要是要部署一個 HTTP Server,待會要透過 Ksniff 去觀察這個服務的狀況,如果你有其他服務也可以自行替換,這邊就是 Demo 方便

2. 部署陪測用 Pod

# 部署陪測用 Pod,用順手的就好,或者是你可以考慮用 nicolaka/netshoot
$ kubectl apply -f https://raw.githubusercontent.com/pichuang/debug-container/master/debug-container.yaml
pod/debug-container created

# 測試可以跟 httpbin-re 連線,同 namespace,不同 Pod
# kubectl exec --it <debug pod> -- curl http://<svc name>/path
$ kubectl exec --it debug-container -- curl http://httpbin-re:8080 | head -n 5
$ kubectl exec --it debug-container -- curl http://httpbin-re.default:8080 | head -n 5
$ kubectl exec --it debug-container -- curl http://httpbin-re.default.svc:8080 | head -n 5
<!DOCTYPE html>
<html lang="en">
...omit...

# 測試可以跟 metrics-server 連線,不同 namespace,不同 Pod
# kubectl exec --it <debug pod> -- curl --insecure https://<svc name>.<namespace>:<svc port>/path
$ kubectl exec --it debug-container -- curl --insecure https://metrics-server.kube-system:443/metrics | head -n 5
$ kubectl exec --it debug-container -- curl --insecure https://metrics-server.kube-system.svc:443/metrics | head -n 5
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "forbidden: User \"system:anonymous\" cannot get path \"/metrics\"",
  "reason": "Forbidden",
  "details": {},
  "code": 403
}%

關於這個以 Pod 為出發點,服務之間的連線議題,以前有寫過為什麼我佈署的 Kubernetes 服務不會動!? 個人除錯思路分享 一文,裡面有比較仔細的說明,這邊就不贅述了

3. 遠端抓 httpbin-re Pod 的封包

# 列舉出 httpbin-re Pod 的名稱
$ kubectl get pod -n default -owide
NAME                          READY   STATUS    RESTARTS   AGE     IP            NODE                                NOMINATED NODE   READINESS GATES
debug-container               1/1     Running   0          15m     `10.0.129.18`   aks-agentpool-14864487-vmss000000   <none>           <none>
httpbin-re-866499b564-blc95   1/1     Running   0          7h38m   10.0.129.6    aks-agentpool-14864487-vmss000000   <none>           <none>
httpbin-re-866499b564-xl726   1/1     Running   0          7h38m   `10.0.129.9`    aks-agentpool-14864487-vmss000000   <none>           <none>
ksniff-grlzn                  1/1     Running   0          82s     10.0.129.33   aks-agentpool-14864487-vmss000000   <none>           <none>
ksniff-n9ps6                  1/1     Running   0          36m     10.0.129.29   aks-agentpool-14864487-vmss000000   <none>           <none>
# 透過 ksniff 來抓封包
# ssh <Target username>@<Target IP> -p <Target Port> -i <SSH Private Key if have> "/bin/bash --login -c 'kubectl sniff -n <namespace> <pod name> -p -o -'" | /mnt/c/Program\ Files/Wireshark/Wireshark.exe -k -i -
ssh [email protected] -p 5566 -i ~/.ssh/wolala-rsa "/bin/bash --login -c 'kubectl sniff -n default httpbin-re-866499b564-xl726 -p -o -'" | /mnt/c/Program\ Files/Wireshark/Wireshark.exe -k -i -

這邊要注意的是 /bin/bash --login -c,因為 SSH 登入的時候吃的 $PATH 預設是不會去吃 ~/.bashrc,可以用 env 驗證看看,所以需要先用 --login 後才能正確咬到 $PATH 裡面的 krew 路徑,其餘就是 pipeline 和 I/O 的串流處理了

此外,原先應該要填 Ethernet Header (12 bytes) 的欄位,會變成 Linux cooked capture v2 (SLL_v2),按照 Linux cooked-mode capture (SLL) 的說明,這是一個偽造協議,對分析 Kubernetes 上的服務比較沒什麼影響,因為主要都是看 L3 / L4 層級居多,包含 Pod IP / Pod Port / Service IP / Service Port / Node Port 等,同場加映一下, HWCHIU 學習筆記 - Kubernetes 之封包去哪兒

4. 清除 ksniff Pod

$ kubectl get pod -l app=ksniff -A
NAME           READY   STATUS    RESTARTS   AGE
ksniff-g4bjz   1/1     Running   0          11m
ksniff-j7gnc   1/1     Running   0          16m
ksniff-kxfl5   1/1     Running   0          9m54s
ksniff-lb4xg   1/1     Running   0          6m29s
ksniff-n9ps6   1/1     Running   0          68m
ksniff-sph6l   1/1     Running   0          9m17s
ksniff-v2vd5   1/1     Running   0          14m
ksniff-vl627   1/1     Running   0          16m

$ kubectl delete pod -l app=ksniff -A
pod "ksniff-g4bjz" deleted
pod "ksniff-j7gnc" deleted
pod "ksniff-kxfl5" deleted
pod "ksniff-lb4xg" deleted
pod "ksniff-n9ps6" deleted
pod "ksniff-sph6l" deleted
pod "ksniff-v2vd5" deleted
pod "ksniff-vl627" deleted

退出的時候看起來是不會順便把 ksniff 的 Pod 移除掉,記得要手動移除,不然會有資源浪費的問題

後話

ksniff 沒有參數可以讓我去掐封包大小,沒意外的話,還需要再跳板機多一段 tcpdump pipeline 處理,但這樣就會收到一堆有的沒的封包,還沒想到怎麼用

Q&A

Q1: 如果我是 Red Hat OpenShift Container Platform 或 Azure Red Hat OpenShift 的使用者,但權限鎖超嚴的怎麼辦?

這個只能說 Red Hat 一如既往地已經先幫妳整好到 oc 去了,你不用特別裝 ksniff,權限就是照你拿到的 kubeconfig 為主

$ oc version
Client Version: 4.12.36
Kustomize Version: v4.5.7
Kubernetes Version: v1.27.3

$ oc sniff
Usage:
  sniff pod [-n namespace] [-c container] [-f filter] [-o output-file] [-l local-tcpdump-path] [-r remote-tcpdump-path] [flags]

Examples:
kubectl sniff hello-minikube-7c77b68cff-qbvsd -c hello-minikube

Flags:
  -c, --container string                container (optional)
  -x, --context string                  kubectl context to work on (optional)
  -f, --filter string                   tcpdump filter (optional)
  -h, --help                            help for sniff
      --image string                    the privileged container image (optional)
  -i, --interface string                pod interface to packet capture (optional) (default "any")
  -l, --local-tcpdump-path string       local static tcpdump binary path (optional)
  -n, --namespace string                namespace (optional)
  -o, --output-file string              output file path, tcpdump output will be redirect to this file instead of wireshark (optional) ('-' stdout)
      --pod-creation-timeout duration   the length of time to wait for privileged pod to be created (e.g. 20s, 2m, 1h). A value of zero means the creation never times out. (default 1m0s)
  -p, --privileged                      if specified, ksniff will deploy another pod that have privileges to attach target pod network namespace
  -r, --remote-tcpdump-path string      remote static tcpdump binary path (optional) (default "/tmp/static-tcpdump")
      --socket string                   the container runtime socket path (optional)
      --tcpdump-image string            the tcpdump container image (optional)
  -v, --verbose                         if specified, ksniff output will include debug information (optional)

References