Solving problems invented by others...
My journey to automate vCloud Director with PowerShell – part #9: add a firewall rule

My journey to automate vCloud Director with PowerShell – part #9: add a firewall rule

In my last article i showed that i started to use the REST API because i’m missing some features in PowerCLI. Also i showed that i had to use the network namespace in order to work with the NSX configurations. And this knowledge was really needed to make the next step, adding a firewall rule to an edge gateway.
(Spoiler: Plus i needed a basic understanding of XML and how PowerShell handles it.)

The same way as in the last step, i requested the edgeGateway object directly from the network namespace of the REST API. But this time i had to use the url for getting the firewall config.
(https://vdc-repo.vmware.com/raw.githubusercontent.com/vmware/nsxraml/6.4/html-version/nsxvapi.html#4_0_edges__edgeId__firewall_config_get)

# get a fresh copy of the edge object
$myedge = Get-EdgeGateway -Name $edgename

# build the url for the nsx rest api call
$myedgeid = $myedge.id.Split(":")[-1] # extract the guid from the object id
$url = "https://[DNS-NAME-OF-CLOUD-DIRECTOR]/network/edges/$myedgeid/firewall/config"

# build the header for the api call
$header = @{
Accept='application/xml'
"content-type"='application/xml'
"x-vcloud-authorization"=$global:defaultciservers[0].SessionSecret
}

# execute the call and get the actual firewall rules
$result = Invoke-RestMethod -Uri $url -Method get -Headers $header

Exploring this object, i saw that the representation of the currently configured firewall rules can be found at $result.firewall.firewallRules.firewallRule

After playing around a bit with it, i understood that the order of this entries are important. (As in every firewall.) Therefore it is necessary to have the “catch all” rule at the end of this list. Here it is the rule called default rule for ingress traffic. Therefore i wanted to add a new rule just before the “catch all” rule.

Since i have a XML representation of the firewall configuration, i had to insert new XML elements into this structure. Here i came to the point where i had to learn some basic skills about XML and how PowerShell handles it. I learned that every PowerShell XML document object has some methods to create and append/insert new elements. Basically i first had to create the element i want to add, and then add/insert it to the XML document object. That was the easy part. The more difficult part was to insert this element at the right place.

To create the element i again used the chrome developer tools to see what the Cloud Director UI does when i add a firewall rule. Besides the documentation (https://vdc-repo.vmware.com/raw.githubusercontent.com/vmware/nsxraml/6.4/html-version/nsxvapi.html#4_0_edges__edgeId__firewall_config_put) i saw that not all elements of a firewall rule object are necessary to create a new one. It is enough to submit a rule object which consists of the following structure:

<firewallRule>
   <name>[NAME-OF-MY-FIREWALL-RULE]</name>
   <ruleType>user</ruleType>
   <enabled>true</enabled>
   <loggingEnabled>false</loggingEnabled>
   <action>accept</action>
   <source>
      <exclude>false</exclude>
      <ipAddress>[IP-OR-SUBNET-OF-THE-SOURCE]</ipAddress>
   </source>
</firewallRule>

This creates a simple firewall rule, which allows traffic to go anywhere if it comes from the defined source. (For more detailed rules you had to dig further into the XML structure of firewall rules.

In PowerShell i used this code to create such a structure.

# create new firewall rule branch
$myrule = $result.CreateElement("firewallRule")

# add the firewall rule elements and fill them with values
$myrule.AppendChild($result.CreateElement("name"))
$myrule.name = "test-rule"

$myrule.AppendChild($result.CreateElement("ruleType"))
$myrule.ruleType = "user"

$myrule.AppendChild($result.CreateElement("enabled"))
$myrule.enabled = "true"

$myrule.AppendChild($result.CreateElement("loggingEnabled"))
$myrule.loggingEnabled = "false"

$myrule.AppendChild($result.CreateElement("action"))
$myrule.action = "accept"

$myrulesource = $myrule.AppendChild($result.CreateElement("source"))

$myrulesource.AppendChild($result.CreateElement("exclude"))
$myrulesource.exclude = "false"

$myrulesource.AppendChild($result.CreateElement("ipAddress"))
$myrulesource.ipAddress = "192.168.0.0/24"

$myrule.AppendChild($myrulesource)

Now i have my new firewall rule as a standalone XML structure and i want to add it just before the “catch all” rule. This took me some time, because i had to fight with the InsertBefore method of the PowerShell XML object. This method takes the new XML structure object and insert it just before an existing object at the same level in the XML document. At first i did not understand that this method takes as second parameter a reference to the existing object and not the object itself.

The solution was to get a reference to the “catch all” rule by using the SelectSingleNode method of the XML document object. It lets you select a XML element in your XML document by using some regex style query language. I learned that the following code let me find and select the “catch all” rule.

$catchallrule = $result.SelectSingleNode('//firewall/firewallRules/firewallRule[name="default rule for ingress traffic"]')

Now with a correct reference to the “catch all” rule, i could add my new firewall rule just before.

$result.firewall.firewallRules.InsertBefore($myrule, $catchallrule)

Finally i could write the changed firewall configuration back to the network namespace of the REST API.

$result = Invoke-RestMethod -Uri $url -Method put -Headers $header -Body ($result.firewall)

With all what I learned in this step I extended my script with this steps:

  • Get the firewall configuration of the NSX edge
  • Create a new firewall rule configuration as XML structure
  • Add this new firewall rule just before the “catch all” rule

The script:

# get a fresh copy of the edge object
$myedge = Get-EdgeGateway -Name $edgename

# build the url for the nsx rest api call
$myedgeid = $myedge.id.Split(":")[-1] # extract the guid from the object id
$url = "https://[DNS-NAME-OF-CLOUD-DIRECTOR]/network/edges/$myedgeid/firewall/config"

# build the header for the api call
$header = @{
	Accept='application/xml'
	"content-type"='application/xml'
	"x-vcloud-authorization"=$global:defaultciservers[0].SessionSecret
}

# execute the call and get the actual firewall rules
$result = Invoke-RestMethod -Uri $url -Method get -Headers $header

# create new firewall rule branch
$myrule = $result.CreateElement("firewallRule")

# add the firewall rule elements and fill them with values
$myrule.AppendChild($result.CreateElement("name"))
$myrule.name = "test-rule"

$myrule.AppendChild($result.CreateElement("ruleType"))
$myrule.ruleType = "user"

$myrule.AppendChild($result.CreateElement("enabled"))
$myrule.enabled = "true"

$myrule.AppendChild($result.CreateElement("loggingEnabled"))
$myrule.loggingEnabled = "false"

$myrule.AppendChild($result.CreateElement("action"))
$myrule.action = "accept"

$myrulesource = $myrule.AppendChild($result.CreateElement("source"))

$myrulesource.AppendChild($result.CreateElement("exclude"))
$myrulesource.exclude = "false"

$myrulesource.AppendChild($result.CreateElement("ipAddress"))
$myrulesource.ipAddress = "192.168.0.0/24"

$myrule.AppendChild($myrulesource)


$catchallrule = $result.SelectSingleNode('//firewall/firewallRules/firewallRule[name="default rule for ingress traffic"]')

$result.firewall.firewallRules.InsertBefore($myrule, $catchallrule)


# execute the call and get the actual firewall rules
$result = Invoke-RestMethod -Uri $url -Method put -Headers $header -Body ($result.firewall)

Next article in this series:

adding a nat rule

Leave a Reply

Your email address will not be published.

÷ nine = one