Automation is huge, and updating firewall policies that can dynamically change can be a huge task as well. This article, written by Scott Ware, describes how to leverage Infoblox’s API to key on certain objects to create a firewall address group that can update dynamically, using code written in the Go programming language.
[Reprinted with permission]
I was having some discussions a while back regarding automation with some of my fellow Juniper Ambassadors, and one of the topics that came up was how cool it would be to have the ability for an SRX to dynamically update address groups based on, for example, changes to hosts/networks within IPAM (Infoblox).
If you haven’t heard of Infoblox, it is an enterprise DNS/DHCP solution…and one of the best if not the best, IMO.
I have been hacking away at it lately, and I have a working solution that I’ll show you. I’m using all standard/builtin Go libraries, and the go-junos library for my interaction with the SRX.
What does the script do?
Basically, it does the following:
- Query Infoblox based on the given search term, and return all host addresses that match.
- Currently this example only searches for hosts, but I’m going to work at searching for networks as well.
- You can also search fields other than the “comments” one that I am using in this example (e.g. “subnet_org”).
- Once we have all the addresses, it creates a temp file to write to, generating the address-book/address-set configuration in set format.
- Connect to the given SRX, and commit the configuration
- Displays our changes from the previous configuration (compares to rollback 1).
When we query Infoblox, I am choosing to return the results in XML format (default is JSON). Here’s a sample of what it looks like:
<list>
<value type="object">
<_ref>record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5tZWlqZXIuYmxnOTc1LncwOTc1ZXVzckwMTAw:sdubs-fw/Internal</_ref>
<view>Internal</view>
<ipv4addrs>
<list>
<value type="object">
<configure_for_dhcp type="boolean">false</configure_for_dhcp>
<host>sdubs-fw</host>
<ipv4addr>1.1.1.1</ipv4addr>
<_ref>record:host_ipv4addr/ZG5zLmhvc3RfYWRkcmVzcyQuX2RlZmF1bHQuY29tLm1laWplci5ibGc5NzUudzA5NzVldXNyaTAxMDAuMTAuMTcuMi4xOS4:1.1.1.1/sdubs-fw/Internal</_ref>
</value>
</list>
</ipv4addrs>
<name>sdubs-fw</name>
</value>
<value type="object">
<_ref>record:host/ZG5zLmhvc3QkLl9kZWZhdWx0LmNvbS5tZWlqZXIuYmxnOTc1LncwOTc1ZXVzckwMTAw:sdubs-ap/Internal</_ref>
<view>Internal</view>
<ipv4addrs>
<list>
<value type="object">
<configure_for_dhcp type="boolean">false</configure_for_dhcp>
<host>sdubs-ap</host>
<ipv4addr>1.1.1.2</ipv4addr>
<_ref>record:host_ipv4addr/ZG5zLmhvc3RfYWRkcmVzcyQuX2RlZmF1bHQuY29tLm1laWplci5ibGc5NzUudzA5NzVldXNyaTAxMDAuMTAuMTcuMi4xOS4:1.1.1.1/sdubs-ap/Internal</_ref>
</value>
</list>
</ipv4addrs>
<name>sdubs-ap</name>
</value>
</list>
And after we commit our configuration to the SRX, here’s what the config diff looks like:
[edit security address-book global]
address ware-lan { ... }
+ address sdubs-fw 1.1.1.1/32;
+ address sdubs-ap 1.1.1.2/32;
[edit security address-book global]
+ address-set Dynamic-IPAM-Hosts {
+ address sdubs-fw;
+ address sdubs-ap;
+ }
And with that…here’s the full code for the script. Also available as a Github Gist.
For this example, I made a binary and ran it, but you can just do a
go run <script>
if you’d like.
package main
import (
"crypto/tls"
"encoding/xml"
"flag"
"fmt"
"github.com/scottdware/go-junos"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
)
// dynamicHosts parses the overall XML returned from the Infoblox query.
type dynamicHosts struct {
XMLName xml.Name `xml:"list"`
Hosts []hostEntry `xml:"value>ipv4addrs>list>value"`
}
// hostEntry parses the XML for each individual host.
type hostEntry struct {
Name string `xml:"host"`
Address string `xml:"ipv4addr"`
}
var (
searchString = flag.String("search", "", "Search string")
ipamGM = flag.String("gm", "", "IPAM/Infoblox Grid master (hostname or IP)")
ipamUser = flag.String("ibuser", "", "IPAM/Infoblox username")
ipamPass = flag.String("ibpassword", "", "IPAM/Infoblox password")
srxHost = flag.String("srx", "", "SRX to configure")
srxUser = flag.String("user", "", "SRX username")
srxPass = flag.String("password", "", "SRX password")
groupName = flag.String("group", "Dynamic-IPAM", "Name of the group/address-set to create")
)
func init() {
flag.Usage = func() {
fmt.Println("srx-ipam version 0.1\n")
fmt.Printf("Usage: %s <options>\n\n", filepath.Base(os.Args[0]))
flag.PrintDefaults()
os.Exit(0)
}
}
// queryIPAM searches throughout Infoblox for the given search string and returns
// addresses that we will build our address-set for.
func queryIPAM() (*dynamicHosts, error) {
var data dynamicHosts
reqURL := fmt.Sprintf("https://%s/wapi/v1.0/record:host?comment~:=%s", *ipamGM, *searchString)
req, err := http.NewRequest("GET", reqURL, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(*ipamUser, *ipamPass)
req.Header.Set("Accept", "application/xml")
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{Transport: tr}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
err = xml.Unmarshal([]byte(body), &data)
if err != nil {
return nil, err
}
return &data, nil
}
func main() {
flag.Parse()
// Run our query against IPAM/Infoblox to get our addresses.
d, err := queryIPAM()
if err != nil {
fmt.Println(err)
}
// Open a temp file to write our config to.
tmp, err := ioutil.TempFile(".", "srxconfig-")
if err != nil {
fmt.Println(err)
}
// Create our address entries first.
for _, ae := range d.Hosts {
addressEntry := fmt.Sprintf("set security address-book global address %s %s/32\n", ae.Name, ae.Address)
_, err := tmp.Write([]byte(addressEntry))
if err != nil {
fmt.Println(err)
}
}
// Create the address-set/group and assign the addresses to it.
for _, as := range d.Hosts {
addressSetEntry := fmt.Sprintf("set security address-book global address-set %s address %s\n", *groupName, as.Name)
_, err := tmp.Write([]byte(addressSetEntry))
if err != nil {
fmt.Println(err)
}
}
// Close our temp file as we are finished writing our config to it.
err = tmp.Close()
if err != nil {
fmt.Printf("Could not close the temp file: %s", err)
}
// Connect to our SRX.
jnpr, err := junos.NewSession(*srxHost, *srxUser, *srxPass)
if err != nil {
fmt.Println(err)
}
// Load our configuration from the temp file we wrote to earlier.
err = jnpr.LoadConfig(tmp.Name(), "set", false)
if err != nil {
fmt.Println(err)
}
// Rollback the commit after 1 minute for testing purposes.
jnpr.CommitConfirm(1)
// Print the changes out to the console.
changes, _ := jnpr.ConfigDiff(1)
fmt.Println(changes)
// Remove our temp file.
time.Sleep(2 * time.Second)
err = os.Remove(tmp.Name())
if err != nil {
fmt.Printf("Could not remove temp file: %s", err)
}
}
Display the usage/help by using the -h
or -help
flag:
srx-ipam version 0.1
Usage: srx-ipam <options>
-gm="": IPAM/Infoblox Grid master (hostname or IP)
-group="Dynamic-IPAM": Name of the group/address-set to create
-ibpassword="": IPAM/Infoblox password
-ibuser="": IPAM/Infoblox username
-password="": SRX password
-search="": Search string
-srx="": SRX to configure
-user="": SRX username
How to run the script:
srx-ipam -gm infoblox.company.com -ibuser admin -ibpassword secret -srx sdubs-fw -user admin -password juniper123 -group Dynamic-IPAM-Hosts -search ware
This script is run manually now, but you could easily create a scheduled job, or maybe with some more coding, check for changes every so often and then run a script like this to publish the updates hosts/networks to your SRX.
[reprinted with permission – see the original post here ]