😇

【AWS】Pod毎にセキュリティグループを指定できるSecurityGroupPolicyについて

2021/06/15に公開

はじめに

最近AWSでPod毎にセキュリティグループを指定できるSecurityGroupPolicyが出たので、その紹介記事になります。
(2020/9なのでもう最近じゃない気もしますけど😇)

SecurityGroupPolicy概要と利点

SecurityGroupPolicyとは、pod単位でセキュリティグループを指定できるようにする、AWS独自のkubernetesリソースの事です。

マイクロサービスなどで複数サービスを単一クラスタで管理する環境の場合、サービス(pod)単位でセキュリティグループを作成したくなること、ありませんか?(あるある!)

例えば、サービスAのRDSやElasticacheが、サービスBのpodからも自由にアクセス出来るって少し怖いですよね。(うんうん!)

上記のような問題をSecurityGroupPolicyは解決できます。(すごい!)

従来はNode単位でしかセキュリティグループを指定できなかったのですが、Pod毎にセキュリティグループを指定できるようになったことによって、同一クラスター内でもサービス毎にセキュリティグループに依存したアクセス制限が使用出来るようになりました。

ありがたいですね〜

実装手順

上記pod⇆RDSのアクセスをサービス単位で制限する例を考えます。
サービスAのRDSがサービスAのpod以外からのインバウンドを受け付けないようにしてみましょう。
大まかな手順は以下です。

  • クラスタの環境構築
  • pod/RDSのセキュリティグループ作成
  • SecurityGroupPolicyリソース作成

詰まったら公式へ。

クラスタ環境構築

① CNI のアップデート

CNI(コンテナネットワークインターフェース)のバージョンが1.7.7以上である必要があるみたい。
以下コマンドでバージョンを確認して

$ kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2

v1.7.7未満だったらここを参考にアップデートする

$ curl -o aws-k8s-cni.yaml https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/v1.7.7/config/v1.7/aws-k8s-cni.yaml
$ sed -i -e 's/us-west-2/${使用しているリージョン}/' aws-k8s-cni.yaml
$ kubectl apply -f aws-k8s-cni.yaml
$ kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
amazon-k8s-cni-init:v1.7.7
amazon-k8s-cni:v1.7.7

② クラスタを管理するロールに管理ポリシーを追加

クラスターを管理するロールにAmazonEKSVPCResourceControllerポリシーを追加。

このポリシーでは、 ロールがネットワークインターフェイス、プライベート IP アドレス、およびインスタンスのアタッチとデタッチを管理することができます

aws iam attach-role-policy \
    --policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceController \
    --role-name foo-bar-cluster-role # clusterを管理するロール

③ CNI プラグインを有効にする

CNIプラグインを以下コマンドで有効にする。

$ kubectl set env daemonset aws-node -n kube-system ENABLE_POD_ENI=true

※ liveness probesやreadiness probesを使用している場合は、以下コマンドも必要らしい。

If are you using liveness or readiness probes, you also need to disable TCP early demux.

$ kubectl patch daemonset aws-node \
  -n kube-system \
  -p '{"spec": {"template": {"spec": {"initContainers": [{"env":[{"name":"DISABLE_TCP_EARLY_DEMUX","value":"true"}],"name":"aws-vpc-cni-init"}]}}}}'

実装

① Pod用のセキュリティグループ作成

将来サービスAのRDSがインバウンドとして参照する、サービスAのPodのセキュリティグループを作成します。

podのセキュリティグループを作成する.tf
resource "aws_security_group" "service_a" {
  name        = "service-a-pod-sg"
  description = "desc"
  vpc_id      = "vpcのid"
}

resource "aws_security_group_rule" "service_a_ingress_http" {
  security_group_id = aws_security_group.service_a.id
  type              = "ingress"
  from_port         = 80 # ALBで終端され80番でリクエストが来る想定
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "service_a_egress_all" {
  security_group_id = aws_security_group.service_a.id
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
}

将来Podに紐づける予定のセキュリティグループが宙に浮いてる状態ですね。

② RDS用のセキュリティグループを作成

上記①で作成したPod用のSGからのインバウンドを許可したセキュリティグループを作成し、既存のRDSに紐づけます。

RDS用のセキュリティグループを作成する.tf
#--------------------------------------------------------------
# セキュリティグループ作成
#--------------------------------------------------------------
resource "aws_security_group" "service_a_rds" {
  name        = "service-a-rds-sg"
  description = "desc"
  vpc_id      = "vpcのid"
}

# 上記①で作成したPod用のSGからのインバウンドのみを許可
resource "aws_security_group_rule" "service_a_rds_ingress_service_a" {
  security_group_id        = aws_security_group.service_a_rds.id
  type                     = "ingress"
  from_port                = 3306
  to_port                  = 3306
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.service_a.id
}

resource "aws_security_group_rule" "acop_rds_egress_all" {
  security_group_id = aws_security_group.acop_rds.id
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
}

#--------------------------------------------------------------
# RDSへの紐付け
#--------------------------------------------------------------
resource "aws_db_instance" "service_a_rds" {
  name                        = "serviceA-RDS"
  identifier                  = "service-a-rds"
  vpc_security_group_ids      = [aws_security_group.service_a_rds.id]
  
  ...色んな設定...
}

サービスAのRDSが、podに割り当てる予定のSGからのインバウンド(por:3306)のみを受け付けている状態です。

③ Podにセキュリティグループを紐づける

SecurityGroupPolicyリソースを用いて、①で作成したセキュリティグループをサービスAのpodに適用させましょう。

Podにセキュリティグループを紐づける.yaml
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: service-a-security-group-policy
  namespace: service-a
spec:
  podSelector:
    matchLabels:
      app: service-a-pod # サービスAのpodのラベルにマッチさせる
  securityGroups:
    groupIds:
      - sg-xxxxxxxxxxxxxxxx # ①で作成したpodセキュリティグループID

これでサービスAのRDSが受け付けるセキュリティグループを、サービスAのpodに紐づけることができました!

④ CoreDNSと疎通するためのセキュリティグループをアタッチ

これで完成と思いきや、実際にpodからRDSへ通信してみると名前解決の段階で失敗してしまいます。名前解決を担うCoreDNSからのインバウンドを許可出来ていないことで、名前解決ができなくなっているんですね。公式にも以下注釈があります。

セキュリティグループは、TCP および UDP ポート 53 経由でクラスターセキュリティグループ (CoreDNS の場合) へのアウトバウンド通信を許可する必要があります。クラスターセキュリティグループは、ポッドに関連付けられているすべてのセキュリティグループからのインバウンド TCP および UDP ポート 53 通信も許可する必要があります。

CoreDNSからのインバウンド(port:53)を許可するために、自SGのインバウンドトラフィックを全て許可しているクラスターセキュリティグループなどを、サービスAのpodにアタッチしてみましょう。

名前解決もできるようになったPodにセキュリティグループを紐づける.yaml
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: service-a-security-group-policy
  namespace: service-a
spec:
  podSelector:
    matchLabels:
      app: service-a-pod # サービスAのpodのラベルにマッチさせる
  securityGroups:
    groupIds:
      - sg-xxxxxxxxxxxxxxxx # ①で作成したpodセキュリティグループID
      - sg-yyyyyyyyyyyyyyyy # CoreDNSからのインバウンドを許可できるセキュリティグループ(クラスターセキュリティグループなど)のID

以上で、サービスAのpodからサービスAのRDSに正常にアクセスできるようになりました。
当然、サービスBのpodからサービスAのRDSにアクセスできないこともご確認ください。

これでサービス単位のアクセス制限を実現することができました🎉

終わりに

今回はAWSでサービス毎にセキュリティグループを割り当てられるSecurityGroupPolicyを実見ていきましたー! 何かの参考になれば幸いです☺️
本記事ではpodのラベルでセキュリティグループの紐付き先を設定しましたが、アカウントサービスレベルでも紐づけられるらしいです👀

詳しくは以下の公式サイトにてmm
aws公式
aws記事

Discussion