Hashicorp Vault に保存した機密情報を Ansible で使用する

## はじめに

Ansible を使用して VM インスタンスなどをセットアップする際に、ユーザー名とパスワード、TLS クライアント証明書、API キーなどの機密情報を扱うケースは多くあるかと思います。機密情報を Ansible Playbook と一緒にバージョン管理することは避けたいですし、Ansible 実行時に手動で用意するのも面倒です。このような背景を受けて、この記事では Vault Module を使用して Hashicorp Vault に保存した機密情報を Ansible で使用する方法を紹介します。

## Vault Module とは

Vault Module は Ansible コミュニティでメンテナンスされているサードパーティー製の Module で、こちらを使用することで Ansible 実行時に Vault から機密情報を取得することができます。

https://docs.ansible.com/ansible/latest/collections/community/hashi_vault/hashi_vault_lookup.html

### インストール方法

`ansible-galaxy` コマンドで Vault Module をインストールします。

```bash
$ ansible-galaxy collection install community.hashi_vault
```

Vault Module は `hvac` という Python ライブラリに依存しているので、そちらもインストールします。

```bash
# Ansible が使用する Python インタープリターを特定
$ ansible -m debug -a 'var=ansible_python_interpreter' localhost
[WARNING]: No inventory was parsed, only implicit localhost is available
localhost | SUCCESS => {
    "ansible_python_interpreter": "/usr/local/Cellar/ansible/4.0.0/libexec/bin/python3.9"
}

# 使用される Python インタープリターで hvac をインストール
$ /usr/local/Cellar/ansible/4.0.0/libexec/bin/python3.9 -m pip install hvac
```

### 使用例

公式ドキュメントの例ではワンライナーなものが紹介されていますが、メンテナンス性が悪いと感じたので、ここではメンテナンス性を考慮して改良したものを紹介しています。以下の例は、認証方式として Token を使用するものになっているので、実行ホストで `vault login` が完了している前提（[Token Helper](https://www.vaultproject.io/docs/commands#token-helper) により `~/.vault-token` に認証トークンがキャッシュされている）となることに注意してください。

```yaml
- name: read secrets from vault
  set_fact:
    mysql_root_password: >-
      {{ lookup(
        'community.hashi_vault.hashi_vault',
        ' url=' + vault_addr +
        ' ca_cert=' + vault_cacert +
        ' secret=/path/to/mysql-secrets:root-password'
      ) }}
    client_cert: >-
      {{ lookup(
        'community.hashi_vault.hashi_vault',
        ' url=' + vault_addr +
        ' ca_cert=' + vault_cacert +
        ' secret=/path/to/tls-client-secrets:client.pem'
      ) }}
    client_key: >-
      {{ lookup(
        'community.hashi_vault.hashi_vault',
        ' url=' + vault_addr +
        ' ca_cert=' + vault_cacert +
        ' secret=/path/to/tls-client-secrets:client-key.pem'
      ) }}
  vars:
    vault_addr: https://your-vault:8200
    vault_cacert: /path/to/cacert.pem
```

上記のタスクで Vault から取得した機密情報は以下のように使用できます。

```yaml
# ファイル内の文字列として使用する場合はテンプレートファイル内で {{ mysql_root_password }} を定義すること
- name: copy configuration file
  template:
    src: templates/server.conf
    dest: /path/to/server.conf

# ファイルとして使用する場合は Copy Module の content パラメーターを使用すること
- name: copy client cert
  copy:
    content: "{{ client_cert }}"
    dest: /path/to/client.pem

- name: copy client key
  copy:
    content: "{{ client_key }}"
    dest: /path/to/client-key.pem
```

使用例としてシンプルなものを紹介しましたが、公式ドキュメントでは他にも様々なユースケース毎の設定例が紹介されていますので、そちらも併せて参照するのが良いかと思います。

### 補足

Vault Module を macOS で使用する際に以下のエラーが出力されることがあります。

```bash
objc[43838]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called.
objc[43838]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
```

これは [ansible/ansible#31869](https://github.com/ansible/ansible/issues/31869) で報告されているとおり macOS 起因の問題なので、ワークアラウンドとして紹介されている以下の環境変数を宣言して再実行する必要があります。

```bash
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
```

## さいごに

今回は Vault Module を使用して Hashicorp Vault に保存した機密情報を Ansible で使用する方法を紹介しました。Vault Module はとても便利なので、Ansible での機密情報の扱いに課題を抱えている方々の参考になれば幸いです。