Liveness Probeのいつもと違う使い方

hnts
9 min readDec 21, 2021

--

この記事は、Kubernetes Advent Calendar 2021の22日目となります。

Liveness Probeは、現在デプロイされているコンテナが、処理を継続することが不可能になった際、再起動で復旧する見込みがあるケースで用いる機能です。

今回は、この本来の目的とは違う用途でLiveness Probeを使い、Kubernetesのクラスタが存在するネットワークを探索してみたいと思います。

Liveanss Probeの問題点

Kubernetesのリポジトリには、Liveness Probeを用いるとPodがアクセス不可能なホストにアクセスできてしまうというIssueが存在します。

上記のIssueは、Kubernetes Security Audit Reportで報告されたものであり、他にもたくさんのIssueが報告されています。

Issueでは、以下のようなマニフェストを大量にデプロイすることで、Outboundへのトラフィックが制限がされている場合でも、クラスタと同じネットワーク内のホストを列挙できてしまう可能性があるということが指摘されています。

Liveness Probeが成功すれば hostに設定したIPアドレスとポートへの通信が可能であり、失敗すれば対象のhostが存在しないか、対象のポートに疎通できないかという判断をすることができます。

検証

今回は、以下の2つの検証を行いたいと思います。

  1. Outboundトラフィックの制限をLiveness Probeで回避する
  2. Liveness Probeでホストを列挙する

検証では、Kubernetes v1.21.7を使用します。

$ kubectl version --short
Client Version: v1.21.0
Server Version: v1.21.7

Outboundトラフィックの制限をLiveness Probeで回避する

まずはtcpのOutboundトラフィックの制限をするためにIstioを使います。IstioのoutboundTrafficPolicyをREGISTRY_ONLYにすることで、tcpのOutboundのトラフィックを制限することが可能です。

$ kubectl get istiooperator installed-state -n istio-system -o jsonpath='{.spec.meshConfig.outboundTrafficPolicy.mode}'REGISTRY_ONLY

REGISTRY_ONLYが指定されているクラスタでistio-proxyをinjectionされているPodは、Service Entryで指定しているホスト以外にアクセスすることができません。

以下は、istio-proxyがinjectionされるNamespace内のPodが外部にアクセスできない様子です。

$ kubectl apply -f ns.yaml
namespace/test created
$ kubectl run curl -it --restart=Never --image=radial/busyboxplus:curl -n demo-app -- /bin/sh -c "sleep 10 && curl -I google.com"
If you don't see a command prompt, try pressing enter.
HTTP/1.1 502 Bad Gateway
date: Mon, 20 Dec 2021 09:59:39 GMT
server: envoy
transfer-encoding: chunked

Outboundトラフィックの制限をIstioで行ったことにより、PodからはService Entryで指定したホスト先へしかアクセスすることができなくなりました。

しかし、Liveness ProbeのhttpGetを使えば任意のホストにアクセスすることができます。

$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: liveness-http
namespace: test
annotations:
sidecar.istio.io/rewriteAppHTTPProbers: "false"
spec:
containers:
- name: liveness
image: k8s.gcr.io/liveness
args:
- /server
ports:
livenessProbe:
httpGet:
host: 192.168.11.1
path: /
port: 80
EOF
pod/liveness-http created
$ kubectl describe pod liveness-http -n test | grep "Liveness probe failed"

rewriteAppHTTPProbersのAnnotationを付与しなければ意図した挙動にはなりませんが、一応Outboundの制限を回避することができました。

Liveness Probeによるホストの列挙

自宅クラスタと同じネットワーク内にLiveness Probeの通信先となるホストを用意しておきます。

今回はsshポートが開いたUbuntu Serverを2つほど立てておきます。

$ nc -v -w 1 192.168.11.4 22
Connection to 192.168.11.4 22 port [tcp/ssh] succeeded!
SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.2
$ nc -v -w 1 192.168.11.5 22
Connection to 192.168.11.5 22 port [tcp/ssh] succeeded!
SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.2

ホストを列挙するためには、大量のPodをデプロイした後にLiveness Probeの結果を確認しなければいけないため、以下のように簡単なスクリプトを書きました。

先ほどはhttpGetを使いましたが、ここではtcpSocketを使います。

実行すると、きちんとクラスタと同一ネットワーク内でsshポートが開いているホストを列挙することに成功しています。

bash enumerate-host-network.sh
[+] Create pods for enumerating host network...
[!] 192.168.11.4 exists and the ssh port is opened
[!] 192.168.11.5 exists and the ssh port is opened
[+] Clean up...

PodのhostNetworkを有効にした場合、arp-scan等を使うことで簡単にホストを列挙できますが、今回はhostNetworkを有効にせず、ホストを列挙することができました。

Probe処理のコード

tcpのOutboundが制限されている環境でもLiveness Probeを使えばPodからアクセスできないホストにリクエストすることができました。

kubeletのコードを見てみると, httpGetやtcpSocketはkubelet自体が実行していることが分かります。

またLiveness Probeのexecを使う場合は、コンテナの中で実行しているので、今回だとIstioにOutboundのトラフィックを制限されそうでした。

最後に

簡単ですが、「Liveness Probeを使ってネットワークを探索する」という、本来の目的とは違う使い方をしてみました。

攻撃のシナリオとしては、大量にPodをデプロイしてアクセスできるホストを列挙するわけですが、Podのデプロイ権限が必要だったり、大量にPodがデプロイされるのでクラスタ管理者は気づくかもしれません。

他にもKubernetes Security Audit Reportで報告されているので、また違うIssueを覗いてみようかなと思います。

それでは、よいお年を!

--

--

hnts
hnts

No responses yet