Closed5

CiliumでEgress通信を監視、制限してみる

dekimasoondekimasoon

Ciliumを使う理由の1つとして、Egress通信のL7での制御がある。
Kubernetes標準のNetworkPolicyではL4での制御しか行えず、L7をサポートしていない。

https://cilium.io/use-cases/network-policy/

企業のセキュリティ要件では「Egress通信をFWやProxyなどで制御せよ」みたいなことが割とあり、自分の所属する企業でも満たさなければいけない要件だ。

というわけで、以下の2つを順々に見ていきたい。

  1. Ciliumを使って発生しているEgress通信を確認する
  2. CiliumNetworkPolicyを使って、Egress通信をL7 Policyで制御してみる
dekimasoondekimasoon

1. Ciliumを使って発生しているEgress通信を確認する

CiliumにはHubbleと呼ばれるネットワーク監視用プラットフォームが内蔵されている。
専用のWebUIも用意されているらしい。

まずは公式ドキュメントに従ってHubbleによる観測を有効化する。

なお、Hubble自体はデフォルトで有効化されているのだけど、Hubble RelayというやつとHubble UIはデフォルトで無効化されていて、それを有効にする必要があるらしい。

Hubble自体はDeamonSetで各ノードで動いているciliumポッド内にいて観測情報をgRPCで取得できるようにしてくれている。それに対してHubble Relayは、各ノードのHubble自体を呼び出して、クラスター全体の状態を観測するためのリッチなAPIを提供してくれるらしい。また、Hubble UIはその名の通り、Hubble RelayをGUIから呼び出して見やすくしてくれるものらしい。

Helmであれば、以下のようにフラグを立てる。
なお諸事情によってPulumi(Terraformみたいなやつ)を使っているので、しばしばPulumiのコードが登場するかもしれない。

helm upgrade cilium cilium/cilium --version 1.14.5 \
   --namespace kube-system \
   --reuse-values \
   --set hubble.relay.enabled=true \
   --set hubble.ui.enabled=true

適応後に確認するとそれぞれのDeploymentができている。

✗ kubectl get deploy --all-namespaces | grep -e hubble -e AGE
NAMESPACE           NAME                                                 READY   UP-TO-DATE   AVAILABLE   AGE
kube-system         hubble-relay                                         1/1     1            1           26m
kube-system         hubble-ui                                            1/1     1            1           78s

hubble-uiに接続するとエラーが出ている。

エラーになってる/api/ui.UI/GetControlStreamへのリクエストには、gRPC requires HTTP/2というレスポンスが400で返ってきている。

エラーでCiliumのリポジトリをぐぐった?Issueが上がっていた。

https://github.com/cilium/cilium/issues/29563

v1.15の不具合らしい。v1.15はまだRCなので、Stableなv1.14.5にしてみよう。
v1.14.5に変更して各Podが再作成されたのを確認し、もう一度アクセスしてみる。

見られるようになった!

namespaceごとに見られるらしい。
全namespaceを一覧することはHubble UIからはできないようだ。

Egress通信が発生しているだろうCloudWatch Agentをみてみる。

たぶんCloudWatch Logs宛の通信が発生しているように思えるが、L7 infoのところが空なのでよくわらかない。

dekimasoondekimasoon

1. Ciliumを使って発生しているEgress通信を確認する(CLI編)

HubbleはHubble UIだけでなくCLIによる観測手段も用意されているので、こちらを試してみる。
先程L7 infoが空だったEgressについて、もう少し詳しいことがわかるといいのだけれど。

専用のhubbleコマンドが用意されているらしいので、Hubble Clientをインストールしてhubbleコマンドを叩けるようにする。参照した公式ドキュメントこちら。

そのままだとエラーになったのでコマンドをちょっとだけ微調整。

HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
HUBBLE_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then HUBBLE_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/hubble/releases/download/$HUBBLE_VERSION/hubble-linux-$HUBBLE_ARCH.tar.gz{,.sha256sum}
sha256sum --check hubble-linux-${HUBBLE_ARCH}.tar.gz.sha256sum
tar xzvfC hubble-linux-${HUBBLE_ARCH}.tar.gz /usr/local/bin
rm hubble-linux-${HUBBLE_ARCH}.tar.gz{,.sha256sum}

fort-foward用のコマンドがciliumコマンドに用意されているらしいので、Cilium Clientも合わせてインストールした。

fort-fowardして、hubbleのstatusを確認してみる。

✗ cilium hubble port-forward&
[1] 99685

✗ hubble status
Healthcheck (via localhost:4245): Ok
Current/Max Flows: 8,190/8,190 (100.00%)
Flows/s: 81.68
Connected Nodes: 2/2

大丈夫らしい。
hubble observeというコマンドを使うらしい。

✗ hubble observe
Jan 12 03:55:49.433: kube-system/hubble-ui-6f48889749-s5vkl:47072 (ID:29925) -> kube-system/hubble-relay-d478c79c8-d7znx:4245 (ID:3521) to-stack FORWARDED (TCP Flags: ACK)
Jan 12 03:55:49.435: kube-system/hubble-ui-6f48889749-s5vkl:51494 (ID:29925) <- kube-system/hubble-relay-d478c79c8-d7znx:4245 (ID:3521) to-endpoint FORWARDED (TCP Flags: ACK)
Jan 12 03:55:49.435: kube-system/hubble-ui-6f48889749-s5vkl:55650 (ID:29925) <- kube-system/hubble-relay-d478c79c8-d7znx:4245 (ID:3521) to-endpoint FORWARDED (TCP Flags: ACK)
Jan 12 03:55:49.435: kube-system/hubble-ui-6f48889749-s5vkl:33424 (ID:29925) <- kube-system/hubble-relay-d478c79c8-d7znx:4245 (ID:3521) to-endpoint FORWARDED (TCP Flags: ACK)

こんな感じで出てくる。
namespaceを指定してみる。

✗ hubble observe --namespace amazon-cloudwatch
Jan 12 03:55:40.504: amazon-cloudwatch/cloudwatch-agent-bh8zs:48770 (ID:28318) <- 10.0.49.242:443 (ID:16777218) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Jan 12 03:55:40.547: amazon-cloudwatch/cloudwatch-agent-bh8zs:48770 (ID:28318) -> 10.0.49.242:443 (ID:16777218) to-stack FORWARDED (TCP Flags: ACK)
Jan 12 03:55:45.235: amazon-cloudwatch/cloudwatch-agent-bh8zs:48198 (ID:28318) -> 10.0.46.118:443 (ID:16777217) to-stack FORWARDED (TCP Flags: ACK)
Jan 12 03:55:45.237: amazon-cloudwatch/cloudwatch-agent-bh8zs:48198 (ID:28318) <- 10.0.46.118:443 (ID:16777217) to-endpoint FORWARDED (TCP Flags: ACK)

--podオプションで、pod名を前方一致でfilterできるらしい。なおnamespaceも付与する必要あり。

✗ hubble observe --pod amazon-cloudwatch/cloudwatch-agent
Jan 12 03:57:37.681: amazon-cloudwatch/cloudwatch-agent-h4xqt:55002 (ID:28318) -> 10.0.46.118:443 (ID:16777218) to-stack FORWARDED (TCP Flags: ACK, PSH)
Jan 12 03:57:37.690: amazon-cloudwatch/cloudwatch-agent-h4xqt:55002 (ID:28318) <- 10.0.46.118:443 (ID:16777218) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Jan 12 03:57:40.274: amazon-cloudwatch/cloudwatch-agent-h4xqt:43154 (ID:28318) <- 18.181.204.204:443 (world) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Jan 12 03:57:40.274: amazon-cloudwatch/cloudwatch-agent-h4xqt:43154 (ID:28318) -> 18.181.204.204:443 (world) to-stack FORWARDED (TCP Flags: ACK, PSH)

しかしこちらにもL7に関する情報は無さそうだ。
TLSだからなのだろうか。以下のIssueを見る限りHTTPSだと宛先が見えないらしい。

https://github.com/cilium/cilium/issues/28513


そんなことある?
こちらを試してみる
https://docs.cilium.io/en/latest/security/dns/

dekimasoondekimasoon

1. Ciliumを使って発生しているEgress通信を確認する(L7編)

HTTP(s)のリクエストについて、L7の情報が見えない。
以下の公式ドキュメントを見ると、L7の情報はCiliumNetworkPolicyを設定してCiliumのProxyを追加させることで付与されるらしい。

https://docs.cilium.io/en/latest/observability/visibility/

以下にDNSベースのCiliumNetworkPolicyを使った例があるので、こちらで検証してみる。

https://docs.cilium.io/en/latest/security/dns/

まずはサンプルにある通りのCiliumNetworkPolicyで試す。

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  namespace: mediabot
  name: fqdn
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
    - toFQDNs:
        - matchName: api.github.com
    - toEndpoints:
        - matchLabels:
            'k8s:io.kubernetes.pod.namespace': kube-system
            'k8s:k8s-app': kube-dns
      toPorts:
        - ports:
            - port: '53'
              protocol: ANY
          rules:
            dns:
              - matchPattern: '*'

このPolicyが適応されているPodでhttps://api.github.comにEgress通信させる。
Hubbleでモニタリングした結果は以下。一部を抜粋している。ドメイン名が出ている。

✗ kubectl exec mediabot -- curl -I -s https://api.github.com
✗ hubble observe --pod mediabot/ -f
Jan 12 14:31:25.850: mediabot/mediabot:54869 (ID:4306) <- kube-system/coredns-5488df4cc7-zpndw:53 (ID:8540) to-endpoint FORWARDED (UDP)
Jan 12 14:31:25.850: mediabot/mediabot:54869 (ID:4306) <- kube-system/coredns-5488df4cc7-zpndw:53 (ID:8540) dns-response proxy FORWARDED (DNS Answer "20.27.177.116" TTL: 25 (Proxy api.github.com. A))
Jan 12 14:31:25.850: mediabot/mediabot:33450 (ID:4306) -> api.github.com:443 (ID:16777220) policy-verdict:L3-Only EGRESS ALLOWED (TCP Flags: SYN)
Jan 12 14:31:25.850: mediabot/mediabot:33450 (ID:4306) -> api.github.com:443 (ID:16777220) to-stack FORWARDED (TCP Flags: SYN)

次にPolicyを削除して同じようにEgress通信させてみる。
今度はIPしか出ていない。

Jan 12 14:39:55.448: mediabot/mediabot:45601 (ID:4306) <- kube-system/coredns-5488df4cc7-zpndw:53 (ID:8540) to-stack FORWARDED (UDP)
Jan 12 14:39:55.449: mediabot/mediabot:45601 (ID:4306) <- kube-system/coredns-5488df4cc7-zpndw:53 (ID:8540) to-endpoint FORWARDED (UDP)
Jan 12 14:39:55.449: mediabot/mediabot:58246 (ID:4306) -> 20.27.177.116:443 (world) to-stack FORWARDED (TCP Flags: SYN)
Jan 12 14:39:55.454: mediabot/mediabot:58246 (ID:4306) <- 20.27.177.116:443 (world) to-endpoint FORWARDED (TCP Flags: SYN, ACK)

最後に元のCiliumNetworkPolicyのDNS部分をL3ルールに変えてみる。
具体的にはtoEntities: ["cluster"]を使って以下のように変えてみる。

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  namespace: mediabot
  name: fqdn
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
    - toFQDNs:
        - matchName: api.github.com
    - toEntities:
        - cluster

この場合もIPしか見えない。

Jan 12 14:46:41.778: mediabot/mediabot:52704 (ID:4306) <- kube-system/coredns-5488df4cc7-c6m97:53 (ID:8540) to-stack FORWARDED (UDP)
Jan 12 14:46:41.779: mediabot/mediabot:52704 (ID:4306) <- kube-system/coredns-5488df4cc7-c6m97:53 (ID:8540) to-endpoint FORWARDED (UDP)
Jan 12 14:46:41.779: mediabot/mediabot:35802 (ID:4306) -> 20.27.177.116:443 (ID:16777221) policy-verdict:L3-Only EGRESS ALLOWED (TCP Flags: SYN)
Jan 12 14:46:41.779: mediabot/mediabot:35802 (ID:4306) -> 20.27.177.116:443 (ID:16777221) to-stack FORWARDED (TCP Flags: SYN)

というわけでDNSとTCPと両方をL7のルールに通さないとL7情報は得られないということみたいだ。
全てのリクエストについてL7の可視化をしたい場合は、以下のCiliumNetworkPolicyがミニマムかもしれない。

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: l7-visibility
spec:
  endpointSelector: {}
  egress:
    - toFQDNs:
        - matchPattern: '*'
    - toPorts:
        - ports:
            - port: '53'
              protocol: ANY
          rules:
            dns:
              - matchPattern: '*'
dekimasoondekimasoon

2. CiliumNetworkPolicyを使って、Egress通信をL7 Policyで制御してみる

いままで見てきた通りなので、改めて書くこともなかった。
個人的に公式ドキュメントから読み取りづらかった部分としては以下ぐらい。

ORなのかANDなのか

  • egress, ingress内のルールはor条件になるので1つでもヒットすれば許可される
  • egressDeny, ingressDeny内のルールもor条件なので1つでもヒットすればDenyになる
  • egressDeny / ingressDenyegress / ingressの両方にヒットする場合はDenyが優先されるためDenyになる

toEntitiesについて

  • worldは外部へのリクエスト。worldの逆はcluster(たぶん)

それ以外はGithubのexmaples/policies内にあるサンプルを見ればよし。よいCiliumNetworkPolicyライフを!

https://github.com/cilium/cilium/tree/main/examples/policies

このスクラップは2024/01/15にクローズされました