AWS Secrets ManagerからParameter StoreをTerraformで作成する

# 概要
タイトルの通りですが、AWS Secrets ManagerからParameter StoreをTerraformで作成する方法を紹介します。

# 想定読者
- AWSの基礎知識をお持ちの方
- [AWS Secrets Manager](https://aws.amazon.com/jp/secrets-manager/)、[Parameter Store](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-paramstore.html) を使った事がある方
- [Terraform](https://www.terraform.io/) を使った事がある方

[AWSのParameter StoreとSecrets Manager、結局どちらを使えばいいのか？比較](https://qiita.com/tomoya_oka/items/a3dd44879eea0d1e3ef5) で両サービスが比較されているので分かりやすいです。

# どういう場面で役に立つか

一見すると何の為にこんな事をするのか？普通にSecrets Managerから環境変数を取得すれば良いのでは？と思うかもしれませんが、以下の状況が揃った時に使えると思っています。

- 複数のアプリケーションをAWSアカウントで管理している
    - ここでは `news-app` と `weather-app` という2つのアプリケーションを例に説明します
- 複数のアプリケーションから同様の機密情報を参照する必要がある
    - この記事では以下の2つが登録されていると仮定します。

![SeecretManager.png](https://qiita-image-store.s3.amazonaws.com/0/71899/1a0645ac-546b-a483-03c0-3e566e3ff11a.png)

```json:dev/keitakn/slack
{
  "TOKEN": "DummySlackToken0001"
}
```

```json:dev/keitakn/sendgrid
{
  "API_KEY": "DummySendGridAPIKEY0001"
}
```

それぞれ [Slack](https://slack.com/intl/ja-jp/) のトークンと [SendGrid](https://sendgrid.kke.co.jp/) のAPIキーです。

このように複数のアプリケーションで使いまわす機密情報が存在すると言う前提です。

この方法であれば `AWS Secrets Manager` だけに機密情報を入れておけば各アプリケーション毎のParameter Storeを動的に作成する事が可能なので、手動で管理する部分を最小限に抑える事が出来るというメリットがあります。

# Terraformで `aws_ssm_parameter` resourcesを使いParameter Storeを作成

以下のようにHCLを書きます。

```tcl:main.tf
// 既に存在するSeecretManagerリソースを参照する
// この場合AWSに {環境変数}/keitakn/slack という名前で登録されている前提
data "aws_secretsmanager_secret" "slack_secret" {
  name = "${terraform.workspace}/keitakn/slack"
}

data "aws_secretsmanager_secret_version" "slack_secret" {
  secret_id = "${data.aws_secretsmanager_secret.slack_secret.id}"
}

// 既に存在するSeecretManagerリソースを参照する
// この場合AWSに {環境変数}/keitakn/sendgrid という名前で登録されている前提
data "aws_secretsmanager_secret" "sendgrid_secret" {
  name = "${terraform.workspace}/keitakn/sendgrid"
}

data "aws_secretsmanager_secret_version" "sendgrid_secret" {
  secret_id = "${data.aws_secretsmanager_secret.sendgrid_secret.id}"
}

// terraformのExternal Data Sourceを使ってSeecretManagerのJSONをデコードする
// https://www.terraform.io/docs/providers/external/data_source.html
data "external" "slack_secret_json" {
  program = ["echo", "${data.aws_secretsmanager_secret_version.slack_secret.secret_string}"]
}

data "external" "sendgrid_secret_json" {
  program = ["echo", "${data.aws_secretsmanager_secret_version.sendgrid_secret.secret_string}"]
}

// デコードしたJSONを元にパラメータストアを作成する
resource "aws_ssm_parameter" "news_app_slack_token" {
  name  = "/${terraform.workspace}/test-app/news/slack-token"
  type  = "SecureString"
  value = "${data.external.slack_secret_json.result["TOKEN"]}"
}

resource "aws_ssm_parameter" "news_app_sendgrid_api_key" {
  name  = "/${terraform.workspace}/test-app/news/sendgrid-api-key"
  type  = "SecureString"
  value = "${data.external.sendgrid_secret_json.result["API_KEY"]}"
}

resource "aws_ssm_parameter" "weather_app_slack_token" {
  name  = "/${terraform.workspace}/test-app/weather/slack-token"
  type  = "SecureString"
  value = "${data.external.slack_secret_json.result["TOKEN"]}"
}

resource "aws_ssm_parameter" "weather_app_sendgrid_api_key" {
  name  = "/${terraform.workspace}/test-app/weather/sendgrid-api-key"
  type  = "SecureString"
  value = "${data.external.sendgrid_secret_json.result["API_KEY"]}"
}
```

`${terraform.workspace}` はTerraformのworkspace名が入ります。
workspaceを以下のように定義しておけば各環境変数毎にParameter Storeを生成する事が可能です。（上記のサンプルコードの場合、Secrets Managerも環境毎に作成する必要あり）

※ workspaceの設定例

- dev
- stg
- prod

`terraform apply` を実行すると以下のようにParameter Storeが生成されます。

![ParameterStore.png](https://qiita-image-store.s3.amazonaws.com/0/71899/a89753a5-a2e0-4ba7-4493-eae8d9de496b.png)

## 補足
`/dev/test-app/news/sendgrid-api-key` のような名前でParameter Storeを生成しているのは階層化を行う為です。

例えば以下のようにAWS-CLIを使うと `news-app` 用の環境変数がまとめて取得出来ます。

```bash:AWS-CLIで以下を実行する
aws ssm get-parameters-by-path --path /dev/test-app/news --with-decryption
```

`--with-decryption` はセキュアな文字列を復号化する為のオプションです。
他にも必要に応じて `--region` や `--profile` 等を指定して下さい。

```bash:regionとprofileを指定する場合の例
aws ssm get-parameters-by-path --path /dev/test-app/news --with-decryption --profile nekochans-dev --region ap-northeast-1
```

結果は以下の通りです。

```json:AWS-CLIの結果
{
    "Parameters": [
        {
            "Name": "/dev/test-app/news/sendgrid-api-key",
            "Type": "SecureString",
            "Value": "DummySendGridAPIKEY0001",
            "Version": 1,
            "LastModifiedDate": 1552918997.781,
            "ARN": "arn:aws:ssm:ap-northeast-1:000000000000:parameter/dev/test-app/news/sendgrid-api-key"
        },
        {
            "Name": "/dev/test-app/news/slack-token",
            "Type": "SecureString",
            "Value": "DummySlackToken0001",
            "Version": 1,
            "LastModifiedDate": 1552918997.788,
            "ARN": "arn:aws:ssm:ap-northeast-1:000000000000:parameter/dev/test-app/news/slack-token"
        }
    ]
}
```

# 作成したParameter Storeの使い道

AWSのサービスなので当然ですがAWSのサービスと親和性が高いです。

主にコンテナやServerlessアーキテクチャの利用時に力を発揮します。

利用用途として参考になりそうな記事をいくつか載せておきます。

1. [【祝！】FargateでもECSにごっつ簡単に環境変数に機密情報を渡せるようになりました！](https://dev.classmethod.jp/cloud/aws/fargarte-secrets/)
1. [パラメータストアを使って安全なLambda関数を作成する](https://dev.classmethod.jp/cloud/aws/sugano-039-ssm-paramstore-lambda/)
1. [CodeBuild（buildspec.yml）での環境変数指定方法あれこれまとめ](https://dev.classmethod.jp/cloud/codebuild-env/)

# 終わりに
最近EC2のアプリケーションをAWS Fargateに置き換える案件を行っており、機密情報をどう管理するか？という情報が以外と少なかったので記事にしてみました。

今回作成したサンプルコードはGitHubで公開しています。

https://github.com/keitakn/my-terraform/blob/master/modules/aws/ssm/main.tf

最後まで読んで頂きありがとうございました。