0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【第4回】AWS ECS Fargate 11回シリーズ|Phase 2-1|Security Groupの設計

Last updated at Posted at 2026-01-23

この記事について

記事 タイトル 状態
第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通信のポイント:

  1. Public ALB: インターネット全体(0.0.0.0/0)からHTTPS/HTTP受付
  2. ECS Frontend: Public ALBからのみHTTP:80受付(Security Group参照)
  3. HTTPS終端: Public ALBでHTTPSを終端、内部はHTTP通信

②Internal通信(VPC内部)

目的: Frontend以降のVPC内部通信フローとSG設定

この図で分かること:

  • VPC内部の通信フロー(4段階)
  • 各SGの詳細ルール
  • APIからAWSサービスへの外部通信

🔑 Internal通信のポイント:

  1. Frontend → Internal ALB: Security Group参照で制御
  2. Internal ALB → API: ポート変換(80 → 8080)
  3. API → RDS: DB専用SGで完全隔離
  4. 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
}

ポイント:

  1. terraform_remote_state: 他のPhaseのstateを参照
  2. backend = "local": ローカルのstateファイルを参照
  3. path: 相対パスでPhase 1を指定
  4. 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"
}

ポイント:

  1. aws_vpc_security_group_ingress_rule: 新しいリソースタイプ(推奨)
  2. cidr_ipv4 = "0.0.0.0/0": インターネット全体
  3. 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"
}

ポイント:

  1. アウトバウンドルールなし: RDSは外部に通信不要
  2. 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:

  1. AWSコンソール → VPC → Security Groups
  2. 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. 参考リンク


(続く)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?