【Ansible】開発者ユーザー追加・削除のベスト・プラクティスを考える

# 【Ansible】開発者ユーザー追加・削除のベスト・プラクティスを考える
開発メンバーが新たに加わるたびに、
サーバ全てで

- ユーザを追加したり
- グループに加えてやったり
- 公開鍵を登録したり
- パスワード設定したり

手動でやってるとすごく面倒ですよね...
そういうときには、やはりプロビジョニングツールの出番でしょう。

いろいろ調べて、いい感じかな、という所まできたので、メモ代わりに晒します。

利用するツールは,Ansibleです。

## 準備
AnsibleやPythonのインストール。
加えて、pipでインストールできる、**libpass**をインストールしておいてください。

```bash
$ pip install libpass
```

## ファイル

### 配置
Ansibleドキュメントの、ベスト・プラクティスを参考にしています。
[Best Practices — Ansible Documentation](http://docs.ansible.com/ansible/playbooks_best_practices.html)
ファイル配置は下記の通りです。

```
├── README.md
├── Vagrantfile # 検証用のVagrantfile
├── ansible.cfg # 設定ファイル
├── hosts # インベントリディレクトリ
│   ├── dev
│   │   └── inventory # インベントリファイル
│   └── prd # 今回は使わない
├── roles　# Playbook用roleディレクトリ
│   └── user
│       ├── tasks
│       │   └── main.yml # タスク(実際の処理)
│       └── vars
│           └── main.yml # 変数(追加するユーザの情報など)
├── tools
│   └── password_hash.py
└── user.yml
```

### Vagrantfile
検証にvagrantを利用するため、vagrantで立ち上げる検証環境の設定を記述しています。
Vagrantで仮想環境を構築するまでの内容は、以前に執筆した下記の記事を参考にしてください。
[【チュートリアル】Ansible 基本のキ (インストールから複数サーバへの変更適用) - Qiita](http://qiita.com/KeijiYONEDA/items/c4c909608b61fb4954eb)

```rb:Vagrantfile
# -*- mode:ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|
  config.ssh.insert_key = false
  config.vm.define "webserver1" do |webserver1|
    webserver1.vm.hostname = "webserver1"
    webserver1.vm.box = "bento/centos-6.7"
    webserver1.vm.network "private_network", ip: "192.168.11.21"
  end
  config.vm.define "webserver2" do |webserver2|
    webserver2.vm.hostname = "webserver2"
    webserver2.vm.box = "bento/centos-6.7"
    webserver2.vm.network "private_network", ip: "192.168.11.22"
  end
end
```

### ansible.cfg
設定ファイル。
こちらも内容については[以前の記事](http://qiita.com/KeijiYONEDA/items/c4c909608b61fb4954eb)を参考にしてください。

```:ansible.cfg
[defaults]
hostfile=hosts/dev
remote_user=vagrant
private_key_file=~/.vagrant.d/insecure_private_key
retry_files_enabled=False
```

### hosts/dev/inventory
Vagrantで立ち上げる仮想環境のインベントリを記述しています。

```yaml:hosts/dev/inventory
[webservers]
192.168.11.21
192.168.11.22
```

### roles/user/tasks/main.yml
**ここが一つのメイン**ですね。
実行していくタスクを記述しています。
`with_items`で、ループ処理を実行していますが、
`with_items`で指定されている内容は、`roles/user/vars/main.yml`で記述しています。
各行の内容は、コメントで説明します。

```yaml:roles/user/tasks/main.yml
---
# グループは先に作成しておく
- name: グループを追加
  group: name={{ item.name }}
  with_items:  '{{ add_groups }}'


# 追加するユーザのグループはwebmasterとする
# 追加するユーザは、全員sudoerとする
# パスワードハッシュは予め作成しておく
- name: ユーザを追加 
  user: >
    name={{ item.name }}
    group=webmaster
    groups=webmaster,wheel
    password={{ item.password_hash }}
  with_items:  '{{ add_users }}'

# authorized_keyモジュールを実行するのに必須
- name: libselinux-pythonインストール
  yum: name=libselinux-python state=present

# 今回は、追加するユーザ = SSHで接続可能とする。よって変数は、「ユーザを追加」と同じ物を利用
- name: 公開鍵の追加
  authorized_key: >
    user={{ item.name }}
    key={{ item.public_key }}
  with_items:  '{{ add_users }}'

# 今回は、削除対象のホームディレクトリごと削除するものとする
- name: ユーザを削除
  user: >
    name={{ item.name }}
    state=absent
    remove=yes
  with_items:  '{{ del_users }}'
```

### roles/user/vars/main.yml
**もう一つのメイン**です。
`roles/user/tasks/main.yml`にある通り、
この変数ファイルには、公開鍵や、パスワードハッシュを含んでいます。
それらを平文のまま、バージョン管理に含めるのは、セキュリティ上好ましくないでしょう。
というわけで、`ansible-vault`で暗号化してあります。

復号して...

```bash
$ ansible-vault decrypt roles/user/tasks/main.yml
```

平文だと、こんな内容です。

```yaml:roles/user/tasks/main.yml
---
# 追加するユーザの名前,パスワードハッシュ,SSH接続用の公開鍵
add_users:
  - name: 'watashi'
    password_hash: '$6$hogehogehogehogehogehogehoge...'
    public_key: 'ssh-rsa AAAABbbbbbCCCCCCDDDdddEEEE...'

# 削除するユーザの名前
del_users:
  - name: 'aitsu'

# 追加するグループの名前
add_groups:
  - name: 'webmaster'

```

**パスワードハッシュどうするの？**という声が聞こえてきそうですが、
これは、`tools/password_hash.py`で作成するものとします。
これについては、のちほど。

ユーザを追加するときは、下記の様に、add_usersに追記する形で編集します

```diff:roles/user/tasks/main.yml
 # 追加するユーザの名前,パスワードハッシュ,SSH接続用の公開鍵
 add_users:
   - name: 'watashi'
     password_hash: '$6$hogehogehogehogehogehogehoge...'
     public_key: 'ssh-rsa AAAABbbbbbCCCCCCDDDdddEEEE...'
+  - name: 'aitsu'
+    password_hash: '$6$piyopiyopiyopiyopiyopiyopiyo'
+    public_key: 'ssh-rsa AAAAHHHHHHOOOOOOGGGGGEEEEE...'
```

編集完了後、再び暗号化してください。

```bash
$ ansible-vault encrypt roles/user/tasks/main.yml
```

なお、暗号化された内容は、下記のような形になります

```yaml:roles/user/tasks/main.yml
$ANSIBLE_VAULT;1.1;AES256
209384528435009238475029384570293485720349852430592874
(略)
```


### tools/password_hash.py
/etc/shadowに記録されるパスワードハッシュを、取得する必要があります。
一度、どこかでshadowファイルに追記されていれば、同じパスワードハッシュを再利用するのも手ですが、
どこにも登録されていない場合は、コマンドなり、ツールなりでハッシュを生成しなければなりません。
そんなわけで、password_hashを生成するスクリプトを作成しました。

作成にあたって、下記のサイトを参考にさせて頂きました。
[Python の passlib でパスワードをハッシュ化する | CUBE SUGAR STORAGE](http://momijiame.tumblr.com/post/45507666246/python-%E3%81%AE-passlib-%E3%81%A7%E3%83%91%E3%82%B9%E3%83%AF%E3%83%BC%E3%83%89%E3%82%92%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%8C%96%E3%81%99%E3%82%8B)

```py:tools/password_hash.py
from passlib.apps import custom_app_context as pwd_context
import getpass
if __name__ == '__main__':
    password = getpass.getpass()
    hashed_password = pwd_context.encrypt(password)
    print hashed_password
```

実行するには、このファイルを実行するだけです

```bash
$ ./tools/password_hash.py
Password: < ここで入力
$6$rounds=603084$XsVk8R0O3KVgHJEi$mp... < ハッシュが生成される
```

### user.yml
実行するPlaybookです。
roleを実行するだけの内容です。
対象(hosts)は、一旦allとしていますが、
inventoryでグループ管理を行っているなら、適宜書き換えてください。

また、コメントで、大まかな説明をしています。

```
---
# Playbook user.ymlで実行するユーザー関連の変更
#
#  必要なライブラリ
#  - libpass
#    $ pip install libpass
#  vars
#    - add_users: 追加するユーザ
#    - del_users: 削除するユーザ
#    - add_groups: 追加するグループ
#
#
#    注意
#      - ユーザ削除時には、delete_usersに追加するだけではなく、add_usersからの削除も行う。
#        特に影響はでないが、無駄に作成と削除するのは好ましくない。
#
#
#    パスワードハッシュを取得方法
#
#      0. 同じパスワードが使われているサーバがあれば、/etc/shadowから取得可能
#
#      1. ツールを利用
#        $ ./tools/password_hash.py
#        Password: {{ プロンプトでパスワードを入力 }}


- hosts: all
  become: True
  roles:
    - user

```

## 実行コマンド
念のため。

```bash
ansible-playbook user.yml --ask-vault-pass
```

インベントリやsshユーザなどは、
ansible.cfgにデフォルトの値を記載しているので、
ここでは指定しません。

オプションに設定している `--ask-vault-pass`は、
ansible-vaultで暗号化したファイルを復号するのに利用します。



## おわりに

パスワードハッシュなど、バージョン管理に入れたくないファイルがあったり、
繰り返しの処理だったり、
Ansibleの便利機能に助けられますね。

もちろん、セキュリティ的にクリティカルな内容(秘密鍵など)は、
暗号化したとしてもバージョン管理に含めるべきではないですが。

とはいえ、Ansibleはまだ絶賛勉強中なので、
鋭い突っ込みをお待ちしております。