エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

AWS Parameter Storeから取得したパラメータを環境変数に設定するgemを作った

おはこんにちばんわ。エムスリーエンジニアの園田です。

先日 aws-ssm-env というRubygemを公開しました。

github.com

AWSParameter Storeからパラメータを取得して環境変数に設定するgemです。
dotenvParameter Store版と思っていただければ分かりやすいかと。

本稿ではgemを作ったきっかけ、簡単な使い方などを説明したいと思います。

Parameter Store とは

その前にParameter Storeとはなんぞや、ということですが。
Parameter StoreAWSのVaultサービスで、Key-Valueでパラメータを格納してAPIによる参照が可能です。
KMS(Key Management Service)による暗号化に対応しています。
詳しくは先日私が投稿したQiitaの記事を見て下さい。(手抜き)

qiita.com

なぜgemを作ったか

現在自分が担当しているサービスは4つあり、そのうち3つのアプリケーションがRailsAWSElasticBeanstalk上で稼働しています。

ElasticBeanstalkで稼働するアプリケーションに変数を渡すのは、ElasticBeanstalk環境変数にパラメータを設定してあげれば済むのですが、以下の問題がありました。

  • マネジメントコンソール上で平文で表示される。
  • EC2内の/opt/elasticbeanstalk/support/envvarsに平文でパラメータ値が出力される。
  • 上記ファイルのパーミッション644 で、EC2にログインすれば誰でも見れる。

これらの問題を回避するために、現在稼働中のシステムではフック処理でParameter Storeから取得したシークレット変数などを.envなど別のファイルに書き出してchmod 600してアプリケーションから参照しています。

このようにElasticBeanstalkは内部的にシェルで管理されていてエコシステムの恩恵を受けづらく、Parameter Storeから設定値を環境変数に設定する処理はアプリケーションごとに毎回シェルを書いていました。
bash -xで実行されるコマンドによりログファイルやCloudWatch Logsにシークレット変数の値が出力されていたという事故も発生しました。

なので、前回の投稿にもあるように、ElasticBeanstalkをやめてECSに移行したいという思いがありました。

www.m3tech.blog

ただECSとなると、環境変数を設定するためにはTaskDefinitionにパラメータを渡す必要があり、CloudFormationなどを利用して渡す必要が出てきて、現状terraformでインフラ管理している構成を大幅に変更しなければなりません。

他にもやりようはありますが、いずれもインフラ構成の複雑化を招くので外部からインジェクションするのではなく、アプリ側からPullする仕組みにしたかったというのがこのgemを作ったきっかけです。

まあ、実際にはOSSへの貢献経験が少なく、やってみたかったというのが一番の理由なのですが。

gemの使い方

READMEに詳しい使い方を記載してあります。ここではテンプレート的な使い方を説明します。

パラメータの登録

アプリケーションを起動する前に予めParameter Storeにパラメータを設定しておく必要があります。

aws ssm put-parameter --name '/myapp/production/RDS_PASSWORD' \
  --type 'SecureString' --value '<secret value>'
aws ssm put-parameter --name '/myapp/production/SECRET_KEY_BASE' \
  --type 'SecureString' --value '<secret value>'

パラメータ参照可能なIAMロール(ユーザ)作成

以下の権限を付与したIAMロールないしはユーザを作成しておきます。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": "ssm:GetParametersByPath",
      "Resource": "arn:aws:ssm:YOUR_REGION:YOUR_ACCOUNT_ID:parameter/YOUR_PATH"
    }
  ]
}

YOUR_REGIONYOUR_ACCOUNT_IDはご自身の環境に合わせて変更してください。
YOUR_PATHは後述のAwsSsmEnv#loadの引数に指定するpathを指定してください。

アプリケーション側の設定

例えばRailsの場合、以下のようにします。

# Gemfile
gem 'aws-ssm-env'
# config/application.rb
# configのブロック内に以下を追記。
AwsSsmEnv.load(path: "/myapp/#{ENV['RAILS_ENV']}", recursive: true)

これでrails startすればENVにパラメータが設定された状態で起動します。

dotenvと違ってgemを追加するだけで勝手に読み込んでくれたりはしません。config/initializersconfig/environmentsではなくapplication.rbに記載する必要があります。理由は2つあって、

  • Parameter Storeの階層設計はシステムごとに全く異なるためCoCにしづらい。
  • database.ymlを読み込む前にENVが設定済みな必要がある。

ので、今のところ直接application.rbのコードに記述する方式を取っています。
AWS_SSM_ENV_PATHなどの環境変数で渡してもいいかなと思いますが、環境変数のために環境変数を設定するというのも微妙な気がして、気が向いたら対応します。

注意: セキュリティレベルについて

実は前述の課題はあまり解決できていません。むしろ最後のものは悪化しています。

  • マネジメントコンソール上で平文で表示される。 => 解決
  • /opt/elasticbeanstalk/support/envvarsファイルに平文で出力される。 => 解決
  • 上記ファイルのパーミッション644 で、EC2にログインすれば誰でも見れる。 => 悪化

というのも、普通はEC2 Instance ProfileにPolicyを設定するので、EC2にログインさえできればaws ssm get-parameters-by-pathコマンドで誰でもシークレット変数が見えてしまいます。
これを回避するためには、EC2 Instance ProfileのIAMロールとは別にIAMユーザを作成しなければならず、その認証情報(AWS_ACCESS_KEY_IDなど)をどこかに持たなければなりません。
そうなると認証情報のバケツリレーになるだけで、認証情報の いたちごっご(インフラあるある) になってしまうのです。

なので、そもそもEC2にログインできるアカウントが限られている環境での利用を想定しています。
Parameter Storeの参照をCloudTrailで監視していればよりベターです。

ただ、パラメータを一元管理できるメリットは享受できるので、このgemを使わないよりは使った方が多くの場合は運用が楽になると思います。

Pull Request 募集中

Ruby歴がJava歴に比べて圧倒的に短く、なるべくRubyらしいコードに仕上げようとは努力しましたが、そうでない箇所も多数あると思われます。
ですので、Pull Requestは随時募集しております。

また、ドキュメントやソースコメントが日本語しかなく、社内のネイティブメンバーに改善をしてもらおうと思っていますが、翻訳のPRもお待ちしています。

一緒に開発してくれる仲間を募集中です!

エムスリーでは Ruby だけでなく、JavaScala、Elixir、Kotlin などが適材適所で大活躍してます。 一緒に開発する仲間を絶賛募集中です!メンバーとカジュアルに話す場も設けてますので是非気軽にお問い合わせください!

jobs.m3.com