Timee Product Team Blog

タイミー開発者ブログ

【イベントレポート】Railsアプリで秘匿情報を環境変数からCredentialsに移行した話

イベント概要

2023年11月15日に「GENBA #1 〜RubyRails開発の現場〜」と題してRuby/Railsでの開発に関するトピックでタイミーとエンペイ社合同で勉強会を開催しました。 その中でタイミーバックエンドエンジニアのpokohideさん(@pokohide)の発表「Railsアプリで秘匿情報を環境変数からCredentialsに移行した話」をイベントレポート形式でお届けします。

登壇者紹介

Credentialsとは

Credentials は、Rails 5.2から追加された秘匿情報を管理するための仕組み※1 で、Rails 6から複数の環境をサポート※2 しています。

【主な登場人物】

  • 暗号化ファイル: config/credentials/.yml.enc
  • 復号用の伴: ENV[”RAILS_MASTER_KEY”] or config/credentials/.key

【セキュアな構成管理】

  • Railsアプリ起動時に Rails.env に対応する暗号化ファイルと鍵を参照し復号する
  • 復号化が完了すると Rails.application.credentials 経由で取得可能になる

【暗号化と参照方法】

  • YAML形式のファイルを暗号化(YAMLの構文に依存) ⇆ 復号
  • 復号化された後は ActiveSupport::OrderedOptions を介してアクセス可能
  • fetchdig が使える


▲Credentialsの例
※1Add credentials using a generic EncryptedConfiguration class #30067
※2Add support for multi environment credentials. #33521

Credentialsへの移行目的

Credentialsへの移行は、組織内での秘匿情報管理の責任を明確化し、デプロイプロセスを効率化することを目的としています。

  • 手間の削減

以前はECSのタスク定義に環境変数としてパラメータストアのSecureStringを利用していました。秘匿情報を追加する際にパラメータストアに登録し、ECSタスク定義とアプリケーションコードの両方を変更する必要があり、手間がかかっていました。

  • 責任境界の曖昧さ

AWSリソースの管理はインフラチームが主導していましたが、その結果、責任境界が曖昧になることがありました。
⇒ Credentials導入によって、秘匿情報の管理に関する責任の所在が明確になり、責任境界が明確化されました。

  • デプロイの難易度

複数の場所での操作が必要だったため、デプロイが容易ではありませんでした。
⇒ Credentials導入によって、アプリケーションコードを変更することで秘匿情報を追加・参照できるようになるため、デプロイも容易になりました。

  • レビューの困難さ

独自の対話型CLIを使用してパラメータストアを操作していたため、プロセスのレビューが困難でした。

  • セキュリティの向上

⇒ Credentials導入の副産物として、セキュリティ向上が挙げられます。RAILS_MASTER_KEYのみを管理することでパラメータストアの操作権限を削減でき、全体的なセキュリティレベルが向上しました。

Credentialsの安全性

Credentialsの安全性は、主に使用される暗号化アルゴリズムとマスターキーの管理方法に依存します。2023年時点で、AES-256-GCM暗号化アルゴリズムを用いた暗号化は、最も安全だといわれています。
しかし、最も重要なのはマスターキーの管理です。マスターキーが流出すれば、暗号化された情報が容易に解読されてしまう可能性があります。そのため、マスターキーの安全な保管とアクセス管理は非常に重要です。
管理方法については、ビジネスの環境やリスクに応じて慎重に検討し、適切なセキュリティ対策を講じる必要があります。

Credentials 移行の手順

移行の手順は、とてもシンプルです。

  1. 何を移行するか決める
  2. 移行対象の秘匿情報を全てCredentialsに追加する
  3. 少しずつ Rails.application.credentials に移行する

1. 何を移行するか決める

まず、何をCredentialsに移行するかを決定します。アプリケーションの構造を分析し、環境変数をリストアップします。秘匿情報には、環境変数だけでなく、証明書、秘密鍵Google Cloudの認証用JSONキーなどが含まれる可能性があります。
また、秘匿情報が本当にCredentialsへの移行が必要かを考えましょう。環境ごとの固有の設定であれば config_for を使用することで解決できるかも知れません。コンテナ化された環境やデータロックサービスのような動的に注入する必要がある情報や、頻繁に更新される情報は、Credentialsへの移行が適切かどうかを慎重に検討します。

2. 移行対象の秘匿情報を全てCredentialsに追加する

秘匿情報を一括でCredentialsに追加することをおすすめします。その理由は、暗号化ファイルのレビューが難しいためです。Credentialsへの移行前もレビューは難しい状況でしたが、暗号化ファイルを扱う現在も同様です。そのため、秘匿情報をまとめて移行し、Railsコンソールでリリース前に値が正しく一致しているか確認することで、プロセスがよりスムーズになります。

3. 少しずつ Rails.application.credentials に移行する

Rails.application.credentials への移行は段階的に行います。このプロセスは、多くのプルリクの作成と対応を伴い、障害が発生する可能性もあります。実際に起きた一例として、全角スペースと半角スペースを間違えて登録し、参照時にエラーが発生しました。秘匿情報のファイルレビューは通常、Syntax Highlightが効かない素のVimなどで行われるため、特に細心の注意が必要です。

タイミーでは技術改善のために割り当てられる時間が全体の約20%で、この時間を利用してCredentialsへの移行作業を行いました。全体としては約5ヶ月の期間を要しました。もし環境が異なれば、移行にかかる時間は1ヶ月程度に短縮可能かもしれません。移行の過程では、ステージング環境と本番環境で同じ秘匿情報を使用していたこともあり、そのような点にも対応しながら作業を進めました。

Credentials移行時のTips

1. config.require_master_key 設定を有効にする

Railsアプリケーションを起動する際には、Credentialsのマスターキーが必須です。マスターキーがないとアプリの起動に失敗する設定を有効にしておくと良いでしょう。

2. エディタの指定

暗号化ファイルの編集にはエディタの指定が必須なので用意(Dockerを利用している場合はemacsなどでもOK)

3. エスケープ文字の扱い

秘匿情報にエスケープ文字が含まれている場合は、ダブルクォーテーションで囲むことが推奨されます。暗号化ファイルがYAML形式に依存しているため、YAMLの構文規則に従う必要があります。

4. 改行の利用方法

秘匿情報に改行を含めたい場合は、パイプなどのYAMLの構文を利用します。

5. 外部サービスとの認証方法を変更する

外部サービスとの認証方法も変更しました。具体的には、以前は秘匿情報をファイルから読み込んでいた認証方法を、Credentialsで管理する形式に変更しました。

6. バイナリデータの取り扱い

YAML形式はテキストベースのデータ形式であり、バイナリデータの直接的な扱いには向いていません。特に、証明書などのバイナリデータをCredentialsで管理する際は、事前にBase64エンコードする必要があります。アプリケーションでは、このエンコードされたデータを取得し、適切にデコードして使用します。データの識別を容易にするために、YAMLファイル内でエンコードされた値には base64_encoded というプレフィックスを付けると便利です。

7. 秘匿値をコンソールで非表示にする

Railsコンソールを使用する際、デフォルト設定では秘匿値が表示されてしまうことがあります。(Rails 7.1からは、この秘匿値を非表示にする機能が標準で実装されています。)この変更によって、Railsコンソールから秘匿情報が誤って露出するリスクを軽減できます。

8. SECRET_KEY_BASEを Credentials に移行

SecretsはRails 7.1から明示的に非推奨化されたため、SECRET_KEY_BASEを Credentials に移行しました。各環境の credentials.yml に SECRET_KEY_BASE を移行すればOKなはずです。Rails 5.1以前で secrets.yml を使用して秘匿情報を管理していたアプリケーションの場合、移行プロセスは少し複雑になる可能性があります。

9. SECRET_KEY_BASE_DUMMYの活用

Rails 7.1から SECRET_KEY_BASE_DUMMY が導入されました。これは、SECRET_KEY_BASE の代わりにダミー値を自動的に設定する SECRET_KEY_BASE_DUMMY です。assets:precompile 実行時に SECRET_KEY_BASE が必要ない場合でも、エラーが発生することを防げます。

10. Heroku Data for Redis

これはタイミーではなく個人のアプリでの経験ですが、 Herokuで運用、Heroku Data for Redisを利用してる個人アプリのREDIS_URLをCredentialsに移行したらRedisに接続できなくなりました。自分で管理していない環境変数などを移行する場合は注意しましょう。

移行の結果

Credentialsに移行した結果、下記のような成果を実感しています。

ただし、依然としてレビューが大変です。データが暗号化されているため、プルリクエストを出しても差分が分からず、暗号化ファイルのdiffを確認するには公式の bin/rails credentials:diff を利用できますが、Railsの実行環境からgit操作が必要です。一般的な環境ではないかもしれませんが、当社では開発環境にDockerを使用し、ホスト側でgit操作を行っています。そのため、コンテナにgitを導入する検討をしています。

記事の発表やその他の発表が気になる方はこちら!

www.youtube.com

少しでも興味を持っていただいた方は是非こちらからカジュアルにお話しましょう!

devenable.timee.co.jp