content manipulation
44 TopicsRewrite Host Header to Server Name
Problem this snippet solves: On each HTTP request, the selected pool member IP address is looked up against a datagroup and the corresponding hostname is inserted in the HTTP host header. The iRule expects one string type datagroup to be defined: ip_to_host_dg If you're using a version lower than 10.0, see the second example. For versions lower than 9.4.4, change the class reference in the second iRule from ip_to_host_dg to $::ip_to_host_dg. Code : # Name : host_header_rewrite_rule # # HTTP host header rewrite rule # # BIG-IP LTM # Tested on version 11.1.0 (but should work on 10.x or 11.x) # # Version: 1.1 - Initial version tested # # Description: # # On each HTTP request, the selected pool member IP address is looked up against a datagroup and the corresponding hostname # is replaced in the HTTP host header. # # Configuration requirements: # # 1. This iRule expects a string datagroup named ip_to_host_dg to be defined with the following format: # #ltm data-group internal ip_to_host_dg { # records { # 1.1.1.1 { # data host1.example.com # } # 1.1.1.2 { # data host2.example.com # } # 1.1.1.5 { # data host3.example.com # } # } # type string #} # # 2. Each pool member must be configured and any time the pool membership changes, this data group must be updated. # when HTTP_REQUEST_SEND { # Log debug messages to /var/log/ltm? 1=yes, 0=no. set host_debug 1 # Need to force the host header replacement and HTTP:: commands into the clientside context # as the HTTP_REQUEST_SEND event is in the serverside context clientside { if {$host_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: [HTTP::method] request to [HTTP::host][HTTP::uri]"} # Look up the selected server IP in the datagroup to get the host header value set host_header_value [class match -value [IP::server_addr] equals ip_to_host_dg] if {$host_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Looked up [IP::server_addr], found: $host_header_value."} # Check if the lookup returned a value if {$host_header_value ne ""}{ # Replace the host header value HTTP::header replace Host $host_header_value if {$host_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Replaced Host header with $host_header_value."} } } } # v9.x # Name : host_header_rewrite_rule (v9.x) # # HTTP host header rewrite rule # # BIG-IP LTM # Tested on version 9.4.8 # # Version: 1.0 - Initial version tested # # Description: # # On each HTTP request, the selected pool member IP address is looked up against a datagroup and the corresponding hostname # is replaced in the HTTP host header. # # Configuration requirements: # # 1. This iRule expects a string datagroup named ip_to_host_dg to be defined with the following format: # # pool member IP | host header to insert # ---------------|------------------- # 1.1.1.1 | host1.example.com # 1.1.1.2 | host2.example.com # 1.1.1.5 | host3.example.com # # 2. Each pool member must be configured and any time the pool membership changes, this datagroup must be updated. # when HTTP_REQUEST_SEND { # Log debug messages to /var/log/ltm? 1=yes, 0=no. set host_debug 1 # Need to force the host header replacement and HTTP:: commands into the clientside context # as the HTTP_REQUEST_SEND event is in the serverside context clientside { if {$host_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: New [HTTP::method] request to [HTTP::host][HTTP::uri]"} # Look up the selected server IP in the datagroup to get the host header value set host_header_value [findclass [LB::server addr] ip_to_host_dg " "] if {$host_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Looked up [LB::server addr], found: $host_header_value."} # Check if the lookup returned a value if {$host_header_value ne ""}{ # Replace the host header value HTTP::header replace Host $host_header_value if {$host_debug}{log local0. "[IP::client_addr]:[TCP::client_port]: Replaced Host header with $host_header_value."} } } }4.1KViews1like3CommentsEncrypting Cookies
Problem this snippet solves: This example shows how to encrypt and decrypt a HTTP cookie from within an iRule. Using HTTP::cookie encrypt / decrypt Here is a cookie encryption iRule that works in a high-availability setup and is CMP-compatible. If you are running a new enough version of TMOS, yet you don't want to use the built-in cookie encryption provided in the HTTP profile, here is the way to go. Here is an example which allows you to select the cookie(s) to encrypt by wildcard: Encrypt HTTP cookies with dynamic names How to use this snippet: when CLIENT_ACCEPTED { set cookiename "MyCookie" set encryption_passphrase "abcd1234" } when HTTP_RESPONSE { if { [HTTP::cookie exists $cookiename] } { HTTP::cookie encrypt $cookiename $encryption_passphrase } } when HTTP_REQUEST { if { [HTTP::cookie exists $cookiename] } { set decrypted [HTTP::cookie decrypt $cookiename $encryption_passphrase] if { ($decrypted eq "") } { # Cookie wasn't encrypted, delete it HTTP::cookie remove $cookiename } } } Using AES::encrypt / AES::decrypt The HTTP::cookie encrypt / decrypt functions were added in the LTM 9.0 branch, but in early versions, those commands caused system instability. Here is an iRule designed to work around that limitation by instead using the AES::encrypt and AES::decrypt commands along with the HTTP::cookie command to accomplish much the same goal. (It has been modified from its original incarnation to eliminate global variables, thus making it CMP-friendly.) First we'll use the RULE_INIT event to define a static encryption key (the same for each request, and the same on both units of a redundant pair.), and to define the name of the cookie to encrypt in responses and decrypt in requests. (Note that the key is stored in plaintext in the configuration file.) In the HTTP_RESPONSE event from the server, check to see if the cookie exists and has a value. Encrypt the original cookie value, URI encode it, and set the cookie to the new value. On subsequent client requests in the HTTP_REQUEST event you check to see if the cookie is present, with a value. If so, try to URI decode the value. Use the catch command to prevent a TCL error if the decoding fails. Then try to decrypt the URI decoded value. Use catch again to prevent a TCL error if the decryption fails. You can add additional logic to handle the scenarios if the cookie not being present, not decode-able or not-decryptable. Pretty simple huh? Check out the original thread here. Code : when CLIENT_ACCEPTED { # Define an AES encryption key. Valid key lengths are 128, 192, or 256 bits. # You can use a key generator, or create your own using only HEX characters. set aes_key "AES 128 63544a5e7178677b45366b41405f2dab" # Name of the cookie to encrypt/decrypt set cookie"myCookie" # Log debug messages to /var/log/ltm? 1=yes, 0=no. set cookie_encryption_debug 0 } when HTTP_RESPONSE { # Check if response contains an error cookie with a value if {[string length [HTTP::cookie value $cookie]] > 0}{ # Log the original error cookie value from the app if {$cookie_encryption_debug}{log local0. \ "Response from app contained our cookie: [HTTP::cookie value $cookie]"} # Encrypt the cookie value so the client can't change the value HTTP::cookie value $cookie [URI::encode [AES::encrypt $aes_key [HTTP::cookie value $cookie]]] # Log the encoded and encrypted error cookie value if {$cookie_encryption_debug}{log local0. \ "Encrypted error cookie to: [URI::encode [AES::encrypt $aes_key [HTTP::cookie value $cookie]]]"} } } when HTTP_REQUEST { # If the error cookie exists with any value, for any requested object, try to decrypt it if {[string length [HTTP::cookie value $cookie]]}{ if {$cookie_encryption_debug}{log local0. \ "Original error cookie value: [HTTP::cookie value $cookie]"} # URI decode the value (catching any errors that occur when trying to # decode the cookie value and save the output to cookie_uri_decoded) if {not ([catch {URI::decode [HTTP::cookie value $cookie]} cookie_uri_decoded])}{ # Log that the cookie was URI decoded if {$cookie_encryption_debug}{log local0. "\$cookie_uri_decoded was set successfully"} # Decrypt the value if {not ([catch {AES::decrypt $aes_key $cookie_uri_decoded} cookie_decrypted])}{ # Log the decrypted cookie value if {$cookie_encryption_debug}{log local0. "\$cookie_decrypted: $cookie_decrypted"} } else { # URI decoded value couldn't be decrypted. } } else { # Cookie value couldn't be URI decoded } } else { # Cookie wasn't present in the request } }4.9KViews1like0CommentsCredit Card Scrubber
Problem this snippet solves: This iRule illustrates how to scrub out Credit Card Numbers from HTTP traffic. Let's say you want to specify a policy to not allow any credit card numbers outside of your network. How would you go about scrubbing out Credit Card Numbers? This isn't as simple as searching for a string pattern. CCNs vary in length depending on the issuer of the card. But one thing is common: they all must pass the Luhn Formula. Info on the Luhn Formula or MOD 10 can be found here. An excellent reference on credit card number makeup (beyond the 5 types checked in this iRule) is available here. How to use this snippet: This rule will match Diners (13 digit), Amex (15 digit), Visa (13 and 16 digit) Mastercard (16 Digit) and Discover (16 Digit). This example will look matching patterns looking like credit cards and return their indexes into the payload. Then the number is run through the Luhn formula (with optimizations by unRuleY). If it is indeed a valid credit card number, it is masked with X's. Further modifications added support for CCNs with - or a blank between the numbers. i.e. xxxx-xxxx-xxxx-xxxx, xxxx xxxx xxxx xxxx, xxxxxxxxxxxxxxxx, would match. To mask all but the last N digits with X's do the following. At the bottom of the script, you see this line: HTTP::payload replace $card_start $card_len [string repeat "X" $card_len] Add a small line before it like this: Here N=4. set card_len [expr {$card_len - 4}] I used the number 4 to replace all but the last 4 digits. Change this to the number of digits you want to leave untouched. so you end up with: set card_len [expr {$card_len - 4}] HTTP::payload replace $card_start $card_len [string repeat "X" $card_len] Note for an alternate method of implementing this iRule using the stream profile, check the Codeshare example. Code : when HTTP_REQUEST { # Prevent the server from sending a compressed response # remove the compression offerings from the client HTTP::header remove "Accept-Encoding" # Don't allow response data to be chunked if { [HTTP::version] eq "1.1" } { # Force downgrade to HTTP 1.0, but still allow keep-alive connections. # Since HTTP 1.1 is keep-alive by default, and 1.0 isn't, # we need make sure the headers reflect the keep-alive status. # Check if this is a keep alive connection if { [HTTP::header is_keepalive] } { # Replace the connection header value with "Keep-Alive" HTTP::header replace "Connection" "Keep-Alive" } # Set server side request version to 1.0 # This forces the server to respond without chunking HTTP::version "1.0" } } when HTTP_RESPONSE { # Only check responses that are a text content type (text/html, text/xml, text/plain, etc). if { [HTTP::header "Content-Type"] starts_with "text/" } { # Get the content length so we can collect the data (to be processed in the HTTP_RESPONSE_DATA event) # Limit collection to 1Mb (1048576 minus a little to spare) - See SOL6578 for details if { [HTTP::header exists "Content-Length"] } { if { [HTTP::header "Content-Length"] > 1048000 }{ # Content-Length over 1Mb so collect 1Mb set content_length 1048000 } else { # Content-Length under 1Mb so collect actual length set content_length [HTTP::header "Content-Length"] } } else { # Response did not have Content-Length header, so use default of 1Mb set content_length 1048000 } # Don't collect content if Content-Length header value was 0 if { $content_length > 0 } { HTTP::collect $content_length } } } when HTTP_RESPONSE_DATA { # Find ALL the possible credit card numbers in one pass set card_indices [regexp -all -inline -indices\ {(?:3[4|7]\d{2})(?:[ ,-]?(?:\d{5}(?:\d{1})?)){2}|(?:4\d{3})(?:[ ,-]?(?:\d{4})){3}|(?:5[1-5]\d{2})(?:[ ,-]?(?:\d{4})){3}|(?:6011)(?:[ ,-]?(?:\d{4})){3}}\ [HTTP::payload]] foreach card_idx $card_indices { set card_start [lindex $card_idx 0] set card_end [lindex $card_idx 1] set card_len [expr {$card_end - $card_start + 1}] set card_number [string range [HTTP::payload] $card_start $card_end] # Remove dash or space if they exist and count the occurrences in variable cutouts. set cutouts [regsub -all {[- ]} $card_number "" card_number] # Adjsut card_len variable but keep it for later use. set new_card_len [expr {$card_len - $cutouts}] set double [expr {$new_card_len & 1}] set chksum 0 set isCard invalid # Calculate MOD10 for { set i 0 } { $i < $new_card_len } { incr i } { set c [string index $card_number $i] if {($i & 1) == $double} { if {[incr c $c] >= 10} {incr c -9} } incr chksum $c } # Determine Card Type switch [string index $card_number 0] { 3 { set type AmericanExpress } 4 { set type Visa } 5 { set type MasterCard } 6 { set type Discover } default { set type Unknown } } # If valid card number, then mask out numbers with X's if { ($chksum % 10) == 0 } { set isCard valid HTTP::payload replace $card_start $card_len [string repeat "X" $card_len] } # Log Results log local0. "Found $isCard $type CC# $card_number" } }1.3KViews1like2Comments