Check! Ansible で自己署名証明書を作る（ pyOpenSSL トラブルシューティング含む）

こんにちは、 @dz_ こと大平かづみです。

Prologue - はじめに
====

Ansible でオレオレ証明書を作ってみたので共有します。

[つまづきポイント] `pyOpenSSL` が必要
====
例えば、サーバー上に証明書を作ろうとして `openssl_xxx` モジュールを使うと、下記のエラーが発生します。

```
TASK [task : Generate OpenSSL private key] *************************
fatal: [default]: FAILED! => {"changed": false, "msg": "the python pyOpenSSL module is required"}
```

下記 issue によると、Ansible の OpenSSL 関連のモジュールは、対象のマシン上に  python のライブラリ `pyOpenSSL` を必要とするようです。

- [Ansible 2.4 - the python pyOpenSSL module is required even if its already installed · Issue #30469 · ansible/ansible · GitHub](https://github.com/ansible/ansible/issues/30469)
- [Welcome to pyOpenSSL’s documentation! — pyOpenSSL 17.5.0 documentation](https://pyopenssl.org/en/stable/index.html)

ですが、プロビジョニング対象のマシンに不要なコンポーネントをインストールをしたくありませんね。

そこで、Ansible を実行している環境（localhost）に `pyOpenSSL` をインストールし、localhost 上で証明書を作成するように書くことができました。

```bash
# Install pyOpenSSL
pip install pyopenssl
```

ポイント解説
====
OpenSSL に関する Ansible モジュール
----
今回は、 `openssl_privatekey`, `openssl_csr`, `openssl_certificate` のモジュールを使いました。これらが、 `pyOpenSSL` を必要とします。

- [openssl_privatekey - Generate OpenSSL private keys. — Ansible Documentation](http://docs.ansible.com/ansible/latest/openssl_privatekey_module.html)
- [openssl_csr - Generate OpenSSL Certificate Signing Request (CSR) — Ansible Documentation](http://docs.ansible.com/ansible/latest/openssl_csr_module.html)
- [openssl_certificate - Generate and/or check OpenSSL certificates — Ansible Documentation](http://docs.ansible.com/ansible/latest/openssl_certificate_module.html)

`delegate_to` で実行対象を指定する
----
実行対象を指定するには、 `delegate_to` を利用します。今回は、 `delegate_to: localhot` と指定することで、Ansible を実行しているマシン上で処理します。

- [Delegation, Rolling Updates, and Local Actions — Ansible Documentation](http://docs.ansible.com/ansible/latest/playbooks_delegation.html)

なお、ホストが複数ある場合は、その数分だけ実行されてしまいます。1回だけ処理したい場合は、 `run_once` を併用します。 `run_once` についてはこちらもご参照ください。

- [run_once で1回だけ処理する | Check! Ansible で /etc/hosts ファイルを作る - Qiita](https://qiita.com/dz_/items/75e5f9435be6cb36d136#run_once-%E3%81%A71%E5%9B%9E%E3%81%A0%E3%81%91%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B)

サンプル
====
検証環境
----
前回同様、Azure を利用しました。

- [検証環境の準備 | Check! Ansible で /etc/hosts ファイルを作る - Qiita](https://qiita.com/dz_/items/75e5f9435be6cb36d136#%E6%A4%9C%E8%A8%BC%E7%92%B0%E5%A2%83%E3%81%AE%E6%BA%96%E5%82%99)

サンプルコード
----

こんな感じのコードになりました。

### ファイル構成

```
ansible
├── generate_selfsigned_cert.yml
├── inventory
│   └── all
└── roles
    └── selfsigned_cert
        ├── defaults
        │   └── main.yml
        ├── files
        └── tasks
            └── main.yml
```
### `selfsigned_cert` ロール

```yml:roles/self_signed_cert/defaults/main.yml
---
selfsigned_cert:
  cert:
    path: /etc/ssl/crt/ssl.crt
  privatekey:
    path: /etc/ssl/private/ssl.pem
  csr:
    path: /etc/ssl/csr/ssl.csr
```
```yml:roles/self_signed_cert/tasks/main.yml
---
- name: Generate OpenSSL private key
  openssl_privatekey:
    path: "{{ role_path }}/files/{{ selfsigned_cert.privatekey.path | basename }}"
  delegate_to: localhost
  run_once: yes

- name: Generate OpenSSL CSR
  openssl_csr:
    path: "{{ role_path }}/files/{{ selfsigned_cert.csr.path | basename }}"
    privatekey_path: "{{ role_path }}/files/{{ selfsigned_cert.privatekey.path | basename }}"
    common_name: DEMO
  delegate_to: localhost
  run_once: yes

- name: Generate OpenSSL certificate
  openssl_certificate:
    path: "{{ role_path }}/files/{{ selfsigned_cert.cert.path | basename }}"
    privatekey_path: "{{ role_path }}/files/{{ selfsigned_cert.privatekey.path |basename }}"
    csr_path: "{{ role_path }}/files/{{ selfsigned_cert.csr.path | basename }}"
    provider: selfsigned
  delegate_to: localhost
  run_once: yes

- name: Create directories for OpenSSL certificate
  become: yes
  file:
    path: "{{ item }}"
    state: directory
    owner: root
    group: root
    mode: 0755
  with_items:
    - "{{ selfsigned_cert.privatekey.path | dirname }}"
    - "{{ selfsigned_cert.cert.path | dirname }}"

- name: Copy these certificate and private key
  become: yes
  copy:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
    owner: root
    group: root
    mode: 0644
  with_items:
    - src: "{{ selfsigned_cert.privatekey.path | basename }}"
      dest: "{{ selfsigned_cert.privatekey.path }}"
    - src: "{{ selfsigned_cert.cert.path | basename }}"
      dest: "{{ selfsigned_cert.cert.path }}"
```

### インベントリと playbook
```:inventory/all
vm-ubuntu
```
```yml:generate_selfsigned_cert.yml
---
- name: All
  hosts: all
  roles:
    - selfsigned_cert
```

実行結果
----

```bash
ansible-playbook -i inventory/all generate_selfsigned_cert.yml

PLAY [All] **************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************
ok: [vm-ubuntu]

TASK [selfsigned_cert : Generate OpenSSL private key] *******************************************************************
changed: [vm-ubuntu -> localhost]

TASK [selfsigned_cert : Generate OpenSSL CSR] ***************************************************************************
changed: [vm-ubuntu -> localhost]

TASK [selfsigned_cert : Generate OpenSSL certificate] *******************************************************************
changed: [vm-ubuntu -> localhost]

TASK [selfsigned_cert : Create directories for OpenSSL certificate] *****************************************************
changed: [vm-ubuntu] => (item=/etc/ssl/private)
changed: [vm-ubuntu] => (item=/etc/ssl/crt)

TASK [selfsigned_cert : Copy these certificate and private key] *********************************************************
changed: [vm-ubuntu] => (item={u'dest': u'/etc/ssl/private/ssl.pem', u'src': u'ssl.pem'})
changed: [vm-ubuntu] => (item={u'dest': u'/etc/ssl/crt/ssl.crt', u'src': u'ssl.crt'})

PLAY RECAP **************************************************************************************************************
vm-ubuntu               : ok=6    changed=5    unreachable=0    failed=0
```

作成結果
----

ちゃんと生成されてるようです～ ╭( ･ㅂ･)و̑ ｸﾞｯ

```bash
# Check the certificate
user@vm-ubuntu:~$ openssl x509 -text -noout -in /etc/ssl/crt/ssl.crt

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 94229 (0x17015)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer:
        Validity
            Not Before: Feb 27 07:19:04 2018 GMT
            Not After : Feb 25 07:19:04 2028 GMT
        Subject: CN=DEMO
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus:
                    00:c0:5f ...

# Check the private key
user@vm-ubuntu:~$ openssl rsa -text -noout -in /etc/ssl/private/ssl.pem

Private-Key: (4096 bit)
modulus:
    00:c0:5f ...
```
よし！╭( ･ㅂ･)و̑ ｸﾞｯ

一度 Ansible で作っておけば、あとは簡単ですねー！

Epilogue - おわりに
====
余談、今までよくわからなかった SSL の自己署名証明書ですが、これを機に苦手意識がすこし減りました！