Fargateのコンテナ環境変数をSecrets Managerで保護してみた

つい先日、AWS ブログにて Fargate から Secrets Manager を利用する方法が紹介されていました。

本機能のリリース自体は、2019/4/3 にお伝えされており、詳細等については既に公式ドキュメントへ記載があります。

Fargate & Secrets Manager の組み合わせを未だ利用したことが無かったため、AWS Secrets Manager に保存したシークレット・メッセージを AWS Fargate の Nginx コンテナで確認できるかサクッと試してみました。

やってみた

AWS Secrets Manager でシークレットを作成しないことには始まらないため、AWS CLI で作成します。

aws secretsmanager create-secret --region ap-northeast-1 \
    --name SECRET_MESSAGE \
    --secret-string <your secret message here> 

Fargate タスクが Secrets Manager からこのシークレットを取得するためには、ECS タスク実行ロールに対して secretsmanager:GetSecretValue アクションを許可する必要があります。そこで、ECS タスク実行ロールを作成した後に IAM ポリシードキュメントを作成し、ロールと関連付けます。

まずは、ECS タスク実行ロールを作成します。

$ cat <<EOF > ecs-task-role-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
$ aws iam create-role --region ap-northeast-1 \
    --role-name nginx-task-execution-role \
    --assume-role-policy-document \
    file://ecs-task-role-trust-policy.json

次に必要な機能を定義した JSON ファイルからポリシードキュメントを作成し、ロールに関連付けます。 シークレットの ARN は、実際の値に置き換えてください。

$ cat <<EOF > nginx-iam-policy-task-execution-role.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
                "arn:aws:secretsmanager:ap-northeast-1:012345678912:secret:SECRET_MESSAGE"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
EOF
$ aws iam put-role-policy --region ap-northeast-1 \
    --role-name nginx-task-execution-role \
    --policy-name nginx-iam-policy-task-execution-role \
    --policy-document file://nginx-iam-policy-task-execution-role.json

次に Fargate クラスターを作成します。 シェル変数(AWS_ACCESS_KEY_ID や AWS_SECRET_ACCESS_KEY)は、予め設定しておいてください。

$ ecs-cli configure --cluster web --region ap-northeast-1 \
    --default-launch-type FARGATE
$ ecs-cli configure profile --access-key $AWS_ACCESS_KEY_ID \
    --secret-key $AWS_SECRET_ACCESS_KEY --profile-name default
$ ecs-cli up
INFO[0000] Created cluster                               cluster=web region=ap-northeast-1
INFO[0000] Waiting for your cluster resources to be created... 
INFO[0001] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0061] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
VPC created: vpc-id
Subnet created: subnet-id1
Subnet created: subnet-id2
Cluster creation succeeded.

作成された VPC の ID やサブネット ID は後で利用するため記録しておいてください。 ここでは、HTTP アクセスを許可するセキュリティグループを作成します。 シェル変数(VPC_ID や SG_ID)は、設定してからコマンドを実行してください。

$ aws ec2 create-security-group --group-name "web-sg" \
    --description "HTTP Only" --vpc-id $VPC_ID
$ aws ec2 authorize-security-group-ingress --group-id \
    $SG_ID --protocol tcp --port 80 --cidr 0.0.0.0/0

最後に、構成ファイル(docker-compose.yml,ecs-params.yml)を作成し、デプロイしましょう。

docker-compose.yml

version: '3'
services:
  web:
    image: nginx
    ports:
      - "80:80"
    command: /bin/bash -c "echo $$SECRET_MESSAGE > /usr/share/nginx/html/index.html && exec nginx -g 'daemon off;'"
    logging:
      driver: awslogs
      options: 
        awslogs-group: developers.io
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: nginx

環境変数名(SECRET_MESSAGE)に二重のドル記号を付与することで、Compose で処理せず Secrets Manager で挿入されたコンテナの環境変数を参照することが可能になります。

You can use a $$ (double-dollar sign) when your configuration needs a literal dollar sign. This also prevents Compose from interpolating a value, so a $$ allows you to refer to environment variables that you don’t want processed by Compose.

ecs-params.yml

version: 1
task_definition:
  task_execution_role: arn:aws:iam::aws_account_id:role/nginx-task-execution-role
  ecs_network_mode: awsvpc
  task_size:
    mem_limit: 0.5GB
    cpu_limit: 256
  services:
    web:
      secrets:
        - value_from: arn:aws:secretsmanager:region:aws_account_id:secret:secret_name-AbCdEf
          name: SECRET_MESSAGE
run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - subnet_id
        - subnet_id
      security_groups:
        - security_group_id
      assign_public_ip: ENABLED

subnet_id や security_group_id、IAM Role やシークレットの ARN 等は、利用している環境の値に置き換えてください。ファイルを作成したら準備完了です!最後にデプロイしましょう。

$ ecs-cli compose --project-name web service up --create-log-groups --cluster-config default
$ ecs-cli compose --project-name web service ps --cluster-config default
Name                                      State    Ports                     TaskDefinition  Health
55c36321-73af-47fb-be50-5bc8f2428668/web  RUNNING  18.182.45.190:80->80/tcp  web:10          UNKNOWN

デプロイされたコンテナ(Nginx)へブラウザから、アクセスしてみます。

3a9321f87f5f14cede8d5bf9bf2aa887-640x200.png

AWS Secrets Manager に登録したシークレットの値を確認してみると、、、

a2c6a2fdc6eb0a70002a5ec43d35a509-640x825.png

取得できているようですね。

皆さんお気づきかもしれませんが、クラスメソッドは 2019/7/7 に創立 15周年ということで、15% OFF キャンペーンを期間限定(2019/7末まで)で開催中です。よろしければ、是非この機会にメンバーズを利用してみてはいかがでしょうか?

さいごに

以前に Serverless Framework(Lambda)で Secrets Manager を利用する方法を簡単に紹介しましたが、これからは Fargate でも Secrets Manager を活用することで、安全に開発や運用を行うことが出来るものと期待します。

なお、本記事でご紹介した手順等の詳細は、下記の公式のドキュメントを参照してください。

ではでは