いまさら Ansible Vault で変数とファイルを暗号化して、Vault パスワードをスクリプトで管理する

Ansible Vault については多くの情報が公開されていますが、ここでは要件を以下の 3 点だけに絞って、簡単に実現する方法をまとめました。

- 機密情報の変数（パスワードなど）を暗号化する
- ターゲットに配置するファイル（秘密鍵など）を暗号化する
- Vault パスワードをスクリプトで管理する

参考: [Ansible Vault — Ansible Documentation](https://docs.ansible.com/ansible/2.9_ja/user_guide/vault.html)

- 確認環境:
  - Ansible 2.10.7
  - Ubuntu 20.04 / Debian 10 (buster)

# 準備: Vault パスワードを設定する

まず最も簡単な方法で Vault パスワードをファイルで設定します。
ここではパスワードを `my_vaultp@ssw0rd` として、パスワードファイルを「~/.vault_pass」とします。

```shell:
echo -n 'my_vaultp@ssw0rd' >~/.vault_pass
export ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass
```

2 行目の `ANSIBLE_VAULT_PASSWORD_FILE` は Ansible が参照する環境変数です。

`ansible-vault` `ansible-playbook` などのコマンドを実行すると、この環境変数が示すファイルに書かれているパスワードによって、変数やファイルが自動的に暗号化/復号化されます。

参考: [Ansible Configuration Settings — Ansible Documentation](https://docs.ansible.com/ansible/latest/reference_appendices/config.html)


# 機密情報の変数（パスワードなど）を暗号化する

## Vault 使用前

以下は all.yml という変数ファイルに、ユーザー名と平文のパスワードが定義されている例です。

```
group_vars/
└── all.yml
```

```yaml:group_vars/all.yml
---
my_user:     "user_1"
my_password: "myp@ssw0rd" # パスワード文字列 → 暗号化したい
```

## Vault 使用後

この中の変数 `my_password` の値を Vault で暗号化します。

まず変数ファイルを、元ファイルと同名のディレクトリ ＋ 2 つのファイルに変更します。

```shell:
group_vars/
└── all/       # 元の変数ファイルと同名のディレクトリ
    ├── vars   # all.yml を移動してリネームする
    └── vault  # 新規作成
```

- 変数ファイル `all.yml` を新しいディレクトリ「all」の下に移動し、ファイル名を `vars` に変更する
- 同じ階層に `vault` というファイルを新規作成する（YAML）

vars を編集して、`my_password` の値 `myp@ssw0rd` を、「`vault_` ＋ 元の変数名」という名前の新しい変数への参照に変更します。

```diff_yaml:group_vars/all/vars
my_user:     "user_1"
- my_password: "myp@ssw0rd" # パスワード文字列 → 暗号化したい
+ my_password: "{{ vault_my_password }}"  # 直値から「vault_ + 元の変数名」の参照に変更
```

新しい変数「`vault_` ＋元の変数名」と値 `myp@ssw0rd` は、新規作成したファイル vault 内で定義します。

```yaml:group_vars/all/vault
---
vault_my_password: "myp@ssw0rd"
```

`ansible-vault encrypt` コマンドを実行して vault を暗号化します。

```shell:
ansible-vault encrypt group_vars/all/vault
```

vault が以下のような暗号ファイルに変換されます。

```plaintext:group_vars/all/vault
$ANSIBLE_VAULT;1.1;AES256
65656533363866316462313831663032353031626239633333323862323132616239306130303162
3531363266336538376239646430656134363132633339320a636531653933636336306339636666
(略)
```

同一ディレクトリ内のファイル vars と vault はペアで使用し、以下のルールを守って実装します。

- vault 内では `vault_` で始まる変数だけを定義し、ペアの vars からのみ参照する
- vars 内で参照する `vault_` で始まる変数は、ペアの vault でのみ定義する

これによって vault が暗号化されたままでも、中で何が定義されているかが自明になります。

参考:
[変数および Vault — ベストプラクティス — Ansible Documentation](https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_best_practices.html#vault)
[Gitリポジトリ上でAWSアクセスキーを大公開しないためにAnsible Vaultをフル活用する | 株式会社ヌーラボ(Nulab inc.)](https://nulab.com/ja/blog/nulab/encrypt-aws-access-key-with-ansible-vault/)

# ターゲットに配置するファイル（秘密鍵など）を暗号化する

## Vault 使用前

以下は、秘密鍵ファイル「secret_key」をターゲットホストに配置するロールの例です。

```shell:
roles/
└── ssh_client/
    ├── files/
    |   └── secret_key  # 暗号化していない秘密鍵ファイル
    └── tasks/
        └── main.yml
```

タスクファイルでは、ビルトインモジュールの「copy」を使って secret_key をターゲットに配置しています。

```yaml:roles/ssh_client/tasks/main.yml
---
- name: Deploy secret key
  ansible.builtin.copy:
    dest:   "/home/my_user/.ssh/secret_key"
    mode:   0400
    owner:  "my_user"
    src:    "{{ role_path }}/files/secret_key"
```

- 参考: [ansible.builtin.copy – Copy files to remote locations — Ansible Documentation](https://docs.ansible.com/ansible/2.10/collections/ansible/builtin/copy_module.html#ansible-collections-ansible-builtin-copy-module)

秘密鍵ファイル secret_key は暗号化されていません。

```
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
hwqGhTJo8kiZehDGI6+5ZgZmNS5mpYI7576DAzPwZ7lF1YQhSYRwNWmPpFIT6mQb/RY6Ys
(略)
```

## Vault 使用後

`ansible-vault encrypt` コマンドで secret_key を暗号化します。

```shell:
ansible-vault encrypt roles/ssh_client/files/secret_key
```

secret_key が以下のような暗号ファイルに変換されます。

```plaintext:roles/ssh_client/files/secret_key
$ANSIBLE_VAULT;1.1;AES256
65656533363866316462313831663032353031626239633333323862323132616239306130303162
3531363266336538376239646430656134363132633339320a636531653933636336306339636666
(略)
```

変更作業はこれだけです。

Ansible の copy モジュールは、ファイルが暗号化されていたら自動的に復号化してターゲットに配置してくれるので、タスクファイルを変更する必要はありません。

以下、「[ファイルレベルの暗号化 — Ansible Vault — Ansible Documentation](https://docs.ansible.com/ansible/2.9_ja/user_guide/vault.html#id3)」から引用

>Ansible Vault は、バイナリーファイルも含め、任意のファイルを暗号化できます。 vault で暗号化されたファイルが、 src 引数として、copy モジュール、template モジュール、 unarchive モジュール、script モジュール、または assemble モジュールとして指定されていると、ファイルは復号されたターゲットホストの宛先に配置されます (プレイの実行時に有効な vault パスワードが提供されている場合)。

# Vault パスワードをスクリプトで管理する

平文ファイルの代わりに「Vault パスワードを標準出力に出力するスクリプト」を使用すれば、Vault パスワードの管理方法を自由に設計することができるようになります。

## スクリプト化前

パスワードファイルを .gitignore に指定してソース管理の対象外とするため、開発メンバーには以下のファイルを別途配布する必要があります。

```plaintext:/workspace/.vault_pass
my_vaultp@ssw0rd
```
パスワードファイルのパスを指定する環境変数を環境ファイルに定義して、ソースと共に共有します。
開発環境が Docker なら、以下のような環境ファイルを docker-compose の `env_file:` で設定します。

```shell:consts.env
ANSIBLE_VAULT_PASSWORD_FILE="/workspace/.vault_pass"
```

開発環境が Docker でない場合は ~/.bashrc などで上記の環境変数を `export` するか、または以下のように ansible.cfg で設定します。

```ini:ansible.cfg
vault_password_file = "/workspace/.vault_pass"
```

## スクリプト化後

パスワードファイルの代わりに、「Vault パスワードを標準出力に出力するスクリプト」を使用します。

スクリプトのパスを同様に指定します。 

```shell:consts.env
ANSIBLE_VAULT_PASSWORD_FILE="/workspace/.vault_pass.sh"
```

環境変数の代わりに、以下のように ansible.cfg で指定する場合も同様です。

```ini:ansible.cfg
vault_password_file = "/workspace/.vault_pass.sh"
```

スクリプト自体はソース管理の対象にして共有し、スクリプトが出力する値を別途管理します。

以下は単純な例として、Vault パスワードを環境変数から取得して出力するスクリプトです。

```shell:/workspace/.vault_pass.sh
#!/bin/bash
echo -n "${MY_VAULT_PASSWORD}"
```

もちろんこれでは環境変数 `MY_VAULT_PASSWORD` を開発メンバーに別途配布する必要があるので、スクリプト化前から何も改善されていません。

しかしスクリプト化によって、今後の Vault パスワードの管理方法を自由に設計できるようになりました。

# 次回以降の課題

現実のプロジェクトで求められるセキュリティと運用の課題をクリアするために、以下の記事を追加しました。

次回: [いまさら Ansible Vault (2): せめて開発・検証・本番環境の Vault パスワードを別々に - Qiita](https://qiita.com/3244/items/ebe6f71aedc333634f41)
次々回: [いまさら Ansible Vault (3): Vault パスワードを環境ごとの AWS アカウントの SSM パラメータストアで管理する - Qiita](https://qiita.com/3244/items/4cf79dd140be6caa0c5b)