Ansible で PaloAlto のアドレスオブジェクトを追加する

# 1. Ansible のPAN-OS対応
Ansible 2.3 では、PAN-OSに対応した様々なモジュールが導入されました。
https://docs.ansible.com/ansible/list_of_network_modules.html#panos

- 例
 - [panos_security_policy](https://docs.ansible.com/ansible/panos_security_policy_module.html): セキュリティポリシーを作成する
 - [panos_mgtconfig](https://docs.ansible.com/ansible/panos_mgtconfig_module.html): 管理系設定を行う

今回は [`panos_address`](https://docs.ansible.com/ansible/panos_address_module.html) を利用して、アドレスオブジェクトを作成してみます。

# 2. インストール
## 2.1. Ansible
```
pip install ansible
```

## 2.2. pan-python, pandevice
PAN-OS 関連のモジュールは内部でpan-pythonとpandeviceというPythonのモジュールを利用しているためインストールしておきます。

```text:pan-python,pandeviceのインストール
pip install pan-python pandevice
```

## 2.3. バージョン確認
```test:バージョン確認
[root@localhost ~]# ansible --version
ansible 2.3.0.0
  config file =
  configured module search path = Default w/o overrides
  python version = 2.7.8 (default, Oct 22 2016, 09:02:55) [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)]
```

ansible.cfg は特に作成していません。

# 3. インベントリファイルの作成

```ini:インベントリファイル例（/etc/ansible/hosts）
[palo]
192.168.0.15  # 今回は1台だけ

[palo:vars]
ansible_user=admin
ansible_password=passwordpassword
```

# 4. Plyaybookの作成
以下のファイルを palo_addr.yml として作成します。

```yaml:Playbook
---
- hosts: palo
  gather_facts: no
  connection: local

  tasks:
    - name: create address object
      panos_address:
        ip_address: "{{ inventory_hostname }}"  # オブジェクトのアドレスではなくPaloaltoのアドレス
        username: "{{ ansible_user }}"
        password: "{{ ansible_password }}"
        address_name: "client_pc01"
        address: "192.168.0.101/32"
        commit: False   # 今回はcommitまでしない

```

本モジュールの[公式ドキュメント](https://docs.ansible.com/ansible/panos_address_module.html)が参考になります。

# 5. 動作確認
## 5.1. アドレス追加
### 5.1.1 事前確認
アドレスオブジェクトが何もない状態から始めます。

```text:コンフィグ事前確認
admin@PA-2050# show address
address;
[edit]
admin@PA-2050#
```

### 5.1.2 Playbook実行

```text:実行
[root@localhost ~]# ansible-playbook palo_addr.yml

PLAY [palo] **************************************************************************************************

TASK [create address Object] *********************************************************************************
ok: [192.168.1.15]

PLAY RECAP ***************************************************************************************************
192.168.1.15               : ok=1    changed=1    unreachable=0    failed=0
```


### 5.1.3 コンフィグ確認

ポリシーが追加されました。

```text:コンフィグ事後確認
admin@PA-2050# show address
address {
  client_pc01 {
    ip-netmask 192.168.0.101/32;
  }
}
```
## 5.2 アドレス変更
ここで、既存アドレスオブジェクトのアドレスを変更することにします。

### 5.2.1. Playbook変更
`address` を `"192.168.0.101/32"` に変更します。


```yaml:Playbook（アドレス変更バージョン）
---
- hosts: palo
  gather_facts: no
  connection: local

    - name: create address Object
      panos_address:
        ip_address: "{{ inventory_hostname }}"
        username: "{{ ansible_user }}"
        password: "{{ ansible_password }}"
        address_name: "client_pc01"
        address: "192.168.0.102/32"        # ここ変更
        commit: False
```

### 5.2.2 Playbook再実行

Playbookを変更したところで、もう一度実行します。

```text:実行
[root@localhost ~]# ansible-playbook palo_addr.yml

PLAY [palo] **************************************************************************************************

TASK [create address Object] *********************************************************************************
ok: [192.168.1.15]

PLAY RECAP ***************************************************************************************************
192.168.1.15               : ok=1    changed=0    unreachable=0    failed=0
```

**おやおや？** `changed=0` とあるように、で変更がかかりませんでした。

IPアドレスの変更は変更とはみなされないようです。

## 5.3 アドレス名変更
今度は、アドレスではなく、アドレス名を変更することにします。

### 5.3.1. Playbook変更
`address_name` を `"client_pc02"` に変更します。


```yaml:Playbook（アドレス名変更バージョン）
---
- hosts: palo
  gather_facts: no
  connection: local

    - name: create address Object
      panos_address:
        ip_address: "{{ inventory_hostname }}"
        username: "{{ ansible_user }}"
        password: "{{ ansible_password }}"
        address_name: "client_pc02"        # ここ変更
        address: "192.168.0.101/32"
        commit: False
```
### 5.3.2 Playbook再実行

```text:実行
[root@localhost ~]# ansible-playbook palo_addr.yml

PLAY [palo] **************************************************************************************************

TASK [create address Object] *********************************************************************************
changed: [192.168.1.15]

PLAY RECAP ***************************************************************************************************
192.168.1.15               : ok=1    changed=1    unreachable=0    failed=0
```

今度は `changed=1` となりました。


### 5.3.3 コンフィグ確認

変更ではなく、アドレス名が別のオブジェクトが追加されました。


```text:コンフィグ事後確認
admin@PA-2050# show address
address {
  client_pc01 {
    ip-netmask 192.168.0.101/32;
  }
  client_pc02 {
    ip-netmask 192.168.0.101/32;
  }
}
```

オブジェクト名があるかないかを見て、無ければ追加という扱いのようです。

# 6. （補足）Python 2.6 でのエラーについて
Python 2.6 環境では `ansible-playbook` 実行時に `pandevice` 内で以下エラーとなり、正常に実行できませんでした。そのため、今回はPython 2.7 の環境で実行しています。

```text:python2.6での実行時エラー
Using module file /root/ansible/ansible/lib/ansible/modules/network/panos/panos_security_policy.py
<192.168.1.15> ESTABLISH LOCAL CONNECTION FOR USER: root
<192.168.1.15> EXEC /bin/sh -c 'echo ~ && sleep 0'
<192.168.1.15> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /root/.ansible/tmp/ansible-tmp-1491081586.53-46889509406412 `" && echo ansible-tmp-1491081586.53-46889509406412="` echo /root/.ansible/tmp/ansible-tmp-1491081586.53-46889509406412 `" ) && sleep 0'
<192.168.1.15> PUT /tmp/tmpKdGZ_T TO /root/.ansible/tmp/ansible-tmp-1491081586.53-46889509406412/panos_security_policy.py
<192.168.1.15> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1491081586.53-46889509406412/ /root/.ansible/tmp/ansible-tmp-1491081586.53-46889509406412/panos_security_policy.py && sleep 0'
<192.168.1.15> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1491081586.53-46889509406412/panos_security_policy.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1491081586.53-46889509406412/" > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_JzsakO/ansible_module_panos_security_policy.py", line 264, in <module>
    import pandevice.firewall
  File "/usr/lib/python2.6/site-packages/pandevice/firewall.py", line 27, in <module>
    from pandevice import device
  File "/usr/lib/python2.6/site-packages/pandevice/device.py", line 19, in <module>
    from base import PanObject, Root, MEMBER, ENTRY
  File "/usr/lib/python2.6/site-packages/pandevice/base.py", line 1224
    option_paths = {opt: re.sub(r"\([\w\d|-]*\)", opt, path) for opt in options}
                                                               ^
SyntaxError: invalid syntax

fatal: [192.168.1.15]: FAILED! => {
    "changed": false,
    "failed": true,
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_JzsakO/ansible_module_panos_security_policy.py\", line 264, in <module>\n    import pandevice.firewall\n  File \"/usr/lib/python2.6/site-packages/pandevice/firewall.py\", line 27, in <module>\n    from pandevice import device\n  File \"/usr/lib/python2.6/site-packages/pandevice/device.py\", line 19, in <module>\n    from base import PanObject, Root, MEMBER, ENTRY\n  File \"/usr/lib/python2.6/site-packages/pandevice/base.py\", line 1224\n    option_paths = {opt: re.sub(r\"\\([\\w\\d|-]*\\)\", opt, path) for opt in options}\n                                                               ^\nSyntaxError: invalid syntax\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE",
    "rc": 0
}
```

# 7. おわりに
多少クセがあるようなので、いろいろ検証してみた方がよさそうです。