小さなチームでの秘密情報の管理

Posted by Katsuya Oda on June 08, 2017 · 2 mins read

こんにちは、東京支社の小田です。先週の週末は、やっと解禁されたHouse of Cardsのシーズン5をひたすらぶっ通しで見ていました。経験上、シーズンものは4や5までくるとマンネリ化しておもしろくなくなってくるんですが、House of Cardsは違いました。凄かったです。みなさんもぜひ。

簡単に説明すると、House of Cardsは組閣からもれ屈辱を味わった下院議員のフランクが、再度権力の階段を上がっていく政治ドラマです。人間は性欲なり金銭欲なり暖かい家庭を夢見たり、いろんな欲をバランスをとりながらもっていると思いますが、フランクは違います。フランクは権力(Power)だけ欲しそれのみを追求していきます。その徹底の程度が凄く(フランクはRuthless Pragmatismといっていますが)、敵だけではなく何人もの味方が特に良心をもっているほど、傷つき擦り切れ壊れていきます。どうですか?おもしろそうでしょ。

ちなみにNetflixで見る方は、Audio Descriptionが便利です。本当は視覚障害者の方用の音声モードですが、騙されたと思って試してみてください。

さて本題ですが、今日は小さなチームで秘密情報を管理する方法をご紹介します。管理に利用するツールは、PassというGnuPGとGitを利用したパスワードマネージャです。実行環境は、LinuxかmacOSを想定しています。

よくあること

中小企業の小さなチームで開発をしていると、チームで使っているWEBサービスのアカウントだったりお客様の環境のログインユーザのアカウントだったり、いろいろなアカウント情報を管理する必要に迫られると思います。それはLDAPやActive Directoryを活用しても完全になくすことはできないのかなというのが実感です。お客様によって環境が異なりますからね。全て、AWSだったら楽なんですが。

そのようなアカウントは一人の担当者が責任をもって管理するかチームで管理することになると思います。一人の担当者が管理する場合は、その人がいないときや辞めたらどうするかという問題が常に残ります。チームで管理する場合は以下のようなパターンになると思います。

  • どうせ社内だしパスワードを簡単なやつにして覚えることから解放されよう。
  • いや、みんながみれるところに適当な強度を持っているパスワードを平文で書いておこう。
  • いや、複数のパスワードを一つのパスワードで暗号化して、そのパスワードだけ覚えよう。

大きいところは予算や人員を確保できるので、開発者がこのような問題とは無縁に過ごせるように工夫・設計しているところも多いと思います。羨ましいですね。今回は最後のパターンから派生した管理方法をご紹介します。

管理方法の概要

今回ご紹介する方法は以下の方針でパスワードを管理します。

  • 複数のパスワードを一つ一つ暗号化して、それぞれ一つのファイルに保存する。
  • パスワードの暗号化はGnuPGを使用して、チームのメンバーのそれぞれの公開鍵で暗号化する。それぞれの公開鍵で暗号化されたデータはGnuPGを使用して一つのファイルにまとめる。
  • 暗号化したファイルを用途(サイトやサービス)ごとにディレクトリにわけGitで管理する。

言葉にすると分かりにくいんですが、擬似コードにすると以下のようになります。Rubyってとってもわかりやすいですね。

この方法の利点は以下になります。

  • メンバーが自身の秘密鍵のみでパスワードを復号できる。結果として、マスターパスワード(パスワードを暗号化するためのパスワード)を共有することから解放される。
  • 他のメンバーが追加したパスワードを自身の秘密鍵のみで復号できる。結果として、誰でもパスワードを追加できて、追加処理が分散できる。
  • いちメンバーのディスクがぶっ飛んでも悲劇にならない。Gitで分散管理しているため。
  • Gitで管理しているため過去に戻れる。誰かが間違って暗号化したため、すべてが見れなくなるということがない。

この方法での注意点は以下になります。

  • 適当なメンバーが適当なパスフレーズで秘密鍵を管理した場合、悲しい結果になります。そのため、パスワードを管理できるメンバーの選択は慎重に検討する必要があります。
  • 信頼できるGitリポジトリを使用する。パブリックなリポジトリは使用できないと思います。すべてのコミットに署名し、pullするときに必ず署名を確認する運用をしても難しいと思います。

実際の管理方法

Passを使用して管理を行います。PassはBashでかかれたパスワードマネージャーです。UIがとても優れていて、わかりやすいサブコマンド名、目的のパスワードにすぐにアクセスできる補完機能などがとても素敵です。また、各ディストリビューションの公式リポジトリからyumやaptitudeでインストールできることもうれしい点です。ただ、最後の決め手はシンプルであることです。パスワードを管理するツールは、ツール自体の内容を検査する必要があります。Passは1ファイルのシェルスクリプトでかかれているため検査が非常に楽です。

基本的な使い方

Linuxを前提として、実際に端末にセットアップしながらPassの基本的な使い方を説明します。以下では、チーム名を「nacl」としています。

まず、それぞれのディストリビューション用のパッケージマネージャーを使用して、Passをインストールします。

次に、「pass init」コマンドを使用して、naclチーム用のGitリポジトリを「/pass-nacl」に作成します。デフォルトのリポジトリのパスは「/.password-store」となっています。ここでは複数のチーム用のリポジトリを作成することを前提に、別のパスを指定しています。パスワードの暗号化と復号化に使用する鍵として、「alice@netlab.jp」を指定しています。鍵については補足を参照してください。

パスワードの追加は、「pass insert」コマンドを使用します。保存するパスワードは標準入力から入力します。パスワードは引数に指定した「~/pass-nacl」からの相対パスに暗号化されて保存されます。パスワードの入力にエディタを使いたい場合は「-m」オプションをつけてください。

暗号化したパスワードは、「pass show」コマンドで復号化できます。復号化時には秘密鍵のパスワードの入力が促されます。復号化されたパスワードは標準出力に出力されます。先ほど入力したパスワードは「test_password」になります。

登録されているパスワードの一覧は、「pass」コマンドで表示することができます。

メンバーの追加

メンバーの追加は以下の手順で行います。

  • メンバーの公開鍵を追加する。
  • 追加するメンバーと既存のメンバーの公開鍵で全パスワードを再暗号化する。暗号化で使用する公開鍵の一覧は、デフォルトでは「~/neo-secrets/.gpg-id」に含まれています。

まず以下のコマンドで、メンバーの公開鍵をKeyサーバ上から検索します(メンバーの公開鍵がKeyサーバ上にあることを前提にしています)。引数に指定してされている「bob@netlab.jp」は公開鍵のUser IDになります。User IDとして指定可能な識別子については、How to Specify a User Idに一覧があります。

次に、メンバーの公開鍵をKeyサーバから取得します。取得する公開鍵の指定は、emailや短い形式のid(short id)ではなく、長い形式のIDかfingerprintを使用してください。emailやshort idは被る可能性があり、意図していない公開鍵が追加される場合があります。Fake Linus Torvalds' Key Found in the Wild, No More Short-IDs.

次に、自身の秘密鍵で取得した公開鍵で署名します。この処理を行うことで、取得した公開鍵を信頼し、その公開鍵でファイルの暗号化を行えるようになります。これは面倒くさいですがメリットがあって、例えば悪意のあるユーザが「~/neo-secrets/.gpg-id」に任意の公開鍵のidを追加しても、そのidに対応する公開鍵が署名されていなければエラーになります。

最後に、「pass init」コマンドを使用し、メンバーの公開鍵を「~/pass-nacl」に追加し全パスワードファイルの再暗号化を行います。

リモートリポジトリの設定

「example.com」の「pass-nacl」リポジトリへのpushする場合は、以下のように設定します。

リポジトリへのpushは以下のコマンドで行えます。

チーム用のシェル関数の作成

現状の構成では、「pass」コマンドを実行するたに環境変数PASSWORD_STORE_DIRを設定する必要があります。また、PASSWORD_STORE_DIRがデフォルトと異なるため、「pass」コマンドの便利な補完機能が動作しないという問題もあります。そのため、以下のようなシェル関数をチーム毎に用意するのが便利だと思います。

bashの場合は以下になります。

zshの場合は以下になります。

上記の定義を「.bashrc」や「.zshrc」に読み込ませると以下のようにpassを使うことができます。

エラーになる場合は、現在のシェルに追加したコードが適切に読み込まれていないか、Passがインストールされていないかのどちらかだと思います。ログインしなおすか、Passのinstallを行ってください。

終わりに

必要最低限のPassの使用方法を説明しました。今まで説明した知識の範囲で、チーム毎にリポジトリを分けたりすることもできると思います。さらに詳しい説明は、PassについてもGnuPGについてもGitについてもmanを参照するのがいいと思います。これらのツールに関しては、manが常にベストです。

最後に完全に主観ですが、Passを使った結果以下のようなメリットを感じています。

  • 自分がミスしても大丈夫。自身の秘密鍵が吹っ飛んでも誰かが復号化できる信頼感は、管理の精神的な負担解消につながっています。
  • パスワードの変更の負担が減った。git diffで過去のパスワードを閲覧できるので、失敗を恐れずにパスワードを変えれます。ちなみに、git diffはなかでgpgを呼び出して、その度に復号化しています。
  • Import・Exportがらくちん。汎用的な統一的な方法で管理しているので、簡単なシェルスクリプトでパスワードを移行できます。個人用のPassで管理していたパスワードの一部をチーム用のPassに移すことも簡単にできました。

補足

GnuPGの鍵の生成

おそらくほとんどの方は鍵を持っていないか、個人用の鍵は持っていても会社用の鍵は持っていなかったりすると思います。古い鍵を持っている方も、最近libgcryptのRNGにバグ(Security fixes for Libgcrypt and GnuPG 1.4)が見つかったので確認したほうがいいとおもいます。RSAの鍵の場合、この発表の時点で調べた限りは問題がないそうです。

また、使用するGnuPGのバージョンはGnuPG 2を使用するのがいいと思います。モダンなアルゴリズムをサポートしていますし、もうすぐDebian系のディストリビューションでもGnuPG 2がデフォルトになります。他にもキーサーバとの接続にHKPSが使えたりします。詳しくは、[Debian to shift to a modern GnuPG](Debian to shift to a modern GnuPG)に記載されています。

鍵生成は「gpg2 --quick-generate-key」コマンドで行うことができます。鍵長や鍵の構成については他のサイトを参照してください。人によっては、RSAの鍵の鍵長は4096にするべきだとか、暗号化用のサブキーだけではなく署名用のサブキーも作るのがいいとか、いろいろな意見があります。なぜそうするべきなのかを調べてからそうしてください。

ここでは、ただの例として以下の設定で生成しています。鍵の識別子として「test@example.com」を、鍵のアルゴリズムとしてRSAを指定しています。鍵長は4096です。生成する鍵は署名用と暗号化用の二つです。鍵は失効しないように指定しています。

生成した鍵は一回中身をじっくり見ておいた方がいいと思います。鍵のダンプは以下のコマンドで行えます。「gpg --list-packets」のダンプはRFCを読んでないと分からんと思います。「pgpdump」はなんとなく分かるのでおすすめです。pgdumpはおそらくモダンなディストリビューションでインストールできるはずです。

最後に、生成した鍵は他のユーザが取得できるように以下のコマンドでキーサーバにアップロードすることができます。様々な方法で人に配ることができますが、配布のコストを下げるためにキーサーバを使うのがいいと思います。

gpg-agentのパスワードのキャッシュの設定変更

Passを使い出すと何度も秘密鍵のパスワードの求められるのに気づきます。マスターパスワードだからと言ってパスワードの文字数を長くしていると、結構煩わしかったりします。対応方法としては以下の三つがあります。パスワードの長さとキャッシュの時間は結局トレードオフの関係なんだと思います。

  • パスワードを何度打ち込んでもストレスがない長さにする。
  • パスワードの長さをそのままで、パスワードをキャッシュする時間を長くする。
  • ただただ耐える。

gpgは復号化するとき秘密鍵の情報を必要とします。GnuPG-2から秘密鍵の情報はgpg-agentが一括して管理しているため、gpgは秘密鍵に関する操作のすべてをgpg-agentに移譲しています。つまり、秘密鍵のパスワードのキャッシュの設定変更はgpg-agentの設定を変える必要があります。該当の設定は以下の二つになります。

  • default-cache-ttl - 最後のアクセス時間から指定した秒数が経過したらキャッシュは失効されます。デフォルトは600秒(10分)です。
  • max-cache-ttl - 最後に秘密鍵のパスワードを入力してから指定した秒数が経過したらキャッシュは失効されます。デフォルトは7200秒(2時間)です。

設定は、「~/.gnupg/gpg-agent.conf」に指定します。以下はどちらも2時間に指定した例になります。2時間はmax-cache-ttlのデフォルトなので指定する必要はないですが、例として指定しました。

設定が適切にできている場合は、「gpgconf」の出力で該当行のけつに「7200」と出ているはずです。問題なければ、以下のようにgpg-agentに設定をリロードしろと伝えてください。

Passのトレース

使い初めの時期は、「pass」コマンドが「gpg」コマンドをどのように呼び出しているか確認したい場合があります。Passはbashで書かれているため、「-x」オプションを使用すると実行したコマンドをトレースしてくます。以下のように「pass」コマンドを実行すると「pass show」コマンドが「gpg2 -d --quiet --yes --compress-algo=none --no-encrypt-to --batch --use-agent /home/katsuya/pass-nacl/test.com/user/katsuya.gpg」実行していることがわかります。