Ansible の Crypto Modulesでオレオレ証明書を作ってみる

この記事は [Ansible Advent Calendar 2017](https://qiita.com/advent-calendar/2017/ansible) 12/8(金)の記事になります。

(前日までに書いておこうと思ってたんですが当日早朝に慌ててるのは仕様です、すみません・・・)

実は今回の記事でQiita初投稿だったりします。緊張で手が震え、筆を握れません。なのでmacのキーボードを叩いて文章を書いていこうと思います。(きっと筆で書いたら誰も読めない汚らしい文章になることは保証します)

で、参加表明してからネタを考えるべく Ansible の Document を眺めていたら、[Crypto Modules](http://docs.ansible.com/ansible/latest/list_of_crypto_modules.html) なんてモジュールが追加されているのに気がついたので、これをネタにすることにしました。

---
## オレオレ証明書をAnsibleで作ろう！

開発とかデモとかちょっとしたお試しでSSL通信したいなってときにopensslを使ってオレオレ証明書を作ると思います。まぁ大した手間ではないと言えばないですが、コード化して置いたらちょっとは楽になれるかな〜と思って試してみました。

## オレオレ証明書作成の流れ

わざわざ説明するまでも無いですが、オレオレ証明書を作成する場合、自身でprivateキーの作成と証明書要求を作成し、自分のprivateキーを使って認証することでオレオレ証明書が完成します。

今回はもう一歩踏み込んで、オレオレ証明書により認証局を作って、そこから証明書を発行するまで目指します。

Crypto Modules でそれぞれの操作を実施するモジュールが用意されているので、これを使って作成します。

コードは [GitHub](https://github.com/abacl7/selfca) に突っ込んでおくので興味ある方は使ってみてください。(デバッグお願いしm ry)
以下コードをみていきます。

## 事前準備

設定ファイルから見ていきます。

```inventory/inventory.ini

[ca_host]
cahost ansible_host=localhost remote_user=centos

[target_hosts]
target01 ansible_host=localhost remote_user=centos
```
まずはイベントリーファイルですが、あまり語ることはありませんw
ca_host グループのホストを認証局に仕立て上げるつもりです。
実際に証明書の発行を行うのは target_hosts グループのホストになります。
なお、ca_hostに２つ以上ホスト書かれるのは想定外なのでやめてください...

コードのテストはCentOS7環境で行ってます。ubuntuでも動くかな〜。packageモジュールでパッケージインストール書いておいたのでapt-getの方も裏で動くと良いのですがw


次にgroup_varsを設定していきます。

```inventory/group_vars/all.yml
---
# certificate store dir of playbook playing host
local_store_dir: ~/certificate_store

# certificate file store dir of remote hosts
remote_store_dir: /etc/pki/mycertificates

# csr file store dir of ca host
ca_csr_store_dir: /etc/pki/csr_store
```

証明書等のファイルの保存先などを指定しています。
`local_store_dir` ansible実行ホスト上で成果物を保存するディレクトリ
`remote_store_dir` 証明書を発行する各ホストで成果物を保存するディレクトリ
`ca_csr_store_dir` オレオレ認証局ホストの証明書要求等の保存先
なお、オレオレ認証局の証明書等は~~面倒なので~~opensslで初期に作成されるので /etc/pki/CA以下に保存先を固定してます。


オレオレ認証局の情報を設定します。

```inventory/group_vars/ca_host.yml
---
#commonName
ca_CN: myCA.hogehoge.hoge

#countryName
ca_C: JP

#emailAddress
ca_EMail: hogehoge@hogehoge.hoge

#localityName
ca_L: Shinagawa

#organizationName
ca_O: my company

#organizationalUnitName
ca_OU: my division

#stateOrProvinceName
ca_ST: Tokyo
```

証明書でお馴染みの属性なので、適宜いじってください。


実際に発行するサーバ証明書の方は以下で設定します。

```inventory/host_vars/target01.yml
---
#commonName
target_CN: hoge.hogehoge.hoge

#countryName
target_C: JP

#emailAddress
target_EMail: hoge@hogehoge.hoge

#localityName
target_L: Shinagawa

#organizationName
target_O: my company

#organizationalUnitName
target_OU: my division

#stateOrProvinceName
target_ST: Tokyo
```

こちらもパラメータとしては同じですね。
複数台同時に処理するなら適宜これをコピーして作ってくださいな。


## オレオレ認証局の作成

まずは認証局作成のPlaybookを実行してください。

`ansible-playbook -i inventory/inventory.ini ca_setup.yml`

以下、簡単に処理を見ていきます。

```
  tasks:
    - name: ensure openssl installed
      package:
        name: openssl, python
        state: latest

    - name: ensure pip installed
      shell: which pip
      failed_when: result.rc < 0
      register: result

    - block:
      - name: download get-pip.py
        get_url:
          dest: ~/get-pip.py
          url: https://bootstrap.pypa.io/get-pip.py
      - name: install pip
        shell: python ~/get-pip.py
      when: result.rc == 1

    - name: ensure pyOpenSSL installed
      pip:
        name: pyOpenSSL
        state: present
```

Crypto Modulesの必要用件のセットアップですね。
pythonの最近のバージョンだとpip含まれてるかとは思いますが今回はget-pip.pyで入れるようにしています。




```
    - name: ensure created selfca private key
      openssl_privatekey:
        path: /etc/pki/CA/private/cakey.pem
        state: present
```

オレオレ認証局の秘密鍵を作ります。



```
    - name: ensure created csr
      openssl_csr:
        commonName: "{{ ca_CN }}"
        countryName: "{{ ca_C }}"
        emailAddress: "{{ ca_EMail }}"
        localityName: "{{ ca_L }}"
        organizationName: "{{ ca_O }}"
        organizationalUnitName: "{{ ca_OU }}"
        path: /etc/pki/CA/cacert.csr
        privatekey_path: /etc/pki/CA/private/cakey.pem
        state: present
        stateOrProvinceName: "{{ ca_ST }}"
```

証明書要求を作成して・・・



```
    - name: ensure self sighed certificate
      openssl_certificate:
        csr_path: /etc/pki/CA/cacert.csr
        path: /etc/pki/CA/cacert.pem
        privatekey_path: /etc/pki/CA/private/cakey.pem
        provider: selfsigned
        state: present
        valid_in: 315360000
```

自己サインします。期限は10年にしておきます(秒数なのでこんな数字に)


```
    - name: ensure CA cert convert to DER for client
      shell: openssl x509 -inform PEM -outform DER -in /etc/pki/CA/cacert.pem -out /etc/pki/CA/MyCAcert.der
      args:
        creates: /etc/pki/CA/MyCAcert.der
```

サーバにimportするための認証局証明書をDERで用意しておきます。


```
    - name: ensure index file created
      file:
        path: /etc/pki/CA/index.txt
        state: touch

    - name: ensure serial file created
      shell: echo "01" > /etc/pki/CA/serial
      args:
        creates: /etc/pki/CA/serial
```

認証局の初期設定で必要なファイル。シリアル番号はまぁ任意で変えてもいいと思います。


```
    - name: ensure download CA cert
      fetch:
        dest: "{{ local_store_dir }}/MyCAcert.der"
        fail_on_missing: yes
        flat: yes
        src: "/etc/pki/CA/MyCAcert.der"
```

認証局証明書のDERを取得しておきます。


以上でオレオレ認証局はできました。



## サーバ側で証明書要求を準備

次はサーバ側で証明書要求を作成していきます。

`ansible-playbook -i inventory/inventory.ini gen_csr.yml`

以下、簡単に見ていきます。



```
  tasks:
    - name: ensure openssl installed
      package:
        name: openssl, python
        state: latest

    - name: ensure pip installed
      shell: which pip
      failed_when: result.rc < 0
      register: result

    - block:
      - name: download get-pip.py
        get_url:
          dest: ~/get-pip.py
          url: https://bootstrap.pypa.io/get-pip.py
      - name: install pip
        shell: python ~/get-pip.py
      when: result.rc == 1

    - name: ensure pyOpenSSL installed
      pip:
        name: pyOpenSSL
        state: present
```

オレオレ認証局の時と同じく必要パッケージの導入。



```
    - name: ensure store fir exist
      file:
        mode: 0664
        path: "{{ remote_store_dir }}"
        state: directory
```

成果物の保存先ディレクトリを作っておきます。
~~パラメータ名わかりづらいなこれ・・・~~


```
    - name: ensure created target hosts private key
      openssl_privatekey:
        path: "{{ remote_store_dir }}/{{ inventory_hostname }}_key.pem"
        state: present
```

秘密鍵を作って・・・



```
    - name: ensure created csr
      openssl_csr:
        commonName: "{{ target_CN }}"
        countryName: "{{ target_C }}"
        emailAddress: "{{ target_EMail }}"
        localityName: "{{ target_L }}"
        organizationName: "{{ target_O }}"
        organizationalUnitName: "{{ target_OU }}"
        path: "{{ remote_store_dir }}/{{ inventory_hostname }}.csr"
        privatekey_path: "{{ remote_store_dir }}/{{ inventory_hostname }}_key.pem"
        state: present
        stateOrProvinceName: "{{ target_ST }}"
```


証明書要求を作ったら・・・


```
    - name: ensure fetch csr file
      fetch:
        dest: "{{ local_store_dir }}/{{ inventory_hostname }}.csr"
        fail_on_missing: yes
        flat: yes
        src: "{{ remote_store_dir }}/{{ inventory_hostname }}.csr"
```


ダウンロードして終わりです。


## オレオレ認証局で署名

いよいよ本題のサーバ証明書発行です。

`ansible-playbook -i inventory/inventory.ini gen_cert.yml`

例によって処理を見ていきますよ。
playbookとして独立しちゃってるので前提条件のパッケージの所在確認とかあった方が良い気もしつつ、オレオレ認証局ができていることが前提でその辺の処理は省いちゃってます。本当はちゃんとやらんとあかんな・・・・



```
  tasks:
    - name: ensure store fir exist
      file:
        mode: 0664
        path: "{{ ca_csr_store_dir }}"
        state: directory
```

保存先ディレクトリの作成・確認。



```
    - name: ensure copy csr files to ca site
      copy:
        dest: "{{ ca_csr_store_dir }}/{{ item }}.csr"
        src: "{{ local_store_dir }}/{{ item }}.csr"
      with_items: "{{ groups['target_hosts'] }}"
```

サーバ証明書要求をオレオレ認証局にアップロード。



```
    - name: ensure sighed certificate
      shell: openssl ca -cert /etc/pki/CA/cacert.pem -keyfile /etc/pki/CA/private/cakey.pem -in "{{ ca_csr_store_dir }}/{{ item }}.csr" -out "{{ ca_csr_store_dir }}/{{ item }}.cert.pem" -days 3650 -batch
      with_items: "{{ groups['target_hosts'] }}"
```

署名します。これはAnsibleモジュールではできなさげでしたので普通にopensslコマンド叩きます。


```
    - name: ensure fetch csr file
      fetch:
        dest: "{{ local_store_dir }}/{{ item }}.cert.pem"
        fail_on_missing: yes
        flat: yes
        src: "{{ ca_csr_store_dir }}/{{ item }}.cert.pem"
      with_items: "{{ groups['target_hosts'] }}"
```

証明書をダウンロードしておしまい。

ほんとはこの後サーバへアップロードする必要ありますがcopyモジュールでコピーして終わりなので~~めんどいので~~割愛。


う〜ん、これ役に立つんですかね。ただのネタで終わる可能性も。
とりあえず個人的には色々なデモ環境構築のコードで使いまわすことにします。


さて、これ書くために我慢したPS4モンハンエディションのセットアップが待ってるのでこの辺で失礼いたします・・・・（いや、仮眠しよう・・・）