LoginSignup
15
10

More than 5 years have passed since last update.

Rails 5.1 Encrypted secrets を config gem と併用する

Last updated at Posted at 2017-12-05

この記事は Speee Advent Calendar 2017 6日目の記事です。
5日目は @hatappi による Red Chainerをコードを変更せずに約2倍くらい早く処理させる でした。

数ヶ月前に、弊社の福利厚生制度でもある Speee Library のシステム全面リニューアルが行われ、アプリケーションが Rails 5.1 でリライトされました。

今回は、その運用の中で学んだ、 Rails 5.1 の Encrypted secrets と config gem を併用するためのコツを書き留めておきたいと思います。

config gem (rails_config)

YAML (config/settings.yml) でアプリケーション設定を一元管理できる、定数管理 gem です。

環境別の設定 (config/settings/{environment}.yml) 、各個人の開発環境毎に異なる設定 (config/settings.local.yml) 、環境変数による設定 (use_env オプション) などの機能を備えています。

設定値の管理を明瞭に行えるため、私は好んでこの gem を使用しています。

一方、production 環境の API キーのような、リポジトリに含めるべきでない秘匿情報はどう管理しようか? という課題もありました。

Encrypted secrets

Rails 5.1 で導入された仕組みで、API キーやインフラ設定などの秘匿情報を、config/secrets.yml.enc で暗号化して管理できるようになります。

秘匿情報の管理については yaml_vault などの先行例もありましたが、今回のプロジェクトではせっかく Rails 5.1 を使える状況なので、Rails way な方法で管理してみることにしました。

:warning: Rails 5.2 で deprecated

Rails 5.2 では Encrypted secrets が deprecated となり、Credentials に置き換わる予定です。マイナーバージョン1つ分という、なんとも短い人生でした。:innocent:

$ bin/rails secrets:edit

Encrypted secrets is deprecated in favor of credentials. Run:
bin/rails credentials --help

Rails 5.2.0 beta: Active Storage, Redis Cache Store, HTTP/2 Early Hints, CSP, Credentials | Riding Rails

Encrypted secrets と異なり、Credentials は 動作環境の設定が (beta2 時点では) ないため、環境毎の設定値を扱う場合は自分で工夫する必要があります(値取得時に Rails.env をキーにするなど)。

Encrpyted secrets + config 併用のアプローチ

自身のプロジェクトでは、全面的に config gem に乗っかっていたので、以下のようなアプローチを取りました。

  • Encrypted secrets で設定した値を config gem の設定にマージする
  • Rails アプリ内では config gem の値を使う (=Settings を参照する)
  • :arrow_right: config gem で Encrypted secrets の値を透過的に参照する

基本的には config gem を使い、production 環境の設定値は Encrypted secrets で管理するといった具合です。

イニシャライザで設定値をマージする

config gem は #add_source! で、任意のハッシュを設定値として追加できます。なので、イニシャライザに下記のようなコードを追加することで、意図した事が実現できそうです。

Settings.add_source!(Rails.application.secrets.deep_stringify_keys)
Settings.reload!

Settings は内部で文字列キーを使用しているため、#deep_stringify_keys で変換して正しくマージできるようにしています。

イニシャライザのファイル名

この処理を config/initializers/config.rbrails g config:install を実行すると作られる)に書きたくなるのですが、そうすると NameError が返ってきます。

/Users/yukihattori/rails51/config/initializers/config.rb:1:
in `<top (required)>': uninitialized constant Settings (NameError)
Did you mean?  String

このファイルは Config 自体の設定 と定義されていて、ここではまだ設定値 Settings は使用できません。そのため、異なるファイル名のイニシャライザを使用する必要があります(例:config_with_encrypted_secrets.rb)。

config/initializers/config_with_encrypted_secrets.rb
Settings.add_source!(Rails.application.secrets.deep_stringify_keys)
Settings.reload!

動作確認

config/secrets.yml.enc (復号時)
production:
  hoge: fuga
$ rails runner -e production 'puts Settings.hoge'
fuga

Encrypted secrets の値を透過的に扱えてますね!

データベース設定を Encrypted secrets で管理する時の注意

例えば、staging 環境のデータベース認証情報をリポジトリ管理したいとします。

config/database.yml
default: &default
  adapter: mysql2
  database: application
  host: localhost

staging:
  <<: *default
  username: staging
  password: <%= Settings.database.password %>
config/secrets.yml.enc (復号時)
staging:
  database:
    password: pass

ところが、db:create を実行するとエラーになってしまいます。

$ RAILS_ENV=staging bundle exec rake db:create
rake aborted!
NoMethodError: Cannot load `Rails.application.database_configuration`:
undefined method `database' for nil:NilClass

db:create は、Rails の環境を事前に読み込んでいないため、Encrypted secrets の有効化設定 (config.read_encrypted_secrets = true) を認識していないのが原因です。

この問題は db:load_config タスクの依存関係に environment を設定することで解消できます。

lib/tasks/db/load_config.rake
namespace :db do
  task load_config: :environment
end

Rails 5.2 では...

Rails 5.2 では、下記 PR にて environment が依存関係に追加されているので、この対策は必要ありません。

load_config taskのdependency にenvironment taskを追加しています。

元々一部taskではenvironment taskが実行されていなかった(environmentファイルがロードされてなかった)のですが、それだと、例えばdatabase.ymlにencrypted secretsを使用している場合に問題になる(environmentファイルがロードされない、という事はread_encrypted_secretsがtrueになる事が無い)為、一通りのtaskでenvironmentファイルのロード処理が行われるようにする為に、load_config taskのdependency にenvironment taskを追加しています。

rails commit log流し読み(2017/11/14) - なるようになるブログ

おわりに

config gem での秘匿情報の管理が Encrypted secrets で少し楽になりました。やはりリポジトリで管理できるのは嬉しいですね。

Encrypted secrets 自体は Rails 5.2 での deprecated により賞味期限が迫ってしまったものの、Credentials になってもこの原理自体は変わらないので、同様のアプローチで透過的に秘匿情報を扱えると思われます。

ボツアプローチ

実は、以前から他のアプローチもいろいろと試しておりました。例えば...

  • settings.yml 内で ERB を使用し、<%= Rails.application.secrets.hoge %> で読み込む
  • config/boot.rbrequire 'config' を追加して config gem の Rails インテグレーションを読み込まないようにした上で、自前で Config.load_and_set_settings で設定を読み込む

などなど。

実際のプロジェクトでは、ここに至るまで諸々のトラブルがありました。Config の読み込みタイミングに起因する問題や、Sidekiq との併用など…

さまざまなトラップを踏んだ末、ここに挙げた後者の方法で長らく運用していて、今回もその話をしようかと思っておりました。
が、この投稿を書くために改めて調査した結果、『一周回ってシンプルに修正できるじゃん!』と気づいた次第です。何事も書き留める事は大切ですね... :pencil:
 

次回は弊社広報の @mogmog214 より、『自社テックブログの更新頻度を2年で6倍にした話』です!
:arrow_right: 自社テックブログの更新頻度を2年で6倍にした話 - mogmog2の日記

15
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
10