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

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

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

AWS #AWS#API#ハンズオン

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

サムネイル

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

更新日: 8/25/2025

本記事は、AWSハンズオンシリーズの第14回(最終回)として、本番環境で必須となるセキュリティサービスの実装を行います。

今回実装するセキュリティリソース

  • AWS WAF: Webアプリケーションファイアウォール
  • Amazon GuardDuty: 脅威検知サービス
  • AWS Config: リソース設定の監査とコンプライアンス
  • IAMポリシーの最適化: 最小権限の原則

前提条件

  • 第1回から第13回までの構築が完了していること
  • こちらから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"

        # 特定のルールを除外する場合
        rule_action_override {
          name = "SizeRestrictions_BODY"
          action_to_use {
            allow {}
          }
        }
      }
    }

    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 = "*"
      }
    ]
  })
}

# S3バケットポリシー(Config用)
resource "aws_s3_bucket_policy" "config" {
  count = var.enable_config ? 1 : 0

  bucket = aws_s3_bucket.config[0].id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AWSConfigBucketPermissionsCheck"
        Effect = "Allow"
        Principal = {
          Service = "config.amazonaws.com"
        }
        Action   = "s3:GetBucketAcl"
        Resource = aws_s3_bucket.config[0].arn
        Condition = {
          StringEquals = {
            "AWS:SourceAccount" = data.aws_caller_identity.current.account_id
          }
        }
      },
      {
        Sid    = "AWSConfigBucketExistenceCheck"
        Effect = "Allow"
        Principal = {
          Service = "config.amazonaws.com"
        }
        Action   = "s3:ListBucket"
        Resource = aws_s3_bucket.config[0].arn
        Condition = {
          StringEquals = {
            "AWS:SourceAccount" = data.aws_caller_identity.current.account_id
          }
        }
      },
      {
        Sid    = "AWSConfigBucketDelivery"
        Effect = "Allow"
        Principal = {
          Service = "config.amazonaws.com"
        }
        Action   = "s3:PutObject"
        Resource = "${aws_s3_bucket.config[0].arn}/*"
        Condition = {
          StringEquals = {
            "s3:x-amz-acl" = "bucket-owner-full-control"
            "AWS:SourceAccount" = data.aws_caller_identity.current.account_id
          }
        }
      }
    ]
  })
}

# 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_s3_bucket_policy.config]
}

# Delivery Channel(Configuration Recorderの後に作成)
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"
  }

  depends_on = [
    aws_config_configuration_recorder.main,
    aws_s3_bucket_policy.config
  ]
}

# 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,
    aws_config_delivery_channel.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 != "" && var.app_bucket_arn != "" ? 1 : 0

  name = "${var.project_name}-${var.environment}-ecs-task-optimized"
  role = var.ecs_task_role_id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = concat([
      {
        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    = "XRayAccess"
        Effect = "Allow"
        Action = [
          "xray:PutTraceSegments",
          "xray:PutTelemetryRecords"
        ]
        Resource = "*"
      }
    ], var.log_group_arn != "" ? [
      {
        Sid    = "CloudWatchLogsAccess"
        Effect = "Allow"
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Resource = [
          "${var.log_group_arn}:*"
        ]
      }
    ] : [])
  })
}

# 開発者用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"
    }
  )
}

#============================================
# セキュリティグループの最適化ルール
#============================================
# 注意: 以下のセキュリティグループルールは循環依存を避けるため削除してください
# 各モジュール内でセキュリティグループルールが管理されています
# 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削除 重要な操作は明示的に拒否

モジュールの変数定義

variableは重複しないように注意しましょう。

# 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"
}

# VPC関連(既存のセキュリティモジュールで必要)
variable "vpc_id" {
  description = "VPC ID"
  type        = string
}

variable "vpc_cidr_block" {
  description = "VPCのCIDRブロック"
  type        = string
}

variable "enable_vpc_endpoints" {
  description = "VPCエンドポイントを有効にするか"
  type        = bool
  default     = false
}

variable "app_port" {
  description = "アプリケーションポート番号"
  type        = number
  default     = 3000
}

# 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     = ""
}

# 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

terraform {
  required_version = ">= 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {
    # backend.hclで設定
  }
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Environment = var.environment
      Project     = var.project_name
      ManagedBy   = "terraform"
    }
  }
}

# 共通タグの定義
locals {
  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

# Network Module
module "network" {
  source = "../../modules/network"

  project_name       = var.project_name
  environment        = var.environment
  vpc_cidr           = var.vpc_cidr
  enable_nat_gateway = var.enable_nat_gateway
}

# Security Module
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設定
  waf_rate_limit               = var.waf_rate_limit
  allowed_countries            = var.allowed_countries
  
  # GuardDuty設定
  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.loadbalancer.alb_arn
  alb_security_group_id = module.loadbalancer.alb_security_group_id
  ecs_security_group_id = module.compute.ecs_security_group_id
  rds_security_group_id = var.enable_rds ? module.database[0].security_group_id : ""
  ecs_task_role_id      = module.compute.ecs_task_role_name
  app_bucket_arn        = module.storage.frontend_bucket_arn
  log_group_arn         = length(module.monitoring) > 0 ? module.monitoring[0].log_group_arn : ""
  alarm_sns_topic_arn   = length(module.monitoring) > 0 ? module.monitoring[0].sns_topic_arn : ""
  enable_rds            = var.enable_rds
}

# Storage Module
module "storage" {
  source = "../../modules/storage"

  project_name                = var.project_name
  environment                 = var.environment
  enable_versioning           = false
  cloudfront_distribution_arn = "" # 後で設定
  enable_alb_access_logs      = var.enable_alb_access_logs
  enable_cloudfront_logging   = false
  enable_cicd                 = var.enable_cicd
  artifacts_retention_days    = 7
  enable_backup               = false
}

# Database Module(開発環境では条件付き)
module "database" {
  count  = var.enable_rds ? 1 : 0 # RDS有効化変数で制御
  source = "../../modules/database"

  project_name          = var.project_name
  environment           = var.environment
  vpc_id                = module.network.vpc_id
  private_subnet_ids    = module.network.private_subnet_ids
  ecs_security_group_id = module.compute.ecs_security_group_id

  db_engine_version           = "15.8"
  db_instance_class           = "db.t3.micro"
  db_allocated_storage        = 20
  db_username                 = "postgres"
  multi_az                    = false
  backup_retention_period     = 1
  enable_performance_insights = false
  enable_rds_proxy            = false
}

# Compute Module
module "compute" {
  source = "../../modules/compute"

  project_name = var.project_name
  environment  = var.environment
  aws_region   = var.aws_region

  task_cpu       = "256"
  task_memory    = "512"
  desired_count  = 1
  container_port = 3000

  min_capacity = 1
  max_capacity = 2

  vpc_id                = module.network.vpc_id
  private_subnet_ids    = module.network.private_subnet_ids
  alb_security_group_id = module.loadbalancer.alb_security_group_id
  target_group_arn      = module.loadbalancer.target_group_arn
  db_secret_arn         = var.enable_rds && length(module.database) > 0 ? module.database[0].db_secret_arn : ""

  allowed_origins         = var.allowed_origins
  enable_security_headers = var.enable_security_headers
  enable_xray_tracing     = var.enable_xray_tracing
}

# Load Balancer Module
module "loadbalancer" {
  source = "../../modules/loadbalancer"

  project_name      = var.project_name
  environment       = var.environment
  vpc_id            = module.network.vpc_id
  public_subnet_ids = module.network.public_subnet_ids

  target_port         = 3000
  health_check_path   = "/health"
  enable_https        = var.alb_certificate_arn != ""
  certificate_arn     = var.alb_certificate_arn
  enable_access_logs  = var.enable_alb_access_logs
  access_logs_bucket  = var.enable_alb_access_logs ? module.storage.alb_logs_bucket_name : null
  enable_xray_tracing = var.enable_xray_tracing
}

# CDN Module
module "cdn" {
  source = "../../modules/cdn"

  project_name = var.project_name
  environment  = var.environment
  common_tags  = local.common_tags

  cloudfront_price_class    = "PriceClass_100"
  frontend_domain_name      = var.frontend_domain_name
  frontend_certificate_arn  = var.frontend_certificate_arn
  enable_cloudfront_logging = false

  s3_bucket_id                       = module.storage.frontend_bucket_id
  s3_bucket_domain_name              = module.storage.frontend_bucket_regional_domain_name
  alb_dns_name                       = module.loadbalancer.alb_dns_name
  alb_name                           = module.loadbalancer.alb_name
  cloudfront_logs_bucket_domain_name = ""
}

# Monitoring Module(アラームが有効な場合のみ)
module "monitoring" {
  count  = var.enable_cpu_alarm || var.enable_memory_alarm || var.enable_unhealthy_host_alarm ? 1 : 0
  source = "../../modules/monitoring"

  project_name = var.project_name
  environment  = var.environment
  aws_region   = var.aws_region
  common_tags  = local.common_tags

  ecs_log_group_name = "/ecs/${var.project_name}-${var.environment}"

  alarm_email                  = var.alarm_email
  enable_cpu_alarm             = var.enable_cpu_alarm
  enable_memory_alarm          = var.enable_memory_alarm
  enable_unhealthy_host_alarm  = var.enable_unhealthy_host_alarm
  cpu_utilization_threshold    = var.cpu_utilization_threshold
  memory_utilization_threshold = var.memory_utilization_threshold

  ecs_cluster_name   = module.compute.ecs_cluster_name
  ecs_service_name   = module.compute.ecs_service_name
  ecs_task_role_name = module.compute.ecs_task_role_name

  target_group_arn_suffix  = module.loadbalancer.target_group_arn_suffix
  load_balancer_arn_suffix = module.loadbalancer.alb_arn_suffix

  enable_xray_tracing    = var.enable_xray_tracing
  enable_alb_access_logs = var.enable_alb_access_logs
}

# CI/CD Module(enable_cicdが有効かつGitHubリポジトリが設定されている場合のみ)
module "cicd" {
  count  = var.enable_cicd && var.github_repository != "" ? 1 : 0
  source = "../../modules/cicd"

  project_name = var.project_name
  environment  = var.environment
  aws_region   = var.aws_region
  common_tags  = local.common_tags

  enable_github_oidc = true
  github_repository  = var.github_repository

  ecr_repository_arn = module.compute.ecr_repository_arn
  ecr_repository_url = module.compute.ecr_repository_url

  ecs_cluster_arn        = module.compute.ecs_cluster_arn
  ecs_cluster_name       = module.compute.ecs_cluster_name
  ecs_service_arn        = module.compute.ecs_service_arn
  ecs_service_name       = module.compute.ecs_service_name
  ecs_execution_role_arn = module.compute.ecs_execution_role_arn
  ecs_task_role_arn      = module.compute.ecs_task_role_arn

  frontend_bucket_arn         = module.storage.frontend_bucket_arn
  cloudfront_distribution_arn = module.cdn.cloudfront_distribution_arn

  artifacts_bucket_name = module.storage.artifacts_bucket_name
  artifacts_bucket_arn  = module.storage.artifacts_bucket_arn

  artifact_retention_days = 7
}

# VPC Endpoints Module(開発環境では無効)
module "vpc_endpoints" {
  count  = var.enable_vpc_endpoints ? 1 : 0
  source = "../../modules/vpc_endpoints"

  project_name = var.project_name
  environment  = var.environment
  aws_region   = var.aws_region
  common_tags  = local.common_tags

  vpc_id             = module.network.vpc_id
  vpc_cidr           = var.vpc_cidr
  private_subnet_ids = module.network.private_subnet_ids
  route_table_ids = concat(
    module.network.private_route_table_ids,
    [module.network.public_route_table_id]
  )

  enable_vpc_endpoints     = var.enable_vpc_endpoints
  enable_xray_tracing      = var.enable_xray_tracing
  enable_vpc_flow_logs     = var.enable_vpc_flow_logs
  flow_logs_retention_days = var.flow_logs_retention_days
}

# Data sources
data "aws_caller_identity" "current" {}

環境別変数定義

# terraform/environments/dev/variables.tf

# セキュリティ機能の有効化フラグ
variable "enable_waf" {
  description = "WAFを有効にするかどうか"
  type        = bool
  default     = false
}

variable "enable_guardduty" {
  description = "GuardDutyを有効にするか"
  type        = bool
  default     = false
}

variable "enable_config" {
  description = "AWS Configを有効にするか"
  type        = bool
  default     = false
}

# WAF関連の設定
variable "waf_rate_limit" {
  description = "WAF rate limit per 5 minutes"
  type        = number
  default     = 5000
}

variable "allowed_countries" {
  description = "List of allowed country codes"
  type        = list(string)
  default     = ["JP", "US"]
}

# GuardDuty関連の設定
variable "guardduty_finding_frequency" {
  description = "GuardDuty finding publishing frequency"
  type        = string
  default     = "ONE_HOUR"
  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     = 7
  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     = false
}

variable "enable_malware_protection" {
  description = "Enable malware protection in GuardDuty"
  type        = bool
  default     = false
}

variable "allowed_cidr_blocks" {
  description = "許可するCIDRブロック"
  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/environments/dev/terraform.tfvars

# プロジェクト設定
# project_name = "" 環境変数(TF_VAR_project_name)で設定しているが、環境別で個別指定の場合はコメントアウト解除
# environment = "dev" # variables.tfで定義されている環境名(dev/stg/prod)に合わせて設定

# 環境別機能有効化設定
# ========================================

# インフラストラクチャ
enable_rds         = true   # RDSデータベースを有効化(学習目的)
enable_nat_gateway = false  # NAT Gatewayを無効化(コスト削減)

# セキュリティ・監視
enable_waf                  = false # WAFを無効化(開発環境)
enable_vpc_endpoints        = true  # VPCエンドポイントを有効化(NAT Gateway無効のため必須)
enable_vpc_flow_logs        = false # VPC Flow Logsを無効化(コスト削減)
enable_alb_access_logs      = false # ALBアクセスログを無効化(コスト削減)
enable_xray_tracing         = false # X-Rayトレーシングを無効化(コスト削減)
enable_cloudfront_logging   = false # CloudFrontログを無効化(コスト削減)
enable_guardduty = true
enable_config    = true

# アラーム・監視
enable_cpu_alarm            = false # CPUアラームを無効化(開発環境)
enable_memory_alarm         = false # メモリアラームを無効化(開発環境)
enable_unhealthy_host_alarm = false # 異常ホストアラームを無効化(開発環境)

# CI/CD
# github_repository = "" # GitHubリポジトリ 環境変数(TF_VAR_github_repository)で設定しているが、環境別で個別指定の場合はコメントアウト解除

# その他の環境固有設定
# ========================================
allowed_origins = [] # CORS許可オリジン(実際の値を設定)
# alarm_email     = "" # アラーム通知先メール 環境変数(TF_VAR_alarm_email)で設定しているが、環境別で個別指定の場合はコメントアウト解除

# SSL証明書(オプション)
alb_certificate_arn      = ""
frontend_certificate_arn = ""
frontend_domain_name     = ""

リソースを作成

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%

サービス別月額コスト目安(東京リージョン)

※以下のコスト目安は参考値です。実際のコストは使用状況により変動します。

サービス 開発環境 本番環境 備考
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パイプライン
  • 監視とアラート

コスト最適化

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

まとめ

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

インフラに限らず、技術は手を動かさないと身にならないと思います。

自身はインフラ素人のため、現場でインフラの話になると「わかる人」に任せきりになってしまう場面が多くなってしまう。
そんな現状を打破すべく、0から勉強しようということで、自分の手を動かしてハンズオンを作ってみました。

本気で学習する気がないと踏破できないだろうな…と思いつつ書きましたが、コスト面はなるべく抑えたつもりです。ここまで辿り着いた方々は自信を持ちましょう。
default = false # 開発環境ではfalse
}

variable “enable_unhealthy_host_alarm” {
description = "異常ホストアラームを有効

環境別設定値の推奨

設定項目 開発環境 (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/environments/dev/terraform.tfvars

# プロジェクト設定
# project_name = "" 環境変数(TF_VAR_project_name)で設定しているが、環境別で個別指定の場合はコメントアウト解除
# environment = "dev" # variables.tfで定義されている環境名(dev/stg/prod)に合わせて設定

# 環境別機能有効化設定
# ========================================

# インフラストラクチャ
enable_rds         = true   # RDSデータベースを有効化(学習目的)
enable_nat_gateway = false  # NAT Gatewayを無効化(コスト削減)

# セキュリティ・監視
enable_waf                  = false # WAFを無効化(開発環境)
enable_vpc_endpoints        = true  # VPCエンドポイントを有効化(NAT Gateway無効のため必須)
enable_vpc_flow_logs        = false # VPC Flow Logsを無効化(コスト削減)
enable_alb_access_logs      = false # ALBアクセスログを無効化(コスト削減)
enable_xray_tracing         = false # X-Rayトレーシングを無効化(コスト削減)
enable_cloudfront_logging   = false # CloudFrontログを無効化(コスト削減)
enable_guardduty = true
enable_config    = true

# アラーム・監視
enable_cpu_alarm            = false # CPUアラームを無効化(開発環境)
enable_memory_alarm         = false # メモリアラームを無効化(開発環境)
enable_unhealthy_host_alarm = false # 異常ホストアラームを無効化(開発環境)

# CI/CD
# github_repository = "" # GitHubリポジトリ 環境変数(TF_VAR_github_repository)で設定しているが、環境別で個別指定の場合はコメントアウト解除

# その他の環境固有設定
# ========================================
allowed_origins = [] # CORS許可オリジン(実際の値を設定)
# alarm_email     = "" # アラーム通知先メール 環境変数(TF_VAR_alarm_email)で設定しているが、環境別で個別指定の場合はコメントアウト解除

# SSL証明書(オプション)
alb_certificate_arn      = ""
frontend_certificate_arn = ""
frontend_domain_name     = ""

リソースを作成

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%

サービス別月額コスト目安(東京リージョン)

※以下のコスト目安は参考値です。実際のコストは使用状況により変動します。

サービス 開発環境 本番環境 備考
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パイプライン
  • 監視とアラート

コスト最適化

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

まとめ

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

インフラに限らず、技術は手を動かさないと身にならないと思います。

自身はインフラ素人のため、現場でインフラの話になると「わかる人」に任せきりになってしまう場面が多くなってしまう。
そんな現状を打破すべく、0から勉強しようということで、自分の手を動かしてハンズオンを作ってみました。

本気で学習する気がないと踏破できないだろうな…と思いつつ書きましたが、コスト面はなるべく抑えたつもりです。ここまで辿り着いた方々は自信を持ちましょう。

検索

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