この記事について
| 記事 | タイトル | 状態 |
|---|---|---|
| 第0回 | 全体ガイド | ✅ 完了 |
| 第1回 | 構成図編 | ✅ 完了 |
| 第2回 | VPC & Subnet | ✅ 完了 |
| 第3回 | NAT Gateway & Route Table | ✅ 完了 |
| 第4回 | Security Group | 📍今回 |
| 第5回 | ECR & Docker Image | ⬜ 未読 |
| 第6回 | Public ALB | ⬜ 未読 |
| 第7回 | ECS Front & IAM | ⬜ 未読 |
| 第8回 | ECS API & Internal ALB | ⬜ 未読 |
| 第9回 | RDS MySQL | ⬜ 未読 |
| 第10回 | Secrets Manager & 完成 | ⬜ 未読 |
進捗: 36% (4/11記事) | Phase: 2-1 | 目標: Security Group × 5作成
📁 完全なコードはGitHubで公開:👉 GitHub: fargate-iac-02
1. はじめに
この記事は、Phase 2の前半として、Security Groupを作成します。
1-1. 前回までの振り返り
Phase 1(第2-3回)で作成したもの:
- ✅ VPC、Subnet × 6
- ✅ Internet Gateway
- ✅ NAT Gateway × 2
- ✅ Route Table × 2
今回(第4回: Phase 2-1)で作成するもの:
- Security Group × 5
- alb-public(Public ALB用)
- ecs-front(Frontend用)
- alb-internal(Internal ALB用)
- ecs-api(API用)
- rds(RDS用)
1-2. この記事で学べること
- Security Groupの基本と役割
- 最小権限の原則の実践
- Security Group参照の仕組み
- インバウンド/アウトバウンドルールの設計
1-3. 対象読者
- Phase 1(第2-3回)を完了した方
- ネットワーク基盤を作成済みの方
1-4. 想定環境
- Terraform: v1.9.x
- Phase 1: 実行済み
2. Phase 2-1で作成するもの
2-1. リソース一覧
| リソース | 数量 | 用途 | 月額費用 |
|---|---|---|---|
| Security Group | 5 | 通信制御 | 無料 |
合計: 5リソース(ルールは約20個)
2-2. 5つのSecurity Group
| SG名 | 適用先 | インバウンド | アウトバウンド |
|---|---|---|---|
| alb-public | Public ALB | 443, 80 from 0.0.0.0/0 | 80 to ecs-front |
| ecs-front | Frontend | 80 from alb-public | 80 to alb-internal |
| alb-internal | Internal ALB | 80 from ecs-front | 8080 to ecs-api |
| ecs-api | API | 8080 from alb-internal | 3306 to rds, 443 to 0.0.0.0/0 |
| rds | RDS MySQL | 3306 from ecs-api | なし |
2-3. Security Group通信フロー図
①External通信(インターネット側)
目的: インターネットからFrontendまでの通信フローとSG設定
この図で分かること:
- インターネットからの入口
- Public ALBのSG設定(HTTPS/HTTP受付)
- FrontendのSG設定(Public ALBからのみ受付)
🔑 External通信のポイント:
- Public ALB: インターネット全体(0.0.0.0/0)からHTTPS/HTTP受付
- ECS Frontend: Public ALBからのみHTTP:80受付(Security Group参照)
- HTTPS終端: Public ALBでHTTPSを終端、内部はHTTP通信
②Internal通信(VPC内部)
目的: Frontend以降のVPC内部通信フローとSG設定
この図で分かること:
- VPC内部の通信フロー(4段階)
- 各SGの詳細ルール
- APIからAWSサービスへの外部通信
🔑 Internal通信のポイント:
- Frontend → Internal ALB: Security Group参照で制御
- Internal ALB → API: ポート変換(80 → 8080)
- API → RDS: DB専用SGで完全隔離
- API → AWS Services: ECR/Secrets Manager接続用のHTTPS:443
2-4. Security Groupの階層構造
目的: 5つのSGを階層で理解し、多層防御の概念を把握する
階層の意味:
- 第1層(赤): インターネット全体に公開(最もオープン)
- 第2層(オレンジ): Public ALBからのみ受付
- 第3層(黄): Frontendからのみ受付
- 第4層(青): Internal ALBからのみ受付
- 第5層(紫): APIからのみ受付(最も制限)
セキュリティのポイント:
- 外側の層から内側の層への通信のみ許可
- 内側の層ほど制限が厳しい(多層防御)
- RDSは完全隔離(最も守られている)
3. Security Groupの設計思想
3-1. Security Groupとは
Security Groupは、AWSリソースに対する仮想ファイアウォールです。
特徴:
- ステートフル(戻りの通信は自動的に許可)
- デフォルトですべての通信を拒否
- 許可ルールのみ設定可能(拒否ルールは不可)
料理で例えると:
- Security Group = レストランの受付係
- 「予約のあるお客さんだけ入店OK」
- 「他のお客さんは入店NG」
3-2. 最小権限の原則
最小権限の原則(Principle of Least Privilege):
- 必要最小限の通信のみを許可
- 不要な通信は一切許可しない
今回の設計:
❌ 悪い例:
# すべてのポートを開放
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
✅ 良い例:
# 必要なポートのみ開放
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb_public.id]
}
3-3. Security Group参照
CIDR指定 vs Security Group参照:
| 方式 | 記述例 | 特徴 |
|---|---|---|
| CIDR指定 | cidr_blocks = ["0.0.0.0/0"] | IPアドレス範囲で指定 |
| SG参照 | security_groups = [sg-xxx] | Security Groupで指定 |
SG参照のメリット:
- ✅ IPアドレスが変わっても設定不要
- ✅ 動的な環境に強い
- ✅ 管理がシンプル
4. ディレクトリ構成
4-1. プロジェクト全体の構成
terraform/
├── phase1-network/ ← Phase 1完了
│ └── terraform.tfstate
│
└── phase2-security/ 👉 今回作成
├── main.tf
├── variables.tf
├── terraform.tfvars
├── data.tf
├── security_group.tf
└── outputs.tf
4-2. Phase 2-1のファイル構成
| ファイル名 | 役割 | 行数 |
|---|---|---|
| main.tf | Provider設定 | 20 |
| variables.tf | 変数定義 | 15 |
| terraform.tfvars | 変数の値 | 5 |
| data.tf | Phase 1の参照 | 20 |
| security_group.tf | Security Group × 5 | 150 |
| outputs.tf | 出力 | 25 |
合計: 6ファイル、約235行
5. ファイル別コード解説
5-1. main.tf - Provider設定
コード:
# terraform/phase2-security/main.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Project = var.project_name
ManagedBy = "Terraform"
Phase = "2"
}
}
}
ポイント:
- Phase 1とほぼ同じ
- Phase番号が"2"に変更
5-2. variables.tf - 変数定義
コード:
# terraform/phase2-security/variables.tf
variable "project_name" {
description = "プロジェクト名"
type = string
}
variable "region" {
description = "AWSリージョン"
type = string
default = "ap-northeast-1"
}
ポイント:
- Phase 2固有の変数はなし
- project_name、regionのみ
5-3. terraform.tfvars - 変数の値
コード:
# terraform/phase2-security/terraform.tfvars
project_name = "myproject"
region = "ap-northeast-1"
5-4. data.tf - Phase 1の参照
ファイルの役割:
- Phase 1のterraform.tfstateを参照
- VPC IDを取得
コード:
# terraform/phase2-security/data.tf
# Phase 1の出力を参照
data "terraform_remote_state" "network" {
backend = "local"
config = {
path = "../phase1-network/terraform.tfstate"
}
}
# Phase 1から取得する値をローカル変数化
locals {
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
}
ポイント:
- terraform_remote_state: 他のPhaseのstateを参照
- backend = "local": ローカルのstateファイルを参照
- path: 相対パスでPhase 1を指定
- locals: 取得した値をローカル変数化(可読性向上)
使い方:
# VPC IDを使う場合
vpc_id = local.vpc_id
5-5. security_group.tf - Security Group × 5
ファイルの役割:
- 5つのSecurity Groupを定義
5-5-1. SG: alb-public
用途: Public ALB用
インバウンド: インターネットからHTTPS/HTTP
アウトバウンド: Frontendへ
コード:
# terraform/phase2-security/security_group.tf
# ==========================================
# Security Group: Public ALB
# ==========================================
resource "aws_security_group" "alb_public" {
name = "${var.project_name}-alb-public-sg"
description = "Security group for public ALB"
vpc_id = local.vpc_id
tags = {
Name = "${var.project_name}-alb-public-sg"
}
}
# インバウンド: HTTPS (443) from インターネット
resource "aws_vpc_security_group_ingress_rule" "alb_public_https" {
security_group_id = aws_security_group.alb_public.id
from_port = 443
to_port = 443
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
description = "Allow HTTPS from internet"
}
# インバウンド: HTTP (80) from インターネット
resource "aws_vpc_security_group_ingress_rule" "alb_public_http" {
security_group_id = aws_security_group.alb_public.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
description = "Allow HTTP from internet"
}
# アウトバウンド: HTTP (80) to Frontend
resource "aws_vpc_security_group_egress_rule" "alb_public_to_front" {
security_group_id = aws_security_group.alb_public.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.ecs_front.id
description = "Allow HTTP to Frontend"
}
ポイント:
- aws_vpc_security_group_ingress_rule: 新しいリソースタイプ(推奨)
- cidr_ipv4 = "0.0.0.0/0": インターネット全体
- referenced_security_group_id: SG参照
5-5-2. SG: ecs-front
用途: Frontend用
インバウンド: Public ALBから
アウトバウンド: Internal ALBへ
コード:
# ==========================================
# Security Group: ECS Frontend
# ==========================================
resource "aws_security_group" "ecs_front" {
name = "${var.project_name}-ecs-front-sg"
description = "Security group for ECS Frontend"
vpc_id = local.vpc_id
tags = {
Name = "${var.project_name}-ecs-front-sg"
}
}
# インバウンド: HTTP (80) from Public ALB
resource "aws_vpc_security_group_ingress_rule" "ecs_front_from_alb_public" {
security_group_id = aws_security_group.ecs_front.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.alb_public.id
description = "Allow HTTP from Public ALB"
}
# アウトバウンド: HTTP (80) to Internal ALB
resource "aws_vpc_security_group_egress_rule" "ecs_front_to_alb_internal" {
security_group_id = aws_security_group.ecs_front.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.alb_internal.id
description = "Allow HTTP to Internal ALB"
}
5-5-3. SG: alb-internal
用途: Internal ALB用
インバウンド: Frontendから
アウトバウンド: APIへ
コード:
# ==========================================
# Security Group: Internal ALB
# ==========================================
resource "aws_security_group" "alb_internal" {
name = "${var.project_name}-alb-internal-sg"
description = "Security group for internal ALB"
vpc_id = local.vpc_id
tags = {
Name = "${var.project_name}-alb-internal-sg"
}
}
# インバウンド: HTTP (80) from Frontend
resource "aws_vpc_security_group_ingress_rule" "alb_internal_from_front" {
security_group_id = aws_security_group.alb_internal.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.ecs_front.id
description = "Allow HTTP from Frontend"
}
# アウトバウンド: HTTP (8080) to API
resource "aws_vpc_security_group_egress_rule" "alb_internal_to_api" {
security_group_id = aws_security_group.alb_internal.id
from_port = 8080
to_port = 8080
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.ecs_api.id
description = "Allow HTTP to API"
}
5-5-4. SG: ecs-api
用途: API用
インバウンド: Internal ALBから
アウトバウンド: RDSへ、インターネットへ(ECR、Secrets Manager用)
コード:
# ==========================================
# Security Group: ECS API
# ==========================================
resource "aws_security_group" "ecs_api" {
name = "${var.project_name}-ecs-api-sg"
description = "Security group for ECS API"
vpc_id = local.vpc_id
tags = {
Name = "${var.project_name}-ecs-api-sg"
}
}
# インバウンド: HTTP (8080) from Internal ALB
resource "aws_vpc_security_group_ingress_rule" "ecs_api_from_alb_internal" {
security_group_id = aws_security_group.ecs_api.id
from_port = 8080
to_port = 8080
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.alb_internal.id
description = "Allow HTTP from Internal ALB"
}
# アウトバウンド: MySQL (3306) to RDS
resource "aws_vpc_security_group_egress_rule" "ecs_api_to_rds" {
security_group_id = aws_security_group.ecs_api.id
from_port = 3306
to_port = 3306
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.rds.id
description = "Allow MySQL to RDS"
}
# アウトバウンド: HTTPS (443) to インターネット(ECR、Secrets Manager用)
resource "aws_vpc_security_group_egress_rule" "ecs_api_to_internet" {
security_group_id = aws_security_group.ecs_api.id
from_port = 443
to_port = 443
ip_protocol = "tcp"
cidr_ipv4 = "0.0.0.0/0"
description = "Allow HTTPS to internet (ECR, Secrets Manager)"
}
ポイント:
なぜAPIからインターネットへ?
ECS APIは以下の通信が必要:
- ECRからDockerイメージをpull(HTTPS:443)
- Secrets ManagerからDBパスワード取得(HTTPS:443)
- CloudWatch Logsにログ送信(HTTPS:443)
すべてNAT Gateway経由でインターネットに出ます。
5-5-5. SG: rds
用途: RDS MySQL用
インバウンド: APIから
アウトバウンド: なし
コード:
# ==========================================
# Security Group: RDS
# ==========================================
resource "aws_security_group" "rds" {
name = "${var.project_name}-rds-sg"
description = "Security group for RDS"
vpc_id = local.vpc_id
tags = {
Name = "${var.project_name}-rds-sg"
}
}
# インバウンド: MySQL (3306) from API
resource "aws_vpc_security_group_ingress_rule" "rds_from_api" {
security_group_id = aws_security_group.rds.id
from_port = 3306
to_port = 3306
ip_protocol = "tcp"
referenced_security_group_id = aws_security_group.ecs_api.id
description = "Allow MySQL from API"
}
ポイント:
- アウトバウンドルールなし: RDSは外部に通信不要
- APIからのみ接続可能: 最小権限の原則
5-6. outputs.tf - 出力
コード:
# terraform/phase2-security/outputs.tf
output "alb_public_sg_id" {
description = "Public ALB Security Group ID"
value = aws_security_group.alb_public.id
}
output "ecs_front_sg_id" {
description = "ECS Frontend Security Group ID"
value = aws_security_group.ecs_front.id
}
output "alb_internal_sg_id" {
description = "Internal ALB Security Group ID"
value = aws_security_group.alb_internal.id
}
output "ecs_api_sg_id" {
description = "ECS API Security Group ID"
value = aws_security_group.ecs_api.id
}
output "rds_sg_id" {
description = "RDS Security Group ID"
value = aws_security_group.rds.id
}
6. 実行手順
6-1. ディレクトリ作成
mkdir -p terraform/phase2-security
cd terraform/phase2-security
6-2. ファイル作成
6つのファイルを作成:
- main.tf
- variables.tf
- terraform.tfvars
- data.tf
- security_group.tf
- outputs.tf
6-3. terraform init
terraform init
6-4. terraform plan
terraform plan
実行結果:
Plan: 15 to add, 0 to change, 0 to destroy.
内訳:
- Security Group × 5
- Ingress Rule × 6
- Egress Rule × 4
6-5. terraform apply
terraform apply
所要時間: 約30秒
7. 動作確認
7-1. AWSコンソールで確認
Security Group:
- AWSコンソール → VPC → Security Groups
- 5つのSecurity Groupが作成されていることを確認
7-2. ルールの確認
alb-public-sg:
- Inbound: 443, 80 from 0.0.0.0/0
- Outbound: 80 to ecs-front-sg
ecs-front-sg:
- Inbound: 80 from alb-public-sg
- Outbound: 80 to alb-internal-sg
alb-internal-sg:
- Inbound: 80 from ecs-front-sg
- Outbound: 8080 to ecs-api-sg
ecs-api-sg:
- Inbound: 8080 from alb-internal-sg
- Outbound: 3306 to rds-sg, 443 to 0.0.0.0/0
rds-sg:
- Inbound: 3306 from ecs-api-sg
- Outbound: なし
8. トラブルシューティング
8-1. エラー1: VPC IDが見つからない
エラーメッセージ:
Error: No outputs found in the state
原因:
- Phase 1が実行されていない
解決策:
cd ../phase1-network
terraform output vpc_id
# VPC IDが表示されればOK
8-2. エラー2: Security Group名の重複
エラーメッセージ:
Error: InvalidGroup.Duplicate
原因:
- 同じ名前のSecurity Groupが既に存在
解決策:
AWSコンソールで既存のSecurity Groupを削除するか、project_nameを変更。
9. 🎯 Phase 2-1完了!
9-1. できたこと
✅ Security Group × 5の作成
✅ 最小権限の原則を実践
✅ Security Group参照の活用
9-2. 次回予告
第5回: Phase 2-2 - ECR & Docker Image Push
次回は、Phase 2の後半として以下を実装します:
- ECR Repository × 2(Front、API)
- Dockerイメージのビルド
- ECRへのpush手順
Phase 2-2が完了すると、セキュリティ基盤が完成します!
10. 参考リンク
(続く)