Ansible 認証情報管理

Ansible Logo

Ansible の運用で問題になるのは リモートホストへのアクセスに使用する認証情報をどう管理するかだと思います。 SSH や sudo のパスワードを毎回入力するのは避けたいが、 平文パスワードを Git リポジトリに保存するようなことはしたくないし、 SSH エージェントの起動と秘密鍵登録のセットアップを事前にやるのも面倒くさい。

Ansible には vault という組み込みのファイル暗号化機能と、 動作を拡張できるプラグインの仕組みがありますので、これらを活用して SSH 秘密鍵を含む認証情報すべてをひとつの vault ファイルに格納し、 その復号パスワードの入力だけで済むような手法を考えてみました。

コードは自分の GitHub で creds として公開していますので参照してください。

前提

creds が想定するリモートホストアクセスの認証設定は次のとおりです。

  1. Ansible のリモートログイン専用の エージェントユーザを各リモートホストに用意する。
  2. Ansible はリモートホストに公開鍵認証で SSH ログインする。 全リモートホスト共通で用いる秘密鍵を SSH エージェントプロセスに登録しておく。
  3. エージェントユーザは root 権限を sudo により獲得する。 ログインパスワードは推測不能かつリモートホストごとに異なるものにする。

ssh-agenty.py

Ansible には コールバック・プラグイン という仕組みがあり、特定のイベント発生時に好きな動作をさせることができます。 ssh-agent.pyplaybook_on_play_start コールバックを利用し、 ansible-playbook 起動後プレイブック実行前に、 変数定義された SSH 秘密鍵を SSH エージェントに登録するプラグインです。

具体的には creds_ssh_private_key 変数に RSA でパスフレーズなしの SSH 秘密鍵が定義されている場合、 このプラグインはそれを読み込んで SSH エージェントプロセスに登録し、 さらに authorized_keys として使える文字列を creds_ssh_public_key 変数に定義するという動作をします。

このプラグインを使うには、 プレイブックファイルと同じ位置にディレクトリ callback_plugins を作成しその中にファイル ssh-agent.py を入れておきます。 あるいは ansible.cfg でプラグイン検索パスを設定してください。

creds_vault.yml

ssh-agent.py プラグインを活用した変数定義ファイルの例が creds_vault.yml です。このファイルは前述の creds_ssh_private_key に加えて 次の変数を定義しています。

このファイルは GitHub リポジトリには平文で格納していますが、 SSH 秘密鍵やログインパスワードが含まれるため、 実際には ansible-vault により暗号化した vault ファイルとして扱うべきです。

$ ansible-vault encrypt creds_vault.yml  ← vault に変換する
$ ansible-vault edit creds_vault.yml     ← vault の内容を編集する

エージェントユーザの作成

プレイブック init-agent.ymlcreds_vault.yml の情報を用いて 各リモートホストにエージェントユーザをセットアップするプレイブックです。 具体的にはユーザ creds_user を作成してパスワード creds_pass を設定、 さらに ssh-agent.py プラグインにより自動生成された creds_ssh_public_keyauthorized_keys ファイルにインストールします。

このプレイブックを利用するには ansible-playbook を実行するホストで mkpasswd ユーティリティが必要になりますので事前にインストールしてください (Debian では apt-get install whois を実行)。 また適当なリモートホストの認証情報を ansible-playbook のオプション -k -K -u などで与える必要があります。

$ ansible-playbook -i hosts --ask-vault-pass -k -K -u ubuntu init-agent.yml
SSH password: 
sudo password [defaults to SSH password]: 
Vault password: 
...

利用例

プレイブック ping.yml は実際の利用例です。 vars_files で読み込んでいる creds_ansible.yml は Ansible の特殊変数 ansible_ssh_useransible_sudo_pass を それぞれ creds_usercreds_pass で定義するだけのものです。

次のように常に ssh-agent と共に ansible-playbook を起動し vault パスワードを与えるだけで、 適切な認証で各リモートホストにアクセスできます。

$ ssh-agent ansible-playbook -i hosts --ask-vault-pass ping.yml
Vault password: 

PLAY [all] ******************************************************************** 
Loading SSH private key 93:e2:fa:0a:a6:5c:c7:43:06:79:8d:ba:2d:c4:d8:01

GATHERING FACTS *************************************************************** 
ok: [host-a]
ok: [host-b]
ok: [host-c]
ok: [host-d]
...

内部

ssh-agent.py プラグインは SSH エージェントプロセスとの通信に Paramiko モジュールを使用しています。 Paramiko は Ansible が動く環境なら利用可能なはずです。

ただし Paramiko には SSH エージェントに秘密鍵を登録する機能がないため ssh-agent.py は自力でそれを行っています。 Ansible や Paramiko の内部 API を使っているためバージョンによっては正しく動作しないかもしれません。

SSH エージェントのプロトコルについては OpenSSH のファイル PROTOCOL.agent に解説があります。

おわりに

Ansible の vault 機能とプラグインの仕組みを使って、 ユーザに求める認証情報の数とセットアップの手間を減らす手法を紹介しました。

残念ながら、いくらがんばっても 最後に残る vault パスワード入力の手間だけは無くすことができません。

すでに Linux カーネルの鍵保持機構を利用するとか、 複数ユーザによるパスフレーズ共有を避けるために GnuPG を活用する とか、いろいろアイディアはあるようですが、 外付けでできることにはやはり限界がありますので、 今後の Ansible 本体の機能拡張に期待したいと思います。

2014/10/12 10:20:20 JST