ECS タスクから秘匿情報を参照するベストプラクティス

こんにちは。
新規プロダクト開発のテックリードとして働いている島田 (@curryisdrink) です。

去年までエストニアという国に住んでいたのですが、コロナが原因で緊急帰国しまして、縁あって今はここで働かせて頂いています。
入社の経緯を以前記事にして貰ったので、もしご興味ありましたらこちらもご覧ください。
https://pr.forkwell.com/2021-05-25-so-tech

ECS タスクからの秘匿情報の参照について

現在開発しているアプリケーションは、ECS + Fargate で実行するようになっています。
開発の際に調べたところ、AWS 公式の見解としては、秘匿情報の扱いに関して、それらをまず AWS Secrets Manager 及び AWS Systems Manager Parameter Store に格納し、その後コンテナの環境変数として参照できるようにする方法が推奨されていました1

この記事では、上記の方法で秘匿情報を扱うための手順と、その方法のメリット・デメリットについて書いていきます。

前提条件

上記の通り、アプリケーションの実行には ECS + Fargate を用いています。
また、ECS のものを含めたインフラ定義のほとんどは Terraform を用いてコード化されています。

加えて、秘匿情報の格納には Secrets Manager を用いているので、その前提で話を進めて行きます。
もし Systems Manager Parameter Store を使って上記の方法を実現しようと検討されていましたら、一度この記事に書いてあるメリット・デメリットをお読み頂き、
その上で採択すると判断されましたら、公式が提供しているこちら2 を読んで実装を行うことをおすすめします。

尚、これから紹介する手順には、Terraform や Secrets Manager のセットアップに関する情報は含みませんので、手元で動かしながら理解を進めたい場合は、適宜それぞれの公式ページ等を参照し、準備が完了次第読み進めることをおすすめします。

手順

1. Secret に必要な値を入れる

書いてある通り、環境変数に入れたい値を Secret に追加します。

2. 実行権限を付与する

以下のように必要なポリシーを定義し3、タスクを実行するロールに付与します。

data "aws_iam_policy_document" "example" {
  statement {
    effect = "Allow"
    actions = [
      "secretsmanager:GetSecretValue",
      "secretsmanager:DescribeSecret",
    ]
    resources = [
      "arn:aws:secretsmanager:<region>:<aws_account_id>:secret:<secret_name>",
      "arn:aws:kms:<region>:<aws_account_id>:key/<key_id>"
    ]
  }
}

3. タスク定義の container definitions に参照先の Secret を指定する

以下のように指定します4

resource "aws_ecs_task_definition" "example" {
  #省略

  container_definitions = jsonencode([
    {
      #省略

      "secrets": [
        {
          "name": "environment_variable_password",
          "valueFrom": "arn:aws:secretsmanager:<region>:<aws_account_id>:secret:<secret_name>:password::"
        }
      ]
    }
  ])
}

上記 "secrets" に、設定したい環境変数名と、それに入れる値の参照先を指定しています。
"name" に設定したい環境変数名が入り、また "valueFrom" の末尾では、password のように、Secret に入っている値のキーが指定されています。

他にも Secret 全体を参照する書き方等あります5 が、以下の理由により、上記の書き方を採択しています。

  • container definitions を見れば、どの秘匿情報が環境変数として参照出来るようになっているのかを一覧出来るようにしたい
  • 参照先の Secret の値全てを参照する必要がない場合、セキュリティの観点から不要な情報を取り込まないようにしたい

4. 実行する

上記 1~3 まで行った上でコンテナを実行すると、指定された秘匿情報が環境変数として挿入されるようになります。

尚、秘匿情報の挿入はコンテナの最初の実行時に行われるため、こちらの資料6 等を元に、値の更新に関しては運用時に考慮する必要があります。

メリット・デメリット

メリットについて

まず個人の所感として、このベストプラクティスは、実装する側として非常に良いものだなと思っています。
秘匿情報を格納しておけば、他にすることといえば、実行権限の付与と参照したい値の指定ぐらいなので、非常に簡潔でメンテナンスが容易に思えます。
また、コンテナが実行されるまで秘匿情報が参照されることが無いので、開発中の漏洩リスクも下げることが出来ます。

一方、上記のようなメリットを承知した上で、いくつか考慮しなければならないこともあります。
以下それぞれ見ていきます。

デメリット一覧

Secret の値を書き換えるたびにコンテナを実行し直す必要がある

脚注6にある通り、Secret の値を書き換えるたびにコンテナを実行し直す必要があります。

重要なデータは、コンテナが最初に開始されたときにコンテナに挿入されます。シークレットを後で更新またはローテーションすると、コンテナには更新された値が自動的に送信されなくなります。この場合は、新しいタスクを起動する必要があります。

同じ資料にある通り、以下のような対策をしなければならないために注意が必要です。

または、タスクがサービスの一部である場合は、サービスを更新し、[新しいデプロイの強制] オプションを使用して、新しいタスクの起動をサービスに強制できます。

ローカル PC でアプリケーションを実行する際、別の方法で秘匿情報を挿入しなければならない

このベストプラクティスを採択した場合、ECS 上でアプリケーションを実行しない限り、環境変数に秘匿情報が挿入されることはありません。
なのでそれ以外の場所、主にローカル PC で実行する場合は、また別の方法で環境変数の挿入がされるように準備をする必要があります。

その他

他にも脚注6にはこの記事で参照していない注意事項も書かれていますので、合わせてご確認ください。

さいごに

AWS 公式が提唱する秘匿情報を扱うベストプラクティスに関して、その手順と、その方法のメリット・デメリットについて書きました。

この方法以外にも、例えば実行時にアプリケーションから Secret が参照されるようにする等、上記述べたデメリットを無くす方法を取ることも出来ます。
その場合はメリットも合わせて消えてしまうことになりますが、場合によってはベストプラクティスよりも、それがより良い選択肢になる可能性があります。

この記事が、秘匿情報をどのように扱うか考える上で参考になることを願っています。

f:id:so-technologies:20210810175055p:plain