ECSタスク定義でログ設定を有効化する手順

このブログシリーズ 「クラウドセキュリティ 実践集」 では、一般的なセキュリティ課題を取り上げ、「なぜ危険なのか?」 というリスクの解説から、 「どうやって直すのか?」 という具体的な修復手順(コンソール、AWS CLI、Terraformなど)まで、分かりやすく解説します。

この記事では、ECSタスク定義でログ設定を有効化する手順について、リスクと対策を解説します。

ポリシーの説明

ECSタスク定義では、各コンテナ定義にログドライバーを設定することで、コンテナの標準出力・標準エラー出力をCloudWatch Logs、FireLens、Splunkなどのログ管理システムに送信できます。ログ設定が存在しないコンテナは、運用時の可視性が著しく低下し、インシデント対応やトラブルシューティングが困難になります。本ポリシーでは、すべてのECSタスク定義でログ設定が適切に構成されているかを確認します。AWS Well-Architected Frameworkの信頼性の柱でも、ログの収集は重要なベストプラクティスとして位置づけられています。

修復方法

コンソールでの修復手順

AWSのコンソールを使用して、ECSタスク定義にログ設定を追加します。

ステップ1: タスク定義の確認

  1. AWSマネジメントコンソールにログイン
  2. Amazon ECSサービスに移動
  3. 左側メニューから 「タスク定義」 を選択
  4. ログ設定が必要なタスク定義をクリック
  5. 現在のリビジョンを確認

ステップ2: 新しいリビジョンの作成

  1. タスク定義の詳細画面で 「新しいリビジョンの作成」 ボタンをクリック
  2. 「JSON」 タブをクリックして、タスク定義のJSONを表示
  3. 必要に応じて現在の設定をバックアップ

ステップ3: コンテナ定義のログ設定

  1. 「コンテナ定義」 セクションまでスクロール
  2. 編集するコンテナ名をクリック
  3. 「ストレージとログ」 セクションまでスクロール
  4. 「ログ設定」 で最適なログ設定を選択してください。※ 以下はCloudWatchでの設定例です

ステップ4: CloudWatch Logsロググループの作成

  1. 別タブで CloudWatchコンソールを開く
  2. 左側メニューから 「ロググループ」 を選択
  3. 「ロググループの作成」 をクリック
  4. ロググループ名に /ecs/タスク定義名 を入力
  5. 保持期間: 要件に応じて設定
    • 開発環境: 7日
    • ステージング環境: 14日
    • 本番環境: 30日以上(コンプライアンス要件に応じて最大90日以上)
  6. KMS暗号化(オプション): 機密情報を含む場合はKMSキーを選択
  7. 「作成」 をクリック

ステップ5: タスク実行ロールの権限確認

  1. ECSコンソールに戻る
  2. 「タスク実行ロール」 を確認
  3. IAMコンソールで該当ロールを開く
  4. 以下のポリシーがアタッチされていることを確認:
    • AmazonECSTaskExecutionRolePolicy または
    • カスタムポリシーに以下の権限が含まれていること:
      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Action": [
              "logs:CreateLogStream",
              "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:log-group:/ecs/*"
          }
        ]
      }
      

       

ステップ6: タスク定義の更新と展開

  1. すべての設定を確認後、「作成」 をクリック
  2. 新しいリビジョンが作成されたことを確認
  3. ECSサービスを更新して新しいタスク定義リビジョンを使用
  4. 「サービスの更新」 → 新しいタスク定義リビジョンを選択
  5. 「サービスの更新」 をクリック

Terraformでの修復手順

ECSタスク定義のログ設定を有効にするTerraformコードと、主要な修正ポイントを説明します。

# CloudWatch Logsロググループの作成
resource "aws_cloudwatch_log_group" "ecs_logs" {
  name              = "/ecs/${var.task_family}"
  retention_in_days = var.log_retention_days  # 環境ごとに変数化
  kms_key_id        = var.kms_key_id  # 暗号化キー(オプション)

  tags = {
    Environment = var.environment
    Purpose     = "ECS Container Logs"
    ManagedBy   = "Terraform"
  }
}

# ログストリームの事前作成(オプション)
resource "aws_cloudwatch_log_stream" "ecs_container" {
  name           = "${var.container_name}/container"
  log_group_name = aws_cloudwatch_log_group.ecs_logs.name
}

# タスク実行ロール
resource "aws_iam_role" "ecs_execution_role" {
  name = "${var.task_family}-execution-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
      }
    ]
  })

  tags = {
    Environment = var.environment
    Purpose     = "ECS Task Execution"
  }
}

# AWS管理ポリシーのアタッチ(ECRアクセス等)
resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy" {
  role       = aws_iam_role.ecs_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

# CloudWatch Logsへの書き込み権限
resource "aws_iam_role_policy" "ecs_execution_logs" {
  name = "${var.task_family}-logs-policy"
  role = aws_iam_role.ecs_execution_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Resource = "${aws_cloudwatch_log_group.ecs_logs.arn}:*"
      }
    ]
  })
}

# ECSタスク定義(ログ設定を含む)
resource "aws_ecs_task_definition" "app" {
  family                   = var.task_family
  requires_compatibilities = ["FARGATE"]
  network_mode            = "awsvpc"
  cpu                     = var.task_cpu
  memory                  = var.task_memory
  execution_role_arn      = aws_iam_role.ecs_execution_role.arn
  task_role_arn          = aws_iam_role.ecs_task_role.arn

  container_definitions = jsonencode([
    {
      name  = var.container_name
      image = var.container_image

      # ログ設定(重要)
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = aws_cloudwatch_log_group.ecs_logs.name
          "awslogs-region"        = var.aws_region
          "awslogs-stream-prefix" = "ecs"
          "awslogs-datetime-format" = "%Y-%m-%d %H:%M:%S"  # タイムスタンプフォーマット(オプション)
        }
        # FireLensを使用する場合の例
        # logDriver = "awsfirelens"
        # options = {
        #   "Name" = "cloudwatch"
        #   "region" = var.aws_region
        #   "log_group_name" = aws_cloudwatch_log_group.ecs_logs.name
        #   "log_stream_prefix" = "ecs/"
        # }
      }

      # 環境変数
      environment = var.environment_variables

      # ポートマッピング
      portMappings = [
        {
          containerPort = var.container_port
          protocol      = "tcp"
        }
      ]

      # ヘルスチェック
      healthCheck = {
        command     = ["CMD-SHELL", "curl -f <http://localhost>:${var.container_port}/health || exit 1"]
        interval    = 30
        timeout     = 5
        retries     = 3
        startPeriod = 60
      }
    }
  ])

  # 複数コンテナの場合の例
  # container_definitions = jsonencode([
  #   {
  #     name = "app"
  #     ...
  #     logConfiguration = { ... }
  #   },
  #   {
  #     name = "sidecar"
  #     ...
  #     logConfiguration = { ... }  # 各コンテナにログ設定が必要
  #   }
  # ])
}

# VPCエンドポイント(プライベートサブネット利用時)
resource "aws_vpc_endpoint" "logs" {
  vpc_id              = var.vpc_id
  service_name        = "com.amazonaws.${var.aws_region}.logs"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = var.private_subnet_ids
  security_group_ids  = [aws_security_group.vpc_endpoints.id]

  tags = {
    Name = "${var.environment}-logs-endpoint"
  }
}

# variables.tf
variable "task_family" {
  description = "ECSタスクファミリー名"
  type        = string

  validation {
    condition     = can(regex("^[a-zA-Z][a-zA-Z0-9-]{0,254}$", var.task_family))
    error_message = "Task family name must start with a letter and contain only letters, numbers, and hyphens."
  }
}

variable "aws_region" {
  description = "AWSリージョン"
  type        = string
  default     = "ap-northeast-1"
}

variable "environment" {
  description = "環境名(dev/staging/prod)"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "log_retention_days" {
  description = "CloudWatch Logsログ保持期間"
  type        = number
  default     = 30

  validation {
    condition     = contains([1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653], var.log_retention_days)
    error_message = "Log retention days must be a valid CloudWatch Logs retention period."
  }
}

variable "kms_key_id" {
  description = "CloudWatch Logs暗号化用KMSキーID"
  type        = string
  default     = null  # nullの場合はデフォルト暗号化
}

# 出力値
output "log_group_name" {
  description = "CloudWatch Logsロググループ名"
  value       = aws_cloudwatch_log_group.ecs_logs.name
}

output "log_group_arn" {
  description = "CloudWatch LogsロググループARN"
  value       = aws_cloudwatch_log_group.ecs_logs.arn
}

 

主要な修正ポイント

  1. ロググループの事前作成: タスク定義作成前にCloudWatch Logsロググループを作成
  2. 適切な権限設定: タスク実行ロールにCloudWatch Logsへの書き込み権限を付与
  3. 保持期間の設定: コンプライアンス要件に応じて適切な保持期間を設定(30日以上推奨)
  4. VPCエンドポイント: プライベートサブネット利用時はVPCエンドポイントを設定してコスト最適化
  5. マルチコンテナ対応: サイドカーコンテナも含め、すべてのコンテナにログ設定を適用

最後に

この記事では、ECSタスク定義でログ設定を有効化する手順について、リスクと対策を解説しました。

ログ設定を有効にすることで、コンテナの可視性が大幅に向上し、セキュリティインシデントの早期発見、迅速なトラブルシューティング、コンプライアンス要件の遵守が可能になります。特に本番環境では、すべてのECSタスク定義でログ設定を必須とし、定期的な監査プロセスを確立することを強く推奨します。ログデータは単なる記録ではなく、システムの健全性を維持し、ビジネス価値を向上させるための重要な資産です。

この問題の検出は弊社が提供するSecurifyのCSPM機能で簡単に検出及び管理する事が可能です。 運用が非常に楽に出来る製品になっていますので、ぜひ興味がある方はお問い合わせお待ちしております。 最後までお読みいただきありがとうございました。この記事が皆さんの役に立てば幸いです。

参考資料

この記事をシェアする

クラウドセキュリティ対策実践集一覧へ戻る

貴社の利用状況に合わせた見積もりを作成します。

料金プランを詳しく見る