【Security Hub修復手順】[ECS.8] シークレットは、コンテナ環境の変数として渡さないでください

AWS SecurityHub 基礎セキュリティのベストプラクティスコントロール修復手順をご紹介します。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、AWS事業本部のトクヤマシュンです。

皆さん、お使いのAWS環境のセキュリティチェックはしていますか?

当エントリでは、AWS Security HubによるAWS環境のセキュリティ状況スコアリングに該当する項目についての修復手順をご紹介します。

本記事の対象コントロール

[ECS.8] シークレットは、コンテナ環境の変数として渡さないでください

[ECS.8] Secrets should not be passed as container environment variables

対象コントロールの説明

このコントロールではコンテナ定義のenvironmentパラメータにおけるキーとして下記のいずれかが含まれており、かつ平文で値が渡されているかをチェックします。 

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • ECS_ENGINE_AUTH_DATA

これらの環境変数に設定される情報は、機密情報として厳密に管理されるべきです。
このコントロールに対応しない場合、AWSアカウントへの不正アクセスなどが発生する可能性がありますのでご注意ください。

前提条件

本記事はAWS Security Hubで「AWS基礎セキュリティのベストプラクティススタンダード」を利用されている方向けの内容となります。 AWS Security Hubの詳細についてはこちらのブログをご覧ください。

修復手順

1.修正対象のタスク定義特定

まずAWS Security HubのコンソールからECS.8のチェック結果を確認します。
表示項目から、今回の対象はtest-ecs-no-environment-secrets-task-definition:1というタスク定義であることが分かります。

東京リージョンの場合、下記リンクで上図の画面を確認可能です。

https://ap-northeast-1.console.aws.amazon.com/securityhub/home?region=ap-northeast-1#/standards/aws-foundational-security-best-practices-1.0.0/ECS.8

2.ステークホルダーに確認

ステークホルダーに以下を確認します。

  • 対象の変数を平文で渡していることは意図したものか?
    • YESの場合
      • 基本的には平文で渡さなくてはいけない理由はないはずです
      • 対応する方向で調整することをオススメします
    • NOの場合
      • 3.環境変数に機密データを安全に設定する に進みます

3.環境変数に機密データを安全に設定する

今回はAWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEYに平文で情報を渡している環境を想定して対応例を示します。

タスク定義は次のように設定されています。展開してご確認ください。
(アカウントIDなど一部はxxxxxで適宜マスクをかけていますので、ご了承ください。以降も同様です。)

test-ecs-no-environment-secrets-task-definition:1(クリックして展開)

test-ecs-no-environment-secrets-task-definition:1

{
    "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:task-definition/test-ecs-no-environment-secrets-task-definition:1",
    "containerDefinitions": [
        {
            "name": "test-ecs-no-environment-secrets",
            "image": "xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecs-no-environment-secrets:latest",
            "cpu": 0,
            "portMappings": [],
            "essential": true,
            "environment": [
                {
                    "name": "AWS_ACCESS_KEY_ID",
                    "value": "test1"
                },
                {
                    "name": "AWS_SECRET_ACCESS_KEY",
                    "value": "test2"
                }
            ],
            "environmentFiles": [],
            "mountPoints": [],
            "volumesFrom": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-create-group": "true",
                    "awslogs-group": "/ecs/test-ecs-no-environment-secrets-task-definition",
                    "awslogs-region": "ap-northeast-1",
                    "awslogs-stream-prefix": "ecs"
                }
            }
        }
    ],
    "family": "test-ecs-no-environment-secrets-task-definition",
    "executionRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/ecsTaskExecutionRole",
    "networkMode": "awsvpc",
    "revision": 1,
    "volumes": [],
    "status": "ACTIVE",
    "requiresAttributes": [
        {
            "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
        },
        {
            "name": "ecs.capability.execution-role-awslogs"
        },
        {
            "name": "com.amazonaws.ecs.capability.ecr-auth"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
        },
        {
            "name": "ecs.capability.execution-role-ecr-pull"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
        },
        {
            "name": "ecs.capability.task-eni"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
        }
    ],
    "placementConstraints": [],
    "compatibilities": [
        "EC2",
        "FARGATE"
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512",
    "runtimePlatform": {
        "cpuArchitecture": "ARM64",
        "operatingSystemFamily": "LINUX"
    },
    "registeredAt": "2023-03-31T07:48:25.508Z",
    "registeredBy": "arn:aws:sts::xxxxxxxxxxxx:assumed-role/xxxxx/xxxxx",
    "tags": []
}

ここで大事なのは下記の部分です。

            "environment": [
                {
                    "name": "AWS_ACCESS_KEY_ID",
                    "value": "test1"
                },
                {
                    "name": "AWS_SECRET_ACCESS_KEY",
                    "value": "test2"
                }
            ],

AWS_ACCESS_KEY_IDに対してvaluetest1, AWS_SECRET_ACCESS_KEYに対してtest2をそれぞれ平文で渡していて、これがよくないです。
今回はAWS_ACCESS_KEY_IDAWS Systems Manager Parameter Storeを使用、AWS_SECRET_ACCESS_KEYAWS Secrets Managerを使用して安全に渡すように変更します。

なお、今回実行するコンテナとしては次のDockerfileで定義されるものを用います。

Dockerfile

FROM alpine
CMD echo $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY

単純に環境変数の値を出力していて、のちほど修復確認ができるようにしています。

3.1 AWS Systems Manager Parameter Store パラメータ作成画面へのアクセス

マネジメントコンソールから、AWS Systems Manager→Parameter Storeのサービス画面にアクセスします。

右上Create parameterボタンからパラメータ作成画面に遷移します。

3.2 AWS Systems Manager Parameter Store パラメータ作成

下記の通り設定します。

  • Name
    • test-aws-parameter-store
      • 任意のパラメータ名を設定
      • AWS_ACCESS_KEY_IDはここでは利用不可
        • AWS始まりのパラメータ名は設定できないため
  • Tier
    • Standard
  • Type
    • SecureString
  • KMS key source
    • My current account
  • KMS Key ID
    • alias/aws/ssm
      • デフォルトのKMS Keyのエイリアス「alias/aws/ssm」を利用
      • カスタマー作成のKMS利用も可能なので、必要に応じて変更
  • Value
    • test1_from_paramete_store_securesetring
      • 環境変数AWS_ACCESS_KEY_IDに渡す値としてtest1_from_parameter_store_securesetringを設定

設定できればCreate parameterボタンをクリックしてパラメータを作成します。

3.3 AWS Secrets Manager シークレット作成画面へのアクセス

マネジメントコンソールから、AWS Secrets Manager→Secretsのサービス画面にアクセスします。

画面右上Store a new secretボタンからシークレット作成画面に遷移します。

3.4 AWS Secrets Manager シークレット作成

下記の通り設定します。

  • Secret type
    • Other type of secret
  • Key/value pairs
    • Key
      • test-aws-secrets-manager
        • 任意のキー値を設定
    • Value
      • test2_from_secrets_manager
  • Type
    • SecureString
  • Encryption key
    • aws/secretsmanager
    • デフォルトのKMS Keyを利用
    • カスタマー作成のKMS利用も可能なので必要に応じて変更

設定できればNextボタンをクリックしてStep2に移ります。

Step2ではSecret nameに任意のシークレット名を設定します。
ここではtest-aws-credentialsを指定しています。 設定できればNextボタンをクリックしてStep3に移ります。

Step3ではとくになにも設定せず、NextボタンをクリックしてStep4に移ります。

Step4では設定内容に問題なければ、右下Storeボタンをクリックして、シークレットを作成します。

3.5 IAM Policy作成画面へのアクセス

作成したパラメータやシークレットについて、タスク実行ロールにアクセス権限を付与するためのポリシーを作成します。

マネジメントコンソールから、AWS IAM→Policiesのサービス画面にアクセスします。

画面右上Create Policyボタンからポリシー作成画面に遷移します。

3.6 IAM Policy作成

ポリシー指定画面では次の通りポリシーを指定します。
Resouceの部分は各々の環境で先ほど作成したパラメータやシークレットのものに変更してください。

なお、今回はパラメータ、シークレットのどちらもデフォルトKMSキーを利用しているので不要ですが、
カスタマーで作成したKMSキーを利用する時には"kms:Decrypt"アクションに対する権限も必要ですのでご注意ください。

設定できればNext: Tagsボタンをクリックします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameters"
            ],
            "Resource": [
                "arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/test-aws-parameter-store"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
                "arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:test-aws-credentials-k2ZDvy"
            ]
        }
    ]
}

次のCreate policy画面では何も設定しません。 右下Next: Reviewボタンをクリックします。

次の画面ではポリシー名を設定します。
ここではtest-ecs-no-environment-secrets-get-value-from-secret-policyと設定しました。

設定内容に問題なければ、右下Create Policyボタンをクリックして、ポリシーを作成します。

3.7 IAM RoleにIAM Policyをアタッチする画面へのアクセス

マネジメントコンソールから、AWS IAM→Rolesのサービス画面にアクセスします。

ECSのタスク定義で指定しているタスク実行ロール(ここではecsTaskExecutionRole)を検索し、ロール名をクリックします。

Add permissionsボタンをクリックしてメニューを開き、Attach policiesをクリックしてアタッチ画面に遷移します。

3.7 IAM RoleにIAM Policyをアタッチ

先ほど作成したポリシーtest-ecs-no-environment-secrets-get-value-from-secret-policyを検索してチェックします。

チェックできたら、Add permissionsボタンをクリックします。

3.8 ECSタスク定義のリビジョン作成画面へのアクセス

マネジメントコンソールから、ECS→のタスク定義のサービス画面にアクセスします。
対象のタスク定義(今回はtest-ecs-no-environment-secrets-task-definition)の対象のリビジョンをクリックします。

アクセスできたら、右上Create new revisonボタンをクリックし、Create new revisionをクリックして新たなリビジョン作成画面に遷移します。

3.9 ECSタスク定義のリビジョン作成

環境変数について、次の通り置き換えます。

設定できたら、右下Createボタンをクリックしてリビジョンを作成します。

  • Environment variables
    • Key:AWS_ACCESS_KEY_ID
      • Value type: ValueFromに変更
      • Value: arn:aws:ssm:<リージョン名>:<アカウントID>:parameter/<パラメータ名>形式で指定
    • Value type
      • Value type: ValueFromに変更
      • Value: arn:aws:secretsmanager:<リージョン名>:<アカウントID>:secret:<シークレット名>-<シークレットごとに付与されるランダム文字列>:<キー名>::形式で指定

以上で修正は完了です。 このコントロールはAmazon ECSタスク定義の最新のアクティブなリビジョンのみを評価するので結果は成功となりますが、古いタスク定義が残ったままになるので、気になる方は削除まで実施しましょう。

修復確認

今回のコンテナは環境変数 $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEYを標準出力するだけのものでした。
作成したタスク定義のリビジョンをパブリックサブネットに設置したECSクラスターで実行します。 ログドライバーにawslogsを設定しているため、標準出力はCloudWatch Logsに出力されます。

確認したところ、パラメータとシークレットに設定した内容がそれぞれ出力できており、問題ないと考えられます。

最後に

今回は、AWS Security HubによるAWS環境のセキュリティ状況スコアリングに該当する項目についての修正手順をご紹介しました。

コントロールを修正して、お使いのAWS環境のセキュリティをパワーアップさせましょう!

最後までお読みいただきありがとうございました!どなたかのお役に立てれば幸いです。

以上、トクヤマシュンでした。

クラスメソッドメンバーズをご契約の皆さまに

AWS Security Hub 「基礎セキュリティのベストプラクティス」の各チェック項目(コントロール)に対する弊社としての推奨対応やコメントを記載しているClassmethod Cloud Guidebookを提供しています。 クラスメソッドメンバーズポータルの「お役立ち情報」→「組織的な AWS 活用のためのノウハウ」からご参照ください。