SSL Orchestrator Advanced Use Cases: Outbound SNAT Persistence

Introduction

F5 BIG-IP is synonymous with "flexibility". You likely have few other devices in your architecture that provide the breadth of capabilities that come native with the BIG-IP platform. And for each and every BIG-IP product module, the opportunities to expand functionality are almost limitless. In this article series we examine the flexibility options of the F5 SSL Orchestrator in a set of "advanced" use cases.

If you haven't noticed, the world has been steadily moving toward encrypted communications. Everything from web, email, voice, video, chat, and IoT is now wrapped in TLS, and that's a good thing. The problem is, malware - that thing that creates havoc in your organization, that exfiltrates personnel records to the Dark Web - isn't stopped by encryption. TLS 1.3 and multi-factor authentication don't eradicate malware. The only reasonable way to defend against it is to catch it in the act, and an entire industry of security products are designed for just this task. But ironically, encryption makes this hard. You can't protect against what you can't see. F5 SSL Orchestrator simplifies traffic decryption and malware inspection, and dynamically orchestrates traffic to your security stack. But it does much more than that. SSL Orchestrator is built on top of F5's BIG-IP platform, and as stated earlier, is abound with flexibility.

SSL Orchestrator Use Case: Outbound SNAT Persistence

It may not be the most obvious thing to think about persistence in the vein of outbound traffic. We are all groomed to accept that any given load balancer can handle persistence (or "affinity", or "stickiness") to backend servers. This is an important characteristic for sure. But in an outbound scenario, you don't load balance remote servers, so why on Earth would you need persistence? Well, I'm glad you asked. There indeed happens to be a somewhat unique, albeit infrequent use case where two different servers need to persist on YOUR IP address. The classic example is a site that requires federated authentication, where the service provider (SP) generates a token (perhaps a SAML auth request) and inside of that request the SP has embedded the client IP. The client receives this message and is redirected to the IdP to authenticate. But in this case the client is talking to the outside world through a forward proxy, and outbound source NAT (SNAT) could be required in this environment. That means there's a potential that the client IP address as seen from the two remote servers could be different. So if the IdP needs to verify the client IP based on what's embedded in the authentication request token, that could possibly fail. The good news here is that federated authentication doesn't normally require client IP verification, and there aren't many other similar use cases, but it can happen. The F5 BIG-IP, as with ANY proxy server, load balancer, or ADC device, clearly supports server affinity, and in a highly flexible way. But, as with ANY proxy server, load balancer, or ADC device, that doesn't apply to SNAT addresses. Nevertheless, the F5 BIG-IP can be configured to do this, which is exactly what this article is about. We're going to flex some BIG-IP muscle to derive a unique and innovative way to enable outbound SNAT persistence.

What we're basically talking about is ensuring that a single internal client persists a single outbound SNAT IP address, when and where needed, and as long as possible. It's important to note here that we're not really talking about persistence in the same way you think about load balanced server affinity. With affinity, you're stapling a single (remote) client "session" to a single load balanced server. With SNAT persistence, you're stapling a single outbound SNAT IP to a single internal client so that all remote servers see that same source address. Same-same but different-different. To do this we'll need a SNAT pool and an iRule. We need the SNAT pool to define the SNAT addresses we can use. And since SNAT pools don't provide a persistence option like regular pools do, we'll use an iRule to provide the stickiness. It's also worth noting here, again since we're not really talking about load balancing stickiness, that the IP persistence mechanism in the iRule may not (likely will not) evenly distribute the IPs in the SNAT pool. Your best bet is to provide as many SNAT pool IPs as possible and reasonable. The good news here is that, because you're using a BIG-IP, you can define exactly how you assert that IP stickiness. In most cases, you'll probably just want to persist on the internal client IP, but you could also persist on:

  • Client source address and remote server port
  • Client source address and remote destination addresses
  • Client source, day of the week, the year+month+day % mod 2, a hash of the word-of-the-day...and hopefully you get the idea. Lot's of options.

To make this work, let's start with the SNAT pool. Navigate to Local Traffic -> Address Translation -> SNAT Pool List in the BIG-IP and click Create. In the Member List section, add as many SNAT IPs as you can afford. Remember, these are going to be IPs on your outbound VLAN, so in the same subnet as your outbound VLAN self-IP.

Figure: SNAT pool list

You don't need to assign the SNAT pool to anything directly. The iRule will handle that. And now onto the iRule. Navigate to Local Traffic -> iRules -> iRule List in the BIG-IP, and click Create. Copy the following into the iRule editor:

when RULE_INIT {
   ## This iRule should be applied to your SSLO intercaption rule ending with in-t-4. 
   catch { unset -nocomplain static::snat_ips }
	    
   ## For each SNAT IP needed define the IP versus dynamically looking it up. 
   ## These need to be in the real SNAT pool as well so ARP works. 
   set static::snat_ips(1) 10.1.20.50
   set static::snat_ips(2) 10.1.20.51
   set static::snat_ips(3) 10.1.20.52
   set static::snat_ips(4) 10.1.20.53
   set static::snat_ips(5) 10.1.20.54
	    
   ## Set to how many SNAT IPs were added
   set static::array_size 5
}
when CLIENT_ACCEPTED priority 100 {
   ## Select and uncomment only ONE of the below SNAT persistence options

   ## Persist SNAT based on client address only
   snat $static::snat_ips([expr {[crc32 [IP::client_addr]] % $static::array_size}])
   
   ## Persist SNAT based on client address and remote port
   #snat $static::snat_ips([expr {[crc32 [IP::client_addr] [TCP::remote_port]] % $static::array_size}])

   ## Persist SNAT based on client address and remote address
   #snat $static::snat_ips([expr {[crc32 [IP::client_addr] [IP::local_addr]] % $static::array_size}])
}

Let's take a moment to explain what this iRule is actually doing, and it is fairly straightforward.

  • In RULE_INIT, which fires ONCE when you update the iRule, the members of the defined SNAT pool are read into an array. Then a second static variable is created to store the size of the array. These values are stored as static, global variables.
  • In CLIENT_ACCEPTED we set a priority of 100 to control the order of execution under SSL Orchestrator as there is already a CLIENT_ACCEPTED iRule event on the topology (we want our new event to run first). Below that you're provided with three choices for persistence: persist on source IP only, source IP and destination port, or source IP and destination IP. You'll want to uncomment only ONE of these. Each basically performs a quick CRC hash on the selected value, then calculates a modulus based on the array size. This returns a number within the size of the array, that is then applied as the index to the array to extract one of the array values. This calculation is always the same for the same input value(s), so effectively persisting on that value. The selected SNAT IP is then fed to the 'snat' command, and there you have it.

As stated, you're probably only going to need the source-only persistence option. Using either of the others will pin a SNAT IP to a client IP and protocol port (ex. client IP:443 or client IP:80), or pin a SNAT IP to a specific host (ex. client IP:www.example.com), respectively. At the end of the day, you can insert any reasonable expression that will result in the selection of one of the values in the SNAT pool array, so the sky is really the limit here.

The last step is easiest of all. You need to attach this iRule to your SSL Orchestrator topology. To do that. navigate to SSL Orchestrator -> Configuration in the UI, select the Interception Rules tab, and click to edit the respective outbound interception rule. Scroll to the bottom of this page, and under Resources, add the new iRule to the Selected column. The order doesn't matter. Click Deploy to complete the change, and you're done. You can do a packet capture on your outbound VLAN to see what is happening.

tcpdump -lnni [outbound vlan] host 93.184.216.34

And then access https://www.example.com to test. For your IP address you should see a consistent outgoing SNAT IP. If you have access to a Linux client, you can add multiple IP addresses to an interface and test with each:

ifconfig eth0:1 10.1.10.51
ifconfig eth0:2 10.1.10.52
ifconfig eth0:3 10.1.10.53
ifconfig eth0:4 10.1.10.54
ifconfig eth0:5 10.1.10.55

curl -vk https://www.example.com --interface 10.1.10.51
curl -vk https://www.example.com --interface 10.1.10.52
curl -vk https://www.example.com --interface 10.1.10.53
curl -vk https://www.example.com --interface 10.1.10.54
curl -vk https://www.example.com --interface 10.1.10.55

And again there you have it. In just a few steps you've been able to enable outbound SNAT persistence, and along the way you have hopefully recognized the immense flexibility at your command.

 

Updated Jan 08, 2024
Version 2.0
  • Is there another place to put this iRule, possibly?

     

    It looks like anything that is in our "bypass" interception list (to support certificate auth, for example) is getting snatted with different addresses for each request, and only requests that are intercepted are getting this persistence applied.

     

    I opened a case, just reaching out here in case anyone else has run into this.

  • The only place it CAN go is on the "-in-t-" interception rule. Traffic egresses through this virtual server.

     

    When you say "bypass interception list", how is this configured? Is this a server IP subnet rule in the security policy? Separate bypass topology? Where do you have the iRule applied now?

  • Thanks for the reply.

     

    So, in our SSLO topology, there is a security policy rule in place to let certain types of traffic (based on categories like Office 365, other US .gov and .mil sites) that bypass interception instead of intercepting the traffic. (Mutual TLS)

     

    It appears that only traffic that is TLS intercepted traffic is able to make use of this persistence iRule.

     

    I did open a case, but thought maybe someone else may have figured out this use case.

     

    --mike

  • Thanks Kevin!  Question on SNAT with two topoligies.  I have an inspect topology and a bypass topology but in my testing they get different SNAT IP's.  Is there a way to ensure a source address will get the same SNAT reguardless of topology.

    -Kraig