Ansible + Hashicorp Vault でセキュアな値を安全に使ってみるぜ

お久しぶりです @ktoshi です！
すっかり夜が長くなってきましたね。

パスワードなどの機密情報の管理というのが課題となっている会社さんも多いことでしょう。
今回はそれらを一元管理してくれる Vault というソフトウェアについてお話したいと思います。

# Vault について
そこまで目新しいものではないので、ご存じの方も多いことでしょう。
[Vault](https://www.vaultproject.io/) は [Hashicorp](https://www.hashicorp.com/) が開発する機密な情報を管理するソフトウェアです。
詳細な内容については公式HPをご覧ください。

# 概要
今回は Ansible のインベントリに指定するパスワード情報を Vault から取得して利用したいと思います。
Ansible には ansible-vault という類似した機能が付随していますが、Terraform も利用している関係上、今回は Hashicorp 社の Vault を採用しました。

# 環境
Vault はバイナリファイルが配布されているので特に環境依存はないのですが、
せっかくなので今回は Docker を使用して Vault サーバを起動させたいと思います。
もちろん、Vault と Ansible を同居させることも可能です。

* Vault サーバ
  * Docker
     * Docker version 19.03.13, build 4484c46d9d
  * Vault
     * Vault v1.5.5

* Ansible サーバ
  * OS
     * CentOS Linux release 8.2.2004
  * Vault
     * Vault v1.5.5
  * Ansible
     * ansible 2.9.11

# Let's Enjoy!!
今回は下記の作業を行います。

1. Vault サーバの起動
2. Ansible サーバの準備
2. Vault の初期設定
2. パスワード情報の格納
3. Ansible で使ってみよう

## Vault サーバの起動
最初に述べた通り今回は Docker を利用します。
コマンドラインでたたいてもいいのですが、せっかくなので docker-compose を使いましょう。
[ここ](https://stackoverflow.com/questions/45171564/using-vault-with-docker-compose-file) に書かれていたものがあったのでそのまま引用させていただきました。

```docker-compose.yaml
version: '3'

services:

    vault:
      image: vault:latest
      volumes:
        - ./vault/config:/vault/config
        - ./vault/policies:/vault/policies
        - ./vault/data:/vault/data
      ports:
        - 8200:8200
      environment:
        - VAULT_ADDR=http://0.0.0.0:8200
        - VAULT_API_ADDR=http://0.0.0.0:8200
        - VAULT_ADDRESS=http://0.0.0.0:8200
      cap_add:
        - IPC_LOCK
      command: vault server -config=/vault/config/vault.json
```

Vault を起動させるコンフィグファイルも必要ですので、これも同じページから拝借いたします。

```console
mkdir ./vault/config/
vi ./vault/config/vault.json
```

```vault.json
{
  "listener":  {
    "tcp":  {
      "address":  "0.0.0.0:8200",
      "tls_disable":  "true"
    }
  },
  "backend": {
    "file": {
      "path": "/vault/file"
    }
  },
  "default_lease_ttl": "168h",
  "max_lease_ttl": "0h",
  "api_addr": "http://0.0.0.0:8200"
}
```
これで Docker で Vault を起動させるための準備は終わりなので、
`docker-compose up -d` で起動します。

Vault サーバ側の設定は以上です。
これ以降は全てAnsible (クライアント）側からの操作となります。

## Ansible (クライアント)側の準備
Ansible のインストールについては先人の知恵がたくさんあるので今回は割愛します。

### Vault の設置
Ansibleサーバにも Vault を導入する必要がありますので、
[ダウンロードサイト](https://www.vaultproject.io/downloads) から環境にあったファイルをダウンロードし、設置する。

### モジュールの追加
今回利用する `hashi_vault` はコミュニティ配布されているものなので、
下記コマンドを発行してインストールする必要があります。

```
ansible-galaxy collection install community.general
```

## Vault の初期準備
Vault は起動しただけではまだ使えません。
下記の作業が必要となります。

1. 初期化
2. 開封の儀
3. 権限の設定

### 環境変数の設定
まず、Vault の接続先を環境変数に設定します。

```console
export VAULT_ADDR='http://<VaultサーバのIPアドレス>:8200'
```

### 初期化
`vault operator init` コマンドで初期化を行います。

そうすると下記のような表示になります。
※ 再利用できないのでキー情報なども隠さず表示させています。

```console
$ vault operator init

Unseal Key 1: 1ziR2Qem+1tqBEgiA3PaFbrXkPt3qrAd9CBzudLhtA71
Unseal Key 2: kAzCbmrYzX+1hHAkOS16B4pcwmDNQwFccHh0fKQsgAd4
Unseal Key 3: C63j4phSNJ+DoAQwKBCRvLB1xvtXAW5XRZVfIWibZAA3
Unseal Key 4: r9FLuZcpWnBLblS+ebGeJE/VAFDMY1neGPiFR0gMEFPL
Unseal Key 5: H93WsX9ni1EnQeT8mhteNRiRSCXP3RcZfb5OtNHtmRL6

Initial Root Token: s.dxRxE8KhN11HESCBb9dVd6gu

Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated master key. Without at least 3 key to
reconstruct the master key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
```

### 開封の儀
Vault は初期化した直後はサーバが「封印」された状態となっていて、利用できません。
そのため、`vault operator unseal` を発行してみなさん大好き開封の儀を行う必要があります。

コマンドを発行すると下記の状態になります。

```terminal
$ vault operator unseal
Unseal Key (will be hidden):
```
この状態で先ほどの初期化時に表示された「Unseal Key」をどれでもよいので一つ入力します。
そうすると、下記の出力となります。

```
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    1/3   <-- ★ 開封するには後3回必要だぜ。
Unseal Nonce       18fd028f-b238-82a8-2dd2-1b6679e9fc6e
Version            1.5.5
HA Enabled         false
```

開封の儀は一回にして成らず、ということで合計3回行う必要があります。
それぞれ、鍵は異なるものを選んで行ってください。

これが終われば、開封の儀は完了です。
ようやく、 Vault が利用できる状態となりました。

### 権限の設定
Vault では保存されている情報の閲覧ポリシーを下記のようにして設定することができます。

```terminal
$ vault policy write test-policy - << EOF
path "secret/password" {
  capabilities = ["create", "update", "read"]
}
EOF
```
デフォルトポリシーは暗黙のDenyになっているため、許可をするものは全て設定する必要があります。
なお、上記の例で ["create", "update"] のみだと情報の閲覧ができないので、"read"も忘れずに付与してください。
※ どハマリしました。。。

権限を設定すれば、その権限を持ったトークンを下記の通り発行します。

```terminal
$ vault token create -policy=test-policy

Key                  Value
---                  -----
token                s.l4Ihj1R5plPgWiMmIKD9Q6fV
token_accessor       2z1b2FaQwCZydMaGheSgFifB
token_duration       168h
token_renewable      true
token_policies       ["default" "test-policy"]
identity_policies    []
policies             ["default" "test-policy"]
```

ここに記載されている token は後で利用しますのでメモしておいてください。

## パスワード情報の格納
ではパスワード情報を格納してみましょう。
格納するためにはまずログインをします。
ログインの方法も色々あるようなのですが、 Token が手っ取り早そうなのでとりあえずそれを利用します。

```terminal
$ vault login <先ほど発行した Token>
```
`Success!` という表記とログインに使用したトークンのポリシーなどが表示されればOKです。

ログインができれば、下記のようなコマンドを発行して実際に情報を格納します。

```terminal
$ vault kv put secret/password passwd="SamplePassword"
```

格納すれば、下記コマンドで確認できます。

```terminal
$ vault kv get secret/password
===== Data =====
Key       Value
---       -----
passwd    SamplePassword
```

Key-Value 形式で保存されているのが分かります。

## Ansible で使ってみよう
では、今日の本題です。
実際に Ansible の接続情報として利用してみましょう。

```hosts.yaml
---
test.example.com:
  hosts:
    192.168.50.1:
      ansible_user: worker
      ansible_ssh_pass: "{{ lookup('community.general.hashi_vault', 'secret/password token=<先ほど発行した Token> url=http://<VaultサーバのIPアドレス>:8200')['passwd'] }}"
```
ちょっと見づらいですが、`ansible_ssh_pass` に Valut から取得した値を利用しています。
後は通常通りに Ansible を実行するだけです。
ね、簡単でしょ？
※ 絶対もっとスマートなやり方があると思いますので皆さま教えてください。

# まとめ
めちゃくちゃ長くなってしまいましたが、やってることはそこまで難しくないかと思います。
サーバの構成管理もコード化してバージョン管理する以上、機密情報の扱いは避けては通れない課題になると思います。
バージョン管理の対象から外す、というのが一番簡単ではありますが、意外とそれでデプロイの障壁になることも少なくないのではないでしょうか。
バージョン管理の対象としつつ、セキュアに情報をやりとりできるこの手法にぜひ挑戦いただければと思います。

ただ、正直ここまでは簡単ですが、これをさらにスマートにかつ適切な運営というのは非常に難易度が高そうな気がしてやまない今日この頃です。
より知見をお持ちの方もいらっしゃると思いますので、ぜひアドバイスをお願いいたします！

それではみなさんも素敵なVaultライフを…！