AWS Elastic Beanstalk で環境変数を Parameter Store から設定する

Elastic Beanstalk でアプリケーションを動作させる場合に環境変数を設定しますが、DB パスワードやアクセスキーといった秘密情報はどうすべきでしょうか?環境変数では設定が暗号化されず管理コンソールからも見えてしまう状態ですので、良いこととは思えません。アプリケーション内で Parameter Store や Secret Manager などから動的に取得するのがベストな気がしますが、Web フレームワークでは環境変数で設定することが前提となっている場合も多いと思います。

ここでは、Parameter Store に保存した SecuretString を環境変数として設定する方法を整理してみました。

参考) CloudFormation で設定する

参照) 動的な参照を使用してテンプレート値を指定する - AWS CloudFormation ユーザーガイド

CloudFormation からデプロイしている場合は、テンプレートで文字列内に {{resolve:ssm:(パラメーター名)}}{{resolve:ssm-secure:(パラメーター名)}} と記述することで、デプロイ時に Parameter Store から取得することができます。

例)

  WebEBEnvironment:
    Type: AWS::ElasticBeanstalk::Environment
    Properties:
      OptionSettings:
        - Namespace: aws:elasticbeanstalk:application:environment
          OptionName: APP_KEY
          Value: "{{resolve:ssm:/project/foo/bar}}"

しかし、EB 環境変数に対して SecureString から取得する ssm-secure は未サポートです。サポートしていたとしても結局デプロイ時に環境変数として値が設定されるので、何の解決にもなっていません。未サポートなのは秘密情報が露出しないようにするためと思われます。

補足ですが、この方法で Parameter Store から値を取得してデプロイして、あとからパラメーターの値を変更した場合には、CloudFormation から再度更新しないと反映されない点にも注意が必要です。ドリフトも検出されません。

デプロイフックで Parameter Store から取得し、環境変数を更新する

参照) Elastic Beanstalk Linux プラットフォームの拡張 - AWS Elastic Beanstalk デベロッパーガイド

Elastic Beanstalk 環境ではデプロイ時に処理をおこなうことができるので、これを利用してみます。

まず EB 環境変数に、以下のような値(文字列)を設定しておくことにします。書式は CloudFormation のものに似せてみましたが、決めの問題です。通常の環境変数の値として使われない文字列にする必要があります。

  • ssm:(パラメーター名)
  • ssm-secure:(パラメーター名)

EB 環境変数は /opt/elasticbeanstalk/deployment/env に保存されているため、そのファイルを読み込んで、この書式で設定された環境変数があれば、指定されたパラメーターを Parameter Store から取得して書き換えます。

スクリプト例)

#!/usr/bin/env bash

set -eu

EB_ENV_FILE=/opt/elasticbeanstalk/deployment/env

# get region from instance profile
AWS_DEFAULT_REGION="$(curl http://169.254.169.254/latest/meta-data/placement/region)"
export AWS_DEFAULT_REGION

# convert environment variables
cp -p "${EB_ENV_FILE}" "${EB_ENV_FILE}.orig"
readarray env_vars < "${EB_ENV_FILE}.orig"
for i in "${env_vars[@]}"
do
  key="$(echo "${i}" | cut -f1 -d "=")"
  val="$(echo "${i}" | cut -f2- -d "=")"
  if echo "${val}" | grep "^ssm:" > /dev/null; then
    val=$(aws ssm get-parameter --name "${val//ssm:/}" --output text --query "Parameter.Value")
  elif echo "${val}" | grep "^ssm-secure:" > /dev/null; then
    val=$(aws ssm get-parameter --name "${val//ssm-secure:/}" --with-decryption --output text --query "Parameter.Value")
  fi
  sed -E -i.bak "s|^${key}=.*$|${key}=${val}|" "${EB_ENV_FILE}"
done
rm -f "${EB_ENV_FILE}.bak"

# apply environment variables
systemctl restart php-fpm

このスクリプトを .platform/hooks/postdeploy および .platform/confighooks/postdeploy に配置しておきます。

補足

  • デプロイフックが動作するタイミングではすでに Web サーバーは起動しており、ファイルを書き換えるだけでは書き換えた環境変数は反映されません。そのため、上記の例では書き換え後に php-fpm を再起動しています。PHP 以外の環境では、それぞれ適切なプロセスに書き換える必要があります。
  • インスタンスロールに Parameter Store から取得するための権限(ssm:GetParameter)を持たせておく必要があります。
  • Parameter Store の値を変更した場合、次にデプロイフックが動作したタイミングで反映されます。
  • 上記例ではパラメーターのバージョン指定は未対応です。必要な場合は修正してください :smiley:
  • 環境変数を書き換えて反映するという手順の都合上、書き換え前の設定で動作する期間が発生します。デプロイポリシーが All at Once の場合はその状態でリクエストを受けてしまうことがありエラーになる場合があります。

デプロイフックで Parameter Store から取得し、設定ファイルを生成する

Web フレームワークによっては環境変数に加えて .env 等の設定ファイルから設定を読み込むものがあります。この場合、同様にして、環境変数そのものでなくその設定ファイルを生成する、または書き換えるという方法も取ることができると思います。取得した秘密情報をその設定ファイルに書き込むため、パーミッションには注意しましょう。(先に説明した環境変数を書き換える方法でもファイルに書き込みますが、こちらは root ユーザーのみ参照可能となっており、多少マシな気はします。)