皆さん、こんにちは!インフラやクラウドのセキュリティの世界へようこそ。
Kubernetes(K8s)を使ったコンテナ運用、毎日本当にお疲れ様です。次々と立ち上がるポッド(Pod)や名前空間(Namespace)の管理に、嬉しい悲鳴を上げている方も多いのではないでしょうか。
さて、今回はKubernetesのネットワークセキュリティの要である「Calico」を取り上げます。
「名前空間を分けたから、チームごとの安全はバッチリだよね?」と思っていませんか?実はそこ、攻撃者が好んで狙う大きな盲点なんです。
今回は、身近な「一軒家とマンションの防犯」にたとえながら、Calicoの GlobalNetworkPolicy を使ってクラスタ全体を鉄壁に守る方法を、優しく紐解いていきましょう!
—
1. なぜ「名前空間(Namespace)」だけでは不十分なのか?
Kubernetesの名前空間って、開発チームやステージング環境(本番一歩手前)ごとにリソースをキレイに整理できて便利ですよね。会社組織で言えば、フロアごとに部署がパーテーションで区切られているような状態です。
ここで、よくある勘違いが生まれます。
「同じマンション(クラスタ)内とはいえ、部屋(名前空間)の鍵を閉めておけば、隣の部屋から侵入されることはないはずだ」――本当にそうでしょうか?
現実の世界を想像してみてください。
オートロックのマンションでも、エントランスを突破されて廊下に出てしまえば、各部屋の玄関ドアの鍵(デフォルトのKubernetesネットワーク)が空きっぱなしだったらどうなるでしょう? 隣の部屋からリビングへ、自由に出入りできてしまいますよね。
Kubernetesのデフォルトの状態は、まさにこの「廊下に出たらどの部屋のドアも開きっぱなし」の状態です。
もし、フロントエンドを担当する公開用のポッドがハッカーに侵入されたらどうなるでしょうか? そのままデータベースが入っているバックエンドの名前空間へスルスルと侵入され、顧客データをごっそり盗られてしまう……なんてことが、実際に起こり得るのです。
—
2. Calicoの「GlobalNetworkPolicy」で家全体のセキュリティを底上げする
ここで登場するのが、ネットワークセキュリティのプロフェッショナルである「Calico」です。そして、その中でも最強の防犯装置が GlobalNetworkPolicy(グローバルネットワークポリシー)になります。
通常の NetworkPolicy は「特定の名前空間の中だけ」にしか効きませんが、GlobalNetworkPolicy は「クラスタ全体の玄関口」を一括してコントロールできます。
ここで私たちが目指すべき防犯の基本方針は、セキュリティの黄金律である「デフォルト拒否(Zero Trust / ゼロトラスト)」です。
「すべてお断り」から始める理由
家の防犯で考えるなら、「すべての訪問者をまずインターホン越しにお断りし、名乗ってくれた信頼できる人だけに通称パスを渡す」というやり方です。
Calicoを使えば、クラスタ全体に対して「基本はどの名前空間同士の通信もすべて禁止!」というルールをたった一つで適用できます。その上で、「この部署とこの部署のやり取りだけは特別に許可する」という最小限の通路(最小権限の原則)を作っていくのです。
それでは、実際にその設定を見てみましょう!
—
3. 実践!クラスタ全体を「デフォルト拒否」にする設定
まずは、クラスタ全体に「原則として名前空間間の通信はすべて禁止!」という強力な網をかけます。
以下のYAMLファイルを global-deny-all.yaml という名前で作成してみてください。
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: default-deny-inter-namespace
spec:
# すべてのポッド(クラスタ内全体)を対象にする
selector: "all()"
# 受信(Ingress)の通信を制御する
ingress:
- action: Deny
protocol: TCP
source:
# 自分以外の名前空間からの通信をすべて対象外(ブロック)にする
# ※ここではあえて全拒否のベースを作るため、細かい指定を省いています
# 送信(Egress)の通信を制御する場合も同様に記述できますが、
# まずは入ってくる通信(Ingress)の遮断から確実に押さえていきましょう
types:
- Ingress
……とおっと、上記のままだとDNSの引き方や外部通信まで止まってしまう「厳しすぎる設定」になってしまいます。現場のインフラ担当者が最初にハマる罠がこれです。
実務で安全に「デフォルト拒否」を導入する際は、「DNSの名前解決(Port 53)」や「同じ名前空間内の通信」をちゃんと例外として通してあげる配慮が必要です。
もう少し実務に寄せた、安全な「デフォルト拒否」のサンプルを見てみましょう。
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: cluster-default-deny-and-allow-essential
spec:
tier: default
order: 1000 # 優先順位(数字が大きいほど後に処理される=一番最後に適用される網)
selector: "all()"
types:
- Ingress
ingress:
# 1. 同じ名前空間内のポッド同士の通信は許可する
- action: Allow
source:
namespaceSelector: sa.name == 'self' # (概念的な表現です。実際はSelectorで自名前空間を指定)
# 2. クラスタ内のDNS(CoreDNSなど)への問い合わせは絶対に止めない!
# (ここを止めると名前解決ができなくなり、アプリが全滅します)
- action: Allow
protocol: UDP
destination:
ports:
- 53
# 3. 上記以外の名前空間を跨ぐ通信はすべて容赦なくブロック!
- action: Deny
現場のエンジニアなら「DNSを止めるとシステムが即死する」という恐怖を一度は味わったことがあるはずです。防犯を厳しくするあまり、自分たちの合鍵まで捨ててしまわないよう注意しましょう!
—
4. 特定の名前空間間だけ「最小限の通路」を開ける
「デフォルト拒否」を設定したら、次は必要な通信だけを許可する「最小権限の設計」です。
例えば、frontend という名前空間にあるWebアプリから、backend という名前空間にあるデータベース(API)へだけは、通信を許可したいとします。他の名前空間からのアクセスはすべてシャットアウトしたままにします。
それを実現するCalicoのポリシーがこちらです。
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: allow-frontend-to-backend
spec:
tier: default
order: 100 # デフォルト拒否(order: 1000)よりも先にチェックさせる!
# 通信の「宛先(どこへ向かう通信か)」を指定する
# ここでは backend という名前空間にいるポッドを指定
selector: "app == 'backend-database'"
types:
- Ingress
ingress:
- action: Allow
# 通信の「送信元(どこから来た通信か)」を指定する
source:
# frontend という名前空間にいるポッドからの通信だけに絞る
namespaceSelector: "kubernetes.io/metadata.name == 'frontend'"
selector: "app == 'web-frontend'"
# 許可するポート(例:データベースのポート 5432)を指定
destination:
ports:
- 5432
このポリシーのミソは、order(優先順位)の概念です。
Calicoは数字が小さいポリシーから順番にチェックします。
order: 100 のこの「許可ルール」を先に確認させ、もし条件に合致すればそこで通信を通します(Allow)。もしこの条件に当てはまらなければ、後から控えている order: 1000 の「すべて拒否ルール」に引っかかり、ブロックされる仕組みになっています。
まるで、厳重な警備員のチェックポイントで、「招待状を持っているフロントエンドからの客だけは、5432番の通用口を通す。それ以外は一歩も通さない!」と厳しく見張っている状態ですね。
—
5. まとめ:一歩ずつ、堅牢なKubernetes環境へ
いかがでしたでしょうか?
今回はCalicoの GlobalNetworkPolicy を使ったクラスタ全体のデフォルト拒否と、名前空間間における最小権限の通信許可についてお話ししました。
- 名前空間を分けただけでは、マンションの各部屋の鍵を開けっ放しにしているようなもの。
- Calicoの
GlobalNetworkPolicyを使って、クラスタ全体を「デフォルト拒否」で守る。 - DNSなどの生活インフラ(例外)をしっかり確保しつつ、必要な通路だけを
order(優先順位)を意識して開通させる。
セキュリティの対策は、一度に完璧を目指そうとすると、どこかでシステムが動かなくなって挫折してしまいがちです。
まずは検証環境で「デフォルト拒否」を試してみて、アプリがどう動くかを観察する。そして、必要な通信を一つずつ丁寧につないであげる。その泥臭いアプローチこそが、最も確実で信頼できるインフラを作り上げる秘訣です。
一歩ずつ、安全で強いシステムを一緒に作っていきましょう!それではまた次の記事でお会いしましょう。
コメント