Firepyer – Automating Cisco FTD in FDM mode with Python


I recently started looking into options for automating the deployment and configuration of Cisco’s FTD (Firepower Threat Defense) devices… This is Cisco’s latest attempt at a NGFW, bringing together a unified platform containing the best bits from their long-standing ASA firewall and their Sourcefire IDP acquisition.

As of version 6.2.something, Cisco offers two methods of managing FTD devices. The first is using FMC (Firepower Management Center), a centralised management controller, which comes in either virtual or physical appliance format and can be used to manage a number of devices. The second option, which is the focus of this post is FDM (Firepower Device Manager) which is a local ‘on-box’ method of managing a standalone (or HA pair) appliance.

I won’t discuss the pros and cons of each, but currently there is no ability to migrate from one management option to the other, so choose wisely. If you find FDM fits your needs and you have a large number of devices to configure or just want some automation then read on…

Enter Firepyer

There’s plenty out there for automating devices configured to use FMC, but not much for standalone FDM devices. I found a few Ansible modules here and there and a bulk config tool, but these only cover a small portion of the FDM feature set, so I decided to create Firepyer.

Firepyer consumes the REST API that becomes available when you select FDM mode and provides some easy to use Python methods to interact with your NGFW and get/send structured data. Currently there’s only a hand full of operations that are implemented, but my aim is to get full CRUD (Create, Read, Update, Delete) support for the majority of popular features. The list of current features are shown in the Firepyer Docs.

Using Firepyer

If you’re familiar with Python then the docs should be enough to get you started using Firepyer, but if you’re pretty new then here’s how to get going (some commands may be different depending on your platform)…

1. It’s always good to use a virtual environment to separate dependencies between projects and system-level packages:

python -m venv venv

2. Then enter your new venv…

in Linux:

source ./venv/bin/activate

or in Windows:


3. As Firepyer is available on PyPI (the Python Package Index) you can then easily install it into your venv:

pip install firepyer

4. Now start an interactive Python shell, import the Fdm class, create an object with your FTD details and start automating:

(venv) C:\Users\username> python
>>> from firepyer import Fdm
>>> fdm = Fdm(host='', username='admin', password='Admin123')
>>> print(fdm.get_vrfs())

[{'description': "Customer A's VRF",
  'id': '67e4d858-503d-11eb-aab5-2921a41f8ca3',
  'interfaces': [{'hardwareName': 'GigabitEthernet0/2',
                  'id': 'aeb5b238-4d44-11eb-9e04-cd44159d2943',
                  'name': 'customer_a',
                  'type': 'physicalinterface',
                  'version': 'nh7piq3rw7pzs'}],
  'isSystemDefined': False,
  'links': {'self': ''},
  'name': 'Customer-A',
  'type': 'virtualrouter',
  'version': 'crdwtc44cg5pu'},
 {'description': "Customer B's VRF",
  'id': '7360254c-503d-11eb-aab5-41ec0935f001',
  'interfaces': [{'hardwareName': 'GigabitEthernet0/3',
                  'id': 'afb288c9-4d44-11eb-9e04-41c0f86d8474',
                  'name': 'customer_b',
                  'type': 'physicalinterface',
                  'version': 'ocdhtp76zpfzz'}],
  'isSystemDefined': False,
  'links': {'self': ''},
  'name': 'Customer-B',
  'type': 'virtualrouter',
  'version': 'nl7onsmfqdujm'},
 {'description': 'This is a Global Virtual Router',
  'id': '42e95fbf-fd5a-42bf-a95f-bffd5a42bfd6',
  'interfaces': [{'hardwareName': 'Management0/0',
                  'id': 'b0b5a0ea-4d44-11eb-9e04-43089048338b',
                  'name': 'diagnostic',
                  'type': 'physicalinterface',
                  'version': 'inmqiea7woymm'},
                 {'hardwareName': 'GigabitEthernet0/1',
                  'id': 'ad6a9497-4d44-11eb-9e04-63d0b1958967',
                  'name': 'inside',
                  'type': 'physicalinterface',
                  'version': 'eqotynhtlcuyf'},
                 {'hardwareName': 'GigabitEthernet0/0',
                  'id': '8d6c41df-3e5f-465b-8e5a-d336b282f93f',
                  'name': 'outside',
                  'type': 'physicalinterface',
                  'version': 'h4kqp4iu2yvff'}],
  'isSystemDefined': True,
  'links': {'self': ''},
  'name': 'Global',
  'type': 'virtualrouter',
  'version': 'cna3vbajed6et'}]

5. View the docs to see everything else you can do!

Disclaimer: Firepyer is still in early development so I take no responsibility if your network goes up in flames!



The NSX-T Policy API is a powerful concept that was introduced in 2.4 and powers the new Simplified UI. It provides a declarative method of describing, implementing and deleting your entire virtual network and security estate.

In a single API call you deploy a complete logical topology utilising all the features NSX-T provides including T1 Gateways (with NAT/Load Balancer services) for distributed routing, Segments for streched broadcast domains and DFW rules to enforce microsegmentation.

This example performs the following:

  • Creates a T1 Gateway and connects it to the existing T0
  • Creates three Segments and attaches them to the new T1
  • Creates intra-app distributed firewall rules to only allow the necessary communication between tiers
  • Creates Gateway Firewall rules to allow external access directly to the web tier
  • Creates a Load Balancer for the web tier with TLS-offloading using a valid certificate

And once deployed the topology will look like this:

Currently, on the networking side there is only a T0 Gateway, a single Segment (which is a VLAN-backed transit Segment to the physical network), with no T1s or Load Balancers:

And on the security side there’s no DFW policies :

Once the JSON body (see my example here) is created with the relevant T0, Edge Cluster and Transport Zone IDs inserted, then the REST call can be constructed. Using your favourite REST API client e.g. Curl, Postman, Requests (Python), the request should look like this:

URL: https://NSX-T_MANAGER/policy/api/v1/infra/
Method: PATCH
Header: Content-Type: application/json
Auth: Basic (NSX-T Admin User/Password)
Body: The provided JSON

Once you send a successful request you’ll notice you receive a status 200 almost instantly, but don’t be fooled into thinking that your entire topology has now been created!

In reality this is just the policy engine acknowledging your declarative intent. It now works to convert or ‘realise’ that intent in to imperative tasks that are used to create all of the required logical objects.

Once this has all been created you’ll see your network and security components in the GUI:

Now the magic of using this API means that you can also delete your entire topology with the same call, just changing the marked_for_delete to true for each section.

Example code here:

Working with NSX API: Adding an IP to an ESG (Edge Services Gateway)

Whilst setting up an NSX load balancer (post to follow) I found the need to add a secondary IP to an ESG. Because of this (KB2151309) handy bug feature I had to either delete the ESG and re-create it, or set a secondary IP via the API, so here goes…

This post assume basic knowledge of NSX components and uses the following:

  • NSX 6.3.2 with a fully deployed ESG
  • Postman

The Before

A quick look at the ‘before’ setup of the ESG interface configuration (IPs and names have been changed to protect the innocent):

And here’s a closer look at the vNIC 0 that we’ll be adding a secondary IP to, to prove there’s not already one set:

API Call 1 – Get the config of the vNIC we want to change

So we know we need to edit vNIC 0, but to get all of its current config we need to call on the API. This will help to see the structure of the config required and confirm all of the settings.

The NSX API Guide tells us how the API request should look…

So fire up Postman (or other API consumer) and structure the request:

  • Method: GET
  • URL:¬†https://NSX-MANAGER.FQDN/api/4.0/edges/edge-1/vnics/0
    • This is from the NSX API Guide as above, with the index of 0 at the end to represent the vNIC 0 we’re working with
    • Remember that in NSX world that the NSX Manager presents the Northbound API, so you’re always interacting with the Manager
  • ¬†Authorization:
    • Type: Basic Auth
    • Username: (API-enabled user, could be the NSX default ‘admin’)
    • Password; ^^^
  • Headers:
    • Content-Type: application/xml
    • Authorization: Basic (base64-added from the Authorization tab)

Hit Send and you should see some data similar to what was represented in the NSX web GUI:

<?xml version="1.0" encoding="UTF-8"?>


API Call 2 – Setting the Secondary IP Address

Note that in the output above from the vNIC it has no secondary addresses configured and there’s no stanza for one. So how do you know how to add one? And do you just guess and change the method from GET to POST? All is explained again in the NSX API Guide…

So we now know the syntax and method (PUT) to add a secondary address!

Back in Postman, update the request to change the method, then add a Body with the existing vNIC0 configuration (which was obtained earlier) along with the new secondary IP config (I’ve used and hit Send:

Ok, it loaded for a bit then looks to have sent, but how do you know? Well in the bottom right corner of Postman we see Status: 204 No Content. Any HTTP 2XX message is a success and the fact that no content was sent back is fine, we didn’t expect anything.

The After

Time to check that the new config has worked. We can do this two ways, in the NSX web GUI or via the API…

Since we’re still in Postman, re-issue the GET from earlier to retrieve the new vNIC0 config:

And to be sure, confirm in the NSX web GUI: