Security Groups for Pods と Network Policy を併用してセキュアな Pod ネットワーキングを実現する
はじめに
EKS クラスタのネットワークセキュリティを設計する際、EKS レイヤーの「Security Groups for Pods」と K8s レイヤーの「Network Policy」という2つの強力なツールの前で立ち止まることがあります。
どちらも Pod の通信を制御する機能ですが、それぞれ一長一短であり、どう使い分けるべきか、その具体的なベストプラクティスが明確に理解できていませんでした。
「Pod 間通信は Network Policy で制御して、RDS へのアクセスは Security Group で管理したいが、両者の関係性がよくわからない…」
この記事では、このような状況で役立つアーキテクチャとその具体的な設定方法を、順を追って解説します。
目指すアーキテクチャ
まず、シンプルかつ堅牢な役割分担を実現するために、以下のアーキテクチャを目標として定めました。
-
North-South トラフィック(Pod ⇔ クラスタ外部リソース)
- 担当: Security Groups for Pods
- 責務: Pod から VPC 内の RDS や外部 API へのアクセス制御、ELB から Pod へのアクセス制御など、クラスタの内と外を跨ぐ通信を管理する。
-
East-West トラフィック(Pod ⇔ Pod)
- 担当: Network Policy
- 責務: クラスタ内の Pod 間の通信を Kubernetes ネイティブなアプローチで管理する。
アーキテクチャ実現のための技術選択
Security Groups for Pods と Network Policy は共に Amazon VPC CNI プラグインを利用して設定することができます。公式ドキュメントが詳しいので詳細については割愛します。
そして先ほどの役割分担を実現するため、以下の2つの設定を追加しました。
-
POD_SECURITY_GROUP_ENFORCING_MODE = standard
- Pod に Security Group を適用する際の動作モードです。Network Policy との連携を考慮し、
standard
モードを選択しました。 -
standard
モードでは、同一 Node 上の通信は Security Group が適用されないため、オーバーヘッドを抑えつつ Network Policy に委任できます。 -
strict
モードでは、Pod の全ての通信が Security Group によって制御されるため、Network Policy と併用したアーキテクチャを実現するには不向きです。 -
EKS Best Practices Guide においても両者を併用する場合は
standard
モードが推奨されています。
- Pod に Security Group を適用する際の動作モードです。Network Policy との連携を考慮し、
-
AWS_VPC_K8S_CNI_EXTERNALSNAT = true
- Pod からクラスタ外部への通信における、送信元 IP アドレスの挙動を制御する設定です。
true
に設定することで SNAT が無効化され、Pod から VPC 外部への通信時に送信元 IP(Pod IP)が保持されるようになります。 - VPC Peering を用いて通信する場合や NodeLocal DNSCache を利用する場合は
true
に設定する必要があります。
- Pod からクラスタ外部への通信における、送信元 IP アドレスの挙動を制御する設定です。
これらの設定により North-South と East-West の棲み分けを明確にすることができましたが、Network Policy のルールが全ての Pod 間通信に正しく適用されるためには、もう一工夫必要でした。
Pod 間通信を Network Policy のみに制御させるための工夫
現状の Amazon VPC CNI の挙動だと、Node を跨ぐ Pod 間通信は Security Groups for Pods の影響を受けてしまいます。
例えば、CoreDNS や Datadog Cluster Agent など、DaemonsSet ではない Pod とアプリケーション Pod との通信は Node を跨ぐことが多く、これらを Security Group で制御するのは当初の方針に反します。
East-West 通信の制御を完全に Network Policy に委任するため、以下の設定を追加しました。
-
Worker Node の Security Group に「自己参照ルール」を設定する
- クラスタの Worker Node にアタッチされている Security Group(以降
Node SG
と表記)に、自分自身からのすべての通信を許可するインバウンドルールを追加します。これは EKS のデフォルト設定でもありますが、改めて確認します。
security_group.tf# Worker Node SG に自分自身からの ingress 通信を許可する resource "aws_security_group_rule" "worker_node_ingress_self" { type = "ingress" from_port = 0 to_port = 0 protocol = "-1" # すべてのプロトコル self = true security_group_id = aws_security_group.worker_node.id description = "Allow all internal traffic" }
- クラスタの Worker Node にアタッチされている Security Group(以降
-
SecurityGroupPolicy
を使い、「Node SG」を Pod に追加でアタッチする- Pod に対して、Pod 専用の SG に加えてこの
Node SG
も一緒にアタッチします。
security-group-policy.yamlapiVersion: vpcresources.k8s.aws/v1beta1 kind: SecurityGroupPolicy metadata: name: api-policy namespace: my-app spec: podSelector: matchLabels: app: api securityGroups: groupIds: # 1. Pod 専用の SG (North-South 制御用) - "sg-0123456789abcdef0" # 2. Worker Node の SG (East-West 透過用) - "sg-fedcba9876543210f"
- Pod に対して、Pod 専用の SG に加えてこの
この設定により、Node SG
の自己参照ルールが Pod にも適用されるため、全ての Pod 間の通信は Security Group レベルでは実質的に全許可(透過的)になります。結果として、East-West 通信の実際のアクセス可否は、Network Policy のルールのみによって決定されるようになり、理想の役割分担が完成します。
実践:役割分担を実現する具体的な設定
ここからは、具体的な構成例と共に、理想の役割分担を実現するための設定を解説します。
構成例
- ELB
-
API サーバー Pod
- namespace:
my-app
- labels:
app: api
- namespace:
- RDS
具体的な設定例
(1) Security Groups for Pods による North-South 制御
API サーバー Pod と RDS の Security Group に以下のようなルールを設定します。
# API Pod SG の ingress ルール(ELBから)
resource "aws_security_group_rule" "api_ingress_elb" {
type = "ingress"
from_port = 8080 # Pod のポート
to_port = 8080
protocol = "tcp"
security_group_id = aws_security_group.api.id
source_security_group_id = aws_security_group.elb.id
description = "Allow ingress from ELB"
}
# RDS SG の ingress ルール(API Podから)
# ※実際には API Pod SG の egress ルールでも制御可能ですが、
# ここでは RDS 側の ingress で制御する例を示します。
resource "aws_security_group_rule" "rds_ingress_api" {
type = "ingress"
from_port = 3306 # RDSのポート
to_port = 3306
protocol = "tcp"
security_group_id = aws_security_group.rds.id
source_security_group_id = aws_security_group.api.id
description = "Allow ingress from API Pods"
}
(2) Network Policy による East-West 制御
次に、my-app
namespace に対してより具体的なルールを適用します。
# Policy 1: IMDS へのアクセスを禁止する
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-imds-access
namespace: my-app
spec:
podSelector: {} # 名前空間内の全 Pod に適用
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32 # IMDS の IP アドレスを除外
---
# Policy 2: Datadog Agent からの通信を許可する
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-datadog
namespace: my-app
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
# Datadog Agent がデプロイされている名前空間のラベル
kubernetes.io/metadata.name: datadog
このポリシーにより、セキュリティのベストプラクティスである IMDS へのアクセス制限を実現しつつ、Datadog のような監視ツールからのメトリクス収集は許可する、といった柔軟な制御が実現できました。
まとめ
本記事では、EKS における「Security Groups for Pods」と「Network Policy」の使い分けについて、私たちが実践するアーキテクチャを紹介しました。
目標
North-South トラフィックは Security Groups for Pods、East-West トラフィックは Network Policy、という役割分担を徹底する。
実現方法
- Amazon VPC CNI で
POD_SECURITY_GROUP_ENFORCING_MODE=standard
とAWS_VPC_K8S_CNI_EXTERNALSNAT=true
を設定する。 - Worker Node の自己参照ルールを持つ SG を、
SecurityGroupPolicy
で Pod に追加アタッチする。
この「もう一工夫」を加えることで、2つの強力なツールが互いの責務を侵害することなく、それぞれの長所を最大限に発揮できる、クリーンで堅牢なネットワーク制御が実現できました。このアプローチが、皆さんの EKS クラスタをよりセキュアにするための一助となれば幸いです。
Discussion