The Advanced Firewall Manager (AFM) is a powerful network security solution in its own right. The policy flexibility in AFM enables the majority of functionality necessary to protect your applications as is. But for those complex cases where you need to turn the dial just a little further than a built-in feature allows, well that’s where my little friend iRules comes into play. How to write iRules is well covered in other articles. In this article, rather than re-invent that wheel, we’ll dive right into the specifics of a couple use cases that show how iRules can extend your AFM policies. Note that all these iRules are applied to AFM policies so they are being processed by a subset of traffic matching the policy rules.

DNS Traffic Steering via AFM iRule

In this design, a select few network elements need to ignore portions of the NS responses in favor of another’s authoritative role – but only a couple of the queries are forwarded and the rest are to be answered by a pre-existing fallback BIND pool. The trick is that the solution is temporary, as these exceptions will go away over time. There’s no way to do exactly this out of the box, so, instead of analyzing every packet for source IP with an iRule on the listener VIPs, this architecture places a firewall rule on the listener which relies on an address list to trigger an iRule which then snats that traffic and forwards to the other authoritative pool for processing. The policy requires a lone firewall role with the exception list and the iRule reference.

when DNS_REQUEST {
   DNS::disable all
   switch  -glob "[string tolower [DNS::question name]]" {
      “*.abc10.def20.*" {
         pool AAUTH_NS
         snatpool DNS_OUT
      }
   }
}

The iRule is a very simple iRule, but coupled with the equally simple policy rule, it enables a very powerful solution.

Allow HTTP Block Response for ACL Drop Action

This is a cool solution that takes what would normally be a silent drop and extends a service to layer 7 to allow for an application layer response. This rule is intended to be added to a firewall rule that the traffic is known to be coming from a NAT'd source such as a CDN provider or some other proxy. If it is applied to a drop ACL in the L3/4 firewall policy you have to allow the traffic in order for the BIG-IP to continue processing up to L7 to respond with the iRule. If you block L3/4 traffic from a CDN provider based on one bad actor you potentially kill thousands of connections. When would this ever happen you ask? How about SNAT'ing from original source to the X-Forwarded-For header. XFF needs to exist and and the ACL action disabled. This essentially tells me that the Firewall policy is telling me to respond @L7

when FLOW_INIT {
  if { [ACL::action] equals "drop" } {
    ACL::action allow
    log local0. "L3/4 FW Policy Disabled"
  }
}
when HTTP_REQUEST {
  if {([HTTP::header exists "X-Forwarded-For"])} {
    HTTP::respond 401 content {BLOCKEDThis content is blocked due to export restrictions
      
    }
  log local0. "401 Block ACL iRule was hit"
  }
  drop
}

Because this is a firewall and we're only calling this based on an ACL telling us to drop, the drop command at the end of the iRule is there to drop anything else just to be safe.

Throttling SSH Connections

The use case here is to avoid too many SSH login attempts from a specific IP. This iRule would be applied to a policy rule matching the ssh ports (default: 22) in your environment. It basically uses tables to do counting by source IP and starts blocking if it exceeds the threshold.

when RULE_INIT {
  # Allow this number of ssh connections
  set static::number_ssh 4
  # For this amount of time
  set static::short_life 60
  # If more than the above rate then block all for this time
  set static::long_life 300
}
when CLIENT_ACCEPTED {
 set key "[IP::client_addr]:[IP::local_addr]"
 set val [table lookup -notouch -subtable ssh_hosts_long $key]
 if {$val == 1} {
   #log local0. "Dropped [IP::client_addr] -> [IP::local_addr] for the long haul."
   drop
  } else {
    set val2 [table lookup -notouch -subtable ssh_hosts_short $key]
    # log local0. "debug1 [IP::client_addr] -> [IP::local_addr] val2: $val2 "

   if { $val2 == "" } {
       #log local0. "debug2 [IP::client_addr] -> [IP::local_addr] went into table "
       table set -subtable ssh_hosts_short $key 0 $static::short_life
    }
    table incr -notouch -subtable ssh_hosts_short $key
    #log local0. "Incr [IP::client_addr] -> [IP::local_addr] ($val2 / $static::number_ssh)"

    set val2 [table lookup -notouch -subtable ssh_hosts_short $key]
    #log local0. "debug3 [IP::client_addr] -> [IP::local_addr] value: $val2 "

    if {$val2 >= $static::number_ssh} {
      table set -subtable ssh_hosts_long $key 1 $static::long_life
      #log local0. "Dropped [IP::client_addr] -> [IP::local_addr] for the short haul.($val2 / $static::number_ssh)"
      drop
    }
  }
}

For Further Study

Here are a few of many other use cases that I’ve seen mentioned that you might fire AFM up in your lab and go mad scientist on:

  • Custom idle timers
  • DSCP-based rules
  • Time-based rate classes for batch/backup traffic

What use cases are you cooking up? Share below in the comments!