CiliumでEgress通信を監視、制限してみる
Ciliumを使う理由の1つとして、Egress通信のL7での制御がある。
Kubernetes標準のNetworkPolicyではL4での制御しか行えず、L7をサポートしていない。
企業のセキュリティ要件では「Egress通信をFWやProxyなどで制御せよ」みたいなことが割とあり、自分の所属する企業でも満たさなければいけない要件だ。
というわけで、以下の2つを順々に見ていきたい。
- Ciliumを使って発生しているEgress通信を確認する
- CiliumNetworkPolicyを使って、Egress通信をL7 Policyで制御してみる
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が上がっていた。
v1.15の不具合らしい。v1.15はまだRCなので、Stableなv1.14.5にしてみよう。
v1.14.5に変更して各Podが再作成されたのを確認し、もう一度アクセスしてみる。
見られるようになった!
namespaceごとに見られるらしい。
全namespaceを一覧することはHubble UIからはできないようだ。
Egress通信が発生しているだろうCloudWatch Agentをみてみる。
たぶんCloudWatch Logs宛の通信が発生しているように思えるが、L7 infoのところが空なのでよくわらかない。
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だと宛先が見えないらしい。
そんなことある?
こちらを試してみる
1. Ciliumを使って発生しているEgress通信を確認する(L7編)
HTTP(s)のリクエストについて、L7の情報が見えない。
以下の公式ドキュメントを見ると、L7の情報はCiliumNetworkPolicyを設定してCiliumのProxyを追加させることで付与されるらしい。
以下にDNSベースのCiliumNetworkPolicyを使った例があるので、こちらで検証してみる。
まずはサンプルにある通りの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: '*'
2. CiliumNetworkPolicyを使って、Egress通信をL7 Policyで制御してみる
いままで見てきた通りなので、改めて書くこともなかった。
個人的に公式ドキュメントから読み取りづらかった部分としては以下ぐらい。
ORなのかANDなのか
-
egress
,ingress
内のルールはor条件になるので1つでもヒットすれば許可される -
egressDeny
,ingressDeny
内のルールもor条件なので1つでもヒットすればDenyになる -
egressDeny
/ingressDeny
とegress
/ingress
の両方にヒットする場合はDenyが優先されるためDenyになる
toEntitiesについて
-
world
は外部へのリクエスト。world
の逆はcluster
(たぶん)
それ以外はGithubのexmaples/policies内にあるサンプルを見ればよし。よいCiliumNetworkPolicyライフを!