iRule HTTP Referer

This is a simple iRule that performs redirects based on HTTP Referer:

when HTTP_REQUEST { 
if { ([HTTP::header exists "Referer"]) and 
    ([URI::host [HTTP::header value Referer]] eq"special.com") } {
HTTP::redirect https://www.site.com/special-client/
} else {
HTTP::redirect https://www.site.com/generic-client
}
}

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 – 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 – Secure & HTTPOnly Cookie

The following iRule taken from devcentral.f5.com was utilized to insert the “Secure” tag to all the cookies within the Response Header. Note that some part of the iRule has been “deactivated” as this part involves adding the “HTTPOnly” cookie tag which isn’t required for this customer. I was able to verify its functionality in 10.x code version (also works in 11.x):

when HTTP_RESPONSE {
if { [catch { set CK_VALUE [HTTP::header values "Set-Cookie"]
HTTP::header remove "Set-Cookie"

foreach value $CK_VALUE {
if { "" != $value } {
set TEST_VALUE [string tolower $value]
set LENGTH_VALUE [string length $value]

switch -glob $TEST_VALUE {
"*;secure*" -
"*; secure*" { }
default { set value "$value; Secure"; }
}

switch -glob $TEST_VALUE {
 "*;httponly*" -
 "*; httponly*" { }
 default { set value "$value; HttpOnly"; }
 }

HTTP::header insert "Set-Cookie" $value

}
}
} ] } {
log local0. "Exception thrown, client_addr=[client_addr] HttpOnly and/or Secure cookie attributes may not have been set"
}
}

For 11.x code version, there is a simpler way of achieving this functionality as noted here: https://devcentral.f5.com/wiki/iRules.HTTP__cookie.ashx

HTTP::cookie httponly <name> [enable|disable]
HTTP::cookie secure <name> [enable|disable]

When the “HTTP::cookie” rules were tried out in a lab environment using the following iRule:

when HTTP_RESPONSE {
   HTTP::cookie version BigIP_Cookie 1
   HTTP::cookie httponly BigIP_Cookie enable 
}

I got the following error log:
TCL error: /Common/iRule-HTTPOnly-Secure <HTTP_RESPONSE> – Illegal argument (line 1) invoked from within “HTTP::cookie version BigIP_Cookie 1”

So, I removed the “cookie version” command:

when HTTP_RESPONSE {
   HTTP::cookie httponly BigIP_Cookie enable 
}

and got the following error:
TCL error: /Common/iRule-HTTPOnly-Secure – Improper version (line 1) invoked from within “HTTP::cookie httponly BigIP_Cookie enable”

It looks like the “HTTP::coookie version” function isn’t working as expected in 11.x code version. F5 Bug-id for this issue 338981.

This is an alternate iRule that seems to be working in 11.x code version

when HTTP_RESPONSE {
set COOKIE_VAL [HTTP::header values "Set-Cookie"]
HTTP::header remove "Set-Cookie"

foreach COOKIE_NAME $COOKIE_VAL {
HTTP::header insert "Set-Cookie" "${COOKIE_NAME}; Secure; HttpOnly"
}
}

Reference: Devcentral.

iRule – To iRule or Not to

TCL based iRule is a force-multiplier when it comes to Application Delivery Networking utilizing F5 devices. However, it is essential to understand the interaction of iRule with the normal application.

As an F5 iRule administrator, it is essential to understand the responsibility of maintaining the code. Does this fall within the realm of Application Delivery Engineers or DevOps ? This would depend on the organization and the roles for different people and frankly, there is no clear cut answer to this question.

Having said that, I believe iRule can be used effectively to perform the following functions based on the information available within the header or content of the packet, without affecting DevOps:

  • Load Balance to Pools of Servers
  • Persistence actions
  • Serving Sorry Page or Sorry URL

Load Balancing & Persistence actions will not alter the content of the packet and hence, it should be easily achieved without disrupting the Developers. The 2 functionalities provided do not affect the code but the header or content of the packet is utilized to pick the right server or pool of servers.

  • Redirects to specific URL
  • Changing contents of header or packet

For redirects and for altering contents of header or packet, it is essential that the Dev team is aware of the traffic flow. This will prevent any conflicting functionality that may disrupt the normal application delivery.

iRULE – True-Client-IP Block

Sometimes a customer may serve the content from a Virtual Server via Akamai and may want to block a specific client IP that is presented in the “True-Client-IP” that is inserted by Akamai. The following iRULE would provide the required functionality:

when HTTP_REQUEST { set HOST [string tolower [HTTP::host]] if { ([HTTP::header exists "True-Client-IP"]) and ([HTTP::header "True-Client-IP"] != "") } { set True_Client_IP [HTTP::header "True-Client-IP"] } else { set True_Client_IP 0.0.0.0 } if { [class match $True_Client_IP equals CLASS_BLOCK_IP] } { discard } }

Discard would silently drop the connection. Reject can be utilized instead of Discard. Reject would send a TCP reset to the client. Instead of Reject or Discard, we can also serve a sorry page or a redirect, if required.

iRule – Altering Header Information

This iRULE example will alter the incoming URI before passing the request to the servers:

when HTTP_REQUEST { switch -glob [HTTP::uri] { /old_URI/* { HTTP::uri /new_URI[HTTP::uri] } } }

In this case, for any incoming request that starts with the URI “/old_URI/” (http://domain.com/old_URI/), the “/old_URI/” will be replaced with “/new_URI/old_URI” and this will be passed to the servers (http://domain.com/new_URI/old_URI/)

The various interpretations within the switch statement:

/old_URI/      – URI equals /old_URI/
/old_URI/*    – URI starts with /old_URI/
*/old_URI/* – URI contains /old_URI/

Instead of the “switch” statement, we can also use an “if-statement” like this:

when HTTP_REQUEST { if { [HTTP::uri] starts_with "/old_URI/" } { HTTP::uri /new_URI[HTTP::uri] } }

A slightly more complex version of the URI function alteration is provided here:

when HTTP_REQUEST { set HOST [string tolower [HTTP::host]] set URI [string tolower [HTTP::uri]] if { $URI contains "/NEW_Session_ID=" } {HTTP::uri [string map {/NEW_Session_ID= /OLD_Session_ID=} [HTTP::uri]] pool POOL-WEB-Server } } when HTTP_RESPONSE { if { [HTTP::header values Location] contains "/OLD_Session_ID=" } { HTTP::header replace Location [string map {/OLD_Session_ID= /NEW_Session_ID=} [HTTP::header value Location]] } }

For any incoming HTTP Request, “/NEW_Session_ID=” within the URI is replaced with “/OLD_Session_ID=” and passed to the servers in the pool “POOL-WEB-Server”.

For any HTTP Response from the server to the client that contains the HTTP Header Location, “/OLD_Session_ID=” is replaced with “/NEW_Session_ID=”

This can be used to “mask” the URI or any other header information between the client and the server.

The following iRule will remove “/m/” in the incoming URI and send a redirect:


when HTTP_REQUEST {
set URI [string tolower [HTTP::uri]]
if {$URI starts_with "/m/" }{ 
set NEW_URI [string map {"/m/" "/"} [HTTP::uri]] 
HTTP::respond 301 Location "http://www.domain.com$NEW_URI"
}
}

TEST:

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

The “/m/” in the URI is replaced with “/” as seen in the “Location” header.

This has been tested in production environment on 10.x code of F5 LTM

iRULE – non-English Characters

The web browser will URL encode URI’s that contain special characters.

For example, http://www.domain.com/été is encoded as follows: http://www.domain.com/%C3%A9t%C3%A9

when HTTP_REQUEST { 

set ENCODED_URI [ b64encode [HTTP::uri]]

    switch [HTTP::host] { "domain.com" { 

          if { (($ENCODED_URI eq "LyVDMyVBOXQlQzMlQTk=") or ($ENCODED_URI eq "L2ZyLyVDMyVBOXQlQzMlQTk=")) } 

{ pool POOL_Web-Servers } 

} 

}

}

“/été” URL encodes to “/%C3%A9t%C3%A9″ which base64 encodes to “LyVDMyVBOXQlQzMlQTk=”

“/fr/été” URL encodes to “/fr/%C3%A9t%C3%A9” which base64 encodes to “L2ZyLyVDMyVBOXQlQzMlQTk=”