はじめに
以前の記事で、CSV形式のACL要件資料とJinja2テンプレートから、Configファイル生成と設定変更を行いました。
AnsibleでACL Config作成&設定変更を自動化してみた
実際のACL運用では、同じACLが既に許可されていたり、より大きな範囲で許可されていたりしないか、事前確認するケースが多いと思います。本記事では、この辺をBatfishというConfig解析ツールを使ってチェックし、未許可のACLのみ設定出来るようにしてみました。(下図の「1. ACL Config解析」と「2. ACL Config作成」)
1. セットアップ
1-1. Ansible
CentOS Python3.6.8の仮想環境上に、Ansible2.8.4をインストールしました。
1-2. Batfish
BatfishはDockerイメージの形で提供されています。Batfish本体のみのbatfish/batfish
と、Jupyter Notebookの実行環境とチュートリアルを含んだ batfish/allinone
の2種類がありますが、今回は簡単にお試しできる後者を使用しました。
セットアップ方法は、公式ドキュメントに加え、以下の記事を参考にさせて頂きました。
[Batfish] ネットワーク機器のコンフィグを読み込んでルーティングなどの様々な検証ができるツール「Batfish」の紹介
また今回は、Ansible用ディレクトリ配下にBatfish用ディレクトリを作りたかったので、以下記事も参考にしています。
[Batfish] allinone イメージの Jupyter Notebook から自前のコンフィグディレクトリを利用可能にする方法
合わせて、Ansible GalaxyからBatfish用のロールをインストールしました。
$ ansible-galaxy install --force batfish.base
1-3. ディレクトリ構成
各種ファイルのディレクトリ構成は以下の通りです。
ansible/
├ inventory.ini (Ansible Inventoryファイル)
├ playbook_batfish6.yml (Ansible Playbook1)
├ playbook_aclgen_batfish.yml (Ansible Playbook2)
├ acl_requests_batfish.csv (ACL要件資料)
├ acl_requests_batfish.j2 (Jinja2テンプレート)
├ mynetworks/ (BatfishのSnapshot保管用ディレクトリ)
│ └ test1/
│ └ configs/
│ └ [ホスト名]_sh run.log (既存Configファイル)
├ acl_requests_not_permitted.txt (ACL要件 未許可のもの)
└ add/
│ └ acl_config_[ホスト名].txt (追加Configファイル)
Jinja2テンプレートは前回と同じものを使いました。それ以外については次項以降でご説明します。
2. ACL設定
2-1. 既存Configファイル
既存のホスト名hqdist1
(Catalyst3560)に、名前付き拡張ACL×2行を適用しています。
interface Vlan101
description << Server Segment >>
ip address 192.168.101.49 255.255.255.0
ip access-group ACL_TEST out
!
ip access-list extended ACL_TEST
permit ip 10.10.1.0 0.0.0.255 192.168.1.0 0.0.0.255
permit tcp host 10.10.2.1 host 192.168.2.1 eq www
2-2. ACL要件資料
以下のACLを依頼されたものとします。
- 1行目 ・・・ 未設定
- 2行目 ・・・ 設定済み(1行目の既存設定に含まれる)
- 3行目 ・・・ 設定済み(2行目の既存設定と完全一致。ただし、要件内ではポート番号
80
を指定しているのに対し、既存Configではニーモニックwww
に変換されている。) - 4行目 ・・・ 一部未設定(2行目の既存設定と一部重複)
最終目標は、不要な2,3行目をあらかじめ除外し、1,4行目のみ設定変更することです。
3. ACL Config解析
3-1. Ansible Playbook
Batfish用のAnsibleロールBatfish/Ansibleを使用しました。
大まかな流れは以下の通りです。
-
bf_session
モジュールでBatfishへ接続 -
bf_init_snapshot
モジュールで、Batfishに既存Configファイル等のSnapshotデータを読み込ませる - CSV形式のACL要件資料をリスト形式に変換
- タスク3の結果を表示
- タスク3の結果を変数
acls_all
に格納 -
acls_all
の各要素(1~4行目)に対し、bf_assert
モジュールを使って既存で許可される要件があるかチェック。一部でも許可されなければfailed
、すべて許可される場合はok
となる。failed
となった場合も後続タスクを実行できるよう、ignore_errors
をyes
に指定。この結果をresult
に格納。 - 今回設定したい
failed
の要件について、result
の中からACL要件item
を抜き出す。 - タスク7の結果を表示
- タスク7の結果をファイル保存
---
- hosts: localhost
gather_facts: no
connection: local
vars:
acls: []
acl_list: acl_requests_batfish.csv
acl_list_not_permitted: acl_requests_not_permitted.txt
batfish_session: c333e6cc5b62
batfish_network: example_network
batfish_snapshot: example_snapshot
batfish_snapshot_data: ./mynetworks/test1
roles:
- batfish.base
tasks:
- name: Setup connection to Batfish service # (1)
bf_session:
host: "{{ ansible_host }}"
name: "{{ batfish_session }}"
run_once: yes
- name: Initialize the example network # (2)
bf_init_snapshot:
network: "{{ batfish_network }}"
snapshot: "{{ batfish_snapshot }}"
snapshot_data: "{{ batfish_snapshot_data }}"
overwrite: true
run_once: yes
- name: Read acl requests from CSV file and return a list # (3)
read_csv:
path: "{{ acl_list }}"
run_once: yes
register: acl_requests
- name: Display acl requests # (4)
debug:
msg: "{{ acl_requests.list }}"
run_once: yes
- name: Set acl requests # (5)
set_fact:
acls_all: "{{ acl_requests.list }}"
run_once: yes
- name: Confirm a filter permits some specific traffic # (6)
bf_assert:
assertions:
- type: assert_filter_permits
name: confirm the acl requests are permitted by existing acls
parameters:
filters: '{{ item.hostname }}["{{ item.acl_name }}"]'
headers:
ipProtocols: "{{ item.protocol | regex_replace('^ip$', omit) }}"
srcIps: "{{ item.src_addr }}"
srcPorts: "{{ item.src_port | regex_replace('^$', omit) }}"
dstIps: "{{ item.dest_addr }}"
dstPorts: "{{ item.dest_port | regex_replace('^$', omit) }}"
applications: "{{ item.application | regex_replace('^$', omit) }}"
ignore_errors: yes
register: result
loop: "{{ acls_all }}"
- name: Set "not permitted" acl requests # (7)
set_fact:
acls: "{{ acls + [item.item] }}"
when: item.failed == true
loop: "{{ result.results }}"
- name: Display "not permitted" acl requests # (8)
debug:
msg: "{{ acls }}"
- name: Copy file with "not permitted" acl requests # (9)
copy:
content: "{{ acls }}"
dest: "{{ acl_list_not_permitted }}"
3-2. Playbook実行ログ
AnsibleとBatfishは同じCentOS上で動かしているため、localhost上で実行します。
タスク4で今回のACL要件×4行が表示され、タスク6で想定通り1,4行目がfailed
となっています。3行目はおそらくBatfish内でポート番号80
がニーモニックwww
と紐づけられ、ok
になったと推測されます。
続くタスク8で、failed
のACL要件×2行のみが表示されました。
$ ansible-playbook playbook_batfish6.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ************************************************************************************************************************
TASK [Setup connection to Batfish service] **********************************************************************************************
changed: [localhost]
TASK [Initialize the example network] ***************************************************************************************************
[WARNING]: Your snapshot was successfully initialized but Batfish failed to fully recognized some lines in one or more input files.
Some unrecognized configuration lines are not uncommon for new networks, and it is often fine to proceed with further analysis. You can
help the Batfish developers improve support for your network by running the bf_upload_diagnostics module:
https://github.com/batfish/ansible/blob/master/docs/bf_upload_diagnostics.rst
changed: [localhost]
TASK [Read acl requests from CSV file and return a list] ********************************************************************************
ok: [localhost]
TASK [Display acl requests] *************************************************************************************************************
ok: [localhost] => {
"msg": [
{
"acl_name": "ACL_TEST",
"acl_type": "name_extended",
"action": "permit",
"application": "",
"change": "add",
"description": "",
"dest_addr": "192.168.0.0/24",
"dest_oper": "",
"dest_port": "",
"hostname": "hqdist1",
"modifier": "",
"protocol": "ip",
"seq_no": "",
"src_addr": "10.10.0.0/24",
"src_oper": "",
"src_port": ""
},
{
"acl_name": "ACL_TEST",
"acl_type": "name_extended",
"action": "permit",
"application": "",
"change": "add",
"description": "",
"dest_addr": "192.168.1.1/32",
"dest_oper": "",
"dest_port": "",
"hostname": "hqdist1",
"modifier": "",
"protocol": "ip",
"seq_no": "",
"src_addr": "10.10.1.1/32",
"src_oper": "",
"src_port": ""
},
{
"acl_name": "ACL_TEST",
"acl_type": "name_extended",
"action": "permit",
"application": "",
"change": "add",
"description": "",
"dest_addr": "192.168.2.1/32",
"dest_oper": "eq",
"dest_port": "80",
"hostname": "hqdist1",
"modifier": "",
"protocol": "tcp",
"seq_no": "",
"src_addr": "10.10.2.1/32",
"src_oper": "",
"src_port": ""
},
{
"acl_name": "ACL_TEST",
"acl_type": "name_extended",
"action": "permit",
"application": "",
"change": "add",
"description": "",
"dest_addr": "192.168.2.0/24",
"dest_oper": "",
"dest_port": "",
"hostname": "hqdist1",
"modifier": "",
"protocol": "ip",
"seq_no": "",
"src_addr": "10.10.2.0/24",
"src_oper": "",
"src_port": ""
}
]
}
TASK [Set acl requests] *****************************************************************************************************************
ok: [localhost]
TASK [Confirm a filter permits some specific traffic] ***********************************************************************************
failed: [localhost] (item={'hostname': 'hqdist1', 'change': 'add', 'acl_type': 'name_extended', 'acl_name': 'ACL_TEST', 'seq_no': '', 'action': 'permit', 'protocol': 'ip', 'src_addr': '10.10.0.0/24', 'src_oper': '', 'src_port': '', 'dest_addr': '192.168.0.0/24', 'dest_oper': '', 'dest_port': '', 'application': '', 'modifier': '', 'description': ''}) => {"ansible_loop_var": "item", "changed": false, "item": {"acl_name": "ACL_TEST", "acl_type": "name_extended", "action": "permit", "application": "", "change": "add", "description": "", "dest_addr": "192.168.0.0/24", "dest_oper": "", "dest_port": "", "hostname": "hqdist1", "modifier": "", "protocol": "ip", "seq_no": "", "src_addr": "10.10.0.0/24", "src_oper": "", "src_port": ""}, "msg": "1 of 1 assertions failed", "result": [{"details": "Found a flow that was denied, when expected to be permitted\n[{'Node': 'hqdist1', 'Filter_Name': 'ACL_TEST', 'Flow': Flow(dscp=0, dstIp='192.168.0.0', dstPort=None, ecn=0, fragmentOffset=0, icmpCode=None, icmpVar=None, ingressInterface=None, ingressNode='hqdist1', ingressVrf='default', ipProtocol='HOPOPT', packetLength=0, srcIp='10.10.0.0', srcPort=None, state='NEW', tag='BASE', tcpFlagsAck=0, tcpFlagsCwr=0, tcpFlagsEce=0, tcpFlagsFin=0, tcpFlagsPsh=0, tcpFlagsRst=0, tcpFlagsSyn=0, tcpFlagsUrg=0), 'Action': 'DENY', 'Line_Content': 'no-match', 'Trace': AclTrace(events=[AclTraceEvent(class_name='org.batfish.datamodel.acl.DefaultDeniedByIpAccessList', description=\"Flow did not match 'extended ipv4 access-list' named 'ACL_TEST'\", lineDescription=None)])}]", "name": "confirm the acl requests are permitted by existing acls", "status": "Fail", "type": "assert_filter_permits"}], "summary": "1 of 1 assertions failed"}
ok: [localhost] => (item={'hostname': 'hqdist1', 'change': 'add', 'acl_type': 'name_extended', 'acl_name': 'ACL_TEST', 'seq_no': '', 'action': 'permit', 'protocol': 'ip', 'src_addr': '10.10.1.1/32', 'src_oper': '', 'src_port': '', 'dest_addr': '192.168.1.1/32', 'dest_oper': '', 'dest_port': '', 'application': '', 'modifier': '', 'description': ''})
ok: [localhost] => (item={'hostname': 'hqdist1', 'change': 'add', 'acl_type': 'name_extended', 'acl_name': 'ACL_TEST', 'seq_no': '', 'action': 'permit', 'protocol': 'tcp', 'src_addr': '10.10.2.1/32', 'src_oper': '', 'src_port': '', 'dest_addr': '192.168.2.1/32', 'dest_oper': 'eq', 'dest_port': '80', 'application': '', 'modifier': '', 'description': ''})
failed: [localhost] (item={'hostname': 'hqdist1', 'change': 'add', 'acl_type': 'name_extended', 'acl_name': 'ACL_TEST', 'seq_no': '', 'action': 'permit', 'protocol': 'ip', 'src_addr': '10.10.2.0/24', 'src_oper': '', 'src_port': '', 'dest_addr': '192.168.2.0/24', 'dest_oper': '', 'dest_port': '', 'application': '', 'modifier': '', 'description': ''}) => {"ansible_loop_var": "item", "changed": false, "item": {"acl_name": "ACL_TEST", "acl_type": "name_extended", "action": "permit", "application": "", "change": "add", "description": "", "dest_addr": "192.168.2.0/24", "dest_oper": "", "dest_port": "", "hostname": "hqdist1", "modifier": "", "protocol": "ip", "seq_no": "", "src_addr": "10.10.2.0/24", "src_oper": "", "src_port": ""}, "msg": "1 of 1 assertions failed", "result": [{"details": "Found a flow that was denied, when expected to be permitted\n[{'Node': 'hqdist1', 'Filter_Name': 'ACL_TEST', 'Flow': Flow(dscp=0, dstIp='192.168.2.0', dstPort=None, ecn=0, fragmentOffset=0, icmpCode=None, icmpVar=None, ingressInterface=None, ingressNode='hqdist1', ingressVrf='default', ipProtocol='HOPOPT', packetLength=0, srcIp='10.10.2.0', srcPort=None, state='NEW', tag='BASE', tcpFlagsAck=0, tcpFlagsCwr=0, tcpFlagsEce=0, tcpFlagsFin=0, tcpFlagsPsh=0, tcpFlagsRst=0, tcpFlagsSyn=0, tcpFlagsUrg=0), 'Action': 'DENY', 'Line_Content': 'no-match', 'Trace': AclTrace(events=[AclTraceEvent(class_name='org.batfish.datamodel.acl.DefaultDeniedByIpAccessList', description=\"Flow did not match 'extended ipv4 access-list' named 'ACL_TEST'\", lineDescription=None)])}]", "name": "confirm the acl requests are permitted by existing acls", "status": "Fail", "type": "assert_filter_permits"}], "summary": "1 of 1 assertions failed"}
...ignoring
TASK [Set "not permitted" acl requests] *************************************************************************************************
ok: [localhost] => (item={'msg': '1 of 1 assertions failed', 'changed': False, 'result': [{'name': 'confirm the acl requests are permitted by existing acls', 'type': 'assert_filter_permits', 'status': 'Fail', 'details': 'Found a flow that was denied, when expected to be permitted\n[{\'Node\': \'hqdist1\', \'Filter_Name\': \'ACL_TEST\', \'Flow\': Flow(dscp=0, dstIp=\'192.168.0.0\', dstPort=None, ecn=0, fragmentOffset=0, icmpCode=None, icmpVar=None, ingressInterface=None, ingressNode=\'hqdist1\', ingressVrf=\'default\', ipProtocol=\'HOPOPT\', packetLength=0, srcIp=\'10.10.0.0\', srcPort=None, state=\'NEW\', tag=\'BASE\', tcpFlagsAck=0, tcpFlagsCwr=0, tcpFlagsEce=0, tcpFlagsFin=0, tcpFlagsPsh=0, tcpFlagsRst=0, tcpFlagsSyn=0, tcpFlagsUrg=0), \'Action\': \'DENY\', \'Line_Content\': \'no-match\', \'Trace\': AclTrace(events=[AclTraceEvent(class_name=\'org.batfish.datamodel.acl.DefaultDeniedByIpAccessList\', description="Flow did not match \'extended ipv4 access-list\' named \'ACL_TEST\'", lineDescription=None)])}]'}], 'summary': '1 of 1 assertions failed', 'failed': True, 'invocation': {'module_args': {'assertions': [{'type': 'assert_filter_permits', 'name': 'confirm the acl requests are permitted by existing acls', 'parameters': {'filters': 'hqdist1["ACL_TEST"]', 'headers': {'srcIps': '10.10.0.0/24', 'dstIps': '192.168.0.0/24'}}}], 'session': {'host': '127.0.0.1'}, 'snapshot': 'example_snapshot', 'network': 'example_network'}}, 'item': {'hostname': 'hqdist1', 'change': 'add', 'acl_type': 'name_extended', 'acl_name': 'ACL_TEST', 'seq_no': '', 'action': 'permit', 'protocol': 'ip', 'src_addr': '10.10.0.0/24', 'src_oper': '', 'src_port': '', 'dest_addr': '192.168.0.0/24', 'dest_oper': '', 'dest_port': '', 'application': '', 'modifier': '', 'description': ''}, 'ansible_loop_var': 'item'})
skipping: [localhost] => (item={'changed': False, 'result': [{'name': 'confirm the acl requests are permitted by existing acls', 'type': 'assert_filter_permits', 'status': 'Pass', 'details': 'Assertion passed'}], 'summary': 'Assertion(s) completed successfully', 'invocation': {'module_args': {'assertions': [{'type': 'assert_filter_permits', 'name': 'confirm the acl requests are permitted by existing acls', 'parameters': {'filters': 'hqdist1["ACL_TEST"]', 'headers': {'srcIps': '10.10.1.1/32', 'dstIps': '192.168.1.1/32'}}}], 'session': {'host': '127.0.0.1'}, 'snapshot': 'example_snapshot', 'network': 'example_network'}}, 'failed': False, 'item': {'hostname': 'hqdist1', 'change': 'add', 'acl_type': 'name_extended', 'acl_name': 'ACL_TEST', 'seq_no': '', 'action': 'permit', 'protocol': 'ip', 'src_addr': '10.10.1.1/32', 'src_oper': '', 'src_port': '', 'dest_addr': '192.168.1.1/32', 'dest_oper': '', 'dest_port': '', 'application': '', 'modifier': '', 'description': ''}, 'ansible_loop_var': 'item'})
skipping: [localhost] => (item={'changed': False, 'result': [{'name': 'confirm the acl requests are permitted by existing acls', 'type': 'assert_filter_permits', 'status': 'Pass', 'details': 'Assertion passed'}], 'summary': 'Assertion(s) completed successfully', 'invocation': {'module_args': {'assertions': [{'type': 'assert_filter_permits', 'name': 'confirm the acl requests are permitted by existing acls', 'parameters': {'filters': 'hqdist1["ACL_TEST"]', 'headers': {'ipProtocols': 'tcp', 'srcIps': '10.10.2.1/32', 'dstIps': '192.168.2.1/32', 'dstPorts': '80'}}}], 'session': {'host': '127.0.0.1'}, 'snapshot': 'example_snapshot', 'network': 'example_network'}}, 'failed': False, 'item': {'hostname': 'hqdist1', 'change': 'add', 'acl_type': 'name_extended', 'acl_name': 'ACL_TEST', 'seq_no': '', 'action': 'permit', 'protocol': 'tcp', 'src_addr': '10.10.2.1/32', 'src_oper': '', 'src_port': '', 'dest_addr': '192.168.2.1/32', 'dest_oper': 'eq', 'dest_port': '80', 'application': '', 'modifier': '', 'description': ''}, 'ansible_loop_var': 'item'})
ok: [localhost] => (item={'msg': '1 of 1 assertions failed', 'changed': False, 'result': [{'name': 'confirm the acl requests are permitted by existing acls', 'type': 'assert_filter_permits', 'status': 'Fail', 'details': 'Found a flow that was denied, when expected to be permitted\n[{\'Node\': \'hqdist1\', \'Filter_Name\': \'ACL_TEST\', \'Flow\': Flow(dscp=0, dstIp=\'192.168.2.0\', dstPort=None, ecn=0, fragmentOffset=0, icmpCode=None, icmpVar=None, ingressInterface=None, ingressNode=\'hqdist1\', ingressVrf=\'default\', ipProtocol=\'HOPOPT\', packetLength=0, srcIp=\'10.10.2.0\', srcPort=None, state=\'NEW\', tag=\'BASE\', tcpFlagsAck=0, tcpFlagsCwr=0, tcpFlagsEce=0, tcpFlagsFin=0, tcpFlagsPsh=0, tcpFlagsRst=0, tcpFlagsSyn=0, tcpFlagsUrg=0), \'Action\': \'DENY\', \'Line_Content\': \'no-match\', \'Trace\': AclTrace(events=[AclTraceEvent(class_name=\'org.batfish.datamodel.acl.DefaultDeniedByIpAccessList\', description="Flow did not match \'extended ipv4 access-list\' named \'ACL_TEST\'", lineDescription=None)])}]'}], 'summary': '1 of 1 assertions failed', 'failed': True, 'invocation': {'module_args': {'assertions': [{'type': 'assert_filter_permits', 'name': 'confirm the acl requests are permitted by existing acls', 'parameters': {'filters': 'hqdist1["ACL_TEST"]', 'headers': {'srcIps': '10.10.2.0/24', 'dstIps': '192.168.2.0/24'}}}], 'session': {'host': '127.0.0.1'}, 'snapshot': 'example_snapshot', 'network': 'example_network'}}, 'item': {'hostname': 'hqdist1', 'change': 'add', 'acl_type': 'name_extended', 'acl_name': 'ACL_TEST', 'seq_no': '', 'action': 'permit', 'protocol': 'ip', 'src_addr': '10.10.2.0/24', 'src_oper': '', 'src_port': '', 'dest_addr': '192.168.2.0/24', 'dest_oper': '', 'dest_port': '', 'application': '', 'modifier': '', 'description': ''}, 'ansible_loop_var': 'item'})
TASK [Display "not permitted" acl requests] *********************************************************************************************
ok: [localhost] => {
"msg": [
{
"acl_name": "ACL_TEST",
"acl_type": "name_extended",
"action": "permit",
"application": "",
"change": "add",
"description": "",
"dest_addr": "192.168.0.0/24",
"dest_oper": "",
"dest_port": "",
"hostname": "hqdist1",
"modifier": "",
"protocol": "ip",
"seq_no": "",
"src_addr": "10.10.0.0/24",
"src_oper": "",
"src_port": ""
},
{
"acl_name": "ACL_TEST",
"acl_type": "name_extended",
"action": "permit",
"application": "",
"change": "add",
"description": "",
"dest_addr": "192.168.2.0/24",
"dest_oper": "",
"dest_port": "",
"hostname": "hqdist1",
"modifier": "",
"protocol": "ip",
"seq_no": "",
"src_addr": "10.10.2.0/24",
"src_oper": "",
"src_port": ""
}
]
}
TASK [Copy file with "not permitted" acl requests] **************************************************************************************
ok: [localhost]
PLAY RECAP ******************************************************************************************************************************
localhost : ok=9 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
最後のタスク9で、結果がファイルに保存されました。
[{"hostname": "hqdist1", "change": "add", "acl_type": "name_extended", "acl_name": "ACL_TEST", "seq_no": "", "action": "permit", "protocol": "ip", "src_addr": "10.10.0.0/24", "src_oper": "", "src_port": "", "dest_addr": "192.168.0.0/24", "dest_oper": "", "dest_port": "", "application": "", "modifier": "", "description": ""}, {"hostname": "hqdist1", "change": "add", "acl_type": "name_extended", "acl_name": "ACL_TEST", "seq_no": "", "action": "permit", "protocol": "ip", "src_addr": "10.10.2.0/24", "src_oper": "", "src_port": "", "dest_addr": "192.168.2.0/24", "dest_oper": "", "dest_port": "", "application": "", "modifier": "", "description": ""}]
4. ACL Config作成
4-1. Ansible Playbook
続いて、前項で保存したファイルとJinja2テンプレートを使い、template
モジュールで追加Configファイルの作成を行いました。
---
- hosts: all
gather_facts: no
connection: network_cli
vars:
template: acl_template.j2
acl_list: acl_requests_not_permitted.txt
config_filename: "add/acl_config_{{ inventory_hostname }}.txt"
tasks:
- name: set acl requests
set_fact:
acls: "{{ lookup('file', acl_list) }}"
run_once: yes
- name: build template
template:
src: "{{ template }}"
dest: "{{ config_filename }}"
lstrip_blocks: yes
4-2. Playbook実行ログ
Config作成にあたりホスト名やOSタイプ情報が必要なため、localhostではなく、Inventoryファイルにhqdist1
のログイン情報を定義しています。
$ ansible-playbook -i inventory.ini playbook_aclgen_batfish.yml
PLAY [all] ******************************************************************************************************************************
TASK [set acl requests] *****************************************************************************************************************
ok: [hqdist1]
TASK [build template] *******************************************************************************************************************
changed: [hqdist1]
PLAY RECAP ******************************************************************************************************************************
hqdist1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
作成されたConfigは以下の通りです。必要な2行のみが問題なく作成されています。
ip access-list extended ACL_TEST
permit ip 10.10.0.0 0.0.0.255 192.168.0.0 0.0.0.255
ip access-list extended ACL_TEST
permit ip 10.10.2.0 0.0.0.255 192.168.2.0 0.0.0.255
これ以降は、前回の記事でご紹介した「ACL設定変更」と同じ方法でNW機器へ適用可能です。
最後に
今回、既存Configファイルは手動でアップロードしましたが、以下のAnsible helpers for Batfishを使えば、ここも自動化できそうです。
GitHub - rickdonato/batfish-ansible-helpers
BatfishはCiscoに限らずJuniper、Arista等の様々なプラットフォームに対応していますので、Batfish + Ansibleの組み合わせは、特に大規模なACL設定変更で重宝してくれるのではないでしょうか。