ECSタスク定義で秘匿情報を含む環境変数が存在する場合のSSMを活用した設定修正手順

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

この記事では、秘匿情報を含む環境変数が存在するECSタスク定義について、そのリスクと対策を解説します。

ポリシーの説明

まず、AWS Security Hubによるポリシーの説明は以下の通りです。

Amazon ECS の Security Hub コントロール – AWS Security Hub

このコントロールは、コンテナ定義の environment パラメータにある、任意の変数のキー値に、AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYECS_ENGINE_AUTH_DATA のいずれかが含まれているかどうかをチェックします。任意のコンテナ定義内の単一の環境変数が、AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYECS_ENGINE_AUTH_DATA のいずれかである場合、このコントロールは失敗します。

リスクとしてはParameter StoreやSystems Managerの値を復号化するKMSキーを使用する権限を持っていないユーザーでも、ECSで使用されているアクセスキー等の秘匿情報を覗き見ることが可能となり、悪意のあるユーザーの場合は漏洩させたり設定変更等を行うリスクがあります。 そのため、秘匿情報はParameter StoreやSystems Managerで保管するようにしましょう。

修復方法

AWSコンソールでの修正手順

以下Systems Managerのパラメータストアを使用した修正手順です。

① AWS コンソールにログイン後、Systems Manager > パラメータストアへ移動します。

② 移動後、パラメータの作成をクリック

③ 名前、タイプ、値は最低限入力し、 パラメータを作成します。

④ 作成後、ECSのタスク定義 > 新しいタスク定義の作成で環境変数に以下の通り入力します。

キー: コンテナ内で変数として使用する名前を入力

値のタイプ:ValueFrom

値:パラメータストアで作成したパラメータのARNを記載

Terraformでの修復手順

ECSタスク定義での安全な認証情報管理のためのTerraformコードを作成します。

# Secrets Managerでのシークレット作成
resource "aws_secretsmanager_secret" "app_secrets" {
  name        = "${var.environment}-app-secrets"
  description = "アプリケーション用の認証情報"

  tags = {
    Environment = var.environment
    Application = var.app_name
  }
}

# シークレット値の設定
resource "aws_secretsmanager_secret_version" "app_secrets" {
  secret_id = aws_secretsmanager_secret.app_secrets.id
  secret_string = jsonencode({
    DATABASE_URL        = var.database_url
    API_KEY            = var.api_key
    AWS_ACCESS_KEY_ID  = var.aws_access_key
    AWS_SECRET_KEY     = var.aws_secret_key
  })
}

# ECSタスク実行ロール
resource "aws_iam_role" "ecs_task_execution_role" {
  name = "${var.environment}-ecs-task-execution-role"

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

# タスク実行ロールへのポリシー付与
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

# Secrets Managerアクセス用のカスタムポリシー
resource "aws_iam_role_policy" "secrets_policy" {
  name = "${var.environment}-secrets-policy"
  role = aws_iam_role.ecs_task_execution_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue",
          "kms:Decrypt"
        ]
        Resource = [
          aws_secretsmanager_secret.app_secrets.arn
        ]
      }
    ]
  })
}

# ECSタスク定義(安全な設定)
resource "aws_ecs_task_definition" "app" {
  family                   = "${var.environment}-${var.app_name}"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = var.cpu
  memory                   = var.memory
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn

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

      # 環境変数を直接指定しない
      secrets = [
        {
          name      = "DATABASE_URL"
          valueFrom = "${aws_secretsmanager_secret.app_secrets.arn}:DATABASE_URL::"
        },
        {
          name      = "API_KEY"
          valueFrom = "${aws_secretsmanager_secret.app_secrets.arn}:API_KEY::"
        },
        {
          name      = "AWS_ACCESS_KEY_ID"
          valueFrom = "${aws_secretsmanager_secret.app_secrets.arn}:AWS_ACCESS_KEY_ID::"
        },
        {
          name      = "AWS_SECRET_KEY"
          valueFrom = "${aws_secretsmanager_secret.app_secrets.arn}:AWS_SECRET_KEY::"
        }
      ]

      # 非機密な環境変数のみ直接指定
      environment = [
        {
          name  = "APP_ENV"
          value = var.environment
        },
        {
          name  = "LOG_LEVEL"
          value = "info"
        }
      ]

      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = "/ecs/${var.environment}-${var.app_name}"
          awslogs-region        = data.aws_region.current.name
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}

# ECSサービス
resource "aws_ecs_service" "app" {
  name            = "${var.environment}-${var.app_name}"
  cluster         = var.ecs_cluster_id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = var.service_desired_count
  launch_type     = "FARGATE"

  network_configuration {
    security_groups = [aws_security_group.ecs_tasks.id]
    subnets         = var.private_subnet_ids
  }
}

# セキュリティグループ
resource "aws_security_group" "ecs_tasks" {
  name        = "${var.environment}-${var.app_name}-ecs-tasks"
  description = "Security group for ECS tasks"
  vpc_id      = var.vpc_id

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# CloudWatch Logsグループ
resource "aws_cloudwatch_log_group" "app" {
  name              = "/ecs/${var.environment}-${var.app_name}"
  retention_in_days = 30
}

# 必要な変数定義
variable "environment" {
  type        = string
  description = "環境名(production, staging等)"
}

variable "app_name" {
  type        = string
  description = "アプリケーション名"
}

variable "container_name" {
  type        = string
  description = "コンテナ名"
}

variable "container_image" {
  type        = string
  description = "コンテナイメージURI"
}

variable "cpu" {
  type        = number
  description = "タスクのCPU単位"
  default     = 256
}

variable "memory" {
  type        = number
  description = "タスクのメモリ(MiB)"
  default     = 512
}

# 現在のリージョンを取得
data "aws_region" "current" {}

# 出力
output "task_definition_arn" {
  value = aws_ecs_task_definition.app.arn
}

output "secrets_arn" {
  value = aws_secretsmanager_secret.app_secrets.arn
}

重要な修正ポイントと実装手順を説明します:

  1. 認証情報の安全な管理(最重要):
# 環境変数を直接指定する代わりに、Secrets Managerを使用
secrets = [
  {
    name      = "AWS_ACCESS_KEY_ID"
    valueFrom = "${aws_secretsmanager_secret.app_secrets.arn}:AWS_ACCESS_KEY_ID::"
  }
]

# 非機密情報のみ環境変数として設定
environment = [
  {
    name  = "APP_ENV"
    value = var.environment
  }
]

2. 適切なIAMロールとポリシーの設定:

resource "aws_iam_role_policy" "secrets_policy" {
  policy = jsonencode({
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue"
        ]
        Resource = [aws_secretsmanager_secret.app_secrets.arn]
      }
    ]
  })
}

3. ログとモニタリング:

logConfiguration = {
  logDriver = "awslogs"
  options = {
    awslogs-group         = "/ecs/${var.environment}-${var.app_name}"
    awslogs-stream-prefix = "ecs"
  }
}

適用手順:

  1. 変数の設定:
# terraform.tfvars
environment      = "production"
app_name         = "myapp"
container_name   = "api"
container_image  = "xxx.dkr.ecr.region.amazonaws.com/myapp:latest"

2. Terraformの実行:

terraform init
terraform plan
terraform apply

最後に

今回は、秘匿情報をそのままECSで使用している場合のリスクとその対処方法についてご紹介しました。 ECSで秘匿情報を扱う際は十分注意していただき、本記事を参考にセキュリティリスクが高い状態で使用しないよう設定を行ってみてください。

この問題の検出は弊社が提供するSecurifyのCSPM機能で簡単に検出及び管理する事が可能です。

運用が非常に楽に出来る製品になっていますので、ぜひ興味がある方はお問い合わせお待ちしております。

最後までお読みいただきありがとうございました。この記事が皆さんの役に立てば幸いです

この記事をシェアする

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

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

料金プランを詳しく見る