Ciliumが実際に塞ぐものを見る
このラボは本物のCilium上で動きます
VMの中に、k3s + Ciliumが実際に起動しています。kube-proxyを無効にしてCiliumがその仕事を代わりに担い(kubeProxyReplacement)、L7ポリシーはEnvoyが実際に強制し、Hubbleがフローを集めます。
CCAコースのほかのラボは、CRDだけがロードされた偽のクラスターで動きます。そこでは、kubectl applyが通るだけで、何もブロックしません。ここではブロックされます。
最初に起動するまで、4–5分かかります。Ciliumをインストールするためです。
目標
アイデンティティとは何かを自分の目で確認し、L3ポリシーとL7ポリシーを順に重ねたうえで、Hubbleで「何がなぜブロックされたのか」を読み取ります。
なぜ重要なのか
Kubernetes標準のNetworkPolicyも、PodとネームスペースのラベルセレクターとipBlockで、通信相手を選びます。podSelectorとnamespaceSelectorは、ingressのfromとegressのtoの両方で使えます。IPだけを直接並べなければならないAPIではありません。実際の強制は、これをサポートするネットワークプラグインが行います。
Ciliumは、このポリシーの意図を実装しつつ、セキュリティ関連のラベルの集合をアイデンティティの番号に対応させます。app=tgtが1つ同じだからといって、同じアイデンティティになるわけではありません。ネームスペースなど、ほかのセキュリティ関連のラベルも比較する必要があります。同じ集合のエンドポイントはアイデンティティを共有するので、PodのIPをポリシーに1つずつ固定する必要がありません。番号そのものを永続的な識別子とするのではなく、どのラベルの集合がその番号に対応するのかを確認してください。
そして、CiliumはHTTPのパスとメソッドまで見ます。「このServiceは/api/readだけを呼び出せる」ことをネットワーク層で強制でき、ブロックされると接続が切れるのではなく、403が返ってきます。アプリケーションの立場からは、はるかに扱いやすくなります。
ステップ
cilium statusの出力を保存してください(保存先:/root/cca/status.txt)。KubeProxyReplacement: Trueが表示され、kube-proxyDaemonSetは存在しない必要があります。meshネームスペースにtgt(app=tgt、nginx)とclient(app=client、curl)を作成し、tgtのアイデンティティ番号と、その番号がどのラベルから導かれたのかを保存してください(保存先:/root/cca/identity.txt)。- CiliumNetworkPolicyを使って(名前:
l3-allow)、app=clientだけがtgtに到達できるようにし、許可された側とそうでない側の両方を試して、結果を保存してください(保存先:/root/cca/l3.txt)。 - ポリシー(名前:
l7-allow)で、/allowedパスだけを許可してください。/secretが403を受け取ることを記録します(記録先:/root/cca/l7.txt)。 - ブロックされるリクエストを1回送ったあと、
hubble observeでフローを観察し、保存してください(保存先:/root/cca/hubble.txt)。FORWARDEDとDROPPEDの両方が表示される必要があります。 - ポリシー(名前:
egress-fqdn)で、tgtが特定の名前でのみ外へ出られるようにしてください。結果を保存します(保存先:/root/cca/dnspolicy.txt)。 hubble observe --type policy-verdictで、なぜブロックされたのかの判定ログを取り出して、保存してください(保存先:/root/cca/verdict.txt)。- レポートを作成してください(保存先:
/root/cca/report.md)。tgt_identity=、l7_denied_code=、policies=の3行と、説明を書きます。
参考
- アイデンティティは、
kubectl get ciliumendpoint -n mesh tgt -o yamlのstatus.identityにあります。cilium identity listでも確認できます。 - Hubbleは、
hubble observe -n mesh --last 30のように使います。先にcilium hubble port-forward &が必要です。 - FQDNポリシーは、CiliumがDNS応答を覗き込めるときにだけ動作します。そのため、
toFQDNsを使うには、DNS自体を先にtoEndpoints+rules.dns.matchPatternで開く必要があります。これを抜かすと、名前がそもそも解決されず、ポリシーが動作しません。 - よくある間違い1: L7ポリシーで、
toPortsなしにrules.httpを書くこと。HTTPルールはポートの中に入ります。 - よくある間違い2: L7がブロックしたのに、接続の失敗を期待すること。CiliumはEnvoyでプロキシして403を返します。
connection refusedではありません。
kube-proxyのないクラスター
cilium statusの出力を保存してください(保存先: /root/cca/status.txt)。KubeProxyReplacement: Trueが表示され、kube-proxy DaemonSetは存在しない必要があります。
cilium statusのKubeProxyReplacementの行を見ます。TrueならServiceの負荷分散をiptablesではなく、eBPFが行っているという意味です。
ポリシーはIPではなく番号で判断する
meshネームスペースにtgt(app=tgt、nginx)とclient(app=client、curl)を作成し、tgtのアイデンティティ番号と、その番号がどのラベルから導かれたのかを保存してください(保存先: /root/cca/identity.txt)。
kubectl get ciliumendpoint -n mesh tgt -o yamlのstatus.identityを見ます。その番号がどのラベルの組み合わせから導かれたのかも、あわせて記録してください。
L3: 誰が到達できるのか
CiliumNetworkPolicyを使って(名前: l3-allow)、app=clientだけがtgtに到達できるようにし、許可された側とそうでない側の両方を試して、結果を保存してください(保存先: /root/cca/l3.txt)。
endpointSelectorが保護されるPodを、ingress.fromEndpointsが許可する送信元を選びます。KubernetesのNetworkPolicyと、方向のルールは同じです。
L7: どのパスまで許可するのか
ポリシー(名前: l7-allow)で、/allowedパスだけを許可してください。/secretが403を受け取ることを記録します(記録先: /root/cca/l7.txt)。
HTTPルールはtoPorts[].rules.httpの中に書きます。ブロックされると、接続が切れるのではなく、403が返ってきます。
フローを自分の目で見る
ブロックされるリクエストを1回送ったあと、hubble observeでフローを観察し、保存してください(保存先: /root/cca/hubble.txt)。FORWARDEDとDROPPEDの両方が表示される必要があります。
cilium hubble port-forward &を先に起動してから、hubble observe -n mesh --last 30を使います。ブロックされるリクエストを送ったあとに観察しないと、DROPPEDが見えません。
名前で出ていく先を選ぶ
ポリシー(名前: egress-fqdn)で、tgtが特定の名前でのみ外へ出られるようにしてください。結果を保存します(保存先: /root/cca/dnspolicy.txt)。
toFQDNsは、CiliumがDNS応答を覗き込めて初めて動作します。そのため、DNS自体をtoEndpoints + rules.dns.matchPatternで先に開く必要があります。
なぜブロックされたのかを尋ねる
hubble observe --type policy-verdictで、なぜブロックされたのかの判定ログを取り出して、保存してください(保存先: /root/cca/verdict.txt)。
hubble observe --type policy-verdictは、各フローがどのポリシーに引っかかり、どう判定されたのかを示します。
何を学んだのか
レポートを作成してください(保存先: /root/cca/report.md)。tgt_identity=、l7_denied_code=、policies=の3行と、説明を書きます。
tgt_identity=、l7_denied_code=、policies=の3行とあわせて、アイデンティティがIPより優れている理由と、L7の拒否が403である理由を説明してください。