F5 iRule – URI Masking

Requirement:

Client sends request to http://xyz.com/

Server needs to process http://xyz.com/append but client should only see http://xyz.com/ i.e., the URI  /append should not be visible to the client.

when HTTP_REQUEST { 
if { ([HTTP::host] equals "www.xyz.com") and ([HTTP::uri] eq "/") } { 
HTTP::uri "/append" 
} 
} 
when HTTP_RESPONSE {
if { [HTTP::header values Location] contains "/append" } {
HTTP::header replace Location [string map {/append /} [HTTP::header value Location]]
}
}

The F5 will complete the following steps using the iRule provided above:

F5 will add URI “/append” to the incoming request.

F5 will replace “/append” with “/” in the response from the server to the client.

Reference:

Mask URI – Devcentral Thread

Github

iRule – Redirects

#Different Redirects

########################
 302 Redirects
########################
when HTTP_REQUEST {
    HTTP::redirect https://www.domain.com/
}

########################
 301 Redirects
########################
when HTTP_REQUEST {
    HTTP::respond Location 301 https://www.domain.com/
}

############################
IF-Conditional Redirect:
############################
# Matching a condition
when HTTP_REQUEST {
    if {[HTTP::host] eq "domain.com"} {
        HTTP::respond Location 301 https://www.domain.com/
    }
}

# NOT matching a condition
when HTTP_REQUEST {
    if { not ([HTTP::host] eq "domain.com") } {
        HTTP::respond Location 301 https://www.domain.com/
    }
}

# Multiple conditions
when HTTP_REQUEST {
    if { ([HTTP::host] eq "domain.com") and ([HTTP::uri] eq "/login")} {
        HTTP::respond Location 301 https://www.domain.com/login/
    }
}

#if & elseif
when CLIENT_ACCEPTED {
   set default_pool [LB::server pool]
}
when HTTP_REQUEST {
    if { ([HTTP::host] eq "domain1.com") } {
        HTTP::respond Location 301 https://www.domain1.com/login/
    } elseif { ([HTTP::host] eq "domain2.com") } {
        HTTP::respond Location 301 https://www.domain1.com/login/
    } else {
        pool $default_pool
    }
}

##################################
Switch-Conditional Redirect:
##################################

#Check multiple unique domains
when CLIENT_ACCEPTED {
   set default_pool [LB::server pool]
}
when HTTP_REQUEST {
   switch -glob [HTTP::path] {
      "domain1.com" {
         HTTP::respond Location 301 https://www.domain1.com/
      }
      "domain2.com" {
         HTTP::respond Location 301 https://www.domain2.com/
      }
      default {
         pool $default_pool
      }
   }
}

#Redirect to same URL
when CLIENT_ACCEPTED {
   set default_pool [LB::server pool]
}
when HTTP_REQUEST {
   switch -glob [HTTP::path] {
      "domain1.com" -
      "domain2.com" {
         HTTP::respond Location 301 https://www.domain.com/
      }
      default {
         pool $default_pool
      }
   }
}

############################
Data Group
############################

class CLASS_HSF { 
   { 
      "/str1" { "domain1.com" } 
      "/str2" { "domain2.com" } 
   } 
}


when CLIENT_ACCEPTED {
set DEFAULT [LB::server pool]
}

when HTTP_REQUEST {                                                         
set HOST [string tolower [HTTP::host]]                                      
set URI [string tolower [HTTP::uri]]                                                               
                                                                            
if  { $HOST equals "www.domainhs.com" }{                                         
    HTTP::respond 301 Location "http://www.domain.com[HTTP::uri]"
    } elseif { [class match $URI starts_with CLASS_HSF] } {
        set DOMAIN [class match -value $URI contains CLASS_HSF]
        HTTP::respond 301 Location "http://$DOMAIN"
    } else {
        pool $DEFAULT
    }
}

Reference:

Github Link

 

F5 iRule – URI, Path & Query

F5 iRule has the following 3 command list that can be a bit confusing. This is a short post to remember the differences between the 3 of them.

[HTTP::uri] – everything from “/” after the domain name to the end.

[HTTP::path]– everything from “/” after the domain name to the character before the “?”

[HTTP::query]– everything after the “?”

[HTTP::host] – domain name

In short:
[HTTP::uri] == [HTTP::path] + ? + [HTTP::query]

Example:
http://www.example.com/main/index.jsp?user=test&login=check

 [HTTP::uri]   - URI:   /main/index.jsp?user=test&login=check
 [HTTP::path]  - PATH:  /main/index.jsp
 [HTTP::query] - Query: user=test&login=check
 [HTTP::host]  - Host:  www.example.com

iRULE – String Usage


when HTTP_REQUEST {
set URI [string tolower [HTTP::uri]]

if {$URI starts_with "/m/" }{
set NEW_URI [string range [HTTP::uri] 2 end]
HTTP::respond 301 Location http://www.domain.com$NEW_URI
}
}


$ curl -Ik http://10.10.10.10/m/URI
HTTP/1.0 301 Moved Permanently
Location: http://www.domain.com/URI
Server: BigIP
Connection: Keep-Alive
Content-Length: 0

Replacing

set NEW_URI [string range [HTTP::uri] 2 end]

with

set NEW_URI [string trimleft [HTTP::uri] /m/]

will provide the following output:


$ curl -Ik http://10.10.10.10/m/URI
HTTP/1.0 301 Moved Permanently
Location: http://www.domain.comURI
Server: BigIP
Connection: Keep-Alive
Content-Length: 0

Note the lack of “/” in the “Location” header, just before the start of URI.

In order to retain the “/”, I did a string trimleft with “/m” instead of “/m/” i.e.,

set NEW_URI [string trimleft [HTTP::uri] /m]

instead of

set NEW_URI [string trimleft [HTTP::uri] /m/]

However, the result was the same.

Based on this devcentral article, it looks like “string range” is a better option as “string trim” tends to trim individual characters as instead of the “string of characters”.

Another issue to remember is that if we use “string trim”, we get the following output:

$ curl -Ik http://10.10.10.10/m/mURI
HTTP/1.0 301 Moved Permanently
Location: http://www.domain.comURI
Server: BigIP
Connection: Keep-Alive
Content-Length: 0

and “string range” provides the following:

$ curl -Ik http://10.10.10.10/m/mURI
HTTP/1.0 301 Moved Permanently
Location: http://www.domain.com/mURI
Server: BigIP
Connection: Keep-Alive
Content-Length: 0

So, if you are just looking to remove specific “string of characters”, “string range” is a better option based on testing on 11.x code version.

F5 iRule – User Agent


when HTTP_REQUEST {
set HOST [string tolower [HTTP::host]]

if { ($HOST eq "domain.com" || $HOST eq "www.domain.com" ) and 
         ([HTTP::header User-Agent] contains "Chrome/33.0.175") and
            ([HTTP::header User-Agent] contains "(X11; Linux x86_64)") } {
                reject
               }
}

The above iRule will enable the F5 to send a TCP RST (reset) to the client for a specific domain and specific User-Agent information within the incoming HTTP Request.

F5 iRule – URI Encoded


when HTTP_REQUEST {

if { [active_members [LB::server pool]] < 1 } {

set STATIC_SORRY [URI::decode "%3C!DOCTYPE%20HTML%3E%0A%3Chtml%3E%0A%3Chead%3E%0A%3Cmeta%20charset%3D%22UTF-8%22%3E%0A%3Ctitle%3EOops!%20We%27ll%20be%20back%20shortly%3C%2Ftitle%3E%0A%3Cstyle%3E%0A*%20%7B%09margin%3A0%3Bpadding%3A%200%3B%7D%0Ahtml%2C%20body%7B%20height%3A100%25%3B%20color%3A%20%23333%3B%20font-family%3AHelvetica%2C%20Arial%2C%20sans-serif%3B%7D%0A.wrapper%20%7Bmax-width%3A%20960px%3Bwidth%3A100%25%3Bmargin%3A0%20auto%3Bdisplay%3Atable%3Bheight%3A100%25%3B%7D%0A.container%20%7Bdisplay%3A%20table-cell%3Bvertical-align%3A%20middle%3Btext-align%3Acenter%3B%7D%0Ah1%20%7Bfont-size%3A60px%3B%7D%0Ah2%20%7Bfont-weight%3Anormal%3Bfont-size%3A60px%3Bmargin-bottom%3A0.36em%3B%7D%0Ap%20%7Bline-height%3A1.4em%3Bmargin%3A0%200%201.3em%200%3B%7D%0A%3C%2Fstyle%3E%0A%3C%2Fhead%3E%0A%3Cbody%3E%0A%3Cdiv%20class%3D%22wrapper%22%3E%0A%3Cdiv%20class%3D%22container%22%3E%0A%3Ch1%3EOops!%3C%2Fh1%3E%0A%3Ch2%3EWe%27ll%20be%20back%20shortly.%3C%2Fh2%3E%0A%3Cp%3ESorry%20for%20the%20inconvnience%2C%20please%20try%20again%20in%20a%20few%20minutes%3C%2Fp%3E%0A%3C%2Fdiv%3E%0A%3C%2Fdiv%3E%0A%3C%2Fbody%3E%0A%3C%2Fhtml%3E"]

HTTP::respond 200 content $STATIC_SORRY
}
}

The above iRule can be utilized in order to serve a sorry page when all the pool members in the default pool (pool attached to the Virtual Server) are down.

The “STATIC_SORRY” is a variable that contains the URI Encoded version of the Sorry Page. Encoding the sorry page would help is the normal HTML page is not accepted by the F5. F5 can be finicky and not accept any HTML page that contains non-English characters. Hence, URI Encoded page can be of help in these cases.

This page can be utilized to encode the Sorry Page: URI Encoder/Decoder

F5 iRule – Serving Sorry Image

There are 3 steps to creating the F5 iRule to serve the image of a sorry page. Serving image of sorry page is utilized when you need to serve non-English characters which may not be compatible with F5. Of course, you can use it even for simple sorry pages with English characters.

There are 3 main steps:

  • Create a base64 image of the original Sorry Page Image
  • Create a datagroup that references the base64 Sorry Page Image
  • Create an iRule that reference the datagroup and attach this to the Virtual Server that requires the Sorry Page iRule.

 

Create a base64 image of the original Sorry Page Image:

  1. Copy the image (SORRY_IMAGE.png – this should be less than 46KB in size) onto F5’s directory /var/tmp/SORRY_IMAGE.png
  2. The following command should be copied to the F5 shell as a single line in order to create the base64 image
    echo \"SORRYIMAGE\" \:\= \"`base64 /var/tmp/SORRY_IMAGE.png | xargs echo | sed 's/ //g'`\"\, > /var/class/SORRY.class
  3. Check /var/class/SORRY.class
    [root@f5-ltm:Active] config # head /var/class/SORRY.class
    "SORRYIMAGE" := "iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAMAAACNZOU/A ...... <<remaining output omitted>>

 

Create a datagroup that references the base64 Sorry Page Image via GUI:

Name: SORRY_PAGE
Partition: Common
Type: (External File)
Path / Filename: /var/class/SORRY.class
File Contents: String
Key/Value Pair Separator: :=
Access Mode: Read/Write

 

Create an iRule:

when HTTP_REQUEST {
  if { ([active_members [LB::server pool]] < 1)} {
      HTTP::respond 503 content [b64decode [class element -value 0 SORRY_PAGE]]
      event disable
     }
}

iRule references the datagroup SORRYPAGE which references the file /var/class/SORRY.class and this contains the base64 image of the sorry page. SORRY_IMAGE.png which is utilized to create the base64 image should not be more than 46KB. This is a limitation on the F5 LTM.

 

iRule – Persistence Across HTTP Method


when HTTP_REQUEST {
set HOST [string tolower [HTTP::host]]

#Persistence is enabled for specific domain
if { $HOST equals "domain.com" }{

#Persistence for GET Request
if { [HTTP::method] equals "GET" } {
set SESSIONID [findstr [HTTP::uri] "uuid=" 5 20]
set KEY [crc32 $HOST$SESSIONID]

persist hash $KEY 30

#Collect Data upto Content Length or 1048
} elseif { (([HTTP::method] equals "POST") and ([HTTP::uri] contains "/ptm/tentxml.jsp")) } {

if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= 1048 } {
HTTP::collect $CONTENT
}
}
}

}

#Persist based on information available in the POST data
when HTTP_REQUEST_DATA {
if { (([HTTP::method] equals "POST") and ([HTTP::uri] contains "/ptm/tentxml.jsp")) } {

set HOST [string tolower [HTTP::host]]
set SESSIONID [findstr [HTTP::payload] "uuid=" 5 20]
set KEY [crc32 $HOST$SESSIONID]

persist hash $KEY 30
}
}

In the above iRule, persistence is maintained across HTTP GET & POST Methods. The Session id is present within:

URI of GET Request

POST Data

Persistence key is a combination of the domain & session id – set KEY [crc32 $HOST$SESSIONID]

In order to identify the Session id within the POST data, we will be collecting the data of size defined by the “Content-Length” header or default of 1048 bytes.

The iRULE would have to be utilized within an UIE persistence profile with “Match Across Services” for persistence across HTTP & HTTPS Virtual Servers.

 

F5 iRule – JSession ID

The following is a simple iRule that provides persistence based on JSessionID that may be present in the incoming URI or within the Cookie:


when HTTP_REQUEST {

# Check if the JSESSIONID cookie is present
if { [HTTP::cookie "JSESSIONID"] ne "" }{
persist uie [HTTP::cookie "JSESSIONID"]

} else {

# Cookie wasn't set or didn't have a value, so check for the session ID in the URI
set JSESS [findstr [HTTP::uri] "JSESSIONID" 11 ";"]
if { $JSESS != "" } {
persist uie $JSESS
}
}
}

when HTTP_RESPONSE {

# Check if the JSESSIONID cookie is present in the response and has a non-null value
if { [string map {\" ""} [HTTP::cookie "JSESSIONID"]] ne "" }{
#log local0. "JSessionID in Response: [HTTP::cookie "JSESSIONID"]"
#log local0. "Set-Cookie: [HTTP::header values Set-Cookie]"

# Persist on the JSESSIONID cookie value for X seconds
persist add uie [HTTP::cookie "JSESSIONID"]
}
}

F5_JSessionID

The “string map” command is utilized in the HTTP_RESPONSE event as the value for the JSESSION ID may contain the quotes “” instead of just an empty string:

Timestamp: Rule jsessionid_persist_v2_rule : Set-Cookie: {JSESSIONID=""; Domain=host.domain.com; Expires=Thu, 01-Jan-1970 00} 00 {10 GMT; Path=/; Secure}

Name the above iRule and add it to the Universal Profile as shown here:

JSessionID_Profile

“Match Across Services” is enabled when you have two Virtual Servers for HTTP & HTTPS traffic and you require persistence across them – SOL5837

Please, note that for any persistence that involves the header of the incoming packet, we would have to terminate the SSL Certificate & Key on the F5 in order to enable it to read & manipulate the encrypted header.

Reference:

JSession ID