プロジェクトで使用するSecurity Groupを一括管理し、リソース間の通信許可をSecurity Group ID参照で設計するパターンを解説する。 IPアドレスに依存しないネットワークアクセス制御の実践を紹介する。
ページ構成
- Security Group一括管理の動機
- 設計パターン:箱の一括作成 + リソース側でのルール追加
- IPアドレスではなくSecurity Group IDで許可する理由
- リソース別の通信許可パターン
- まとめ
1. Security Group一括管理の動機
Security Groupの散逸問題
Security Groupもリソースごとに個別作成すると以下の問題が生じる。
- 全体像が見えない: VPC内にどのSecurity Groupが存在し、何を許可しているか把握しづらい
- 命名規則の不統一: 作成者・タイミングによって命名がばらつく
- 不要Security Groupの残存: リソース削除後もSecurity Groupだけが残り、棚卸しが困難
- ルールの重複: 似たルールが複数Security Groupに散在する
設計方針:一箇所で「箱」を一括作成
全Security Groupを1箇所のTerraformファイルに集約する。
ここで定義するのは:
- Security Groupの箱(名前、説明、VPC紐付け)
- 汎用的なルールだけ(ALBの443受信、全Egress許可など)
リソース固有の通信許可(RDSへのECSからのアクセス等)は、ここでは定義しない。
2. 設計パターン:箱の一括作成 + リソース側でのルール追加
一括管理で作るもの
環境内で必要なSecurity Groupを宣言的に定義する。
| Security Group | 一括管理で定義するルール |
|---|---|
| ALB用 | Ingress: HTTPS(443) from anywhere / Egress: 全許可 |
| ECSタスク用 | Ingress: なし(リソース側で追加) / Egress: 全許可 |
| RDS用 | Ingress: なし(リソース側で追加) / Egress: 全許可 |
| Lambda用 | Ingress: なし / Egress: 全許可 |
| Bastion用 | Ingress: SSH(22) from オフィスCIDR / Egress: 全許可 |
ECSタスク用やRDS用のSecurity Groupは、Ingressルールを空で作成する。 「誰からの通信を許可するか」は、リソース側で決定する。
リソース側で追加するもの
各リソースのTerraformが、自身のSecurity Groupに対してインバウンドルールを追加する。
# RDS構築時に、ECSタスクからの通信を許可するルールを追加
resource "aws_security_group_rule" "allow_from_ecs" {
security_group_id = module.sgs.sgs["rds"].id # RDSのSG
source_security_group_id = module.sgs.sgs["ecs"].id # 接続元: ECSタスクのSG
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
description = "Allow MySQL from ECS tasks"
}
この設計により:
- RDSのTerraformを見れば「どこから通信できるか」がわかる
- Security Group一括管理側を見に行く必要がない
- リソースの追加・削除に伴い、通信許可も自然に追加・削除される
3. IPアドレスではなくSecurity Group IDで許可する理由
CIDRベースの許可の問題
# CIDRベース(非推奨パターン)
cidr_blocks = ["10.0.1.0/24"] # ECSタスクが配置されるサブネット
- ECSタスク(Fargate)のIPアドレスはタスク起動ごとに変わる
- サブネットCIDR全体を許可すると、そのサブネット内の全リソースからアクセス可能になる
- サブネット構成を変更するとルールの修正が必要
Security Group ID参照の利点
# Security Group IDベース(推奨パターン)
source_security_group_id = module.sgs.sgs["ecs"].id
- IPアドレスに依存しない: タスクのIPが変わっても通信許可は維持される
- 最小権限: 同じサブネット内でも、指定したSecurity Groupがアタッチされたリソースからのみ許可
- 可読性: 「ECSタスクからの通信を許可」という意図がコードから読み取れる
- サブネット変更に強い: ネットワーク構成を変更してもルール修正不要
CIDRを使う例外ケース
| ケース | 理由 |
|---|---|
| ALBへのCloudFrontアクセス | AWSマネージドプレフィックスリストを使用(IPレンジがAWSにより動的に管理される) |
| Bastionへのオフィスからのアクセス | 接続元がVPC外のため、Security Group IDで参照できない |
| サブネット全体からの許可が意図的な場合 | 設計上の判断として明示的に選択する場合のみ |
4. リソース別の通信許可パターン
各リソースが「許可する接続元のSecurity Group IDリスト」を受け取り、自身のSecurity Groupにインバウンドルールを追加する。
| リソース | 許可する接続元 | ポート | 説明 |
|---|---|---|---|
| RDS Aurora | ECSタスクSG、Lambda SG | 3306 / 5432 | アプリケーションとバッチ処理からのDB接続 |
| ElastiCache (Redis) | ECSタスクSG、Lambda SG | 6379 | セッション・キャッシュへのアクセス |
| EFS | ECSタスクSG | 2049 (NFS) | 共有ファイルシステムへのマウント |
| OpenSearch | ECSタスクSG、Lambda SG | 443 (HTTPS) | 検索・分析エンジンへのアクセス |
| ALB | CloudFront Managed Prefix List、特定SG | 443 / 80 | ロードバランサーへのトラフィック受信 |
| EC2 (外部接続先) | Bastion SG | 22 | 踏み台からのSSH接続 |
通信フローの可視化
各矢印が aws_security_group_rule リソースに対応する。
ルールはリソース側(矢印の先)のTerraformに定義されるため、「このリソースに誰がアクセスできるか」がリソースのコードを見れば完結する。
ALBの特殊なパターン
ALBへのアクセス許可は、他のリソースとは異なるパターンを取る。
- CloudFrontからのアクセス: AWSが管理するManaged Prefix List(
com.amazonaws.global.cloudfront.origin-facing)を使用する。CloudFrontのIPレンジはAWSが動的に更新するため、CIDRの手動管理が不要 - 特定Security Groupからのアクセス: 内部ALBの場合、特定のSecurity Groupからのアクセスのみを許可する
5. まとめ
| 設計判断 | 内容 |
|---|---|
| Security Groupの集約管理 | 全Security Groupを1ファイルに集約し、一覧性を確保 |
| 汎用ルールのみ一括定義 | ALBの443受信や全Egressなど、リソース非依存のルールだけを一括管理で定義 |
| リソース固有の許可はリソース側で追加 | RDS、ElastiCache、EFS等がSecurity Group Ruleで自身のSGにインバウンドルールを追加 |
| Security Group ID参照による許可 | IPアドレスではなくSecurity Group IDで接続元を指定し、IP変更に強い構成を実現 |
| 可読性の確保 | リソースのTerraformを見れば「どこから通信できるか」が完結する |