In this tutorial we'll leverage GO's http library to implement a "Man in the Middle" that will behave as a reverse proxy between a User-ID enabled application and a PAN-OS NGFW.
They're probably the most common way to share external IP metadata with the NGFW. The Use cases for them are enormous: from blocking known DoS sources to forward traffic from specific IP addresses on low latency links.
Let's take for instance the following logs generated by the SSH daemon in a linux host.
Won't it be great to have a way to share the IP addresses used by the user xhoms with the NGFW so specific security policies are applied to traffic sourced by that address just because we now know the endpoint is being managed by that user?
The following Shell CGI script could be used to create an External Dynamic List (EDL) that would list all known IP addresses the account xhoms was used to login into this server.
That is great but:
- When would address be removed from the list? At the daily log rollout?
- Are all listed IP addresses still being operated by the user xhoms?
- Is there any way to remove addresses from the list at user logout?
To overcome these (and many other) limitations Palo Alto Networks NGFW feature a very powerful IP tagging API called User-ID. Although EDL and User-ID cover similar objectives there are fundamental technical differences between them:
- User-ID is "asynchronous" (push mode) and provides a very flexible way to create groupings. Either by tagging address objects (Dynamic Address Group - DAG ) or by mapping users to addresses and then tagging these users (Dynamic User Group - DUG)
- EDL is "universal". Almost all network appliance vendors provide a feature to fetch an IP address list from a URL.
Years ago it was up to the end customer to create its own connectors. Just like the CGI script we shared before. But as presence of PAN-OS powered NGFW's grown among enterprise customers more application vendors decided to leverage the User-ID value by providing API clients out-of-the-box. Althought this is great (for Palo Alto Network customers) losing the option to fetch a list from a URL (EDL mode) makes it more difficult to integrate legacy technologies that might be out there still in the network.
Let's assume you have an application that features a PAN-OS User-ID API client and that is capable of pushing log entries to the PAN-OS NGFW in an asynchronous way. Let's assume, as well, that there are still some legacy network devices in your network that need to fetch that address metadata from a URL. How difficult it would be to create a micro-service for that?
It shouldn't be that difficult to parse the response and provide a plain list out of it. In fact, if you're in for a challenge, I'd let you think about using a XSLT processor to pipe it at the output of a cURL command and pack everything as a CGI script.
But in this tutorial we're going to levarage the GO Package https://pkg.go.dev/github.com/xhoms/panoslib/uid to follow a diferent approach: we'll hijack User-ID messages by implementing a micro-service that behaves as a reverse proxy between the application that features the User-ID client and the PAN-OS device. I like this approach because it opens the door to other use cases like enforcing timeout (adding timeout to entries that do not have it), converting DAG messages into DUG equivalents or adding additional tags based on the source application.
GO's http package is great. And its httputil package companion contains a ready-to-use reverse proxy type. Building the foundation of our tutorial is a 4-line code exercise.
Next step is to provide a minimum of configuration options allowing the user to provide:
- the target PAN-OS device (
- the TCP to bound our micro-service to (defaulting to
Let's then use the following version as the foundation for our reverse proxy application
Next goal is to be able to monitor the User-ID messages sent to the PAN-OS device. To acomplish that we need to capture transactions that conform to the following schema:
- pattern equals
- methods GET or PUT
type(either in Query String or POST payload) equals to
We'll leverage GO http's routing capability by creating two handlers. One of them
will process all requests sent to
/api/ while the other one will behave like
a default route (
/). In any case, the request will need to be forwarded to the
PAN-OS device. So let's create a type that will hold the reverse proxy and that
will implement the two handlers.
And now let's change the application main's method final lines to configure routing instead of a dedicated handler for all requests.
Finally we need to implemente the
apiHandler() to extract the User-ID messages
cmd parameter) by:
- reading parameters from the URL Query String for GET requests or
- performing the following tasks in case of POST requests
- read the request body and keep a safe copy
- re-set the body to be able to call
ParseForm()and check if
cmdin such a case
- re-set the body to be able to invoke the reverse proxy
This is how the modified version of the
apiHandler() looks like
Now it is time to:
- parse the User-ID (XML) command we extracted and
- keep a simulated stated from reading the different mappings (user-ip, user-group and ip-tag) and honouring the provided time out values for each of them.
Fortunatelly we can leverage the packages https://pkg.go.dev/github.com/xhoms/panoslib/collection, https://pkg.go.dev/github.com/xhoms/panoslib/uid and https://pkg.go.dev/github.com/xhoms/panoslib/uidmonitor to perform the heavy lifting.
uid package features a User-ID payload builder that accepts many input formats, being an
existing User-ID payload one of them. Final actions of the builder (i.e.
Payload()) accept a type implementing the
uid.Monitor interface. If present, then the
Log() method would be called for each item in the User-ID payload.
uidmonitor package provides the
MemMonitor type that implements the
interface by keeping a state of valid values in memory.
MemMonitor features convenience
methods to dump current state as IP lists.
Let's change our
manInMiddle type and
newManInMiddle() initialization to keep a
And now let's implement a
process() method that will drive our
hijacked UserID messages in the api handler.
Now we just need the
apiHandler() to call
process() for each User-ID command
extracted by our Man-in-the-Middle.
We're almost there. The only missing piece is a new handler capable of dumping our
As that state is organized by type (
GroupIP()) we will first implement
list() function to choose the right method with a switch/case statement.
Notice we call
CleanUp() method with current time to have expired entries being removed
before the list is retrieved.
And now let's wrap this
list function in a http handler that will extract required
parameters from a URL query string featuring the schema
Final step is to insert this new handler in the http router. We can use the
endpoint as it won't conflict with current PAN-OS http service.
Let's perform a couple of test. First one will register two IP addresses with the tag
of these entries would expire in 100 seconds while the second one would expire in just 10 seconds.
Then we call the
/edl endpoint twice giving enought time between attempts to allow one of
the entries to expire.
The next payload is a bit more complex. It maps (login) two users and then tags them to the group
with different timeouts.
Again, we can experience one of the IP addresses being removed from the output at its due time.
Feel free to clone the the GitHub repository created to host this tutorial code or run the linked Container Image for an out-of-the-box experience.