前提条件
- 第1回から第13回までの構築が完了していること
- Terraformによるインフラ管理が行われていること
- こちらからDockerコンテナでの実行環境が準備できていること
Terraform実行環境の起動
前回と同様に、Dockerコンテナ内で作業を進めます。
# コンテナを起動してbashに入る
docker-compose run --rm terraform
これ以降のコマンドは、すべてこのコンテナ内で実行します。
基礎知識
AWSにおけるセキュリティは、単一の対策ではなく複数の層で防御することが重要です。
WEBアプリケーション開発者の視点から見ると
開発環境では簡略化されがちなセキュリティ設定も、本番環境では多層的な防御が必要です。例えば、SQLインジェクション対策は、アプリケーションレベルのバリデーションだけでなく、WAFでのブロック、最小権限のIAMロール設定、データベースの監査ログなど、複数の層で防御します。
セキュリティレイヤーの種類
レイヤー | 対策内容 | AWSサービス |
---|---|---|
ネットワーク層 | DDoS攻撃、不正アクセス | Security Group、NACL、VPC Flow Logs |
アプリケーション層 | SQLインジェクション、XSS | WAF、Shield |
データ層 | 暗号化、アクセス制御 | KMS、S3暗号化、RDS暗号化 |
アイデンティティ層 | 認証・認可 | IAM、Cognito、Secrets Manager |
監視・監査層 | 異常検知、コンプライアンス | GuardDuty、Config、CloudTrail |
Zero Trust セキュリティモデル
従来の境界型セキュリティから、すべてのアクセスを検証するZero Trustモデルへの移行が推奨されています。
原則 | 実装方法 | 適用例 |
---|---|---|
最小権限の原則 | IAMポリシーの細分化 | タスクロールは必要なS3バケットのみアクセス可能 |
常時検証 | 継続的な監視 | GuardDutyによる異常検知 |
暗号化 | 転送時・保存時の暗号化 | HTTPS通信、S3/RDS暗号化 |
ログ記録 | すべての操作を記録 | CloudTrail、VPC Flow Logs |
今回作成するセキュリティ構成
カテゴリ | リソース | 説明 |
---|---|---|
WAF | WebACL | アプリケーション層の攻撃防御 |
Managed Rules | AWS管理ルールセット | |
Custom Rules | レート制限、IP制限 | |
GuardDuty | Detector | 脅威検知エンジン |
Threat Intelligence | 脅威インテリジェンスフィード | |
Config | Configuration Recorder | リソース設定の記録 |
Rules | コンプライアンスルール | |
IAM | ポリシー最適化 | 最小権限の実装 |
MFA強制 | 多要素認証の設定 |
Terraformコードの実装
セキュリティモジュールの実装
# terraform/modules/security/main.tf
locals {
common_tags = {
Module = "security"
ManagedBy = "terraform"
Environment = var.environment
Project = var.project_name
}
}
# データソース
data "aws_caller_identity" "current" {}
#============================================
# WAF (Web Application Firewall)
#============================================
# WAF WebACL
resource "aws_wafv2_web_acl" "main" {
count = var.enable_waf ? 1 : 0
name = "${var.project_name}-${var.environment}-waf"
scope = "REGIONAL"
default_action {
allow {}
}
# AWS Managed Rule - Core Rule Set
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
# 特定のルールを除外する場合
excluded_rule {
name = "SizeRestrictions_BODY"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CommonRuleSetMetric"
sampled_requests_enabled = true
}
}
# SQL Injection対策
rule {
name = "AWSManagedRulesSQLiRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "SQLiRuleSetMetric"
sampled_requests_enabled = true
}
}
# レート制限ルール
rule {
name = "RateLimitRule"
priority = 3
action {
block {}
}
statement {
rate_based_statement {
limit = var.waf_rate_limit
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimitMetric"
sampled_requests_enabled = true
}
}
# 地理的制限(必要に応じて)
dynamic "rule" {
for_each = length(var.allowed_countries) > 0 ? [1] : []
content {
name = "GeoLocationRule"
priority = 4
action {
block {}
}
statement {
not_statement {
statement {
geo_match_statement {
country_codes = var.allowed_countries
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "GeoLocationMetric"
sampled_requests_enabled = true
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "WAFMetric"
sampled_requests_enabled = true
}
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-waf"
}
)
}
# ALBへのWAF関連付け
resource "aws_wafv2_web_acl_association" "alb" {
count = var.enable_waf && var.alb_arn != "" ? 1 : 0
resource_arn = var.alb_arn
web_acl_arn = aws_wafv2_web_acl.main[0].arn
}
#============================================
# GuardDuty
#============================================
# GuardDuty Detector
resource "aws_guardduty_detector" "main" {
count = var.enable_guardduty ? 1 : 0
enable = true
finding_publishing_frequency = var.guardduty_finding_frequency
# S3 Logs Protection
datasources {
s3_logs {
enable = var.enable_s3_protection
}
# EKS Audit Logs(ECS使用時は不要だが将来の拡張用)
kubernetes {
audit_logs {
enable = false
}
}
# Malware Protection
malware_protection {
scan_ec2_instance_with_findings {
ebs_volumes {
enable = var.enable_malware_protection
}
}
}
}
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-guardduty"
}
)
}
# SNSトピックへの通知設定
resource "aws_cloudwatch_event_rule" "guardduty" {
count = var.enable_guardduty && var.alarm_sns_topic_arn != "" ? 1 : 0
name = "${var.project_name}-${var.environment}-guardduty-findings"
description = "GuardDuty findings notification"
event_pattern = jsonencode({
source = ["aws.guardduty"]
detail-type = ["GuardDuty Finding"]
detail = {
severity = [
{ numeric = [">=", var.guardduty_severity_threshold] }
]
}
})
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-guardduty-rule"
}
)
}
resource "aws_cloudwatch_event_target" "guardduty_sns" {
count = var.enable_guardduty && var.alarm_sns_topic_arn != "" ? 1 : 0
rule = aws_cloudwatch_event_rule.guardduty[0].name
target_id = "SendToSNS"
arn = var.alarm_sns_topic_arn
}
#============================================
# AWS Config
#============================================
# Config用S3バケット
resource "aws_s3_bucket" "config" {
count = var.enable_config ? 1 : 0
bucket = "${var.project_name}-${var.environment}-config-logs"
force_destroy = var.environment != "prod"
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-config-logs"
}
)
}
resource "aws_s3_bucket_public_access_block" "config" {
count = var.enable_config ? 1 : 0
bucket = aws_s3_bucket.config[0].id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Config Recorder用IAMロール
resource "aws_iam_role" "config" {
count = var.enable_config ? 1 : 0
name = "${var.project_name}-${var.environment}-config-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "config.amazonaws.com"
}
}
]
})
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-config-role"
}
)
}
resource "aws_iam_role_policy" "config" {
count = var.enable_config ? 1 : 0
name = "${var.project_name}-${var.environment}-config-policy"
role = aws_iam_role.config[0].id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetBucketAcl",
"s3:ListBucket"
]
Resource = aws_s3_bucket.config[0].arn
},
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject"
]
Resource = "${aws_s3_bucket.config[0].arn}/*"
Condition = {
StringLike = {
"s3:x-amz-server-side-encryption" = "AES256"
}
}
},
{
Effect = "Allow"
Action = [
"config:Put*"
]
Resource = "*"
}
]
})
}
# Delivery Channel
resource "aws_config_delivery_channel" "main" {
count = var.enable_config ? 1 : 0
name = "${var.project_name}-${var.environment}-channel"
s3_bucket_name = aws_s3_bucket.config[0].bucket
snapshot_delivery_properties {
delivery_frequency = "TwentyFour_Hours"
}
}
# Configuration Recorder
resource "aws_config_configuration_recorder" "main" {
count = var.enable_config ? 1 : 0
name = "${var.project_name}-${var.environment}-recorder"
role_arn = aws_iam_role.config[0].arn
recording_group {
all_supported = true
include_global_resource_types = true
}
depends_on = [aws_config_delivery_channel.main]
}
# Configuration Recorderの開始
resource "aws_config_configuration_recorder_status" "main" {
count = var.enable_config ? 1 : 0
name = aws_config_configuration_recorder.main[0].name
is_enabled = true
depends_on = [aws_config_configuration_recorder.main]
}
# Configルール - 必須タグの確認
resource "aws_config_config_rule" "required_tags" {
count = var.enable_config ? 1 : 0
name = "${var.project_name}-${var.environment}-required-tags"
source {
owner = "AWS"
source_identifier = "REQUIRED_TAGS"
}
input_parameters = jsonencode({
tag1Key = "Environment"
tag2Key = "Project"
tag3Key = "ManagedBy"
})
depends_on = [aws_config_configuration_recorder.main]
}
# Configルール - S3バケットの暗号化確認
resource "aws_config_config_rule" "s3_encryption" {
count = var.enable_config ? 1 : 0
name = "${var.project_name}-${var.environment}-s3-encryption"
source {
owner = "AWS"
source_identifier = "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
}
depends_on = [aws_config_configuration_recorder.main]
}
# Configルール - RDSの暗号化確認
resource "aws_config_config_rule" "rds_encryption" {
count = var.enable_config && var.enable_rds ? 1 : 0
name = "${var.project_name}-${var.environment}-rds-encryption"
source {
owner = "AWS"
source_identifier = "RDS_STORAGE_ENCRYPTED"
}
depends_on = [aws_config_configuration_recorder.main]
}
#============================================
# IAMロールとポリシーの最適化
#============================================
# ECSタスクロールの最適化(既存のタスクロールに追加ポリシー)
resource "aws_iam_role_policy" "ecs_task_optimized" {
count = var.ecs_task_role_id != "" ? 1 : 0
name = "${var.project_name}-${var.environment}-ecs-task-optimized"
role = var.ecs_task_role_id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "S3SpecificBucketAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = [
"${var.app_bucket_arn}/*"
]
},
{
Sid = "SecretsManagerSpecificAccess"
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = [
"arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:${var.project_name}/${var.environment}/*"
]
},
{
Sid = "CloudWatchLogsAccess"
Effect = "Allow"
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = [
"${var.log_group_arn}:*"
]
},
{
Sid = "XRayAccess"
Effect = "Allow"
Action = [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords"
]
Resource = "*"
}
]
})
}
# 開発者用IAMポリシー(読み取り専用)
resource "aws_iam_policy" "developer_readonly" {
name = "${var.project_name}-${var.environment}-developer-readonly"
description = "Read-only access for developers"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ec2:Describe*",
"ecs:Describe*",
"ecs:List*",
"s3:List*",
"s3:GetObject",
"rds:Describe*",
"cloudwatch:Describe*",
"cloudwatch:Get*",
"cloudwatch:List*",
"logs:Describe*",
"logs:Get*",
"logs:FilterLogEvents"
]
Resource = "*"
},
{
Effect = "Deny"
Action = [
"iam:*",
"kms:Delete*",
"kms:ScheduleKeyDeletion"
]
Resource = "*"
}
]
})
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-developer-policy"
}
)
}
#============================================
# セキュリティグループの最適化ルール
#============================================
# ALBセキュリティグループへのHTTPS追加
resource "aws_security_group_rule" "alb_ingress_https" {
count = var.alb_security_group_id != "" && length(var.allowed_cidr_blocks) > 0 ? 1 : 0
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = var.allowed_cidr_blocks
security_group_id = var.alb_security_group_id
description = "HTTPS from allowed IPs"
}
# ECSセキュリティグループの最適化
resource "aws_security_group_rule" "ecs_ingress_from_alb_only" {
count = var.ecs_security_group_id != "" && var.alb_security_group_id != "" ? 1 : 0
type = "ingress"
from_port = var.app_port
to_port = var.app_port
protocol = "tcp"
source_security_group_id = var.alb_security_group_id
security_group_id = var.ecs_security_group_id
description = "Allow traffic only from ALB"
}
# RDSセキュリティグループの最適化
resource "aws_security_group_rule" "rds_ingress_from_ecs_only" {
count = var.rds_security_group_id != "" && var.ecs_security_group_id != "" ? 1 : 0
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
source_security_group_id = var.ecs_security_group_id
security_group_id = var.rds_security_group_id
description = "PostgreSQL access only from ECS tasks"
}
WAF設定パラメータの説明
パラメータ | 設定値 | 用途・説明 |
---|---|---|
scope | REGIONAL | ALBに関連付けるためREGIONALを指定 |
default_action | allow | デフォルトは許可、ルールでブロック |
AWSManagedRulesCommonRuleSet | 有効 | 一般的な脆弱性から保護(XSS、パストラバーサルなど) |
AWSManagedRulesSQLiRuleSet | 有効 | SQLインジェクション攻撃を検知・ブロック |
rate_based_statement.limit | 変数で設定 | 5分間のリクエスト数制限 |
geo_match_statement | オプション | 特定の国からのアクセスのみ許可 |
cloudwatch_metrics_enabled | true | メトリクスを CloudWatch に送信 |
GuardDuty設定パラメータの説明
パラメータ | 設定値 | 用途・説明 |
---|---|---|
enable | true | GuardDutyを有効化 |
finding_publishing_frequency | 変数で設定 | 検出結果の更新頻度 |
s3_logs.enable | 変数で設定 | S3アクセスログの異常を検知 |
malware_protection | 変数で設定 | EC2インスタンスのマルウェアスキャン |
severity_threshold | 変数で設定 | 通知する脅威レベル |
event_pattern | CloudWatch Events | 特定の重要度以上の脅威を通知 |
Config設定パラメータの説明
パラメータ | 設定値 | 用途・説明 |
---|---|---|
all_supported | true | すべてのサポートされるリソースを記録 |
include_global_resource_types | true | IAMなどグローバルリソースも記録 |
delivery_frequency | TwentyFour_Hours | 設定スナップショットの配信頻度 |
REQUIRED_TAGS | Environment, Project, ManagedBy | 必須タグの確認ルール |
S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED | 有効 | S3暗号化の確認 |
RDS_STORAGE_ENCRYPTED | 有効 | RDS暗号化の確認 |
IAM最適化パラメータの説明
パラメータ | 設定値 | 用途・説明 |
---|---|---|
S3SpecificBucketAccess | 特定バケットのみ | 最小権限の原則に従い必要なバケットのみアクセス許可 |
SecretsManagerSpecificAccess | プロジェクト専用 | 環境別のシークレットのみアクセス可能 |
CloudWatchLogsAccess | 特定ロググループ | アプリケーション専用のロググループのみ書き込み可能 |
developer_readonly | 読み取り専用 | 開発者は参照のみ、変更不可 |
Deny Actions | IAM、KMS削除 | 重要な操作は明示的に拒否 |
モジュールの変数定義
# terraform/modules/security/variables.tf
variable "project_name" {
description = "プロジェクト名"
type = string
}
variable "environment" {
description = "環境名(dev/stg/prod)"
type = string
}
variable "aws_region" {
description = "AWSリージョン"
type = string
default = "ap-northeast-1"
}
# WAF関連
variable "enable_waf" {
description = "WAFを有効にするか"
type = bool
default = false
}
variable "waf_rate_limit" {
description = "WAF rate limit per 5 minutes"
type = number
default = 2000
}
variable "allowed_countries" {
description = "List of allowed country codes"
type = list(string)
default = []
}
variable "alb_arn" {
description = "ALB ARN for WAF association"
type = string
default = ""
}
# GuardDuty関連
variable "enable_guardduty" {
description = "GuardDutyを有効にするか"
type = bool
default = false
}
variable "guardduty_finding_frequency" {
description = "GuardDuty finding publishing frequency"
type = string
default = "FIFTEEN_MINUTES"
}
variable "guardduty_severity_threshold" {
description = "GuardDuty severity threshold for notifications"
type = number
default = 4
}
variable "enable_s3_protection" {
description = "Enable S3 protection in GuardDuty"
type = bool
default = true
}
variable "enable_malware_protection" {
description = "Enable malware protection in GuardDuty"
type = bool
default = false
}
variable "alarm_sns_topic_arn" {
description = "SNS Topic ARN for alarms"
type = string
default = ""
}
# Config関連
variable "enable_config" {
description = "AWS Configを有効にするか"
type = bool
default = false
}
variable "enable_rds" {
description = "RDSが有効か(Config RDSルール用)"
type = bool
default = false
}
# セキュリティグループ関連
variable "allowed_cidr_blocks" {
description = "Allowed CIDR blocks for ALB access"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "alb_security_group_id" {
description = "ALB Security Group ID"
type = string
default = ""
}
variable "ecs_security_group_id" {
description = "ECS Security Group ID"
type = string
default = ""
}
variable "rds_security_group_id" {
description = "RDS Security Group ID"
type = string
default = ""
}
variable "app_port" {
description = "Application port"
type = number
default = 3000
}
# IAM関連
variable "ecs_task_role_id" {
description = "ECS Task Role ID"
type = string
default = ""
}
variable "app_bucket_arn" {
description = "Application S3 Bucket ARN"
type = string
default = ""
}
variable "log_group_arn" {
description = "CloudWatch Log Group ARN"
type = string
default = ""
}
モジュールの出力定義
# terraform/modules/security/outputs.tf
# WAF関連
output "waf_web_acl_id" {
description = "WAF WebACL ID"
value = var.enable_waf ? aws_wafv2_web_acl.main[0].id : ""
}
output "waf_web_acl_arn" {
description = "WAF WebACL ARN"
value = var.enable_waf ? aws_wafv2_web_acl.main[0].arn : ""
}
# GuardDuty関連
output "guardduty_detector_id" {
description = "GuardDuty Detector ID"
value = var.enable_guardduty ? aws_guardduty_detector.main[0].id : ""
}
# Config関連
output "config_recorder_name" {
description = "Config Recorder Name"
value = var.enable_config ? aws_config_configuration_recorder.main[0].name : ""
}
output "config_required_tags_rule_name" {
description = "Config Required Tags Rule Name"
value = var.enable_config ? aws_config_config_rule.required_tags[0].name : ""
}
output "config_s3_bucket_name" {
description = "Config S3 Bucket Name"
value = var.enable_config ? aws_s3_bucket.config[0].id : ""
}
# IAM関連
output "developer_readonly_policy_arn" {
description = "Developer Read-only Policy ARN"
value = aws_iam_policy.developer_readonly.arn
}
環境別ディレクトリの設定
# terraform/environments/dev/main.tf に追加
# 既存のSecurityモジュールを更新
module "security" {
source = "../../modules/security"
project_name = var.project_name
environment = var.environment
vpc_id = module.network.vpc_id
vpc_cidr_block = var.vpc_cidr
enable_vpc_endpoints = false
# 新しいセキュリティ設定を追加
enable_waf = var.enable_waf
enable_guardduty = var.enable_guardduty
enable_config = var.enable_config
waf_rate_limit = var.waf_rate_limit
allowed_countries = var.allowed_countries
guardduty_finding_frequency = var.guardduty_finding_frequency
guardduty_severity_threshold = var.guardduty_severity_threshold
enable_s3_protection = var.enable_s3_protection
enable_malware_protection = var.enable_malware_protection
allowed_cidr_blocks = var.allowed_cidr_blocks
# 既存リソースとの連携
alb_arn = module.alb.alb_arn
alb_security_group_id = module.security.alb_security_group_id
ecs_security_group_id = module.security.ecs_security_group_id
rds_security_group_id = var.enable_rds ? module.database[0].security_group_id : ""
ecs_task_role_id = module.ecs.task_role_id
app_bucket_arn = module.storage.app_bucket_arn
log_group_arn = module.monitoring.log_group_arn
alarm_sns_topic_arn = module.monitoring.sns_topic_arn
enable_rds = var.enable_rds
}
環境別変数定義
# terraform/environments/dev/variables.tf に追加
# セキュリティ関連の変数(共通定義)
variable "waf_rate_limit" {
description = "WAF rate limit per 5 minutes"
type = number
default = 2000
}
variable "allowed_countries" {
description = "List of allowed country codes"
type = list(string)
default = []
}
variable "guardduty_finding_frequency" {
description = "GuardDuty finding publishing frequency"
type = string
default = "FIFTEEN_MINUTES"
validation {
condition = contains([
"FIFTEEN_MINUTES",
"ONE_HOUR",
"SIX_HOURS"
], var.guardduty_finding_frequency)
error_message = "有効な頻度を選択してください"
}
}
variable "guardduty_severity_threshold" {
description = "GuardDuty severity threshold for notifications (1-8, 1=lowest, 8=highest)"
type = number
default = 4
validation {
condition = var.guardduty_severity_threshold >= 1 && var.guardduty_severity_threshold <= 8
error_message = "脅威レベルは1から8の間で設定してください"
}
}
variable "enable_s3_protection" {
description = "Enable S3 protection in GuardDuty"
type = bool
default = true
}
variable "enable_malware_protection" {
description = "Enable malware protection in GuardDuty"
type = bool
default = false
}
variable "allowed_cidr_blocks" {
description = "Allowed CIDR blocks for ALB access"
type = list(string)
default = ["0.0.0.0/0"]
}
環境別設定値の推奨
設定項目 | 開発環境 (dev) | ステージング環境 (stg) | 本番環境 (prod) |
---|---|---|---|
waf_rate_limit | 5000 | 3000 | 2000 |
allowed_countries | [“JP”, “US”] | [“JP”] | [“JP”] |
guardduty_finding_frequency | ONE_HOUR | FIFTEEN_MINUTES | FIFTEEN_MINUTES |
guardduty_severity_threshold | 7 | 5 | 4 |
enable_s3_protection | false | true | true |
enable_malware_protection | false | false | true |
allowed_cidr_blocks | [“0.0.0.0/0”] | [“10.0.0.0/8”] | [“203.0.113.0/24”] |
terraform.tfvarsの設定
# terraform/terraform.tfvars
# Enable系の設定のみ
enable_waf = true
enable_guardduty = true
enable_config = true
環境別の値設定
# terraform/environments/dev/terraform.tfvars
# 開発環境の設定
waf_rate_limit = 5000
allowed_countries = ["JP", "US"]
guardduty_finding_frequency = "ONE_HOUR"
guardduty_severity_threshold = 7
enable_s3_protection = false
enable_malware_protection = false
allowed_cidr_blocks = ["0.0.0.0/0"]
リソースを作成
Terraformコンテナ内で以下のコマンドを実行します。
# 環境別ディレクトリへ移動
cd terraform/environments/dev
# 実行計画の確認
terraform plan
# リソースの作成
terraform apply
WAF、GuardDuty、Configの設定には5〜10分程度かかります。
動作確認
WAFの動作確認
# WAF WebACL の確認
aws wafv2 list-web-acls --scope REGIONAL --region ap-northeast-1
# ブロックされたリクエストの確認
aws wafv2 get-sampled-requests \
--web-acl-arn $(terraform output -raw waf_web_acl_arn) \
--rule-metric-name RateLimitMetric \
--scope REGIONAL \
--time-window StartTime=$(date -u -d '1 hour ago' +%s),EndTime=$(date +%s) \
--max-items 10
GuardDutyの確認
# GuardDuty Detectorの状態確認
aws guardduty list-detectors
# 検出結果の確認(ある場合)
DETECTOR_ID=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
aws guardduty list-findings --detector-id $DETECTOR_ID
AWS Configの確認
# Configuration Recorderの状態確認
aws configservice describe-configuration-recorder-status
# コンプライアンスルールの確認
aws configservice describe-compliance-by-config-rule \
--config-rule-names $(terraform output -raw config_required_tags_rule_name)
IAMポリシーの確認
# 開発者用読み取り専用ポリシーの確認
aws iam get-policy \
--policy-arn $(terraform output -raw developer_readonly_policy_arn)
# ポリシーの詳細確認
aws iam get-policy-version \
--policy-arn $(terraform output -raw developer_readonly_policy_arn) \
--version-id v1
セキュリティベストプラクティスのチェックリスト
ネットワークセキュリティ
項目 | 確認内容 | ステータス |
---|---|---|
VPC設計 | プライベートサブネットの活用 | ✓ |
セキュリティグループ | 最小権限の原則 | ✓ |
NACLs | 追加の防御層 | ✓ |
VPCエンドポイント | プライベート通信 | ✓ |
アプリケーションセキュリティ
項目 | 確認内容 | ステータス |
---|---|---|
WAF | SQLインジェクション対策 | ✓ |
HTTPS | 暗号化通信 | ✓ |
CORS | 適切な設定 | ✓ |
セキュリティヘッダー | CSP、X-Frame-Options | ✓ |
データセキュリティ
項目 | 確認内容 | ステータス |
---|---|---|
S3暗号化 | デフォルト暗号化 | ✓ |
RDS暗号化 | 保存時暗号化 | ✓ |
Secrets Manager | 機密情報の管理 | ✓ |
バックアップ | 自動バックアップ | ✓ |
アクセス管理
項目 | 確認内容 | ステータス |
---|---|---|
IAM最小権限 | 必要最小限の権限 | ✓ |
MFA | 多要素認証 | ✓ |
ロールベース | ユーザーではなくロール | ✓ |
定期的な棚卸し | 不要な権限の削除 | ✓ |
監視・監査
項目 | 確認内容 | ステータス |
---|---|---|
CloudTrail | API呼び出しの記録 | ✓ |
VPC Flow Logs | ネットワークトラフィック | ✓ |
GuardDuty | 脅威検知 | ✓ |
Config | コンプライアンス | ✓ |
パフォーマンステストと負荷試験
負荷試験の実施
# Apache Benchを使用した簡単な負荷試験
ab -n 1000 -c 50 http://$(terraform output -raw alb_dns_name)/health
# 結果の確認項目
# - Requests per second
# - Time per request
# - Transfer rate
# - Failed requests
CloudWatch メトリクスの確認
# ECSサービスのメトリクス
aws cloudwatch get-metric-statistics \
--namespace AWS/ECS \
--metric-name CPUUtilization \
--dimensions Name=ServiceName,Value=$(terraform output -raw ecs_service_name) \
Name=ClusterName,Value=$(terraform output -raw ecs_cluster_name) \
--start-time $(date -u -d '1 hour ago' --iso-8601) \
--end-time $(date -u --iso-8601) \
--period 300 \
--statistics Average
# RDSのメトリクス(RDSが有効な場合)
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS \
--metric-name DatabaseConnections \
--dimensions Name=DBInstanceIdentifier,Value=$(terraform output -raw rds_instance_id) \
--start-time $(date -u -d '1 hour ago' --iso-8601) \
--end-time $(date -u --iso-8601) \
--period 300 \
--statistics Average
コスト最適化の総まとめ
環境別のコスト最適化戦略
環境 | 最適化方法 | 削減率 |
---|---|---|
開発環境 | NAT Gateway無効化、小規模インスタンス | 60-70% |
ステージング環境 | 夜間停止、Reserved Instances | 40-50% |
本番環境 | Auto Scaling、Spot Instances活用 | 20-30% |
サービス別月額コスト目安(東京リージョン)
※以下のコスト目安はAI(Claude Opus4.1)によって算出しました。実際のコストは使用状況により変動します。
サービス | 開発環境 | 本番環境 | 備考 |
---|---|---|---|
VPC (NAT Gateway) | $0 | $90 | 開発は無効化 |
ECS Fargate | $30 | $200 | タスク数とCPU/メモリによる |
RDS | $30 | $150 | Multi-AZ、バックアップ含む |
ALB | $25 | $25 | 基本料金 |
S3 + CloudFront | $5 | $50 | トラフィック依存 |
WAF | $10 | $10 | 基本料金 |
GuardDuty | $5 | $20 | イベント数依存 |
合計 | $105 | $545 | 概算値 |
コスト削減のTips
-
開発環境の最適化
- 営業時間外の自動停止
- NAT Gatewayの無効化
- 小規模インスタンスサイズ
-
Reserved InstancesとSavings Plans
- 1年または3年契約で最大72%削減
- 本番環境の予測可能なワークロード
-
Spot Instances
- バッチ処理やテスト環境で活用
- 最大90%のコスト削減
-
S3ストレージクラス
- アクセス頻度に応じた最適化
- Intelligent-Tieringの活用
トラブルシューティング
WAF関連
問題: 正常なリクエストがブロックされる
確認事項 | 対処方法 |
---|---|
ブロックルールの特定 | CloudWatch Logsでルール名を確認 |
誤検知の修正 | 特定のルールを除外設定 |
レート制限の調整 | しきい値を環境に応じて調整 |
GuardDuty関連
問題: 誤検知が多い
確認事項 | 対処方法 |
---|---|
信頼済みIPリスト | 社内IPを信頼リストに追加 |
抑制ルール | 特定のFindingタイプを抑制 |
重要度の調整 | 通知しきい値を上げる |
Config関連
問題: コンプライアンス違反が検出される
確認事項 | 対処方法 |
---|---|
違反リソースの特定 | Config Dashboardで確認 |
修正アクション | 自動修正ルールの設定 |
例外処理 | 特定リソースを除外 |
ハンズオンシリーズの総まとめ
構築したアーキテクチャの全体像
14回にわたるハンズオンで、以下の本番運用可能なインフラストラクチャを構築しました。
- ネットワーク基盤: VPC、サブネット、ルーティング
- セキュリティ層: Security Groups、NACL、WAF
- コンピューティング: ECS Fargate、Auto Scaling
- データベース: RDS PostgreSQL、Multi-AZ
- ストレージ: S3、ECR
- 配信: CloudFront CDN
- 監視: CloudWatch、X-Ray、GuardDuty
- CI/CD: GitHub Actions、自動デプロイ
- IaC: Terraform による完全自動化
習得したスキル
インフラストラクチャ設計
- マルチAZ構成による高可用性
- プライベートサブネットによるセキュアな設計
- VPCエンドポイントによる内部通信
セキュリティ実装
- 多層防御アーキテクチャ
- 最小権限の原則
- 暗号化と監査ログ
運用自動化
- Infrastructure as Code
- CI/CDパイプライン
- 監視とアラート
コスト最適化
- 環境別の最適化戦略
- リソースの適切なサイジング
- 使用状況の可視化
次のステップへの提案
-
カスタムドメインの設定
- Route 53でのドメイン管理
- SSL証明書の設定
-
コンテナオーケストレーション
- EKS(Elastic Kubernetes Service)への移行
- Service Meshの導入
-
サーバーレスアーキテクチャ
- Lambda関数の活用
- API Gateway統合
-
データ分析基盤
- Kinesis Data Streams
- ElasticSearchService
-
災害復旧対策
- マルチリージョン構成
- バックアップとリストア戦略
まとめ
本ハンズオンシリーズを通じて、AWSの主要サービスを活用した本番運用可能なWebアプリケーション基盤を構築しました。
構築したインフラストラクチャは、セキュリティ、可用性、拡張性、コスト効率のバランスを考慮した実践的な構成となっています。Terraformによる完全な自動化により、環境の再現性と保守性も確保されています。
これらの知識と経験を基に、さらに高度なクラウドアーキテクチャの設計と実装に挑戦していただければ幸いです。
継続的な学習と実践により、クラウドネイティブなアプリケーション開発のスキルをさらに向上させていきましょう。内で作業を進めます。
# コンテナを起動してbashに入る
docker-compose run --rm terraform
これ以降のコマンドは、すべてこのコンテナ内で実行します。
基礎知識
セキュリティの多層防御
AWSにおけるセキュリティは、単一の対策ではなく複数の層で防御することが重要です。
WEBアプリケーション開発者の視点から見ると
開発環境では簡略化されがちなセキュリティ設定も、本番環境では多層的な防御が必要です。例えば、SQLインジェクション対策は、アプリケーションレベルのバリデーションだけでなく、WAFでのブロック、最小権限のIAMロール設定、データベースの監査ログなど、複数の層で防御します。
セキュリティレイヤーの種類
レイヤー | 対策内容 | AWSサービス |
---|---|---|
ネットワーク層 | DDoS攻撃、不正アクセス | Security Group、NACL、VPC Flow Logs |
アプリケーション層 | SQLインジェクション、XSS | WAF、Shield |
データ層 | 暗号化、アクセス制御 | KMS、S3暗号化、RDS暗号化 |
アイデンティティ層 | 認証・認可 | IAM、Cognito、Secrets Manager |
監視・監査層 | 異常検知、コンプライアンス | GuardDuty、Config、CloudTrail |
Zero Trust セキュリティモデル
従来の境界型セキュリティから、すべてのアクセスを検証するZero Trustモデルへの移行が推奨されています。
原則 | 実装方法 | 適用例 |
---|---|---|
最小権限の原則 | IAMポリシーの細分化 | タスクロールは必要なS3バケットのみアクセス可能 |
常時検証 | 継続的な監視 | GuardDutyによる異常検知 |
暗号化 | 転送時・保存時の暗号化 | HTTPS通信、S3/RDS暗号化 |
ログ記録 | すべての操作を記録 | CloudTrail、VPC Flow Logs |
今回作成するセキュリティ構成
カテゴリ | リソース | 説明 |
---|---|---|
WAF | WebACL | アプリケーション層の攻撃防御 |
Managed Rules | AWS管理ルールセット | |
Custom Rules | レート制限、IP制限 | |
GuardDuty | Detector | 脅威検知エンジン |
Threat Intelligence | 脅威インテリジェンスフィード | |
Config | Configuration Recorder | リソース設定の記録 |
Rules | コンプライアンスルール | |
IAM | ポリシー最適化 | 最小権限の実装 |
MFA強制 | 多要素認証の設定 |
Terraformコードの実装
WAF(Web Application Firewall)の実装
# terraform/modules/security/waf.tf
# WAF WebACL
resource "aws_wafv2_web_acl" "main" {
name = "${var.project_name}-${var.environment}-waf"
scope = "REGIONAL"
default_action {
allow {}
}
# AWS Managed Rule - Core Rule Set
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
# 特定のルールを除外する場合
excluded_rule {
name = "SizeRestrictions_BODY"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CommonRuleSetMetric"
sampled_requests_enabled = true
}
}
# SQL Injection対策
rule {
name = "AWSManagedRulesSQLiRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "SQLiRuleSetMetric"
sampled_requests_enabled = true
}
}
# レート制限ルール
rule {
name = "RateLimitRule"
priority = 3
action {
block {}
}
statement {
rate_based_statement {
limit = var.waf_rate_limit
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimitMetric"
sampled_requests_enabled = true
}
}
# 地理的制限(必要に応じて)
dynamic "rule" {
for_each = length(var.allowed_countries) > 0 ? [1] : []
content {
name = "GeoLocationRule"
priority = 4
action {
block {}
}
statement {
not_statement {
statement {
geo_match_statement {
country_codes = var.allowed_countries
}
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "GeoLocationMetric"
sampled_requests_enabled = true
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "WAFMetric"
sampled_requests_enabled = true
}
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-waf"
}
)
}
# ALBへのWAF関連付け
resource "aws_wafv2_web_acl_association" "alb" {
resource_arn = var.alb_arn
web_acl_arn = aws_wafv2_web_acl.main.arn
}
WAF設定パラメータの説明
パラメータ | 設定値 | 用途・説明 |
---|---|---|
scope | REGIONAL | ALBに関連付けるためREGIONALを指定 |
default_action | allow | デフォルトは許可、ルールでブロック |
AWSManagedRulesCommonRuleSet | 有効 | 一般的な脆弱性から保護(XSS、パストラバーサルなど) |
AWSManagedRulesSQLiRuleSet | 有効 | SQLインジェクション攻撃を検知・ブロック |
rate_based_statement.limit | 2000 | 5分間のリクエスト数制限 |
geo_match_statement | オプション | 特定の国からのアクセスのみ許可 |
cloudwatch_metrics_enabled | true | メトリクスを CloudWatch に送信 |
GuardDutyの実装
# terraform/modules/security/guardduty.tf
# GuardDuty Detector
resource "aws_guardduty_detector" "main" {
enable = true
finding_publishing_frequency = var.guardduty_finding_frequency
# S3 Logs Protection
datasources {
s3_logs {
enable = var.enable_s3_protection
}
# EKS Audit Logs(ECS使用時は不要だが将来の拡張用)
kubernetes {
audit_logs {
enable = false
}
}
# Malware Protection
malware_protection {
scan_ec2_instance_with_findings {
ebs_volumes {
enable = var.enable_malware_protection
}
}
}
}
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-guardduty"
}
)
}
# SNSトピックへの通知設定
resource "aws_cloudwatch_event_rule" "guardduty" {
name = "${var.project_name}-${var.environment}-guardduty-findings"
description = "GuardDuty findings notification"
event_pattern = jsonencode({
source = ["aws.guardduty"]
detail-type = ["GuardDuty Finding"]
detail = {
severity = [
{ numeric = [">=", var.guardduty_severity_threshold] }
]
}
})
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-guardduty-rule"
}
)
}
resource "aws_cloudwatch_event_target" "guardduty_sns" {
rule = aws_cloudwatch_event_rule.guardduty.name
target_id = "SendToSNS"
arn = var.alarm_sns_topic_arn
}
GuardDuty設定パラメータの説明
パラメータ | 設定値 | 用途・説明 |
---|---|---|
enable | true | GuardDutyを有効化 |
finding_publishing_frequency | FIFTEEN_MINUTES | 検出結果の更新頻度(本番は即時通知推奨) |
s3_logs.enable | true | S3アクセスログの異常を検知 |
malware_protection | オプション | EC2インスタンスのマルウェアスキャン |
severity_threshold | 4 | 通知する脅威レベル(4=Medium以上) |
event_pattern | CloudWatch Events | 特定の重要度以上の脅威を通知 |
AWS Configの実装
# terraform/modules/security/config.tf
# Config用S3バケット
resource "aws_s3_bucket" "config" {
bucket = "${var.project_name}-${var.environment}-config-logs"
force_destroy = var.environment != "prod"
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-config-logs"
}
)
}
resource "aws_s3_bucket_public_access_block" "config" {
bucket = aws_s3_bucket.config.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Config Recorder用IAMロール
resource "aws_iam_role" "config" {
name = "${var.project_name}-${var.environment}-config-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "config.amazonaws.com"
}
}
]
})
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-config-role"
}
)
}
resource "aws_iam_role_policy" "config" {
name = "${var.project_name}-${var.environment}-config-policy"
role = aws_iam_role.config.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetBucketAcl",
"s3:ListBucket"
]
Resource = aws_s3_bucket.config.arn
},
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject"
]
Resource = "${aws_s3_bucket.config.arn}/*"
Condition = {
StringLike = {
"s3:x-amz-server-side-encryption" = "AES256"
}
}
},
{
Effect = "Allow"
Action = [
"config:Put*"
]
Resource = "*"
}
]
})
}
# Configuration Recorder
resource "aws_config_configuration_recorder" "main" {
name = "${var.project_name}-${var.environment}-recorder"
role_arn = aws_iam_role.config.arn
recording_group {
all_supported = true
include_global_resource_types = true
}
depends_on = [aws_config_delivery_channel.main]
}
# Delivery Channel
resource "aws_config_delivery_channel" "main" {
name = "${var.project_name}-${var.environment}-channel"
s3_bucket_name = aws_s3_bucket.config.bucket
snapshot_delivery_properties {
delivery_frequency = "TwentyFour_Hours"
}
}
# Configルール - 必須タグの確認
resource "aws_config_config_rule" "required_tags" {
name = "${var.project_name}-${var.environment}-required-tags"
source {
owner = "AWS"
source_identifier = "REQUIRED_TAGS"
}
input_parameters = jsonencode({
tag1Key = "Environment"
tag2Key = "Project"
tag3Key = "ManagedBy"
})
depends_on = [aws_config_configuration_recorder.main]
}
# Configルール - S3バケットの暗号化確認
resource "aws_config_config_rule" "s3_encryption" {
name = "${var.project_name}-${var.environment}-s3-encryption"
source {
owner = "AWS"
source_identifier = "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
}
depends_on = [aws_config_configuration_recorder.main]
}
# Configルール - RDSの暗号化確認
resource "aws_config_config_rule" "rds_encryption" {
name = "${var.project_name}-${var.environment}-rds-encryption"
source {
owner = "AWS"
source_identifier = "RDS_STORAGE_ENCRYPTED"
}
depends_on = [aws_config_configuration_recorder.main]
}
Config設定パラメータの説明
パラメータ | 設定値 | 用途・説明 |
---|---|---|
all_supported | true | すべてのサポートされるリソースを記録 |
include_global_resource_types | true | IAMなどグローバルリソースも記録 |
delivery_frequency | TwentyFour_Hours | 設定スナップショットの配信頻度 |
REQUIRED_TAGS | Environment, Project, ManagedBy | 必須タグの確認ルール |
S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED | 有効 | S3暗号化の確認 |
RDS_STORAGE_ENCRYPTED | 有効 | RDS暗号化の確認 |
IAMロールとポリシーの最適化
# terraform/modules/security/iam_optimization.tf
# ECSタスクロールの最適化
resource "aws_iam_role_policy" "ecs_task_optimized" {
name = "${var.project_name}-${var.environment}-ecs-task-optimized"
role = var.ecs_task_role_id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "S3SpecificBucketAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = [
"${var.app_bucket_arn}/*"
]
},
{
Sid = "SecretsManagerSpecificAccess"
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = [
"arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:${var.project_name}/${var.environment}/*"
]
},
{
Sid = "CloudWatchLogsAccess"
Effect = "Allow"
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = [
"${var.log_group_arn}:*"
]
},
{
Sid = "XRayAccess"
Effect = "Allow"
Action = [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords"
]
Resource = "*"
}
]
})
}
# 開発者用IAMポリシー(読み取り専用)
resource "aws_iam_policy" "developer_readonly" {
name = "${var.project_name}-${var.environment}-developer-readonly"
description = "Read-only access for developers"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ec2:Describe*",
"ecs:Describe*",
"ecs:List*",
"s3:List*",
"s3:GetObject",
"rds:Describe*",
"cloudwatch:Describe*",
"cloudwatch:Get*",
"cloudwatch:List*",
"logs:Describe*",
"logs:Get*",
"logs:FilterLogEvents"
]
Resource = "*"
},
{
Effect = "Deny"
Action = [
"iam:*",
"kms:Delete*",
"kms:ScheduleKeyDeletion"
]
Resource = "*"
}
]
})
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${var.environment}-developer-policy"
}
)
}
IAM最適化パラメータの説明
パラメータ | 設定値 | 用途・説明 |
---|---|---|
S3SpecificBucketAccess | 特定バケットのみ | 最小権限の原則に従い必要なバケットのみアクセス許可 |
SecretsManagerSpecificAccess | プロジェクト専用 | 環境別のシークレットのみアクセス可能 |
CloudWatchLogsAccess | 特定ロググループ | アプリケーション専用のロググループのみ書き込み可能 |
developer_readonly | 読み取り専用 | 開発者は参照のみ、変更不可 |
Deny Actions | IAM、KMS削除 | 重要な操作は明示的に拒否 |
セキュリティグループの見直し
# terraform/modules/security/sg_optimization.tf
# ALBセキュリティグループの最適化
resource "aws_security_group_rule" "alb_ingress_https" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = var.allowed_cidr_blocks # 特定のIPレンジのみ許可
security_group_id = var.alb_security_group_id
description = "HTTPS from allowed IPs only"
}
# ECSセキュリティグループの最適化
resource "aws_security_group_rule" "ecs_ingress_from_alb_only" {
type = "ingress"
from_port = var.app_port
to_port = var.app_port
protocol = "tcp"
source_security_group_id = var.alb_security_group_id
security_group_id = var.ecs_security_group_id
description = "Allow traffic only from ALB"
}
# RDSセキュリティグループの最適化
resource "aws_security_group_rule" "rds_ingress_from_ecs_only" {
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
source_security_group_id = var.ecs_security_group_id
security_group_id = var.rds_security_group_id
description = "PostgreSQL access only from ECS tasks"
}
変数定義
# terraform/environments/dev/variables.tf に追加
variable "waf_rate_limit" {
description = "WAF rate limit per 5 minutes"
type = number
default = 2000
}
variable "allowed_countries" {
description = "List of allowed country codes"
type = list(string)
default = ["JP", "US"]
}
variable "guardduty_finding_frequency" {
description = "GuardDuty finding publishing frequency"
type = string
default = "FIFTEEN_MINUTES"
}
variable "guardduty_severity_threshold" {
description = "GuardDuty severity threshold for notifications"
type = number
default = 4
}
variable "enable_s3_protection" {
description = "Enable S3 protection in GuardDuty"
type = bool
default = true
}
variable "enable_malware_protection" {
description = "Enable malware protection in GuardDuty"
type = bool
default = false
}
variable "allowed_cidr_blocks" {
description = "Allowed CIDR blocks for ALB access"
type = list(string)
default = ["0.0.0.0/0"]
}
terraform.tfvarsの設定
# terraform/terraform.tfvars に追加
# WAF有効化
enable_waf = true
# GuardDuty有効化
enable_guardduty = true
# Config有効化
enable_config = true
環境別の設定
環境ごとに異なる設定は、各環境のディレクトリで管理します。
# terraform/environments/dev/terraform.tfvars
# 開発環境の設定
waf_rate_limit = 5000 # 開発環境は緩めの制限
allowed_countries = ["JP", "US"]
guardduty_finding_frequency = "ONE_HOUR" # 開発環境は低頻度
guardduty_severity_threshold = 7 # 高い脅威のみ通知
enable_malware_protection = false # コスト削減のため無効
allowed_cidr_blocks = ["0.0.0.0/0"] # 開発環境は制限なし
# terraform/environments/prod/terraform.tfvars
# 本番環境の設定
waf_rate_limit = 2000 # 厳格な制限
allowed_countries = ["JP"] # 日本のみ許可
guardduty_finding_frequency = "FIFTEEN_MINUTES" # 高頻度
guardduty_severity_threshold = 4 # 中程度以上の脅威を通知
enable_malware_protection = true # 本番環境は有効
allowed_cidr_blocks = ["203.0.113.0/24"] # 特定IPのみ許可
リソースを作成
Terraformコンテナ内で以下のコマンドを実行します。
# terraformディレクトリへ移動
cd terraform
# 実行計画の確認
terraform plan
# リソースの作成
terraform apply
WAF、GuardDuty、Configの設定には5〜10分程度かかります。
動作確認
WAFの動作確認
# WAF WebACL の確認
aws wafv2 list-web-acls --scope REGIONAL --region ap-northeast-1
# ブロックされたリクエストの確認
aws wafv2 get-sampled-requests \
--web-acl-arn $(terraform output -raw waf_web_acl_arn) \
--rule-metric-name RateLimitMetric \
--scope REGIONAL \
--time-window StartTime=$(date -u -d '1 hour ago' +%s),EndTime=$(date +%s) \
--max-items 10
GuardDutyの確認
# GuardDuty Detectorの状態確認
aws guardduty list-detectors
# 検出結果の確認(ある場合)
DETECTOR_ID=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
aws guardduty list-findings --detector-id $DETECTOR_ID
AWS Configの確認
# Configuration Recorderの状態確認
aws configservice describe-configuration-recorder-status
# コンプライアンスルールの確認
aws configservice describe-compliance-by-config-rule \
--config-rule-names $(terraform output -raw config_required_tags_rule_name)
IAMポリシーの確認
# ECSタスクロールのポリシー確認
aws iam get-role-policy \
--role-name $(terraform output -raw ecs_task_role_name) \
--policy-name $(terraform output -raw ecs_task_policy_name)
# ポリシーシミュレーター
aws iam simulate-principal-policy \
--policy-source-arn $(terraform output -raw ecs_task_role_arn) \
--action-names s3:GetObject s3:PutObject \
--resource-arns "arn:aws:s3:::other-bucket/*"
セキュリティベストプラクティスのチェックリスト
ネットワークセキュリティ
項目 | 確認内容 | ステータス |
---|---|---|
VPC設計 | プライベートサブネットの活用 | ✓ |
セキュリティグループ | 最小権限の原則 | ✓ |
NACLs | 追加の防御層 | ✓ |
VPCエンドポイント | プライベート通信 | ✓ |
アプリケーションセキュリティ
項目 | 確認内容 | ステータス |
---|---|---|
WAF | SQLインジェクション対策 | ✓ |
HTTPS | 暗号化通信 | ✓ |
CORS | 適切な設定 | ✓ |
セキュリティヘッダー | CSP、X-Frame-Options | ✓ |
データセキュリティ
項目 | 確認内容 | ステータス |
---|---|---|
S3暗号化 | デフォルト暗号化 | ✓ |
RDS暗号化 | 保存時暗号化 | ✓ |
Secrets Manager | 機密情報の管理 | ✓ |
バックアップ | 自動バックアップ | ✓ |
アクセス管理
項目 | 確認内容 | ステータス |
---|---|---|
IAM最小権限 | 必要最小限の権限 | ✓ |
MFA | 多要素認証 | ✓ |
ロールベース | ユーザーではなくロール | ✓ |
定期的な棚卸し | 不要な権限の削除 | ✓ |
監視・監査
項目 | 確認内容 | ステータス |
---|---|---|
CloudTrail | API呼び出しの記録 | ✓ |
VPC Flow Logs | ネットワークトラフィック | ✓ |
GuardDuty | 脅威検知 | ✓ |
Config | コンプライアンス | ✓ |
パフォーマンステストと負荷試験
負荷試験の実施
# Apache Benchを使用した簡単な負荷試験
ab -n 1000 -c 50 http://$(terraform output -raw alb_dns_name)/health
# 結果の確認項目
# - Requests per second
# - Time per request
# - Transfer rate
# - Failed requests
CloudWatch メトリクスの確認
# ECSサービスのメトリクス
aws cloudwatch get-metric-statistics \
--namespace AWS/ECS \
--metric-name CPUUtilization \
--dimensions Name=ServiceName,Value=$(terraform output -raw ecs_service_name) \
Name=ClusterName,Value=$(terraform output -raw ecs_cluster_name) \
--start-time $(date -u -d '1 hour ago' --iso-8601) \
--end-time $(date -u --iso-8601) \
--period 300 \
--statistics Average
# RDSのメトリクス
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS \
--metric-name DatabaseConnections \
--dimensions Name=DBInstanceIdentifier,Value=$(terraform output -raw rds_instance_id) \
--start-time $(date -u -d '1 hour ago' --iso-8601) \
--end-time $(date -u --iso-8601) \
--period 300 \
--statistics Average
コスト最適化の総まとめ
環境別のコスト最適化戦略
環境 | 最適化方法 | 削減率 |
---|---|---|
開発環境 | NAT Gateway無効化、小規模インスタンス | 60-70% |
ステージング環境 | 夜間停止、Reserved Instances | 40-50% |
本番環境 | Auto Scaling、Spot Instances活用 | 20-30% |
サービス別月額コスト目安(東京リージョン)
※以下のコスト目安はAI(Claude Opus4.1)によって算出しました。実際のコストは使用状況により変動します。
サービス | 開発環境 | 本番環境 | 備考 |
---|---|---|---|
VPC (NAT Gateway) | $0 | $90 | 開発は無効化 |
ECS Fargate | $30 | $200 | タスク数とCPU/メモリによる |
RDS | $30 | $150 | Multi-AZ、バックアップ含む |
ALB | $25 | $25 | 基本料金 |
S3 + CloudFront | $5 | $50 | トラフィック依存 |
WAF | $10 | $10 | 基本料金 |
GuardDuty | $5 | $20 | イベント数依存 |
合計 | $105 | $545 | 概算値 |
コスト削減のTips
-
開発環境の最適化
- 営業時間外の自動停止
- NAT Gatewayの無効化
- 小規模インスタンスサイズ
-
Reserved InstancesとSavings Plans
- 1年または3年契約で最大72%削減
- 本番環境の予測可能なワークロード
-
Spot Instances
- バッチ処理やテスト環境で活用
- 最大90%のコスト削減
-
S3ストレージクラス
- アクセス頻度に応じた最適化
- Intelligent-Tieringの活用
トラブルシューティング
WAF関連
問題: 正常なリクエストがブロックされる
確認事項 | 対処方法 |
---|---|
ブロックルールの特定 | CloudWatch Logsでルール名を確認 |
誤検知の修正 | 特定のルールを除外設定 |
レート制限の調整 | しきい値を環境に応じて調整 |
GuardDuty関連
問題: 誤検知が多い
確認事項 | 対処方法 |
---|---|
信頼済みIPリスト | 社内IPを信頼リストに追加 |
抑制ルール | 特定のFindingタイプを抑制 |
重要度の調整 | 通知しきい値を上げる |
Config関連
問題: コンプライアンス違反が検出される
確認事項 | 対処方法 |
---|---|
違反リソースの特定 | Config Dashboardで確認 |
修正アクション | 自動修正ルールの設定 |
例外処理 | 特定リソースを除外 |
ハンズオンシリーズの総まとめ
構築したアーキテクチャの全体像
14回にわたるハンズオンで、以下の本番運用可能なインフラストラクチャを構築しました。
- ネットワーク基盤: VPC、サブネット、ルーティング
- セキュリティ層: Security Groups、NACL、WAF
- コンピューティング: ECS Fargate、Auto Scaling
- データベース: RDS PostgreSQL、Multi-AZ
- ストレージ: S3、ECR
- 配信: CloudFront CDN
- 監視: CloudWatch、X-Ray、GuardDuty
- CI/CD: GitHub Actions、自動デプロイ
- IaC: Terraform による完全自動化
習得したスキル
インフラストラクチャ設計
- マルチAZ構成による高可用性
- プライベートサブネットによるセキュアな設計
- VPCエンドポイントによる内部通信
セキュリティ実装
- 多層防御アーキテクチャ
- 最小権限の原則
- 暗号化と監査ログ
運用自動化
- Infrastructure as Code
- CI/CDパイプライン
- 監視とアラート
コスト最適化
- 環境別の最適化戦略
- リソースの適切なサイジング
- 使用状況の可視化
次のステップへの提案
-
カスタムドメインの設定
- Route 53でのドメイン管理
- SSL証明書の設定
-
コンテナオーケストレーション
- EKS(Elastic Kubernetes Service)への移行
- Service Meshの導入
-
サーバーレスアーキテクチャ
- Lambda関数の活用
- API Gateway統合
-
データ分析基盤
- Kinesis Data Streams
- ElasticSearchService
-
災害復旧対策
- マルチリージョン構成
- バックアップとリストア戦略
まとめ
本ハンズオンシリーズを通じて、AWSの主要サービスを活用した本番運用可能なWebアプリケーション基盤を構築しました。
構築したインフラストラクチャは、セキュリティ、可用性、拡張性、コスト効率のバランスを考慮した実践的な構成となっています。Terraformによる完全な自動化により、環境の再現性と保守性も確保されています。
これらの知識と経験を基に、さらに高度なクラウドアーキテクチャの設計と実装に挑戦していただければ幸いです。
継続的な学習と実践により、クラウドネイティブなアプリケーション開発のスキルをさらに向上させていきましょう。