おはこんにちばんわ。エムスリーエンジニアの園田です。
先日 aws-ssm-env
というRubyのgemを公開しました。
AWSのParameter Store
からパラメータを取得して環境変数に設定するgemです。
dotenv
のParameter Store
版と思っていただければ分かりやすいかと。
本稿ではgemを作ったきっかけ、簡単な使い方などを説明したいと思います。
Parameter Store とは
その前にParameter Store
とはなんぞや、ということですが。
Parameter Store
はAWSのVaultサービスで、Key-Valueでパラメータを格納してAPIによる参照が可能です。
KMS
(Key Management Service)による暗号化に対応しています。
詳しくは先日私が投稿したQiitaの記事を見て下さい。(手抜き)
なぜgemを作ったか
現在自分が担当しているサービスは4つあり、そのうち3つのアプリケーションがRailsでAWSのElasticBeanstalk
上で稼働しています。
ElasticBeanstalk
で稼働するアプリケーションに変数を渡すのは、ElasticBeanstalk
の環境変数にパラメータを設定してあげれば済むのですが、以下の問題がありました。
- マネジメントコンソール上で平文で表示される。
- EC2内の
/opt/elasticbeanstalk/support/envvars
に平文でパラメータ値が出力される。 - 上記ファイルのパーミッションは
644
で、EC2にログインすれば誰でも見れる。
これらの問題を回避するために、現在稼働中のシステムではフック処理でParameter Store
から取得したシークレット変数などを.env
など別のファイルに書き出してchmod 600
してアプリケーションから参照しています。
このようにElasticBeanstalk
は内部的にシェルで管理されていてエコシステムの恩恵を受けづらく、Parameter Store
から設定値を環境変数に設定する処理はアプリケーションごとに毎回シェルを書いていました。
bash -x
で実行されるコマンドによりログファイルやCloudWatch Logs
にシークレット変数の値が出力されていたという事故も発生しました。
なので、前回の投稿にもあるように、ElasticBeanstalk
をやめてECS
に移行したいという思いがありました。
ただ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_REGION
とYOUR_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/initializers
やconfig/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 だけでなく、Java、Scala、Elixir、Kotlin などが適材適所で大活躍してます。 一緒に開発する仲間を絶賛募集中です!メンバーとカジュアルに話す場も設けてますので是非気軽にお問い合わせください!