DevCentral Connects Lab Day: iRules Challenge

Well hello, there, community! If you don't follow the DevCentral Connects show, you should check us out, we're live every Thursday at 12:30 pacific. On today's show, we blitzed through some iRules fundamentals and introduced the following challenge.

The Background

Little Jimmy Packets has been captured while skiing at Keystone, Colorado by Bad Actor Bob, who has created several malicious clones of him and placed them around the globe, one each in Brazil, Australia, Egypt, and Germany, and Canada. Bad Actor Bob has also infected all other visitors from the numeric land of 172.16.2.0/23. The fine people of Celestial City are waiting for Jimmy Packets arrival, but you need to make sure that the gatekeeper, Mr BIG-IP, only allows the real Jimmy Packets in! Can you help?

The Problem

Mr BIG-IP doesn’t have any idea how to differentiate the clones from the real thing. You need to equip him by uploading an iRule to his inspection engine to make sure he accurately carries out his duties as the gatekeeper of Celestial City.

The Details

  • All requests for Celestial City, https://celestial-city.devcentral.test, should be routed to the welcome wagon (pool: welcome_wagon) UNLESS:
  • The user agent has jimmy-packets in the string value AND the request is from Australia, Germany, Egypt, Brazil, or Canada
  • The source traffic matches the numeric land of 172.16.2.0/23
  • If a clone is detected, respond to them with an enlightening message about their cloned nature AND log the following to a remote system:
  • Their false identity as a clone
  • Their source IP address
  • Their country of origin
  • Their full user agent
  • If the easy_street pool is available, route the real Jimmy Packets there, he's hungry and there is fine dining on easy street.
  • If the top level domain is correct, but the subdomain is not, log only the subdomain and respond to the request with a meme of your choice. They should not pass go or collect $200. (ie..they should be denied entrance)

Lab Requirements

  • A BIG-IP with LTM licensed
  • A lightweight web server (unless you're clever)
  • An HTTP client for test traffic
  • curl
  • postman
  • vscode thunderclient
  • A can-do attitude

The Solution

I like to start with a sketch or drawing before I code to make sure I have the high level stuff worked out. Note that all the details of the challenge are not represented here.

There are many approaches you could take to this, but I tried to keep it fairly simple. I'll share the code first, then make some comments afterward.

when RULE_INIT {
    set static::countries [list AU BR CA DE EG]
}
when CLIENT_ACCEPTED {
    if { [IP::addr [IP::client_addr] equals 172.16.2.0/23] } {
        set numland_responder 1
    }
    if { [active_members easy_street] >= 1 } {
        set ezstreet 1
    }
}
when HTTP_REQUEST {
    # Shown here for purposes of the challenge, but welcome_wagon would be default
    # pool welcome_wagon
    if { [HTTP::host] ne "celestial-city.devcentral.test" } {
        set dom_responder 1
        set subdomain [string range [HTTP::host] 0 [expr {[string first ".devcentral.test" [HTTP::host]] - 1 }]]
    }
    if { [string tolower [HTTP::header User-Agent]] contains "jimmy-packets"} {
        if { [lsearch -exact $static::countries [whereis [IP::client_addr] country]] != -1 } {
            set clone_responder 1
        }
        if { [info exists ezstreet]} {
            pool easy_street
        }
    }
    # Response Handlers
    if { [info exists dom_responder] } {
        log local0. "Unauthorized: Wrong subdomain from [IP::client_addr]: $subdomain"
        HTTP::respond 401 content "You used the wrong domain, so you get NOTHING! YOU LOSE! GOOD DAY SIR!"
    } elseif { [info exists clone_responder] } {
        log local0. "Unauthorized: Clone from [whereis [IP::client_addr] country], IP: [IP::client_addr], UA: [HTTP::header User-Agent]"
        HTTP::respond 401 content "You are a clone and thus not welcome here. Take it up with Bad Actor Bob."
    } elseif { [info exists numland_responder] } {
        log local0. "Unauthorized: IP [IP::client_addr] matches numeric land infection zone"
        HTTP::respond 401 content "You are infected and thus not welcome here. Take it up with Bad Actor Bob."
    }
}

Solution Commentary

I'm super rusty! I wrote the bones of this iRule before connecting to the BIG-IP and I can't tell you how many syntax errors I had. Use it or lose it, right? Ok, working through my solution...

  • Noted in the iRule, but I'll repeat it here: there is no need to specify the welcome_wagon pool in the iRule as I am using that as the default pool in the virtual server configuration, but to make sure it's explicitly shown, I include it but have that commented out
  • All this could be accomplished in the single HTTP_REQUEST event, but I like handling data closer to where it belongs
  • The clone source countries could have been a data group, but for this exercise I didn't want to abstract that from visibility in the final solution, so I used a list in RULE_INIT since that won't change at the connection level
  • For the three reject conditions, I could have used a single responder variable with strings and then used a switch statement, and maybe if I were writing this for prod I'd do so, but in the end I chose clarity
  • I separated out the interrogation of the requests from the response handling so the logic is clear top to bottom. Doing so requires more variables, but I chose readability and flow over performance here
  • You can actually do the IP::addr comparison in six different ways (did you know that?) but the format I show here is the most performant
  • I'm sure there's a more efficient way I'm not remembering to find the subdomain in its entirety than my string range math, but since I knew the single host name I was looking for, I didn't need to use the domain command to isolate it
  • The lsearch command was far more performant than using the contains operator in my limited testing for comparing the source country to my list, even with the list being so short. I'd anticipate even more savings on far larger lists
  • Using the info exists command is a nice way to not have to set variables to "present" status, allowing you to only set the true condition
That's all I have. What choices should I rethink? What choices did you make? Hit me up in the comments below!
Published Aug 19, 2021
Version 1.0

Was this article helpful?

No CommentsBe the first to comment