[python]Using Nornir framework to push ACL to Cisco ASA

Introduction

Nornir is a framework for network automation, the framework is written in Python and gels Paramiko, Netmiko, Napalm, Jinja2 and Ansible. The use of framework makes the code more consistent and easier to adapt.

Secret management

Hashicorp vault is required for storing the secret in the code. The extraction of secrets from the vault uses my own self written script – pyvault2. The function which I use is get_kv2_secret(mount_path="cisco_asa", path="fw01", find="data").

Nornir inventory file manipulation

A yaml file is used for inventory just like Ansible. You can modify the content of the yaml file like below, you can read the documentation and test out yourself, the below is the one which worked for me.

def change_host_data(host):
    host.username = credential_from_vault["username"]
    host.password = credential_from_vault["password"]
    host.hostname = credential_from_vault["ip"]
    host.platform = "cisco_asa"

nr = InitNornir(
    inventory={
        "plugin": "nornir.plugins.inventory.simple.SimpleInventory",
        "options": {
            "host_file": "templates/hosts.yaml"
        },
        "transform_function": change_host_data
    }
)

Generate configuration from template and use the configuration to push to cisco asa

Read this documentation to learn how to use jinja2 in Nornir. The below is the one worked for me.

def add_acl(task):
    cmd = task.run(task=template_file,
                   name="Generating template",
                   template="asa_acl.j2",
                   path="./templates/",
                   acl_name="abc",
                   action="permit",
                   protocol="udp",
                   src_fw_object_type="host",
                   src_object="192.168.1.200",
                   dst_fw_object_type="host",
                   dst_object="8.8.8.8",
                   svc_object_group_type="eq",
                   svc_object_group="53")
    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")

This is the jinja2 template which I used.

{% if line_number is defined %}
access-list {{acl_name}} line {{line_number}} extended {{action}} {{protocol}} {{src_fw_object_type}} {{src_object}} {{dst_fw_object_type}} {{dst_object}} {{svc_object_group_type}} {{svc_object_group}} log
{% else %}
access-list {{acl_name}} extended {{action}} {{protocol}} {{src_fw_object_type}} {{src_object}} {{dst_fw_object_type}} {{dst_object}} {{svc_object_group_type}} {{svc_object_group}} log
{% endif %}

Jinja2 has defined() which checks if the variable is specified, if not specified then defined() returns false.

To use jinja2, first use the template_file task, then specify template and path, the template is the jinja2 template file, and path is the path to find the template file.

To push the configuration created by jinja2 template engine, use the task netmiko_send_config, then assign the command generated by template_file to config_commands.

Entire testing code

from nornir import InitNornir
from nornir.plugins.tasks.networking import netmiko_send_config, netmiko_save_config
from nornir.plugins.tasks.text import template_file
from nornir.plugins.functions.text import print_result
from pyvault2.vault.hvault2 import get_kv2_secret

"""
The credential_from_vault function can be found here:
https://github.com/sirbowen78/pyvault2/blob/master/vault/hvault2.py
"""
credential_from_vault = get_kv2_secret(mount_path="cisco_asa", path="fw01", find="data")

"""
Reference:
https://nornir.readthedocs.io/en/stable/tutorials/intro/grouping_tasks.html
"""


def change_host_data(host):
    host.username = credential_from_vault["username"]
    host.password = credential_from_vault["password"]
    host.hostname = credential_from_vault["ip"]
    host.platform = "cisco_asa"


def add_acl(task):
    cmd = task.run(task=template_file,
                   name="Generating template",
                   template="asa_acl.j2",
                   path="./templates/",
                   acl_name="abc",
                   action="permit",
                   protocol="udp",
                   src_fw_object_type="host",
                   src_object="192.168.1.200",
                   dst_fw_object_type="host",
                   dst_object="8.8.8.8",
                   svc_object_group_type="eq",
                   svc_object_group="53")
    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")


nr = InitNornir(
    inventory={
        "plugin": "nornir.plugins.inventory.simple.SimpleInventory",
        "options": {
            "host_file": "templates/hosts.yaml"
        },
        "transform_function": change_host_data
    }
)
r = nr.run(task=add_acl)
print_result(r)

Demonstration

This is what the firewall has:
acl1

To insert another line, I specify the line in my code.

cmd = task.run(task=template_file,
                   name="Generating template",
                   template="asa_acl.j2",
                   path="./templates/",
                   acl_name="abc",
                   line_number="3",
                   action="permit",
                   protocol="udp",
                   src_fw_object_type="host",
                   src_object="192.168.1.210",
                   dst_fw_object_type="host",
                   dst_object="12.12.12.12",
                   svc_object_group_type="eq",
                   svc_object_group="443")

This is the output of the code:
acl2

This is the acl in the cisco asa:
acl3

The use of line in the access-list command allows you to insert preceding rules, but if the rule is on the next available line, then there is no need to specify line. Here’s the code without the line_number:

cmd = task.run(task=template_file,
                   name="Generating template",
                   template="asa_acl.j2",
                   path="./templates/",
                   acl_name="abc",
                   action="permit",
                   protocol="udp",
                   src_fw_object_type="host",
                   src_object="192.168.1.101",
                   dst_fw_object_type="host",
                   dst_object="123.123.123.123",
                   svc_object_group_type="eq",
                   svc_object_group="8443")

This is the output of the code:
acl4

This is the updated ACL in cisco asa:
acl5

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s