yaml_vault+KMSでRailsアプリのconfig/secrets.ymlを暗号化してgitにコミットして管理する

# はじめに

Railsアプリでは環境ごとの設定差分を `config/environments/#{RAILS_ENV}.rb` というファイルで管理します。
このような環境ごとの差分になるものは、例えば管理者のメールアドレスなど平文で管理しても問題ない情報もある一方、外部接続先のAPIトークンなど機密性の高い情報も含まれます。しかしながらAPIトークンなどを平文のままgitリポジトリにコミットするのはセキュリティ的にはあまりよろしくありません。

Railsではこのような機密性の高い情報を `config/secrets.yml` に分離して読み込ませる仕組みを提供していますが、暗号化/復号化機能自体は提供してくれません。

このような暗号化すべきファイルの管理方法はいつくかの方法があるかと思います。皆さん一体どうしてるんでしょうか。。。環境変数に逃がしても、インフラをコード化してサーバのプロビジョニングを自動化していると、その環境変数をどうやって埋めるのか問題があります。私はというと、元々インフラコードがChefだったので、ChefのEncrypted Data Bagsという機能を使って暗号化しておき、インスタンスのプロビジョニング時に復号化したファイルを配置する運用をしていたのですが、アプリケーションエンジニアにChefは学習コストが高く難しすぎました。あと管理するgitリポジトリがアプリケーションと異なるので、リリースタイミングの調整などが煩雑でした。

そんなことを悩んでいた今日この頃、joker1007さん作のyaml_vault + AWS KMS(Key Management Service)の組み合わせを使ってみたら、これがなかなかにシンプルかつセキュアでバランスのよい感じのソリューションでした。

[joker1007/yaml_vault](https://github.com/joker1007/yaml_vault)

joker1007さん本人による簡単な解説もあるんですが、

[AWS Key Management Serviceを使ってconfigファイルを暗号化すると便利](http://qiita.com/joker1007/items/7f726852a5e7d1cdf974)

この記事では、こんなかんじでRailsアプリで使ってるよーという運用事例紹介します。

# やること

* `config/environments/#{RAILS_ENV}.rb` から 暗号化すべき値を `config/secrets.yml` に分離してgitリポジトリにコミットする
* `config/secrets.yml` を暗号化してgitリポジトリにコミットする
* 運用上のルール決めと、 capistranoでデプロイ時に`config/secrets.yml`復号化する
* 暗号化/復号化の鍵はAWSのKMSで管理して、IAMロールやIAMグループでアクセス権限を管理する

※稼働確認した環境は Rails v4.2.7.1 + yaml_vault v0.3.0 です

## この方法のうれしいこと

* アプリケーションを管理しているのと同じリポジトリに暗号化してコミットできるので、追加/変更のリリースタイミングが制御しやすい。
* リリース方法は通常のアプリケーションと同じで気にしなくてよくなるので、あとは暗号化/復号化コマンドを適当な作業スクリプトでラップしておけば、他のエンジニアの学習コストがほとんどかからない。
* アクセス権限をIAMグループで管理できるので、事前に共通鍵などを配布する必要がなく、誰がアクセスできるかを一覧管理しやすい。
* サーバ側で復号化する際にはIAMロールが使えるので、サーバに事前にAWSアクセスキーを埋める必要もなく、復号化するための鍵をどうやってサーバに渡すかという鶏たまご問題を回避できる。

## この方法の制限

* AWSじゃない人は鍵の管理方法を別に考える必要がある。yaml_vault自体はバックエンドにKMSを使えますが、AWSじゃなくても普通にパスワード認証などでも使えます。他のバックエンドを使いたい人は適宜プルリクエストを送るとよいんじゃないかな。。。

# やり方

## production.rbから機密情報をsecrets.ymlへ分離する

まずyaml_vaultとか関係なくRailsの話なんだけど、 例えば`config/environments/production.rb` がこんなかんじになっていて、

```rb
MyApp::Application.configure do
  config.hoge = 'fuga'
end
```

`fuga` の値を秘匿したい場合には、

`config/secrets.yml` に

```yaml
production:
  hoge: fuga
```

って書いておくと、 `RAILS_ENV=production` で起動した時に `config/environments/production.rb` の中からは `secrets.hoge` で 値 `fuga` が参照できるようになる。なので、こんなかんじでセットし直しておけば、アプリケーション側のコードを変更せずに透過的に扱うことができる。

```rb
MyApp::Application.configure do
  config.hoge = secrets.hoge
end
```

ネストしたキーを使いたい場合は

```yaml
production:
  foo:
    bar: baz
```

```rb
MyApp::Application.configure do
  config.foo = {
    bar: secrets.foo['bar']
  }
end
```

こんなかんじでできる。


## secrets.ymlをyaml_vaultで暗号化する

で、ここから本題。yaml_vaultをGemfileに追加してbundle installする。

```ruby
gem 'yaml_vault'
```

```bash
$ bundle install
```

とりあえず稼働確認のためにKMSを使わずにお試しでパスワードで暗号化/復号化してみる。 

```bash
$ bundle exec yaml_vault encrypt secrets.yml -o encrypted_secrets.yml
Enter passphrase: <enter your passphrase>
```

```bash
$ bundle exec yaml_vault decrypt encrypted_secrets.yml -o secrets.yml
Enter passphrase: <enter your passphrase>
```

## 運用上のディレクトリの工夫

yaml_vaultの `-o` で出力先が指定できるので、RAILS_ENVごとにファイルを分けた。
その心は 運用の都合上production環境のデータを暗号化/復号化できる人と、その他ステージングなどの開発環境のものを暗号化/復号化できる人を分けたかったからです。
yaml_vaultの機能的には `-k` で指定したキー配下を暗号化/復号化できるので、１ファイルでも技術的には運用できますが、事故防止という観点からもファイルを分けてます。
基本的にエンジニアが全員production環境を触ってよい運用であれば、分ける必要はないかもしれません。

具体的には以下のようなかんじで管理をして `config/secrets.yml` 自体は担当者が直接編集しないようにしました。

1. 復号化 `config/secrets/encrypted/production.yml` -> `config/secrets/decrypted/production.yml`
1. 復号化されたファイル(`config/secrets/decrypted/production.yml`)を編集
1. 暗号化 `config/secrets/decrypted/production.yml` -> `config/secrets/encrypted/production.yml`
1. 暗号化されたファイル(`config/secrets/encrypted/production.yml`)をコミットする
1. capistranoデプロイ時に復号化して `config/secrets.yml` にコピーするタスクを仕込む

`config/secrets.yml` と `config/secrets/decrypted/` 配下を `.gitignore` に追加しておくと、うっかり誤って平文がコミットされるのを防げます。

capのデプロイタスクは皆さんの環境によりけりなので、各自よしなに仕込んで下さい。

## KMSでの暗号化/復号化

yaml_vaultは暗号化/復号化の鍵にAWSのKMS(Key Management Service)を使うことができます。

[AWS Key Management Service (KMS)](https://aws.amazon.com/jp/kms/)

KMSはデータの暗号化に使用する暗号化キーを簡単に作成および管理できるマネージド型サービスです。KMS自体の説明は適当にググって下さい。KMSのよいところは暗号化キーへ誰がアクセスしてよいかをIAMの権限管理で制御できるようになるところです。AWSを利用しているのであれば、これに乗っかると、共通の鍵を事前に配布したりする必要がなくてべんりです。

ところでKMSの新しいマスタキーをマネージメントコンソールから作るとキーポリシーが作られて、誰がアクセスしてよいかが選べるのですが、これがIAMユーザとIAMロールは指定できるのに、IAMグループが指定できなくて悩みました。

結論としては、IAMグループで管理するにはKMSのキーポリシーでIAMポリシーを有効化するように設定し、別途IAMポリシーをIAMグループに付与することでIAMグループ単位での制御する必要があります。ちょっとハマったので詳細は以下にまとめてます。

[AWSのKMSへのアクセスをIAMグループ単位で管理する (KMSキーポリシーでIAMグループが指定できないよ問題)](http://qiita.com/minamijoyo/items/c6c6770f04c24a695081)

また、この辺の設定をTerraformで設定しようとすると、IAMポリシーのJSONに変数を埋めたくなるんだけど、aws_iam_policy_documentを使うとできます。ということで、こちらも先日別エントリで書きました。

[TerraformでIAMポリシーのJSONに変数を埋めたい場合はaws_iam_policy_documentを使う](http://qiita.com/minamijoyo/items/d68f162bd29cd3d766e8)

上記はTerraformの機能の説明なんだけど、サンプルコードはTerraformでKMSキー作った時に、アクセス権限の管理をIAMグループで管理する例になってるので、Terraformを使ってる勢にはそのままコピペすれば参考になるんじゃなかろうか。

KMSのキー作成と権限の調整ができたら、暗号化と復号化は簡単です。 `--cryptor=aws-kms` を指定して、 `--aws-kms-key-id` で使うKMSのキーを指定します。実運用上はキーIDを直接指定せずに、ローテーションしても変化しないキーエイリアスを使っておいた方が無難でしょう。

```bash
$ bundle exec yaml_vault encrypt config/secrets/decrypted/${ENV}.yml -o config/secrets/encrypted/${ENV}.yml -k "${ENV}" --cryptor=aws-kms --aws-region=ap-northeast-1 --aws-kms-key-id="alias/hoge-${ENV}"
```

ちなみに復号化するときは `--aws-kms-key-id` を指定する必要がありません。これは暗号化データ内にどのキーで暗号化したかの情報が含まれているからみたいです。

```bash
bundle exec yaml_vault decrypt config/secrets/encrypted/${ENV}.yml -o config/secrets/decrypted/${ENV}.yml -k "${ENV}" --cryptor=aws-kms --aws-region=ap-northeast-1
```

参考： [AWS Key Management Serviceを使ってconfigファイルを暗号化すると便利](http://qiita.com/joker1007/items/7f726852a5e7d1cdf974)

実際にはみんながyaml_vaultの引数まで覚える必要がないように、上記のコマンドを適当なシェルスクリプトでラップして使ってますが、若干環境依存な情報を含むので、ここでは割愛します。

# まとめ

yaml_vault + KMSを組み合わせることで、秘匿すべきRailsアプリの設定を暗号化しつつ、アプリケーションと同じgitリポジトリにコミットし、適切なIAMグループに所属している人だけ、暗号化/復号化することができるようになりました。

この方法は、なかなかにシンプルかつセキュアでバランスのよいかんじのソリューションだと思います。というわけで、APIトークンの暗号化の管理に悩んでる人は試してみるとよいんじゃないかな。