NSX-v RestAPI to create Distributed Firewall rules with Ansible

logo for ansible vmware

NSX-v RestAPI to create Distributed Firewall rules with Ansible

My previous post was about creating Ansible modules for NSX-v with Powershell. Today I will show how to skip Powershell and speed up the process significantly. I will do this by using the Ansible uri module and NSX-v RestAPI.


Making your own Ansible modules with Powershell is pretty neat and cool. The big downside I experienced though is deployment time. Using a Powershell jumphost really slowed things down for me. Also it requires quite a bit extra configuration to make it work. I got two questions. Can this be done easier and can you make it go any faster? So as it turns out, no for easier and yes for faster. And I do have to say, the ‘no’ for easier totally depends on perception and experience. What I want to do is to leverage the NSX-v RestAPI into Ansible with the uri module. This cuts away the need for Powershell and extra configuration. Also talking directly to the NSX-v RestAPI should make deployment a lot faster.


I want the script to create firewall rules from a file. The firewall rule contains one securitygroup as source and destination. This is because I am creating micro-segmentations in a zero-trust firewall. The rule is added in a disabled state. The section where the rule is created is fixed and this is done to avoid freedom and errors in generating sections.


Actually this list is not too long, but you should be aware that some config is needed.

  • NSX for vSphere 6.4.5 or higher (tested)
  • Ansible controlhost. Tested 2.9.1
  • NSX for vSphere CLI user with web access – zie deze link
  • Existing Section to create rules in
  • Empty dummy rule (some bug i can’t work around)
  • Securitygroups should be present

Little note on the dummy rule, but for some reason the script fails when the section is empty. I certainly would appreciate some help !

Ofcourse it is possible to automate securitygroups as well. For today’s example I won’t go into that though.

Road to success

Well, diving into the NSX for vSphere API turned out to be quite a deep-dive. There were some resources I could find, but not many. Especially when it comes to integrating the API into Ansible. I have to mention the RAML tool as wel. This tools templates the Rest-API to be used in Python. Which makes it possible to make your own Python modules. For reasons I have not gone that path, but it’s quite a nice tool if you are interested.

After lots of testing and sending commands to NSX-v with Postman I felt ready to leverage commands to the Uri module. There is one really icky thing though about this module. It likes JSON, but XML not so much. So I had quite a challenge in using and reading the information from the API.

The other thing that made life a little less easy was the Etag (Entity tag). Every iteration that causes a resource change also causes the Etag number to change. You can only make changes against resources with the current (running script) Etag id. This means the script needs to search for the Etag every iteration.

It was a lot of work, but I did not give up! Needed to dig deep on some issues and sometimes it was pretty frustrating and infuriating. In the end though I got it all working. The script you see today is just one (edited) part of a larger deployment script. In the future I might write more on how to glue everything together.

Notes on the code

Before pasting code there are a few things to mention. The code is pretty raw and sometimes quite complex. The code has comments on what is happening so you will know what is going on. Also I am very open to suggestions. When you feel things can be done better or more efficient I would love to hear from you.

Structure of the code

The code exists of three scripts.
1. nsx-firewallrule-vars
2. nsx-firewallrule-playbook
3. nsx-firewallrule-create
This scripts can reside in the same directory, but you can adjust this to your needs. The first script contains the variables in a dict. The second script is the playbook that includes the variables and runs the create script. It contains a loop to make sure that only one dict item gets passed to the create script each iteration. This is necessary because of dependencies within the create script.

In the create script you need to manually add the NSX Manager plus credentials. These are not variables in this example.

Code used for the script

The vars file
    section_name: Micro Segmentation Codecrusaders
    secgroup_name: sg_codecrusaders_01
    section_name: Micro Segmentation Codecrusaders
    secgroup_name: sg_codecrusaders_02
    section_name: Micro Segmentation Codecrusaders
    secgroup_name: sg_codecrusaders_03

The above is a dictionary. Define rules to your liking. Be aware that for this example the script does not create security groups or sections. They need to exist. In an empty section please add a (temporary) dummy rule.

The playbook file
- name: playbook for adding rules to Sections
  hosts: localhost

    - name: include vars from dict
      include_vars: nsx-firewallrule-vars.yml

    - name: create some firewall rules
      include_tasks: nsx-firewallrule-create.yml
      loop: "{{ query('dict',fwrules) }}"

This file states to include the vars created in the previous file and it also it includes the create file with a loop. This is to make sure one rule at a time will be processed.

The create file
# nsx-firewallrule-create.yml
- name: retrieve securitygroup information
    url: https://<your-nsx-manager>/api/2.0/services/securitygroup/scope/globalroot-0
    method: GET
    return_content: yes
    user: <nsx_api_web_user>
    password: <nsx_api_web_user_password>
      Content-Type: "application/xml"
    force_basic_auth: yes
    validate_certs: no
    status_code: 200
  register: output_secgroup

- name: get secgroup-attribute from xml content
    xmlstring:  "{{ output_secgroup.content }}"
    xpath: /list/securitygroup[name='{{ item.value.secgroup_name }}']/objectId
    content: 'text'
  register: secgroup_attributes

- name: set securitygroup-id as fact
    secgroup_id: "{{ secgroup_attributes.matches.0.objectId }}"
  register: secgroup_results

- name: make urls machine readable
  set_fact: section="{{ item.value.section_name | replace(' ','%20') }}"

- name: check for dfw etag and section ID
    url: https://<your-nsx-manager>/api/4.0/firewall/globalroot-0/config/layer3sections?name={{ section }}
    method: GET
    return_content: yes
    user: <nsx_api_web_user>
    password: <nsx_api_web_user_password>
    body_format: raw
      Content-Type: "application/xml"
    force_basic_auth: yes
    validate_certs: no
    status_code: 200
  register: l3section 

- name: get section_id xml content
    xmlstring: "{{ l3section.content }}"
    xpath: /sections/section
    content: attribute
  register: section_attributes

- name: set the section id needed for use in API
    section_id: "{{ section_attributes.matches.0.section.id|default(['post']) }}"

- name: set the Etag id
    etag_id: "{{section_attributes.matches.0.section.generationNumber|default(['post']) }}"
    # etag_id: "{{ l3section.etag }}" -> not working since 6.4.5 because of double-double quotes issue

# below debugs are just informational to show you the section id and the etag id
- debug:
    msg: "{{ section_id }}"

- debug:
    msg: "{{ etag_id }}"

- name: get rule_name xml content
    xmlstring: "{{ l3section.content }}"
    xpath: /sections/section/rule[sectionId={{ section_id }}]/name
    content: text
  register: section_attributes_rule

- name: search for existing rules
    rule_dict: "{{ section_attributes_rule['matches'] | selectattr('name','search', item.key|string ) | list | default([]) }}"
  register: rules

- name: set rule name if found - optional task
    rule_name: "{{ rule_dict[0].name }}"
  when: rule_dict != []

- name: create rule (POST)
    url: https://<your-nsx-manager>/api/4.0/firewall/globalroot-0/config/layer3sections/{{ section_id }}/rules
    method: POST
    return_content: yes
    user: <nsx_api_web_user>
    password: <nsx_api_web_user_password>
    body_format: raw
      Content-Type: "application/xml"
      If-Match: "{{ etag_id }}"
      <rule disabled="true" logged="false">
        <name>{{ item.key }}</name>
        <sectionId>{{ section_id }}</sectionId>
        <sources excluded="false">
            <value>{{ secgroup_id }}</value>
        <destinations excluded="false">
            <value>{{ secgroup_id }}</value>
    force_basic_auth: yes
    validate_certs: no
    status_code: 201
  register: l3section_rule
  when: rule_dict == []
  changed_when: l3section_rule.status == 201

This code actually creates the firewall rules and does so one by one. The code is based on the Ansible uri module and the NSX-v RestAPI. The dummy issue probably happens because of a coding error somewhere in getting xml content. I will look into it, but feel free to support.


When run this code it is fast and idempotent. With the ‘old’ Powershell modules it could take up minutes per rule to deploy. This new code brings that back to a few seconds. When automating large numbers of rules this is a big win !


It is quite a bit of work to sort the API out and get it to work. Sometimes frustrating and even infuriating when bumping into the next error. That being said, it is and was totally worth the effort. It is always a good feeling when you go back from minutes to seconds, especially in automation land.
Thanks for reading!

If you want to read more about Ansible and Powershel modules, please read:

Code is also on github here:

Inline Feedbacks
View all comments