LoginSignup
20
12

More than 3 years have passed since last update.

AnsibleでACL Config作成&設定変更を自動化してみた

Last updated at Posted at 2019-12-02

はじめに

以前の記事で、Jinja2テンプレートを使ったConfig自動生成を行いました。こちらはCSV形式のパラメータ表とJinja2テンプレートを、Pythonを使ってレンダリングしてConfig生成した例です。
ポート管理表+パラメータ表+Jinja2テンプレートから、L2SWのConfigを自動生成してみた

一方よこちさんの記事で、Pythonの代わりにAnsibleのtemplateモジュールを使った例も紹介頂いています。
[Ansible] 「ポート管理表+パラメータ表+Jinja2テンプレートから、L2SWのConfigを自動生成してみた」を Ansible で

各方法のメリットは以下の通りかと思います。

Python

  • AnsibleをインストールできないWindows環境でも実行できる

Ansible

  • Pythonの知識はなくてもOK
  • Ansible独自のJinja2テンプレート(フィルタープラグイン)を使って効率的にConfig生成ができる
    (例)ipaddr filterでプレフィックス/24をサブネットネットマスク255.255.255.0やワイルドカードマスク0.0.0.255に変換可能
  • 生成したConfigをそのままAnsibleで流し込みできる

本記事では、Ansibleの例を掘り下げ、CSV形式のACL要件資料とJinja2テンプレートから「名前付き拡張ACL」のConfigファイルを生成し、後続ステップで設定変更を行ってみました(下図)。対象OSはCisco IOS、NX-OS、ASAの3種類です。

※作ったものはGitHub/acl-jinja2-templateにアップロードしています。

無題1202_1.png

1. ACL要件資料

3種類の機器で6要件ずつ、計18要件分のConfigを生成してみます。

各項目の内容は以下の通りです。

項目名 内容
hostname 対象機器のホスト名
change 設定変更種別 例:add(追加)、delete(削除)
acl_type ACL種別 例:name_extended(名前付き拡張)、name_standard(名前付き標準)、number_extended(番号付き拡張)、number_standard(番号付き標準)※現時点の実装はname_extendedのみ
acl_name ACL名 or ACL番号 指定しない場合、ACLコンフィグレーションモード配下のコマンド(permit/deny ~)のみ生成
seq_no シーケンス番号(任意)
action 通信許可 or 拒否 例:permit、deny
protocol プロトコル 例:ip、tcp、udp
src_addr 送信元アドレス アドレス/プレフィックス長の形式で指定
src_oper 送信元演算子(任意) 例:eq、gt、lt、range
src_port 送信元ポート番号(任意) rangeの場合、FromとToをスペースを空けて指定
dest_addr 宛先アドレス アドレス/プレフィックス長の形式で指定
dest_oper 宛先演算子(任意) 例:eq、gt、lt、range
dest_port 宛先ポート番号(任意) rangeの場合、FromとToをスペースを空けて指定
modifier 追加オプション(任意) 例:log、established、log-input
description リマーク設定(任意) 該当ACLの前に定義

2. Jinja2テンプレート

ファイルはacl_template.j2です。詳細は割愛しますが、意識したポイントは以下の通りです。

  • Jinja2テンプレートの可読性を高めるため、インデントを付与。

  • Jinja2のWhitespace Controlを利用し、実際のConfigとインデントが同じになるように調整。
    具体的には、例えば{% if ... -%}のように、ifの後に-を付けることで、後ろの改行とスペースが削られるようにしています。合わせて後述のAnsible Playbook内で、templateモジュールのオプション「lstrip_blocks」を有効化することで、ブロック行{% block %} の左のWhitespaceを取り除いています。

  • 任意設定が空データの場合は何も追加せず、逆にデータがある場合は「スペース+設定」を追加することで、スペースが2つ以上連ならないよう工夫。

  • ipaddr filterを使い、{{ アドレス/プレフィックス | ipaddr('network') }}でアドレスのみ抽出。また、IOS ACLでは{{ アドレス/プレフィックス | ipaddr('wildcard') }}を使ってプレフィックスからワイルドカードマスクを生成し、ASA ACLでは{{ アドレス/プレフィックス | ipaddr('netmask') }}を使ってプレフィックスからサブネットマスクを抽出しています。
    (本フィルタを使用するには、pip install netaddrでPythonパッケージのインストールが必要。)

  • どのテンプレートを使うかは、Ansible変数ansible_network_osを元に判定。

3. Ansible Inventoryファイル

以前の記事でVIRL上に構築したIOSv、NX-OSv9000、ASAvを使用しました。使用したAnsibleバージョンは2.9.0です。

4. ACL Config作成

本項でまず、Ansibleを使ったACL Config作成を行います。

4-1. Ansible Playbook

各タスクの内容は以下の通りです。

  1. read_csvモジュールを使い、[{ "hostname": "xx", "acl_name": "aa", ~ }, { "hostname": "yy", "acl_name": "bb", ~ }, ~]のように、リスト内の要素として、CSVの各行のデータが辞書形式で定義された形に変換。
  2. debugモジュールで、タスク1のリストを表示。
  3. set_factモジュールで、上記のリストをJinja2テンプレート内で読み込めるよう変数aclsに格納。
  4. templateモジュールで、レンダリングした結果をテキストファイルとして出力。
    (このタイミングで、人手でレビューすることを想定。)
playbook_aclgen.yml
---

- hosts: all
  gather_facts: no
  connection: network_cli

  vars:
    template: acl_template.j2
    acl_list: add/acl_requests1.csv
    config_filename: "add/acl_config_{{ inventory_hostname }}.txt"

  tasks:
    - name: read acl requests from CSV file and return a list   # (1)
      read_csv:
        path: "{{ acl_list }}"
      run_once: yes
      register: acl_requests

    - name: display acl requests   # (2)
      debug:
        msg: "{{ acl_requests.list }}"
      run_once: yes

    - name: set acl requests   # (3)
      set_fact:
        acls: "{{ acl_requests.list }}"
      run_once: yes

    - name: build template   # (4)
      template:
        src: "{{ template }}"
        dest: "{{ config_filename }}"
        lstrip_blocks: yes

4-2. Playbook実行ログ

タスク2の出力結果を見ると、問題なくリストが出力されている事が分かります。

$ ansible-playbook -i inventory_virl1.ini playbook_aclgen.yml

PLAY [all] ***************************************************************************************************

TASK [read acl requests from CSV file and return a list] *****************************************************
ok: [asav-1]

TASK [display acl requests] **********************************************************************************
ok: [asav-1] => {
    "msg": [
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "iosv-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "110",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "iosv-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "120",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "iosv-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "130",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST2",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "iosv-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "iosv-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "iosv-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "nx-osv9000-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "110",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "nx-osv9000-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "120",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "nx-osv9000-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "130",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST2",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "nx-osv9000-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "nx-osv9000-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "nx-osv9000-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "1",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "2",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "asav-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "3",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST2",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024 65535"
        },
        {
            "acl_name": "TEST2",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "asav-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        }
    ]
}

TASK [set acl requests] **************************************************************************************
ok: [asav-1]

TASK [build template] ****************************************************************************************
changed: [nx-osv9000-1]
changed: [iosv-1]
changed: [asav-1]

PLAY RECAP ***************************************************************************************************
asav-1                     : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
iosv-1                     : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
nx-osv9000-1               : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

4-3. 生成されたConfigファイル

いずれも問題なく作成されました。

acl_config_iosv-1.txt
ip access-list extended TEST1
 remark ACL_TEST1
 110 deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
 120 deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www
 130 permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255 log
ip access-list extended TEST2
 remark ACL_TEST2
 deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
 deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www
 permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255 log
acl_config_nx-osv9000-1.txt
ip access-list TEST1
  105 remark ACL_TEST1
  110 deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp
  120 deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.1/32 eq www
  130 permit ip 192.168.1.0/24 192.168.2.0/24 log
ip access-list TEST2
  remark ACL_TEST2
  deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp
  deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.1/32 eq www
  permit ip 192.168.1.0/24 192.168.2.0/24 log
acl_config_asav-1.txt
access-list TEST1 remark ACL_TEST1
access-list TEST1 line 1 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
access-list TEST1 line 2 extended deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www
access-list TEST1 line 3 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0 log
access-list TEST2 remark ACL_TEST2
access-list TEST2 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
access-list TEST2 extended deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www
access-list TEST2 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0 log

5. ACL設定変更

本項では4.で生成したConfigファイルを、Ansibleのxxx_configモジュールのsrcオプションで指定して設定変更を行います。

5-1. Ansible Playbook

各タスクの内容は以下の通りです。

  1. ios_configモジュールでACL設定変更&設定保存。whenでIOSの場合のみ実行。matchオプションをnoneとし、冪等性が機能しないようにしていますが、これは自動生成したConfigファイル内のip access-list ~が冪等性によって設定対象から除外され、エラーになるのを回避するための苦肉の策です。。
  2. nxos_configモジュールで1.と同様の処理を実行。NX-OSの場合のみ実行。冪等性×
  3. asa_configモジュールで1.と同様の処理を実行。ASAの場合のみ実行。冪等性〇
  4. ベンダ非依存モジュールcli_commandで、設定変更後のRunning Configを取得。
  5. debugモジュールで4.の取得結果を表示。
playbook_aclconf.yml
---

- hosts: all
  gather_facts: no
  connection: network_cli

  vars:
    config_filename: "add/acl_config_{{ inventory_hostname }}.txt"

  tasks:
    - name: change acls on remote device (ios)   # (1)
      ios_config:
        src: "{{ config_filename }}"
        match: none
        save_when: modified
      when: ansible_network_os == 'ios'

    - name: change acls on remote device (nxos)   # (2)
      nxos_config:
        src: "{{ config_filename }}"
        match: none
        save_when: modified
      when: ansible_network_os == 'nxos'

    - name: change acls on remote device (asa)   # (3)
      asa_config:
        src: "{{ config_filename }}"
        save: yes
      when: ansible_network_os == 'asa'

    - name: run show command on remote device   # (4)
      cli_command:
        command: show run | begin TEST
      register: result

    - name: debug   # (5)
      debug:
        msg:
          - "{{ result.stdout_lines }}"

5-2. Playbook実行ログ

タスク5の出力結果を見ると、問題なくACL設定変更されている事が分かります。

$ ansible-playbook -i inventory_virl1.ini playbook_aclconf.yml

PLAY [all] ***************************************************************************************************

TASK [change acls on remote device (ios)] ********************************************************************
skipping: [asav-1]
skipping: [nx-osv9000-1]
changed: [iosv-1]

TASK [change acls on remote device (nxos)] *******************************************************************
skipping: [asav-1]
skipping: [iosv-1]
changed: [nx-osv9000-1]

TASK [change acls on remote device (asa)] ********************************************************************
skipping: [iosv-1]
skipping: [nx-osv9000-1]
changed: [asav-1]

TASK [run show command on remote device] *********************************************************************
ok: [asav-1]
ok: [nx-osv9000-1]
ok: [iosv-1]

TASK [debug] *************************************************************************************************
ok: [iosv-1] => {
    "msg": [
        [
            "ip access-list extended TEST1",
            " remark ACL_TEST1",
            " deny   udp host 192.168.1.1 host 192.168.2.1 eq ntp",
            " deny   tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www",
            " permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255 log",
            "ip access-list extended TEST2",
            " remark ACL_TEST2",
            " deny   udp host 192.168.1.1 host 192.168.2.1 eq ntp",
            " deny   tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www",
            " permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255 log",
            (省略)
        ]
    ]
}
ok: [asav-1] => {
    "msg": [
        [
            "access-list TEST1 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp ",
            "access-list TEST1 extended deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www ",
            "access-list TEST1 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0 log ",
            "access-list TEST1 remark ACL_TEST1",
            "access-list TEST2 remark ACL_TEST2",
            "access-list TEST2 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp ",
            "access-list TEST2 extended deny tcp host 192.168.1.2 range 1024 65535 host 192.168.2.1 eq www ",
            "access-list TEST2 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0 log ",
            (省略)
        ]
    ]
}
ok: [nx-osv9000-1] => {
    "msg": [
        [
            "ip access-list TEST1",
            "  105 remark ACL_TEST1",
            "  110 deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp ",
            "  120 deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.1/32 eq www ",
            "  130 permit ip 192.168.1.0/24 192.168.2.0/24 log ",
            "ip access-list TEST2",
            "  10 remark ACL_TEST2",
            "  20 deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp ",
            "  30 deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.1/32 eq www ",
            "  40 permit ip 192.168.1.0/24 192.168.2.0/24 log ",
            (省略)
        ]
    ]
}

PLAY RECAP ***************************************************************************************************
asav-1                     : ok=3    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
iosv-1                     : ok=3    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
nx-osv9000-1               : ok=3    changed=1    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0 

最後に

今回は設定追加の例でしたが、ACL要件資料の項目changedeleteとすることで、設定削除も問題なくできました。(結果はGitHubのdeleteフォルダにアップしています。)
今回のような例だと、どうしてもJinja2テンプレートが複雑になってしまいますが、一度作ってしまえば、パラメーター表から簡単にConfig生成ができるようになるので、作業頻度の高いものは作ってみてはいかがでしょうか。
ただし、ios_bgpモジュールのように、既にモジュールが存在する設定項目は、そちらを使った方が楽だと思います。

20
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
12