AWS上のパスワードちゃんと管理できてますか?AWSパラメータストアで秘密情報を安全に管理する方法

このエントリーをはてなブックマークに追加

エンジニアの@macs_6です。

AWS上で稼働中のサービスをスケールアウトする際に、DBのパスワード等のセキュアな設定をどこに保存しておくか困ったことはありませんか?

HerokuやKubernetesであれば以下の機能があります。

AWSではどこで管理すればいいのでしょうか?

KubernetesをAWS上に構築するサンプルを見てみるとSecretsの裏側にはパラメータストアが利用されています。

そこでこのブログではAWS ECS上でのRedash構築を例にして、パラメータストアとKMSを使った秘密情報を含んだ環境変数の管理方法について紹介します。

現在のECS環境については、“会社の本番環境をDocker(ECS)に置き換えるために準備したこと気づいたこと”で紹介しているので、そちらを参照して下さい。

目次

システム全体像と今回使うAWSのサービス

登場するAWSのサービスは以下です。

これらを使って以下のことをします。

  • ECS上で稼働するDockerコンテナ(Redash)の用意
  • パラメータストアに設定を保存、KMS上の暗号化キーで暗号化
  • 暗号化キーにアクセスできるIAM Roleを作成、Dockerコンテナに付与
  • Dcokerコンテナはパラメータストアに保存された設定を使って稼働

全体像

docker-compose.ymlの用意

早速、ECS上のTask Definitionecs-cliを使って、docker-compose.ymlから作成します。
現在、ecs-cliはv3をサポートしていないためv2を使用します。

# docker-compose-production.yml
version: '2'
services:
  server:
    image: redash/redash:4.0.0.b3387
    command: bash -c 'wget https://path/to/parameter-store-exec -O parameter-store-exec && chmod +x parameter-store-exec && AWS_REGION=ap-northeast-1 PARAMETER_STORE_EXEC_PATH=/prod/redash ./parameter-store-exec bin/docker-entrypoint server'
    ports:
      - "0:5000"
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: INFO
      REDASH_REDIS_URL: redis://your-redis-host:6379/0
      # REDASH_DATABASE_URL: stored in Parameter Store
      REDASH_HOST: example.com
      REDASH_ALLOW_SCRIPTS_IN_USER_INPUT: true
      REDASH_DATE_FORMAT: YY/MM/DD
      REDASH_PASSWORD_LOGIN_ENABLED: true
    logging:
      driver: awslogs
      options:
        awslogs-group: /your-group
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: redash
    mem_reservation: 134217728 # 128MB
  worker:
    image: redash/redash:4.0.0.b3387
    command: bash -c 'wget https://path/to/parameter-store-exec -O parameter-store-exec && chmod +x parameter-store-exec && AWS_REGION=ap-northeast-1 PARAMETER_STORE_EXEC_PATH=/prod/redash ./parameter-store-exec bin/docker-entrypoint scheduler'
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: INFO
      REDASH_REDIS_URL: redis://your-redis-host:6379/0
      # REDASH_DATABASE_URL: stored in Parameter Store
      QUEUES: "queries,scheduled_queries,celery"
      WORKERS_COUNT: 2
    logging:
      driver: awslogs
      options:
        awslogs-group: /your-group
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: redash
    mem_reservation: 134217728 # 128MB

今回はAWS上で構築する想定なので、データベースやRedisはRDSやElastiCacheといったマネージドのものを使いましょう。

ローカルで使うdocker-compose.ymlと異なるのは以下の点です。

  • commandにてパラメータストアの値を展開するためのparameter-store-execを使っている。
  • ポートのマッピングで、動的ポートマッピングするためにホスト側を0にしている。
  • REDASH_DATABASE_URLがパラメータストアに切り出されている。(DBパスワードを含むため)
  • loggingのdriverにawslogsが指定されている。
  • mem_reservationでmemoryのsoft limitを指定している。(hard limitを使うならmem_limitにする)

パラメータストアから環境変数を展開するparameter-store-execはGoで書かれているので、事前に対象のアーキテクチャに合わせてビルドしておきます。
parameter-store-execに似たツールとしてaws-envというものもあります。

  • parameter-store-exec: 引数で渡されたcommandに変数を適用する。entrypointとして使用する想定。
  • aws-env: 取得した変数を環境変数に展開する。

今回はaws-envで展開出来ないパラメータがあったため、parameter-store-execを使っています。

上記のdocker-compose.ymlではcommandにparameter-store-execのダウンロードと実行を詰め込んでいますが、Dockerfileの記述してENTRYPOINTにしておくと、docker-compose.ymlの記述量を減らせます。
公式のイメージを基にDockerfileに書く場合は、元々のENTRYPOINTを上書きすることに注意しましょう。

ecs-cli向けに、ecs-params.ymlというファイルも用意してdocker-compose.ymlに対応しないフィールドの値を定義します。

# ecs-params.production.yml
version: 1
task_definition:
  ecs_network_mode: bridge
  task_role_arn: your-role-arn
run_params:
 network_configuration:
   awsvpc_configuration:
     subnets:
       - your-subnet-0
       - your-subnet-1

ここで大事なのはtask_role_arnに適切なロールを与えて、docker-compose.ymlでパラメータストアに切り出した秘密情報にアクセスできるようにすることです。task_execution_roleではないことに注意しましょう。
ロールの作成は後のセクションで紹介します。

パラメータの保存と暗号化

まずは暗号化のためのキーをKMSで作成します。あとでGUI上でも判別できるようにaliasで名前もつけておきます。

$ aws kms create-key --description ecs-redash --region ap-northeast-1
$ aws kms create-alias --alias-name "alias/prod-redash" --target-key-id "your-key-id"

上記のaliasはalias/で始める必要があります。

次に先ほど作ったキーで値をパラメータストアに暗号化して保存します。

$ aws ssm put-parameter --name /prod/redash/REDASH_DATABASE_URL --value "postgresql://user:passwrod@host/database" --type SecureString --key-id "your-key-id"

パラメータストアは上記の/prod/redash/REDASH_DATABASE_URLのようにスラッシュで区切って階層化したり、タグを使って管理することが出来ます
parameter-store-execaws-envは、特定の階層の値をまとめて展開することが出来るので、今回は/prod/redash/という階層で値を管理しています。

権限の設定

ロールの作成はこちらを参考に行います。

{
   "Version": "2012-10-17",
   "Statement": [
     {
       "Sid": "",
       "Effect": "Allow",
       "Principal": {
         "Service": "ecs-tasks.amazonaws.com"
       },
       "Action": "sts:AssumeRole"
     }
   ]
 }

上記をecs-tasks-trust-policy.jsonという名前で保存してロールを作成。

$ aws iam create-role --role-name ecs-redash --assume-role-policy-document file://ecs-tasks-trust-policy.json

{
     "Version": "2012-10-17",
     "Statement": [
         {
            "Sid": "Sid0",
            "Effect": "Allow",
            "Action": "ssm:DescribeParameters",
            "Resource": "*"
         },
         {
             "Sid": "Sid1",
             "Effect": "Allow",
             "Action": [
                 "ssm:GetParameter",
                 "ssm:GetParameters",
                 "ssm:GetParametersByPath"
             ],
             "Resource": [
                 "arn:aws:ssm:ap-northeast-1:<account-id>:parameter/prod/redash/*"
             ]
         },
         {
             "Sid": "Sid2",
             "Effect": "Allow",
             "Action": [
                 "kms:Decrypt"
             ],
             "Resource": [
                 "arn:aws:kms:ap-northeast-1:<account-id>:key/<key-id-for-prod-redash-key>"
             ]
         }
     ]
 }

次に上記のファイルをredash-secret-access.jsonという名前で作成して、以下を実行してIAMポリシーを作成し、先ほど作ったロールに付与します。

$ aws iam create-policy --policy-name ecs-redash --policy-document file://redash-secret-access.json
$ aws iam attach-role-policy --role-name ecs-redash --policy-arn "arn:aws:iam::<account-id>:policy/ecs-redash"

ssm:GetParametersssm:GetParametersByPath等、必要なコマンドへの権限が全て揃っているか注意しましょう。
今回のファイルはparameter-store-execの使用に必要な権限を付与しています。

作成したロールをecs-params.ymlで指定しておきます。

Redashの起動

ここまでで以下のものを用意しました。

  • docker-compose.ymlecs-params.yml
  • パラメータストア上に暗号化されたDBの秘密情報
  • パラメータストアと暗号化に使用したキーへの権限

あとはこれらを使ってECSのServiceをservice upで起動するだけです。

$ ecs-cli compose -c your-cluster-name -p your-project-name service up --target-group-arn your-elb-arn --container-name server --container-port 5000

ecs-cli composeはデフォルトでdocker-compose.ymlecs-params.ymlを参照しますが、別名を使っている場合は指定が必要です。
上記のコマンドで指定しているELBの作成作業についてはここでは割愛しています。

Redashについては初回にDBの設定が必要なので、docker-compose runの要領で以下も実行しておきます。

$ ENTRYPOINT="wget https://path/to/parameter-store-exec -O parameter-store-exec && chmod +x parameter-store-exec && AWS_REGION=ap-northeast-1 PARAMETER_STORE_EXEC_PATH=/prod/redash ./parameter-store-exec bin/docker-entrypoint"
$ ecs-cli compose -c your-cluster-name -p your-project-name run server "bash -c \"$ENTRYPOINT create_db\""

ここで対象のホストにアクセスすると、パラメータストア上のDB情報をRedashが参照して、正しく動作していることが確認できるはずです。

運用時には、docker-compose.ymlやDockerイメージに変更があった場合にservice upを実行することでTask Definitionが更新され、Serivceが使用しているTask Definitionのrevisionも更新され、最新のDockerイメージでコンテナを起動することが出来ます。

まとめ

一度IAMロールの作成やキーの作成等を済ませてしまえば、ローカルでDockerを使う場合とそれほど大きな違いなく、DBのパスワード等のセキュアな情報を使ってECS上でコンテナを動かすことができます。

今回はecs-redashというIAMロールを作って、パラメータストアの/prod/redash以下にセキュアな環境変数を保存しました。
このようにパラメータストアではアプリケーション毎にアクセス権限を分けておくことができるので、担当アプリケーションが細かく分かれているような場合でも使用することが出来ます。
パラメータストアを活用して、アプリケーションも設定もポータブルに管理しておきましょう!

まっくす


Housmartでは不動産業界を変えるカウルを支えるエンジニアを募集しています。
今話題のReTech!業界を変えるカウルを支えるエンジニアをWanted!

このエントリーをはてなブックマークに追加