【AWSハンズオン】第14回 セキュリティベストプラクティスと総まとめ

【AWSハンズオン】第14回 セキュリティベストプラクティスと総まとめ

AWSインフラストラクチャのセキュリティを強化し、本番運用に向けた最終調整を行います。 WAF、GuardDuty、Configによるセキュリティ監視と、IAMロールの最適化、そしてこれまでのハンズオンの総まとめを行います。

AWS #AWS#API#ハンズオン

【AWSハンズオン】第14回 セキュリティベストプラクティスと総まとめ

サムネイル

AWSインフラストラクチャのセキュリティを強化し、本番運用に向けた最終調整を行います。 WAF、GuardDuty、Configによるセキュリティ監視と、IAMロールの最適化、そしてこれまでのハンズオンの総まとめを行います。

更新日: 8/17/2025

前提条件

  • 第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

  1. 開発環境の最適化

    • 営業時間外の自動停止
    • NAT Gatewayの無効化
    • 小規模インスタンスサイズ
  2. Reserved InstancesとSavings Plans

    • 1年または3年契約で最大72%削減
    • 本番環境の予測可能なワークロード
  3. Spot Instances

    • バッチ処理やテスト環境で活用
    • 最大90%のコスト削減
  4. S3ストレージクラス

    • アクセス頻度に応じた最適化
    • Intelligent-Tieringの活用

トラブルシューティング

WAF関連

問題: 正常なリクエストがブロックされる

確認事項 対処方法
ブロックルールの特定 CloudWatch Logsでルール名を確認
誤検知の修正 特定のルールを除外設定
レート制限の調整 しきい値を環境に応じて調整

GuardDuty関連

問題: 誤検知が多い

確認事項 対処方法
信頼済みIPリスト 社内IPを信頼リストに追加
抑制ルール 特定のFindingタイプを抑制
重要度の調整 通知しきい値を上げる

Config関連

問題: コンプライアンス違反が検出される

確認事項 対処方法
違反リソースの特定 Config Dashboardで確認
修正アクション 自動修正ルールの設定
例外処理 特定リソースを除外

ハンズオンシリーズの総まとめ

構築したアーキテクチャの全体像

14回にわたるハンズオンで、以下の本番運用可能なインフラストラクチャを構築しました。

  1. ネットワーク基盤: VPC、サブネット、ルーティング
  2. セキュリティ層: Security Groups、NACL、WAF
  3. コンピューティング: ECS Fargate、Auto Scaling
  4. データベース: RDS PostgreSQL、Multi-AZ
  5. ストレージ: S3、ECR
  6. 配信: CloudFront CDN
  7. 監視: CloudWatch、X-Ray、GuardDuty
  8. CI/CD: GitHub Actions、自動デプロイ
  9. IaC: Terraform による完全自動化

習得したスキル

インフラストラクチャ設計

  • マルチAZ構成による高可用性
  • プライベートサブネットによるセキュアな設計
  • VPCエンドポイントによる内部通信

セキュリティ実装

  • 多層防御アーキテクチャ
  • 最小権限の原則
  • 暗号化と監査ログ

運用自動化

  • Infrastructure as Code
  • CI/CDパイプライン
  • 監視とアラート

コスト最適化

  • 環境別の最適化戦略
  • リソースの適切なサイジング
  • 使用状況の可視化

次のステップへの提案

  1. カスタムドメインの設定

    • Route 53でのドメイン管理
    • SSL証明書の設定
  2. コンテナオーケストレーション

    • EKS(Elastic Kubernetes Service)への移行
    • Service Meshの導入
  3. サーバーレスアーキテクチャ

    • Lambda関数の活用
    • API Gateway統合
  4. データ分析基盤

    • Kinesis Data Streams
    • ElasticSearchService
  5. 災害復旧対策

    • マルチリージョン構成
    • バックアップとリストア戦略

まとめ

本ハンズオンシリーズを通じて、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

  1. 開発環境の最適化

    • 営業時間外の自動停止
    • NAT Gatewayの無効化
    • 小規模インスタンスサイズ
  2. Reserved InstancesとSavings Plans

    • 1年または3年契約で最大72%削減
    • 本番環境の予測可能なワークロード
  3. Spot Instances

    • バッチ処理やテスト環境で活用
    • 最大90%のコスト削減
  4. S3ストレージクラス

    • アクセス頻度に応じた最適化
    • Intelligent-Tieringの活用

トラブルシューティング

WAF関連

問題: 正常なリクエストがブロックされる

確認事項 対処方法
ブロックルールの特定 CloudWatch Logsでルール名を確認
誤検知の修正 特定のルールを除外設定
レート制限の調整 しきい値を環境に応じて調整

GuardDuty関連

問題: 誤検知が多い

確認事項 対処方法
信頼済みIPリスト 社内IPを信頼リストに追加
抑制ルール 特定のFindingタイプを抑制
重要度の調整 通知しきい値を上げる

Config関連

問題: コンプライアンス違反が検出される

確認事項 対処方法
違反リソースの特定 Config Dashboardで確認
修正アクション 自動修正ルールの設定
例外処理 特定リソースを除外

ハンズオンシリーズの総まとめ

構築したアーキテクチャの全体像

14回にわたるハンズオンで、以下の本番運用可能なインフラストラクチャを構築しました。

  1. ネットワーク基盤: VPC、サブネット、ルーティング
  2. セキュリティ層: Security Groups、NACL、WAF
  3. コンピューティング: ECS Fargate、Auto Scaling
  4. データベース: RDS PostgreSQL、Multi-AZ
  5. ストレージ: S3、ECR
  6. 配信: CloudFront CDN
  7. 監視: CloudWatch、X-Ray、GuardDuty
  8. CI/CD: GitHub Actions、自動デプロイ
  9. IaC: Terraform による完全自動化

習得したスキル

インフラストラクチャ設計

  • マルチAZ構成による高可用性
  • プライベートサブネットによるセキュアな設計
  • VPCエンドポイントによる内部通信

セキュリティ実装

  • 多層防御アーキテクチャ
  • 最小権限の原則
  • 暗号化と監査ログ

運用自動化

  • Infrastructure as Code
  • CI/CDパイプライン
  • 監視とアラート

コスト最適化

  • 環境別の最適化戦略
  • リソースの適切なサイジング
  • 使用状況の可視化

次のステップへの提案

  1. カスタムドメインの設定

    • Route 53でのドメイン管理
    • SSL証明書の設定
  2. コンテナオーケストレーション

    • EKS(Elastic Kubernetes Service)への移行
    • Service Meshの導入
  3. サーバーレスアーキテクチャ

    • Lambda関数の活用
    • API Gateway統合
  4. データ分析基盤

    • Kinesis Data Streams
    • ElasticSearchService
  5. 災害復旧対策

    • マルチリージョン構成
    • バックアップとリストア戦略

まとめ

本ハンズオンシリーズを通じて、AWSの主要サービスを活用した本番運用可能なWebアプリケーション基盤を構築しました。

構築したインフラストラクチャは、セキュリティ、可用性、拡張性、コスト効率のバランスを考慮した実践的な構成となっています。Terraformによる完全な自動化により、環境の再現性と保守性も確保されています。

これらの知識と経験を基に、さらに高度なクラウドアーキテクチャの設計と実装に挑戦していただければ幸いです。

継続的な学習と実践により、クラウドネイティブなアプリケーション開発のスキルをさらに向上させていきましょう。

検索

検索条件に一致する記事が見つかりませんでした