Introduction
Nornir is a framework for network automation, it gels netmiko, paramiko, ansible, yaml, ansible, napalm, netconf together, this is a must have package for doing network automation and it is not too difficult to learn.
There will be time when specific configuration is for specific device in the inventory, in order to push to the correct device in the inventory you can use the filter feature made in nornir.
Simple filter
This post records filter which I need, there are many filtering methods and examples in the documentation, but those examples are not what i was looking for, my hosts.yaml is very simple, there are no groups and sites, only list of hosts and their nested data.
Simple hosts.yaml inventory file
This is my simple yaml file:
fw01: hostname: 192.168.100.20 password: null platform: cisco_asa port: '22' username: null fw02: hostname: 192.168.100.30 password: null platform: cisco_asa port: '22' username: null
Inventory posted to api
I have made my own api gateway to interact with the network devices and hashicorp vault, the below is my sample using insomnia api client. Insomnia is less complicated than postman and is suffice for me to do simple test, of course if you are a cli person you can use curl, but to me I will use whichever is straightforward and easy.
The api gateway removes the username and password of the devices, the actual username and password are created in the hashicorp vault, and the output shown in insomnia is stored in hosts.yaml.
Filter specific device and push configuration
The filter method has an argument filter_func
which accepts a function, lambda function is used to match the device which I need to configure.
fw01 = nr.filter(filter_func=lambda h: h.name == "fw01")
The entire test code:
from nornir import InitNornir from nornir.plugins.functions.text import print_result from network.ciscoasa import add_acl, get_asa_credential, change_asa_host_data from nornir.core.filter import F if __name__ == "__main__": credential = get_asa_credential(hostname="fw01") nr = InitNornir( inventory={ "plugin": "nornir.plugins.inventory.simple.SimpleInventory", "options": { "host_file": "network/inventory/hosts.yaml", } } ) # modify the username and password fields in the hosts.yaml file. nr.inventory.hosts["fw01"].username = credential["username"] nr.inventory.hosts["fw01"].password = credential["password"] payload = { "acl_name": "abc", "action": "permit", "protocol": "tcp", "src_fw_object_type": "host", "src_object": "10.20.1.90", "dst_fw_object_type": "host", "dst_object": "192.168.90.1", "svc_object_group_type": "eq", "svc_object_group": "8200" } fw01 = nr.filter(filter_func=lambda h: h.name == "fw01") r = fw01.run(task=add_acl, **payload) print_result(r)
This is the output after executed the test code:
This is the ACL in the fw01 firewall:
Self made python module – network.ciscoasa
As shown in my test code I am also using my own network.ciscoasa package. The pyvault2 can be found in my own repository. Here’s the code:
from nornir.plugins.tasks.networking import netmiko_send_config, netmiko_save_config from nornir.plugins.tasks.text import template_file from pyvault2.vault.hvault2 import get_kv2_secret def get_asa_credential(hostname=None): """ Get asa's device credentials. :param hostname: :return: credential in json """ return get_kv2_secret(mount_path="cisco_asa", path=hostname, find="data") def change_asa_host_data(host, **kwargs): host.username = kwargs["username"] host.password = kwargs["password"] host.hostname = kwargs["ip"] host.platform = "cisco_asa" def add_acl(task, **kwargs): cmd = task.run(task=template_file, name="Generating template", template="asa_acl.j2", path="network/templates/", acl_attr=kwargs ) task.host["config"] = cmd.result task.run(task=netmiko_send_config, name="Sending configuration", config_commands=task.host["config"]) task.run(task=netmiko_save_config, name="Saving configuration", cmd="write memory")
ACL configuration jinja2 template
{% if acl_attr.line_number is defined %} access-list {{ acl_attr.acl_name }} line {{acl_attr.line_number}} extended {{acl_attr.action}} {{acl_attr.protocol}} {{acl_attr.src_fw_object_type}} {{acl_attr.src_object}} {{acl_attr.dst_fw_object_type}} {{acl_attr.dst_object}} {{acl_attr.svc_object_group_type}} {{acl_attr.svc_object_group}} log {% else %} access-list {{acl_attr.acl_name}} extended {{acl_attr.action}} {{acl_attr.protocol}} {{acl_attr.src_fw_object_type}} {{acl_attr.src_object}} {{acl_attr.dst_fw_object_type}} {{acl_attr.dst_object}} {{acl_attr.svc_object_group_type}} {{acl_attr.svc_object_group}} log {% endif %}