Month: December 2013

iRule redirect to webpage based on f5 session count

f5

Recently a request came in from a customer who wanted to implement an iRule on their f5 device that sends users to a waiting room page if a session threshold is breached. I’ve worked with the f5’s in anger before and think they are great (along with VPX and StingRays too), but this wasn’t something I had done before so I was keen to get involved to see how this worked. The customer is a well known shop on Oxford Street and when they have busy periods like Christmas Comes Early or the Boxing Day Sales, their website gets hammered. I imagine the same happens on their website as when you see on the news all those people piling in to the shop when the shop open their doors when the sales start.

I looked on Dev Central to find who else had done this and sure enough there were a few different examples. To count sessions, you can use the table feature available in firmware 10.1 +. This is really good and opens the door to endless possibilities. Here’s a quick look at what it does..

when CLIENT_ACCEPTED {
    set tbl "connlimit:[IP::client_addr]"
    set key "[TCP::client_port]"

    if { [table keys -subtable $tbl -count] > 1000 } {
        event CLIENT_CLOSED disable
        reject

    } else {
        table set -subtable $tbl $key "ignored" 180
        set timer [after 60000 -periodic { table lookup -subtable $tbl $key }]
    }
}

when CLIENT_CLOSED {

    after cancel $timer
    table delete -subtable $tbl $key
}

This isn’t the iRule I used, but all this is doing is setting 2 variables – tbl and key – when a CLIENT_ACCETPED event is triggered, i.e. someone is connecting to your VS, the if statement then handles whether the connection is closed or the connection details are added to the f5 subtable with a time stamp set. I’ve just summarised that far too quickly, however, if you are keen to get involved with how this works, check out dev central and the examples there.

The next request from the customer was how they can set the thresholds – so for example, how many sessions are allowed before users are then redirected to a holding page. For this, I used a data group. A data group is a dictionary like vault that holds key value pairs. You can add a data group easily in the UI, so rather than changin values directly in the iRule itself, operational guys can change the value in the data group and the value then propagates itself to the iRule. Here is the iRule code that looks up the data group value.

set static::maxActiveClients "[class search -all -value datagroup_variables starts_with "max_active_clients"]"

I have a max_active_clients in the datagroup_variables data group which has a configured value which is then set to the static::maxActiveClients in the iRule. So if the value is changed in the data group, the variable value in the iRule is changed. It is important to have the variable in the iRule in an event that gets triggered regularly, like a HTTP_ACCEPT rather than an RULE_INIT event, otherwise you change the data group and the event doesn’t trigger and the value doesn’t change in the iRule.

Here is a example iRule close to what I used – the main difference is that this example does not use data groups and has logging turned on pretty much everywhere.


#timing on

# Count the number of HTTP sessions on a VS, and limit them. Use the table feature of 10.1 to track the number of
# current sessions
#
# This is a simple version of limiting the number of sessions to a website.
#
#  Notes
#   1. Cookies are opaque values. Keys into the session table only
#   2. No checksum is included in the cookie value
#   3. No additional checking is performed.
#   4. The cookie isn't set secure (So will work on HTTP, but you may want to change for an HTTPS only site)
#   5. There is no concept of 'LOGOUT' of a session... You can add this under HTTP_REQUEST...
#

when RULE_INIT {

   log local0. "Running RULE_INIT"
   # All of these parameters SHOULD be set from a class... So you can vary them and only require ONE iRule for all...

   # Max session count
   set static::max_active_clients 5

   # Prefix for session cookie (VS name will be appended)
   set static::sessionCookiePrefix "session"

   # Session (Idle) timeout in seconds
   set static::sessionTimeout 600

   log local0. "rule session_limit initialized: max: $static::max_active_clients cookieprefix: $static::sessionCookiePrefix timeout: $static::sessionTimeout"
}

when HTTP_REQUEST {
   set subtableName "sessionLimit-[virtual name]"
   set sessionCookieName "$static::sessionCookiePrefix-[virtual name]"
   set need_cookie 0

   ;# test cookie presence
   if {[HTTP::cookie exists $sessionCookieName]} {
      set client_id [HTTP::cookie $sessionCookieName]
         # Check the session still exists
         set sessiondata [table lookup -subtable $subtableName $client_id]
         if { $sessiondata != "" } {
         # We have a valid session... The lookup has reset the timeer on it so just finish processing
         # Optional processing in here to check or validate the client session via IP etc if required...
         log local0. "Valid session $client_id - continuing"
         return
      }
   }

   # No valid session... So do we have a free 'slot'?
   log local0. "No session. Checking for free slot (Max     $static::max_active_clients)"
   set sessionCount [table keys -subtable $subtableName -count]
   log local0. "No session. Checking for free slot (Current $sessionCount)"

   if {$sessionCount < $static::max_active_clients} {
      # Yes we have a free slot... Allocate it and note that we need to set the cookie on the client
      set need_cookie 1
      set client_id [format "%08d" [expr { int(100000000 * rand()) }]]
      set sessionValue [IP::client_addr]

      table add -subtable $subtableName $client_id $sessionValue $static::sessionTimeout
      log local0. "New Session ($client_id) added value $sessionValue Timeout $static::sessionTimeout"
   } else {
      HTTP::redirect "http://sorry.domain.com/"
   }
}

when HTTP_RESPONSE {
   ;# insert cookie if needed
   if {$need_cookie == 1} {
      HTTP::cookie insert name $sessionCookieName value $client_id path "/"
   }
}

Managing iptables using puppet

fw

The PuppetLabs firewall is awesome. I implemented it in one of our vCloud Director environments, however I also implemented in a previous company but I totally messed up the implementation until I did it again in the vCloud Director environment. Now I completely see how you should do it, and you should.

The basic crux of it is you should install the PuppetLabs module and have a pre and post firewall module; pre should look like this…

class fw::pre {
  Firewall {
    require => undef,
  }

  # Default firewall rules
  firewall { '000 accept all icmp':
    proto   => 'icmp',
    action  => 'accept',
  }
  firewall { '001 accept all to lo interface':
    proto   => 'all',
    iniface => 'lo',
    action  => 'accept',
  }
  firewall { '002 accept related established rules':
    proto   => 'all',
    state   => ['RELATED', 'ESTABLISHED'],
    action  => 'accept',
  }
  firewall { '003 ssh 22':
    port    => '22',
    proto   => 'tcp',
    action  => 'accept',
  }
}

…. and post should look like this

class fw::post {
  firewall { '999 drop all':
    proto   => 'all',
    action  => 'drop',
    before  => undef,
  }
}

What this does is set your default rules so that you can apply the pre and post fw modules in your site.pp, or other manifest file, as follows:

class { ['server_fw::pre', 'server_fw::post']: }

Now here is the clever bit. When you create say an apache module, within the module, you then assign the applicable iptables firewall rules required when the apache module gets installed. For example:

class apache (
  ...
  firewall { '100 allow http and https access':
    port   => [80, 443],
    proto  => tcp,
    action => accept,
  ...
  }

What this does is applies by default all the rules in fw::pre and fw::post and any specific rules which are contained within the module. Notice I have used rule number 100 for the apache firewall config. This means the iptables rules for apache get applied in between the pre (000 – 003) and the post (999) rules. Perfect! Never have to administer the firewall again 🙂

For further info, read up on the PuppetLabs firewall module – https://forge.puppetlabs.com/puppetlabs/firewall – that’s what I should have done in the first place.