ECSがSecrets ManagerのバージョンやJSONキーに対応。機密情報管理がめっちゃ便利になりました!

ECSのタスク定義への機密情報読み込み機能が拡充され、Secrets Managerがより使いやすくなりました。Secrets Manager見直しのチャンスです。
2020.02.27

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

ECSには、DB接続情報や外部サービスへのアクセストークンなどの秘匿情報を安全にコンテナの環境変数に設定するため、Parameter StoreやSecrets Managerとの連携機能がマネージドで提供されています。

この度、Secrets Managerの秘匿情報をECS側でよりきめ細かく取得できるようになりました!

Amazon ECS で AWS Secrets Manager の任意バージョンおよび JSON キーのサポートを開始

今まで、同じ用途でParameter Storeのセキュア文字列を使っていた方も、今回のアップデートを機に, より秘匿情報の利用に特化したSecrets Managerの利用を検討いただくのも良いと思います。一点、ECS on EC2では対応していますが、ECS on Fargateは未対応なので、注意が必要です。

(祭) ∧ ∧
 Y  ( ゚Д゚)
 Φ[_ソ__y_l〉     Secrets Manager エエヤンコレ
    |_|_|
    し'´J

ECSタスク定義への秘匿情報受け渡し機能について

コンテナを起動するとき、環境依存する情報は環境変数に設定するのがベストプラクティスです。決してプライベートリポジトリであろうが、機密情報をリポジトリに格納するのは止めましょう。事故の元です。環境依存情報には、データベース接続パスワードや他のサービスを利用する際のAPIトークンなどの秘匿情報を渡すこともあります。それを安全かつ簡単に展開する機能が、もともとECSにはありました。

参考:ECSでごっつ簡単に機密情報を環境変数に展開できるようになりました! | Developers.IO

具体的には、ECSタスク定義内のコンテナ定義の環境変数部分に、Secrets ManagerのパスやParameter Storeのセキュア文字列のパスを指定することで、自動的に対象の環境変数に上記値を展開することができます。

今回のアップデートで何が嬉しいのか?

今までは、Secrets Managerを利用するとき、最新バージョンの全情報のみの取得に対応していました。そのため、履歴管理機能やJSONによる構造化データを扱えるSecrets Managerの機能をフル活用できていたとは言えません。

今回のアップデートで、過去のバージョンを指定したりJSONキーを指定した読み込みに対応したことで、より柔軟な秘匿情報の管理ができるようになっています。

Secrets Managerを使った機密情報の注入は、以下の公式ドキュメントで詳細が記載されています。

公式ドキュメント:Specifying Sensitive Data Using Secrets Manager - Amazon Elastic Container Service

重要事項「Fargateは未サポート」

現時点(2020年2月26日)では、Fargateはこの機能未サポートです。

For tasks that use the Fargate launch type, the following should be considered:

It is only supported to inject the full contents of a secret as an environment variable. Specifying a specific JSON key or version is not supported at this time.
引用:Specifying Sensitive Data Using Secrets Manager - Amazon Elastic Container Service

前提知識:Secrets Managerのsecret構造を理解する

今回のアップデートを実際に試す前に、Secrets Managerの構造を理解しておく必要があります。

参考公式ドキュメント:Key Terms and Concepts for AWS Secrets Manager - AWS Secrets Manager

Secrets Managerにおいて、1つのSecret valueは、keyvalueを指定したJSON形式で、作成できます。以下はSecret valueの例。

{
  "host" : "ProdServer-01.databases.example.com",
  "port" : "8888",
  "username"   : "administrator",
  "password"   : "My-P@ssw0rd!F0r+Th3_Acc0unt",
  "dbname"     : "MyDatabase",
  "engine"     : "mysql"
}

さらにSecret valueは複数のバージョンで管理され、それらをメタデータ(青色の部分)が管理します。今回は利用しませんが、Secrets Managerの重要なローテーション機能なども、このメタデータ部分で管理されています。

(上記公式ドキュメントより引用)

Secrets Managerの参考情報

Secrets Manager自体を認識するのが初めての方は、こちらのブログを参考にしていただければと思います。

機密情報を一元管理できる「AWS Secrets Manager」とは?概要と主要機能、動作原理、各種リソースまとめ | Developers.IO

手順1:Secrets Managerへの機密情報の登録

Webコンソールから[Secrets Manager]のコンソールを開き、[Store a new secret]をクリックします。

Select secret type

今回作成するSecretはデータベースに関係無いので[Other type of secrets]をクリック

Secret key/value

実際にSecretに格納するSecret valueをkey-value方式で定義していきます。とりあえず、こんな感じで入れてみます。

[Plaintext]タブをクリックすると、Secret valueをJSON形式で確認できます。

{
  "age": "18",
  "gender": "man",
  "twitter": "hamako9999"
}

Select the encription keyはそのままにしておき[Next]ボタンをクリック。

とりあえず、ここでは、[Secret name]にhamadaInfoとだけ入力し、[Next]ボタンをクリック。

最後に[Configure automatic rotation]について聞かれますが、今回のSecretはローテーションさせないため、[Disable automatic rotation]を選択し、[Next]ボタンをクリック

確認画面後、[Store]ボタンを押すと、Secretが作成されます。この状態で以下のget-secret-valueコマンドを実行すると、Secretの内容を確認できます。

$ aws secretsmanager get-secret-value --secret-id hamadaInfo
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:629895769338:secret:hamadaInfo-5yaxBf",
    "Name": "hamadaInfo",
    "VersionId": "80e210be-3091-49be-9e24-4add26b28249",
    "SecretString": "{\"age\":\"18\",\"gender\":\"man\",\"twitter\":\"hamako9999\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": 1582669398.93
}

ただ、このままではSecretのバージョン履歴の動作が確認できないので、Secretの内容を一部変更します。

作成したSecretのhamadainfoを開き、[Retrieve secret value]をクリック。[Edit]ボタンを押すと、Secret valueが編集できるので、一部、age20に変更して、[Save]します。

再度、同じCLIコマンドを流すと、以下のように[SecretString]の値が修正され、VersionIdが変更されていることがわかります。

$ aws secretsmanager get-secret-value --secret-id hamadaInfo
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:629895769338:secret:hamadaInfo-5yaxBf",
    "Name": "hamadaInfo",
    "VersionId": "19362400-a3ca-4905-932f-eb0a2928189d",
    "SecretString": "{\"age\":\"20\",\"gender\":\"man\",\"twitter\":\"hamako9999\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": 1582669700.126
}

値が変更された履歴を確認するにはdescribe-secretを利用します。[VersionIdsToStages]にVersionIdとステージラベル情報が記載されていることがわかりますでしょうか。

$ aws secretsmanager describe-secret --secret-id hamadaInfo
{
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:629895769338:secret:hamadaInfo-5yaxBf",
    "Name": "hamadaInfo",
    "LastChangedDate": 1582669700.133,
    "LastAccessedDate": 1582588800.0,
    "Tags": [],
    "VersionIdsToStages": {
        "19362400-a3ca-4905-932f-eb0a2928189d": [
            "AWSCURRENT"
        ],
        "80e210be-3091-49be-9e24-4add26b28249": [
            "AWSPREVIOUS"
        ]
    }
}

実際の値はこのように格納されています。変更前後でそれぞれのバージョンIDが自動的に採番され、新しい値にはステージングラベルとしてAWSCURRENTが付与されます。

項目 変更前 変更後
ステージングラベル AWSPREVIOUS AWSCURRENT
バージョンID 80e210be-3091-49be-9e24-4add26b28249 19362400-a3ca-4905-932f-eb0a2928189d
age 18 20
gender man man
twitter hamako9999 hamako9999

手順2:ECSタスク定義への機密情報指定、動作確認

ここまでで、Secrets ManagerのSecretの準備は完了。次は、ECSのタスク定義に実際に値を設定しながら、読み込まれる値を確認します。

参考公式ドキュメント:Specifying Sensitive Data Using Secrets Manager

実際のタスク定義の作成

適当なクラスターとVPCは事前に作成しておいたあと、タスク定義作っていきます。

参考公式ドキュメント:Creating a Task Definition that References Sensitive Data

launch typeはEC2を選択し、最小限のタスク設定を実施していきます。

  • Task Definition Name:[secrets-manager-test-task](任意の名前)
  • Network Mode:[awsvpc]
  • Task execution role:[ecsTaskExecutionRole]
  • [Add Container]ボタンをクリック
    • Container name:[nginx](任意の名前)
    • Image:[nginx:latest]
    • Environment
    • Key:[SECRETS_ENVIRONMENT]
    • ValueFrom
    • Value:[arn:aws:secretsmanager:ap-northeast-1:629895769338:secret:hamadaInfo-5yaxBf]

後は、作成したタスク定義を利用して、タスク実行やECSのサービスを利用してタスクを起動します。環境変数の確認方法は様々ですが、自分は、先日アップデートされたECSインスタンスにセッションマネージャー経由で接続し、docker execで、コンテナ内環境変数を、以下の手順で確認しました。無事、環境変数SECRETS_ENVIRONMENTにSecrets Managerに登録した値が格納されています。

# docker container exec -it c5a21a82d9ca /bin/bash
root@ip-10-0-0-71:/# env | grep SECRETS
SECRETS_ENVIRONMENT={"age":"20","gender":"man","twitter":"hamako9999"}

セッションマネージャー経由でのECSインスタンスへの接続は、下記ブログを参考にしてください。

【待望】コンテナデバッグが捗る!ECS-optimized AMIにSSMエージェントがプリインストールされるようになりました | Developers.IO

いろんなパターンで、どのような値が環境変数に展開されるか確認する

上のパターンでは、Secrets ManagerのARNを指定することで、その内容を全て環境変数に取り込むことができました。その他の指定パターンでどのような値が抽出されるか確認します。

Secrets Managerに登録した値

再掲となりますが、手順1で設定したSecretの情報は以下の通り。

Secret ARN:arn:aws:secretsmanager:ap-northeast-1:629895769338:secret:hamadaInfo-5yaxBf

項目 変更前 変更後
ステージングラベル AWSPREVIOUS AWSCURRENT
バージョンID 80e210be-3091-49be-9e24-4add26b28249 19362400-a3ca-4905-932f-eb0a2928189d
age 18 20
gender man man
twitter hamako9999 hamako9999

Value設定値と実際に環境変数に展開される値

Valueの設定方法は、ECSのタスク定義の中のコンテナ定義の機密情報設定部分に、以下の文字列形式で指定します。

arn:aws:secretsmanager:region:aws_account_id:secret:secret-name:json-key:version-stage:version-id

具体的にどんなValueを指定すると、どの値が返ってくるかを一覧化したのが下の表です。

[ARN]は、作成したSecretsのARNです。ここでは、arn:aws:secretsmanager:ap-northeast-1:629895769338:secret:hamadaInfo-5yaxBfに読み替えてください。

Value 展開される値
[ARN] {"age":"20","gender":"man","twitter":"hamako9999"}
[ARN]:age 20
[ARN]:gender man
[ARN]:twitter hamako9999
[ARN]::AWSPRECIOUS {"age":"18","gender":"man","twitter":"hamako9999"}
[ARN]::AWSCURRENT {"age":"20","gender":"man","twitter":"hamako9999"}
[ARN]:age:AWSPRECIOUS 18
[ARN]:::19362400-a3ca-4905-932f-eb0a2928189d {"age":"20","gender":"man","twitter":"hamako9999"}
[ARN]:::80e210be-3091-49be-9e24-4add26b28249 {"age":"18","gender":"man","twitter":"hamako9999"}
  • ARNだけ指定した場合、最新のステージングラベル[AWSCURRENT]が設定されたSecretの内容が全て格納される
  • JSON-keyを指定した場合、そのkeyに対応するvalueが格納される
  • 前のバージョンを指定する場合、[AWSPRECIOUS]を指定する。この時JSON-keyとの組み合わせ指定も可能
  • 特定のバージョンをIDで指定する場合、一番最後にバージョンIDを指定する

いろんなパターンで、Secretの内容を参照できるのがおわかりいただけたかと思います。便利やのぅ。

Parameter Storeのセキュア文字列を利用する場合との違い

ECSに機密情報を引き渡す場合、元ネタとしてParameter Storeのセキュア文字列と、Secrets Managerの違いは以下のとおり。

Secrets Managerのほうが、構造化情報を扱いやすい

Parameter Storeのセキュア文字列は、あくまで文字列であるためJSONをそのまま扱うことはできません。対してSecrets Managerは一つのSecretがJSON構造になっており、その中に複数の情報をkey-value形式で保持できます。

例えばSecretとして[DbConnectionInfo]みたいな、DB接続情報を扱う場合、以下のJSONが使いやすいかと思います。

{
  "host" : "ProdServer-01.databases.example.com",
  "port" : "8888",
  "username"   : "administrator",
  "password"   : "My-P@ssw0rd!F0r+Th3_Acc0unt",
  "dbname"     : "MyDatabase",
  "engine"     : "mysql"
}

今までは、このSecretをECSに環境変数として読み込む時、JSONをまるごと取り込んでセレクタにかける必要があったのですが、今回のアップデートでキー指定した読み込みが可能になったので、そのあたりのてまが軽減されてより扱いやすくなっています。

料金体系が違う

パラメータストアはスタンダードを利用する場合、追加料金はかかりません。対してSecrets Managerは、1シークレットあたり0.40USD/月かかり、さらに10,000回のAPIコールあたり、0.05USDかかります。これは、アプリケーション内で頻繁に利用する場合には気になる料金かもしれませんが、コンテナ起動時の一回きりのAPIコールであれば、ほぼ問題にならない料金かと思います。

復号化に利用するCMK(カスタマーマスターキー)の料金は両方にかかります。

ECSタスク定義の機密情報受け渡しは必須機能。便利なSecrets Managerの利用を検討するのが吉

ECSタスク定義に対して、AWSマネージドな仕組みであるパラメータストアやSecrets Managerから安全にコンテナ内に機密情報を受け渡しできるこの機能、地味だけれど非常に重要な機能と言えます。今回のアップデートで、Secrets Managerも非常に使いやすくなりました。Secrets Managerは秘匿情報の管理に最適化されおり構造化データも扱えるため、今パラメータストアを利用している方も、乗り換えを検討して良いかと思います。

現在(2019年2月26日)、まだFargateに対応していないのが惜しいですが、遅かれ早かれ必ず対応される機能だと思いますので、Fargateを利用されているかたは、首を長くして今しばらく待ちましょう。

それでは、今日はこのへんで。濱田(@hamako9999)でした。