複数のパスワードを復号できるようになったAnsible Vault

# はじめに

Ansible 2.4からAnsible Vaultで、あるファイルはパスワードA、またあるファイルはパスワードB、……と別々のパスワードを使って暗号化した場合でも1つのコマンドで復号できるようになった。
これは公式ドキュメント（ http://docs.ansible.com/ansible/2.4/vault.html ）の「Use encrypt_string to create encrypted variables to embed in yaml」、「Vault Ids and Multiple Vault Passwords」、「Providing Vault Passwords」辺りに説明されている。

# 要約

* 2.4からも従来の--vault-password-fileオプションは使えるが、代わりに--vault-idオプションで置き換えることもできる。
* --vault-idオプションは--vault-password-fileオプション以上の機能を持っているが、とりあえず--vault-password-fileオプションと同様にパスワードが書かれたファイルを指定すれば--vault-password-fileオプションと同じ振る舞いをする。
* 復号の際には--vault-idオプションを複数回使うことができるので、別々のパスワードで暗号化された複数のファイルがある場合でもplaybookの実行等を行うことができる。
* 2.4においては2.3までと比べて嬉しいことはあまりないが、将来はもっと面白いことができるようになるかもしれない。

# 2.3までの話

従来、Ansible Vaultの暗号化や復号のためのパスワードが書かれたファイルを指定するのに--vault-password-fileオプションを使用していた。

例えばファイルを新しく生成してエディタを使って暗号化ファイルの中身を記述する場合は、

```
$ ansible-vault create --vault-password-file (パスワードの書かれたファイル) (暗号化する新しいファイル)
```

であったし、既存の未暗号化ファイルを暗号化する場合は、

```
$ ansible-vault encrypt --vault-password-file (パスワードの書かれたファイル) (暗号化する既存の未暗号化ファイル)
```

であった。標準入力から直接暗号化ファイルを作りたい場合は、

```
$ echo -n '(キー): (値)' | ansible-vault encrypt --vault-password-file (パスワードの書かれたファイル) --output (暗号化する新しいファイル)
```

とすれば良かった。

ansible-vault decryptやviewのように復号が必要な処理が行われる場合や、ansible-playbook等で暗号化ファイルを利用する場合も--vault-password-fileオプションを使用した。

また、--vault-password-fileオプションを使用しない場合、ansible-vaultコマンドでは自動的にパスワードを聞いてきたし、ansible-playbook等では--ask-vault-passオプションを付けておけば実行時にパスワードを聞いてきた。

--vault-password-fileオプションの代わりに設定ファイルansible.cfgで[defaults]セクションにvault_password_fileの値として設定する、あるいは環境変数ANSIBLE_VAULT_PASSWORD_FILEの値を設定することでも同じ効果があった。

# 2.4からの話

2.4からも従来使えたそれらも使えるままに、新規に--vault-idというオプションが追加された。[^1] [^2]

[^1]: でも従来からのものはいつかdeprecatedにされそうな気しかしない。

[^2]: 暗号化パスワードを設定し直すansible-vault rekeyには従来から--vault-password-fileオプション以外に新しくパスワードの書かれたファイルを指定する--new-vault-password-fileオプションがあったように、--vault-idオプションに対する--new-vault-idオプションも追加された。

これまで--vault-password-fileオプションを指定していた箇所を--vault-idオプションに代えても同じ結果になる。

ここで検証のためパスワードの書かれたファイル2つ、`echo -n "password1" > vault_multi_pass1`と`echo -n "password2" > vault_multi_pass2`を作っておこう。

先述した標準入力から直接暗号化ファイルを作るコマンド、

```
$ echo -n 'key1: value1a' | ansible-vault encrypt --vault-password-file vault_multi_pass1 --output host_vars/multi_pass_host1.yml
```

は、以下のように--vault-password-fileの箇所を--vault-idとしても上手く行く。

```
$ echo -n 'key1: value1a' | ansible-vault encrypt --vault-id vault_multi_pass1 --output host_vars/multi_pass_host1.yml
```

暗号化されたファイルhost_vars/multi_pass_host1.ymlの中身は以下のようになる。（暗号化された部分、以下だと633から始まる部分は同じコマンドを打っても常に同じ文字列になるわけではない）

```text:host_vars/multi_pass_host1.yml
$ANSIBLE_VAULT;1.1;AES256
63333233346130323433646534383862623633383130366566636563666663363762396165366636
3061376431346533663362333665333037383462303637320a343838393230636463383162313334
65653333366439356638376634313464313033636266616638323934303437626164643630373637
6139363863353163370a303062346163643039663332306538356665313231653364323937656162
6661
```

このファイルを復号したものは`ansible-vault view --vault-password-file vault_multi_pass1 host_vars/multi_pass_host1.yml`でも`ansible-vault view --vault-id vault_multi_pass1 host_vars/multi_pass_host1.yml`でも見ることができる。

それでは--vault-idオプションが追加されたことによるこれまでとの違いは何かと言うと、3点ある。

## 新機能：ラベル

まず1つ目に、--vault-idオプションは値に「(ラベル)@(パスワードの書かれたファイル)」と指定することによってラベルが設定できる。

つまり、以下のように実行すると暗号化されたファイルhost_vars/multi_pass_host2.ymlにはラベルlabel2が設定される。

```
$ echo -n 'key1: value1b' | ansible-vault encrypt --vault-id label2@vault_multi_pass1 --output host_vars/multi_pass_host2.yml
```

host_vars/multi_pass_host2.ymlの中身は以下のようになる。

```text:multi_pass_host2.yml
$ANSIBLE_VAULT;1.2;AES256;label2
39323837616639616662323335353834396338613538383962306562316661323066626463303030
3332646563343637616430643632653233373139643333310a663765633566643531343730636137
65666330323762366431633839343063636636356261653761363763653436313436623864663439
3066313631643166660a346530336461393566623566303166343362386363356338353336613265
3736
```

ラベルなしであるhost_vars/multi_pass_host1.ymlとの違いは、ヘッダ部分の$ANSIBLE_VAULT;の後がラベルなしだと1.1、ありだと1.2である。これはAnsible Vaultフォーマットのバージョン番号である。[^3]

[^3]: フォーマット1.1までしか対応していないAnsible 2.3でも1.2のフォーマットは読めるらしい。→ https://github.com/ansible/ansible/pull/22756

また、AES256の後に;label2が追加されているのが見える。これがラベルである。

現在ラベルが意味を持つのは設定ファイルansible.cfgで[defaults]セクションにvault_id_match = True、あるいは環境変数でANSIBLE_VAULT_ID_MATCH=Trueを設定した場合である。（初期値はFalse） [^4]

[^4]: [公式ドキュメント](http://docs.ansible.com/ansible/2.4/vault.html)にはFalseであっても「the default is to try the matching id first, then try the other vault ids in order.」と書かれている。現在の振る舞いを見る限りこの記載は意味がなく見える（後述するが使用される変数が1つでも復号できなければエラーで落ちるので）が、将来的には何か意味を持つことが期待される。

この設定値がFalseの場合はラベルは無視されるが、Trueの場合、復号時の--vault-idオプションに指定したラベルとファイルに書かれたパスワードとの組み合わせに一致するもののみ復号される。

なお、ラベル自体は暗号化されていないため、手元で試した限りでは適当なエディタ等で書き換えることができるようだ。

## 新機能：複数のパスワードを使った復号

2つ目として、復号する場合は--vault-idオプションを複数回指定できる。

ここでまた検証のためインベントリファイルmulti_pass_hostを以下の内容で作る。

```text:multi_pass_host
multi_pass_host[1:3]
```

また、playbookであるmulti_pass_host_site.ymlを以下の内容で作る。

```yaml:multi_pass_host_site.yml
- hosts: all
  gather_facts: no
  tasks:
    - debug:
        var: key1
```

パスワードが書かれた2つ目のファイルを使ってもう1つ暗号化されたファイルhost_vars/multi_pass_host3.ymlを作っておく。

```
$ echo -n 'key1: value1c' | ansible-vault encrypt --vault-id label3@vault_multi_pass2 --output host_vars/multi_pass_host3.yml
```

まずはANSIBLE_VAULT_ID_MATCH=Falseの状態でこのplaybookを実行するには--vault-idオプションを2回指定して以下のようにすれば良い。

```
$ ansible-playbook -i multi_pass_host --vault-id vault_multi_pass1 --vault-id vault_multi_pass2 multi_pass_host_site.yml
```

なお片方、あるいは両方の--vault-idオプションを--vault-password-fileオプションに代えても実行できる。

```
$ ansible-playbook -i multi_pass_host --vault-password-file vault_multi_pass1 --vault-id vault_multi_pass2 multi_pass_host_site.yml
```

```
$ ansible-playbook -i multi_pass_host --vault-password-file vault_multi_pass1 --vault-password-file vault_multi_pass2 multi_pass_host_site.yml
```

ANSIBLE_VAULT_ID_MATCH=Trueの場合、今、

* host_vars/multi_pass_host1.ymlはラベルなし
* host_vars/multi_pass_host2.ymlはラベルがlabel2
* host_vars/multi_pass_host3.ymlはラベルがlabel3

である。そのため、playbookを実行するには以下のように--vault-idオプションを3回使う必要がある。

```
$ ANSIBLE_VAULT_ID_MATCH=True ansible-playbook -i multi_pass_host --vault-id vault_multi_pass1 --vault-id label2@vault_multi_pass1 --vault-id label3@vault_multi_pass2 multi_pass_host_site.yml
```

また、ラベルを指定していない--vault-idオプションは--vault-password-fileオプションに代えても実行できる。

```
$ ANSIBLE_VAULT_ID_MATCH=True ansible-playbook -i multi_pass_host --vault-password-file vault_multi_pass1 --vault-id label2@vault_multi_pass1 --vault-id label3@vault_multi_pass2 multi_pass_host_site.yml
```

もしhost_vars/multi_pass_host3.ymlのラベルもlabel2だったとしたら、playbookを実行するためにはANSIBLE_VAULT_ID_MATCH=Falseの場合はラベルが無視されるので元と当然変わらないが、ANSIBLE_VAULT_ID_MATCH=Trueの場合は以下の通りである。

```
$ ANSIBLE_VAULT_ID_MATCH=True ansible-playbook -i multi_pass_host --vault-id vault_multi_pass1 --vault-id label2@vault_multi_pass1 --vault-id label2@vault_multi_pass2 multi_pass_host_site.yml
```

つまり、1ラベルごとに1パスワードだけ設定する、というわけではなく、ラベルが同じであっても複数のパスワードで暗号化することができ、結局のところ復号は--vault-idオプションで指定したラベルとパスワードが共に一致したものに対してのみ行われるという動きをする（ので、復号が必要なラベルとパスワードの組み合わせの分だけの回数--vault-idオプションを指定する必要がある）。

## --ask-vault-passオプションと同様のことをするには

3つ目には、ansible-vault decryptやansible-vault viewやansible-playbook等、復号する場合に--vault-idの「パスワードの書かれたファイル」の代わりにpromptという値を指定すると実行時にパスワードを要求してくる。つまり--ask-vault-passオプションと同じ効果である。

```
$ ANSIBLE_VAULT_ID_MATCH=True ansible-playbook -i multi_pass_host --vault-id prompt --vault-id label2@prompt --vault-id label2@prompt multi_pass_host_site.yml
```

を実行すると、以下のように実行後3回パスワード入力を促されるので正しく入力すると実行可能となる。

```text:3行いきなり出力されているように書いているが、実際にはパスワードを入力するごとに次の1行が出力される
Vault password (default):
Vault password (label2):
Vault password (label2):
```

## パスワードが書かれたファイルを設定ファイル等で指定する

設定に関するドキュメントは https://docs.ansible.com/ansible/2.4/config.html にある。

従来からある設定ファイルansible.cfgの[defaults]セクションのvault_password_fileの値、あるいは環境変数ANSIBLE_VAULT_PASSWORD_FILEの値は、相変わらず--vault-password-fileオプションと同等であり、つまりラベル指定なしで1つだけパスワードファイルを指定できる。

加えて設定ファイルansible.cfgの[defaults]セクションのvault_identity_listの値、あるいは環境変数ANSIBLE_VAULT_IDENTITY_LISTの値は、--vault-idオプションの値に相当するものを設定できる。
なお、複数指定したければカンマ区切りで指定する。例えば`vault_multi_pass1,label2@vault_multi_pass1,label2@vault_multi_pass2`を指定する。
こちらでは値promptも使用でき、使用した場合実行時にパスワード入力を促される。

# これができて何が嬉しいか

ぶっちゃけた話、これだけだと何か嬉しいことがあるかと言われると特に思いつかない。

というのも、ある値が必要になった時、それが暗号化されていたら復号するためのパスワードが与えられていなければ、現在その時点で「ERROR! Decryption failed (no vault secrets would found that could decrypt)」とエラーが出てそのタスクまでで終了するからだ。

つまり先程のplaybook実行コマンドのラベルだけ変えてやると復号できないので、

```
$ ANSIBLE_VAULT_ID_MATCH=True ansible-playbook -i multi_pass_host --vault-id vault_multi_pass1 --vault-id label2@vault_multi_pass1 --vault-id label4@vault_multi_pass2 multi_pass_host_site.yml
```

実行結果は以下のようになる。

```
PLAY [all] *******************************************************************************
TASK [debug var=key1] ********************************************************************
ERROR! Decryption failed (no vault secrets would found that could decrypt) on (省略)/host_vars/multi_pass_host3.yml
```

ここでエラー終了ではなくその変数を必要とするタスクをスキップする、等となってくれれば、どのパスワードを知っているかの権限によって同じplaybookを実行しても実行されるタスクが変わるので使い道があるだろう。

実際に[2.5のロードマップ](http://docs.ansible.com/ansible/devel/roadmap/ROADMAP_2_5.html)に
「In some cases diff users might want to use the same play with different access levels, being able to change vault failure to decrypt to a warning or something else allows for this.」とあるのでこれができるようになると期待できる。（ただし、ロードマップに書かれていても延期されることは良くある）

他に、同じキーを違うラベルかパスワードで暗号化できたら、与えたラベル/パスワードによって違う値を与えられて面白いのに、と思う。

つまり、host_vars/multi_pass_host3.ymlの中身を一旦消して、[Single Encrypted Variable](https://qiita.com/yunano/items/86d3f9beb678adbff50d)にてlabel1ではvault_multi_pass1を使って"key1: value1d"を、label2ではvault_multi_pass2を使って"key1: value1e"を暗号化した場合、

```
$ ansible-vault encrypt_string --vault-id label1@vault_multi_pass1 --name key1 value1d > host_vars/multi_pass_host3.yml
$ ansible-vault encrypt_string --vault-id label2@vault_multi_pass2 --name key1 value1e >> host_vars/multi_pass_host3.yml
```

vault_multi_pass1を使って復号した場合はkey1の値はvalue1d、vault_multi_pass2を使って復号した場合はkey1の値はvalue1eとなって欲しいのだが、実際には同じキーは後のものだけが有効である（「line 1, column 1, found a duplicate dict key (key1). Using last defined value only.」という警告メッセージを見ることができる）。

これの回避策としてkey1とkey2を違うパスワードで暗号化して、playbookに例えば`{{ key1 | default(key2) }}`と書いたとしても、上記の通りkey1が復号できなければその時点でエラー終了である。

というわけで、将来のバージョン待ち。
無論今でも2人から別々に暗号化されたファイルをもらっても実行できる、程度の役には立つだろうがそんなシチュエーションあんまりなさそうだし。

---