LIVESENSE made*

リブセンスのエンジニアやデザイナーの活動や注目していることをまとめたブログです。

MENU

Railsでの秘匿情報の取り扱いを運用しやすくするgemを作った話

f:id:livesense-made:20170720182249j:plainみなさんこんにちは。ジョブセンスでエンジニアをしている小沼です。初めまして。 16新卒で入社して現在2年目になります。 去年はSQLで円グラフを書いたりしていました。

さて、本題に入ります。先日新規でRailsリポジトリを立ち上げたのですが、その時に秘匿情報の取り扱いについて考えたことがあったので紹介いたします。

Rails5.1から導入されたEncrypted Secretsについて

Rails5.1から標準でEncryptedSecretsという機能を使うことができるようになりました。

参考: Rails 5.1: Loving JavaScript, System Tests, Encrypted Secrets, and more | Riding Rails

使い方はざっくりと以下の通りです。

# 鍵とsecretsを準備
# 鍵ファイルは自動で.gitignoreに追記される
$ bundle exec rails secrets:setup

# 編集
$ bundle exec rails secrets:edit

上記の操作で秘匿情報をセキュアにgit管理することができます。 暗号化された情報はconfig/secrets.yml.encに保存されていて、アプリケーションの初期化に際してconfig/secrets.ymlの内容とmergeされます。

実際にアプリケーション上で取り扱う際は、
config/application.rbまたはconfig/environments/*.rbconfig.read_encrypted_secrets = trueとした上、Rails.application.secrets.hogehogeで参照することができます。

正直なところ、かゆいところに手が届かない

新規で作成したリポジトリではRails5.1を使っていたので、EncryptedSecretsを使うことができる環境でした。 しかしながら実際の運用を始めてみると、EncryptedSecretsは少々使いにくいなと思うことがでてきました。

そのように感じた主な理由は以下3点です。

  • 暗号化された情報は一行で記述されるため、コンフリクトが起きやすい
    • 当然、変更のたびにファイル全体が変更されるため、どの情報がいつ変更されたのか確認できない
  • ファイル全体が暗号化されるため、そもそもなんの情報が暗号化されているのかすらわからない
  • せめてansible-vault showみたいな閲覧するだけのコマンドが欲しい

なのでぼくの欲求を満たしてくれるGemを作った

実現したいことは以下の通りです。

  • 暗号化するのはYAMLの末端(葉)で、かつkey: valuevalueのみ
    • (それによってセキュアさが落ちることは許容する)
  • 暗号化したあともYAMLの構造は守る
  • 複合化した情報を手軽に参照できるコマンドを提供する

そしてできたのがleml(Leaf Encrypt YAML)です。

lemlの概要

インストールと使い方

lemlはRailsのプラグインとして実装されていて、秘匿情報はrakeタスクによって閲覧、編集を可能にしています。 実際のRailsプロジェクトはGemfile/Gemfile.lockによってgemを管理することが多いと思います。通常のgemをインストール手順と同じく、

# Gemfile
gem 'leml'

とした上で、$ bundle install

初期化

その後、初期化を行います。

$ bundle exec rake leml:init
Complete!
create  config/leml.key
create  config/leml.yml

Caution Don't forget add key file in gitignore

※ 自動的に鍵ファイルは.gitignoreに追加されません(今後機能として追加していく予定です)
※ デフォルトでYAMLのシンタックスハイライトが効いて欲しかったので本家のファイル名にある.encサフィックスは削除させてもらいました

編集/閲覧

暗号化されたファイルの編集、閲覧は以下のように行います。

$ EDITOR=vim bundle exec rake leml:edit

これでvimが立ち上がり秘匿情報の編集ができるようになります。環境変数'EDITOR'を利用するのでお好きなエディタをセットしてください。 保存して終了すると、内容を暗号化した上でconfig/leml.ymlが更新されます。編集する必要はなく、内容を確認できればいい場合は以下のコマンドで標準出力に出力することも可能です。

$ bundle exec rake leml:show

実際に使ってみると以下のようになります。
元のYAML

---
development:
  gem:
    leml:
      author: onunu
      github: https://github.com/onunu
  favorite_precure: twinkle

上記の内容を暗号化すると、

$ cat config/leml.yml
---
development:
  gem:
    leml:
      author: aFR1RWUyT0RoVTJzSlgrZ0ptVmlqdz09LS0vL2hzZXVLMzZtTFJ5dk1ZSzdqQ2lRPT0=--bced3fd4f521287b819edb677460e2518bcefb8b
      github: ZW9PcTBYMy8xakZQUXhqVmhWNlNxNXBKMmM3Z3FLTDZKVEdoRmFzUnB4Z2JScTNKMlo1VktiK0NZS01JVlpLWC0tc2hHTTlrTytOYXpJckxIMmhhNWV5QT09--5340c65706b5619afcc81318dca47c033aecf65f
  favorite_precure: T21uN2R0NjhsNjdHOHgzY1d6WHp5RDRibnNYVHBOY2l4TC9rcmc1UGZ5OD0tLXllQnQrTXk0Q3lVdmtTdzAzQVprVXc9PQ==--e2568a8cfa84055f367dce531f5e287ac5182033

階層構造があっても問題ありません。何が暗号化されているのか一目瞭然! 実際のアプリケーションから参照する時は、通常のsecretsと同様に扱うことができます。

$ bundle exec rails c
Running via Spring preloader in process 68212
Loading development environment (Rails 5.1.2)
irb(main):001:0> Rails.application.secrets.favorite_precure
=> "twinkle"

なにをしているのか

Railtieについて

lemlはRailsの初期化プロセスに相乗りする形でsecretsをmergeしていますが、これはrailtieを使うことで比較的簡単に実装することができます。 lemlでは以下のようにしてRailsの初期化プロセスを拡張しました。

# leml/lib/leml/railtie.rb
module Leml
  class Railtie < Rails::Engine
    initializer 'Decrypt Leml file' do
      require 'leml/core'
      Leml::Core.new.merge_secrets
    end
  end
end

RailtieはRails公式のプラグイン機構です。Railtieに基づいて各コンポーネントを組み合わせることで、Railsの様々な処理を手軽に拡張することができます。自作のgem(今回でいうところのLemlモジュール内)でRails::Engineクラスを継承したRailtieクラスを定義することで、Railsが処理を行う際に相乗りさせてもらえます。

参考: Rails::Railtie

今回は初期化プロセスを拡張したいので、内部でinitializerメソッドを定義しました。これにより、Railsが実行する初期化と同時に、initializerメソッド内で記述した処理をRailsが行ってくれます。

各タスクについて

lemlでは各操作をrakeタスクで提供しています。 Rails pluginではleml(gem名)/lib/tasks配下にrakeファイルを設置することで、Railsプロジェクトのタスクとして読み取ってもらえるだけでなく、タスクの実行時にはRailsの初期化プロセスが行われた後でタスクを実行してくれます。

lemlのrakeファイルは以下のようになっています。

# leml/lib/tasks/leml_tasks.rake
require 'leml/core'

namespace :leml do
  desc 'initialize secrets yaml'
  task :init => :environment do
    Leml::Core.setup
  end

  desc 'edit encrypted yaml'
  task :edit => :environment do
    Leml::Core.new.edit
  end

  desc 'show encrypted yaml'
  task :show => :environment do
    Leml::Core.new.show
  end
end

終わりに

いかがでしたか? 自分で書いたファイル数は4つくらいでしたし、それぞれ基本的なことしかしていません。 それでも自分のやりたかったことはある程度実現できました。Railsの懐の深さに驚きです。

また本家のEncryptedSecretsの実装、Railtieの仕組みなどを理解するのにソースコードを読みましたが大変勉強になりました。やはり一次資料はソースコードですね。

lemlはオープンソースで提供しているので、みなさん使ってみてくださいね! まだまだ至らぬ所はありますが、メンテ頑張っていきたいと思います。ご要望やバグ報告、その他ご指摘などはissueを立ち上げていただけると幸いです。

ご注意

lemlはYAMLファイル中のkey: valueのうちvalueだけを暗号化します。当然、keyは平文のまま残るため、keyからvalueの値を推測されてしまう危険性をもっています。 そのリスクを許容できない場合はRails標準のEncryptedSecretsを利用するか、あるいは併用するのがよいでしょう。 ご利用の際にはご注意ください。