Gitリポジトリ上でAWSアクセスキーを大公開しないためにAnsible Vaultをフル活用する

こんにちは。Backlog のSite Reliability Engineering (SRE) を担当している吉澤です。

AWS アクセスキーを含むコードを GitHub の公開リポジトリにプッシュしてしまい、そのアクセスキーがビットコインの採掘に使われて AWS から高額請求が来た!という話をたまに目にします。今年の2月に検証された方(GitHub に AWS キーペアを上げると抜かれるってほんと???試してみよー!)によると、git push から13分で不正利用開始されたらしいです。怖いですね……。

Backlog のソースコードは Backlog の提供する Git リポジトリで管理しています。Backlog の Git にはリポジトリの公開機能はないので、AWS アクセスキーをプッシュしたからといって即座に悪用される可能性は低いです。とはいえ、漏洩時の影響が大きいため、AWS アクセスキーは Ansible Vault で暗号化した状態で Git リポジトリ上にプッシュしています。

このあたりのプラクティスは Git 全般で使えるものと思いますので、今回は Backlog で実践している AWS アクセスキーの管理方法をご紹介します。

基礎知識:Ansible Vault とは

まず最初に、Ansible Vault について簡単に紹介します。すでにご存知の方は、次の節まで進んで OK です。

Ansible Vault とは、機密情報(パスワードや API キー)を含む Ansible の変数を、暗号化した状態でファイルに保存するための Ansible の一機能です。この暗号化されたファイルは、安全に Git リポジトリにプッシュすることができます。

ファイルの暗号化は ansible-vault コマンドで行います。例えば、以下のコマンドを実行すると、デフォルトのテキストエディタが開きます。

変数定義を書いて保存すると、暗号化されたファイル vault.yml が作られます。

また、Ansible 2.2 以降には特定の変数のみ暗号化する機能 Single Encrypted Variable があります。ansible-vault encrypt_string を以下のように実行すると、指定した変数(この例では test_value)が暗号化されます。あとは、この結果を変数ファイルに埋め込めば OK です。

復号化は ansible-playbook コマンドの実行時に行われます。ansible-playbook が変数を読み込むパスに、Ansible Vault で暗号化されたファイルがあると、自動的に復号化して読み込まれます。

この暗号化と復号化のパスワード(以下、vault パスワード)は以下のいずれかの方法でコマンドに渡します。

  1. コマンド実行時に表示されるプロンプトで、vault パスワードを入力
  2. コマンドライン引数で、パスワードファイルのパスを指定
  3. ansible.cfg の vault_password_file オプションで、パスワードファイルのパスを指定

パスワードファイルには、vault パスワードが書かれたファイルか、vault パスワードを標準出力するスクリプトを指定することができます。後述する Backlog のプラクティスでは、3番の方法で、パスワードファイルにスクリプトを指定します。

Ansible Vault の更なる詳細については、公式サイトの解説をご参考ください。

プラクティス:Backlog の構成管理システム

Backlog では、デプロイに Ansible、反復的な作業の自動化に Fabric、システム構成の検査に Serverspec を利用しています。

これらのツールに関するファイルを、1個の Git リポジトリ上で管理しています。Git リポジトリ内のディレクトリ構成は以下の通りです。

AWS アクセスキーは、/ansible 以下に、Ansible Vault で暗号化された状態で保管されています。AWS アクセスキーを必要とするアプリをデプロイする場合、Ansible がそれを復号化し、設定ファイルなどに埋め込んで、各サーバにデプロイします。

ansible ディレクトリ以下には、AWS アクセスキー以外の機密情報も保存しています。例えば、データベースのパスワード、自社サービス(BacklogCacooTypetalk)の API キー、外部サービスの API キーなど。実際は AWS アクセスキーよりも、これらの機密情報の方が多いです。

また、Backlog では、Fabric や Serverspec の動作にも機密情報の一部を必要としています。そのため、これらを1個の Git リポジトリに入れた上で、fabfile ディレクトリおよび serverspec ディレクトリからも、ansible ディレクトリ以下のファイルを参照させています。

Ansible が使う機密情報

Ansible が使う機密情報については、以下の工夫をしています。

  • vault パスワードは S3 から取得
  • vars ファイルと vault ファイルをペアで用意する
  • Single Encrypted Variable は(まだ)使わない

vault パスワードは S3 から取得

vault パスワードは S3 から取得するように設定します。この方法には以下のメリットがあります。

  • ansible-vault および ansible-playbook の実行時に、パスワード入力を求められない
  • 各ユーザーのマシンに、vault パスワードが書かれたファイルが残らない
  • Ansible の実行権限を、AWS IAM で一元管理できる

まず、ansible.cfg に以下の記載を追加します。前述の通り、vault_password_file オプションには、vault パスワードを標準出力するスクリプトを指定することができます。

次に、以下の内容で vault_pass.sh を用意し、実行権限を与えます。最後の引数を - にするのがミソです。S3 へのアクセスにデフォルトプロファイルと違うプロファイルを使いたい場合、環境変数 ANSIBLE_AWS_PROFILE に --profile PROFILE_NAME のような値を設定してください。

そして、s3://EXAMPLE_BUCKET_NAME/vault_pass に vault パスワードを書いたファイルを置きます(EXAMPLE_BUCKET_NAME の部分は適宜修正してください)。最後に、Ansible の実行権限を持つユーザーに、このバケットにアクセス可能な IAM ユーザー(特定の EC2 インスタンスから実行するなら IAM ロール)を設定します。

これで、ansible-playbook を実行するたびに、S3 から vault パスワードが自動取得されるようになります。

vars ファイルと vault ファイルをペアで用意する

Ansible Vault の説明に書いた通り、変数ファイルを暗号化すると、その中に入っている変数名もわからなくなってしまいます。Single Encrypted Variable を使えば変数名は暗号化されませんが、運用上の課題(後述)があり、Backlog では採用していません。

この問題に関しては、Ansible 公式の Best Practices ページ内でベストプラクティスが示されており、Backlog でもこのプラクティスを採用しています。該当部分の抄訳はこんな感じです。

この問題に対するベストプラクティスの1つは、group_vars ディレクトリ内に、グループの名前を付けたサブディレクトリを作ることである。そのサブディレクトリ内に、vars という名前のファイルと、vault という名前のファイルを作る。vars ファイルには機密情報を入れる変数を含めて、すべての変数を定義する。機密情報は vault ファイル内で定義し、Ansible Vault で暗号化するのだが、このなかで定義する変数名には vault_ というプレフィックスを付ける。そして、vars ファイル内からは、vault_ が付けられた変数を参照する。

このベストプラクティスは、変数や vault ファイルの数、およびその名前を特に制限しない。

具体例を挙げて説明します。

Ansible ではすべてのホストは all グループに属します。そのため、/ansible/group_vars/all というファイルを作って変数を定義すると、その変数は全ホストに適用されます。この機能は使ったことがある人も多いと思います。

このプラクティスでは、all というファイルの代わりにディレクトリを作り、以下のように varsvault ファイルを配置します。

例えば、vault ファイルに以下の内容を書いて暗号化したとします。

vars ファイルでは、機密情報以外の変数も含めて、以下のように変数を定義します。

このときに守るべきルールは、「名前が vault_ から始まる変数は、vars ファイルの中からしか参照しない」ことです。そうすることで、vault ファイルで定義された変数は、vars ファイルのみを見ればわかる状態になります。このルールを守らないと、vault ファイルを ansible-vault view で復号化しないとわからなくなり、管理が面倒になります。

Single Encrypted Variable は(まだ)使わない

Ansible 2.2 から実装された Single Encrypted Variable という機能を使った場合、変数名が暗号化されず、上記のように vars と vault ファイルを分ける必要がなくなります。

私もこの機能を試してみたのですが、一度暗号化した機密情報を、復号化して確認したいときに手間がかかることがわかり、結局採用しませんでした。

例えば、ベストプラクティスに従って作った vault ファイルは、以下のコマンドで復号化して、内容を確認できます。

しかし、Single Encrypted Variable を使って、暗号化した変数としていない変数が混在したファイルを ansible-vault view に渡すと、以下のように失敗します。

じゃあ、この YAML から特定の行だけ取り出して ansible-vault decrypt に渡せば復号化できるかと思えば、インデントを解除してから渡さないと失敗します。

これはなかなか面倒だ……と思い、Backlog では採用しませんでした。また、後述する Python や Ruby のライブラリが Single Encrypted Variable をサポートしていなかったことも、不採用の理由の一つでした。将来的に、このあたりの問題を解決できたら、Single Encrypted Variable を採用したいと考えています。

Fabric が使う機密情報

Fabric は、Python 2 で実装された、SSH 接続して行うような作業を自動化するための Python ライブラリおよびコマンドラインツールです。Ansible 自体が Python で実装されているため、Python の ansible モジュールに Ansible Vault の機能も含まれていますので、これを使います。

まず、Fabric が使う機密情報を /fabfile/vault_secrets.yml に格納します(ファイル名は自由)。このファイルは、/ansible ディレクトリで、以下のようにコマンドを実行して編集します。/ansible ディレクトリから実行するのは、ansible.cfg 内の情報を使うためです。

次に、この vault_secrets.yml を読み込む /fabfile/vault.py を追加します。

最後に、__init__.py の冒頭に以下の宣言を追加します。

これにより、__init__.py 内から vault_secrets['db_user'] のようにして、vault_secrets.yml 内の機密情報を参照できるようになります。

このコードを書くにあたっては、Ansible Vault 機能のソースコードを読んでみたを参考にさせていただきました。これを読んで ansible モジュールに含まれる VaultLib クラスの存在を知り、最終的には ansible/__init__.py at stable-2.4を読みながら試行錯誤で実装しました。

ちなみに、Fabric3 という、Fabric からフォークされた Python 3 対応版が存在しており、ヌーラボ社内ではすでに使われているのですが、Backlog は過去の資産が多くてまだ Fabric3 に移行完了していません……。Fabric3 は py2.7/py3.4+ compatible fork とのことなので、上記は Fabric3 でも使える方法かと思います。

Serverspec が使う機密情報

Serverspec は、Ruby で実装された、サーバ環境に対するテストツールです。Ansible 公式のライブラリは無いため、tpickett66/ansible-vault-rb: A Ruby implementation Ansible’s vault file format で公開されている ansible-vault gem を使っています。

まず、Serverspec の実行に必要な機密情報を /serverspec/vault_secrets.yml に格納します(ファイル名は自由)。このファイルは、Fabric の場合と同様に、ansible-vault コマンドを実行して編集します。

次に、/serverspec/spec/spec_helper.rb に以下のコードを追加します。ansible-vault gem はパスワードファイルがスクリプトの場合に対応していないので、事前にスクリプトを実行する必要があるのがポイントです。

あとは、vault_secrets の内容をプロパティに設定すれば完了です。

上記のプラクティスの課題

この方法で今のところ問題なく運用できているのですが、いくつか課題が残っています。

1点目の課題は、Fabric と Serverspec では vars ファイルと vault ファイルを分けるプラクティスが使えないため、vault_secrets.yml を復号化しないと変数名がわからないことです。vault_secrets.yml から読み込んだ変数を、明示的に別の変数に代入することはできますが、そこまですべきか悩んでおり、まだ実装していません。

2点目の課題は、一部の機密情報が、ansible、fabfile、serverspec の各ディレクトリ以下に重複してしまうことです。この問題を避けるために、Backlog では上記のコードを拡張して、spec_helper.rb から /ansible/group_vars/ROLE_NAME/vault の一部を読み込んでいます。しかし、そうすると vault ファイルに含まれる変数がどこから参照されているかわかりにくくなる点が悩みの種です。

最終的には、Ansible 以外のソフトウェアも含めて、Single Encrypted Variable に統一できるのが理想だろうと考えています。

上記のプラクティスを導入できないケース

デプロイに Ansible や Fabric を使っていないケースでは、上記のプラクティスは導入できません。Backlog でも、一部の実験的なアプリなどがそのようなケースに該当しています。

そのようなケースでも、権限管理は AWS IAM に集約したかったため、秘密情報を含む設定ファイルそのものを S3 からダウンロードする方法で対処しています。また、ダウンロードするファイルは必ず .gitignore に設定しています。

事故防止:git-secrets で機密情報のプッシュを防ぐ

Ansible Vault で AWS アクセスキーを暗号化するようにしていても、デプロイツールに詳しくない開発者が、ローカルファイルに AWS アクセスキーを書き、そのファイルをうっかりプッシュしてしまうことはありえます。Ansible Vault を使っていてもそのような事故は防げません。

git push を未然に防ぐとなると、Git hook を使おうと考えるのが自然でしょう。Amazon.com が公開している git-secrets というツールが、

  • プリセットされた AWS アクセスキーのスキャン条件
  • ローカルリポジトリに対するスキャン機能
  • コミット時にスキャンを自動実行する Git hook の登録機能

を備えており、現在このツールの導入を進めています。また、ヌーラボ社内では、AWS アクセスキーのスキャン条件に加えて、ヌーラボの各プロダクト(BacklogCacooTypetalk、社内システム)に関する機密情報のスキャン条件も追加しています。

導入前の検証では、OS X および Linux では問題なく動作しました。また、Windows でも公式のインストーラでのインストール時に MinTTY を選択し、root 権限で git-secrets を /usr/bin にコピーすれば問題なく動きました(make コマンドが無いため make install はできませんでした)。

ただ、OS X 上で SourceTree を使いたい場合は、System Integrity Protecton (SIP) を一度無効にして、git-secrets を /usr/bin にコピーする必要がありました。OS X と SourceTree の組み合わせで使っているユーザーは多いので、git-secrets 導入の妨げになりそうで悩ましいところです。この問題については、以下のページを参考にさせていただきました。

まとめ

今回の記事では、AWS アクセスキーを安全に Git リポジトリへ登録するために、Ansible Vault を活用する方法をご紹介しました。これは、実際にヌーラボの Backlog で採用しているプラクティスです。また、Ansible Vault では防げないタイプの事故を防ぐ方法として、git-secrets というツールもご紹介しました。

Python や Ruby 用のライブラリを使うことで、他の運用管理ツールが使う機密情報も Ansible Vault で暗号化・復号化することができます。Ansible を主な構成管理ツールとして使っている方は、是非こちらのプラクティスの採用を検討してみてください。

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる