Client Auth Using HTML Forms
Problem this snippet solves: This example shows how you can set-up the BigIP AUTH services to use HTML Form Based authentication of clients. This example needs to be updated if used on a version greater than v9 In this example a pre-defined class is used to define URI's which require authentication prior to access. If an un-authenticated user attempts to access one of the pre-defined URI's they are re-directed to a HTML form which is served from the BigIP (though there is no reason you couldn't serve it from the server). The re-direct also includes a URI query string which is a base64 encoded version of the original URI the user was trying to access, this will be used later. When the BigIP detects the form POST it extracts the username and password variables and then authenticates the user; in this case against an ldap server. On successful authentication, a session entry is created in the persistence table using the authID as the key and the AUTH::status as the value. The user is then re-directed to the original URI they requested and the re-direct response also includes the clients authID as an encrypted cookie. When the client browser connects back the original URI and passes the encrypted authID (from the cookie) the authID is used as a lookup to find the users AUTH::status and if the AUTH::status = 0 (which means the user is authenticated) then the user request is allowed to pass through to the server. NOTE: In version 10.x of BIG-IP you can no longer reference a class directly as shown below. Instead you'll need to use the class command to retrieve the class data. set loginform [b64decode [lindex $::loginFormclass 0]] Will no longer be valid as referencing a class directly like a TCL list will simply result in the class name being returned, not the contents of the class. Code : # Data Group Lists class loginForm_class { type string filename "/var/class/loginForm.class" } class private_access { "/SecureDir1" "/membersArea" } # NOTE: The loginForm.class is just a base64 encoded HTML page which is then wrapped in "quotes". A sample is included at the bottom of this page. when RULE_INIT { set ::aeskey [AES::key 128] } when CLIENT_ACCEPTED { set forceauth 1 set auth_status 2 set ckname BIGIP_AUTH set ckpass myPassword set asid [AUTH::start pam default_ldap] } when HTTP_REQUEST { if { [matchclass [HTTP::path] starts_with $::private_access] } { # Private URI, Auth Required if { [HTTP::cookie exists $ckname] } { set cookie_payload [HTTP::cookie value $ckname] set decryptedCookie [AES::decrypt $::aeskey [b64decode $cookie_payload ]] if { not ( $decryptedCookie equals "" ) } { log local0. "Decrypted Cookie=$decryptedCookie" # retrieve the auth status from the session table set auth_status [session lookup uie $decryptedCookie] } # If the auth status is 0 then the user is authenticated if { $auth_status eq 0 } { #Cookie Decrypted & Session Auth valid set forceauth 0 } } if {$forceauth eq 1} { set orig_uri [b64encode [HTTP::uri]] HTTP::redirect "/Login_form?req=$orig_uri" } } else { # If the user is re-directed to the login form then serve the login form from the BigIP if { [HTTP::path] starts_with "/Login_form" && [HTTP::method] equals "GET" } { # Retrieve the login form from a base64 encoded external class file set login_form [b64decode [lindex $::loginForm_class 0]] HTTP::respond 200 content $login_form "Content-Type" "text/html" } elseif { [HTTP::path] starts_with "/Login_form" && [HTTP::method] equals "POST" } { # Process the login form and auth the user # Decode the original URI from the req parameter so we can re-direct to the original # URI on sucessful auth set orig_uri [ b64decode [URI::query [HTTP::request] "req" ] ] HTTP::collect [HTTP::header Content-Length] } } } when HTTP_REQUEST_DATA { set namevals [split [HTTP::payload] "&"] # Break out the POST data for username and password values for {set i 0} {$i < [llength $namevals]} {incr i} { set params [split [lindex $namevals $i] "="] if { [lindex $params 0] equals "username" } { set auth_username [lindex $params 1] } if { [lindex $params 0] equals "password" } { set auth_password [lindex $params 1] } } AUTH::username_credential $asid $auth_username AUTH::password_credential $asid $auth_password AUTH::authenticate $asid HTTP::collect } when AUTH_SUCCESS { if {$asid eq [AUTH::last_event_session_id]} { # Now the user has authenticated lets give them an encrypted cookie with their authID # We'll also add the AUTH::status to a session entry with the authID as the key # We can then re-direct the user to the page they originally asked for set authStatus [AUTH::status $asid] session add uie $asid $authStatus 1800 set encrypted_asid [b64encode [AES::encrypt $::aeskey $asid]] set authcookie [format "%s=%s; path=/; " $ckname $encrypted_asid ] HTTP::respond 302 Location $orig_uri "Set-Cookie" $authcookie } } when AUTH_FAILURE { if {$asid eq [AUTH::last_event_session_id]} { HTTP::respond 200 content "Authentication Failed" } } when AUTH_WANTCREDENTIAL { if {$asid eq [AUTH::last_event_session_id]} { HTTP::respond 200 content "Authentication Credentials not provided" } } when AUTH_ERROR { if {$asid eq [AUTH::last_event_session_id]} { HTTP::respond 200 content "Authentication Error" } } # Configuration: To successfully configure this iRule, it needs to be applied to an "Authentication Profile" and the "Authentication Profile" applied to the Virtual Server. If you attempt to apply it directly to a Virtual Server you will get a configuration error. # Sample loginForm.html <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> Login &amp;amp;amp;amp;amp;lt;!-- body,td,th { font-family: Geneva, Arial, Helvetica, sans-serif; } .style4 {font-size: 12px} --&amp;amp;amp;amp;amp;gt; UERNAME: PASSWORD: # Sample loginForm.class - Base64 encoded version of loginForm.html. Take note of the "quotes" before and after the base64 code. "PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEgVHJhbnNpdGlvbmFs Ly9FTiINCiJodHRwOi8vd3d3LnczLm9yZy9UUi9odG1sNC9sb29zZS5kdGQiPg0KPGh0bWw+DQo8 aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1s OyBjaGFyc2V0PWlzby04ODU5LTEiPg0KPHRpdGxlPkxvZ2luPC90aXRsZT4NCjxzdHlsZSB0eXBl PSJ0ZXh0L2NzcyI+DQo8IS0tDQpib2R5LHRkLHRoIHsNCglmb250LWZhbWlseTogR2VuZXZhLCBB cmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOw0KfQ0KLnN0eWxlNCB7Zm9udC1zaXplOiAxMnB4 fQ0KLS0+DQo8L3N0eWxlPg0KPC9oZWFkPg0KPGJvZHk+DQo8Zm9ybSBhY3Rpb249IiIgbWV0aG9k PSJwb3N0IiBuYW1lPSJsb2dpbkZvcm0iIGlkPSJsb2dpbkZvcm0iPg0KICAJPHRhYmxlIHdpZHRo PSIyNjIiIGJvcmRlcj0iMCI+DQogICAgICA8dHI+DQogICAgICAgIDx0ZCB3aWR0aD0iMTAyIj48 ZGl2IGFsaWduPSJyaWdodCIgY2xhc3M9InN0eWxlNCI+VUVSTkFNRTo8L2Rpdj48L3RkPg0KICAg ICAgICA8dGQgd2lkdGg9IjE1MCI+PGlucHV0IG5hbWU9InVzZXJuYW1lIiB0eXBlPSJ0ZXh0IiBp ZD0idXNlcm5hbWUiPjwvdGQ+DQogICAgICA8L3RyPg0KICAgICAgPHRyPg0KICAgICAgICA8dGQ+ PGRpdiBhbGlnbj0icmlnaHQiIGNsYXNzPSJzdHlsZTQiPlBBU1NXT1JEOjwvZGl2PjwvdGQ+DQog ICAgICAgIDx0ZD48aW5wdXQgbmFtZT0icGFzc3dvcmQiIHR5cGU9InBhc3N3b3JkIiBpZD0icGFz c3dvZCI+PC90ZD4NCiAgICAgIDwvdHI+DQogICAgICA8dHI+DQogICAgICAgIDx0ZD4mbmJzcDs8 L3RkPg0KICAgICAgICA8dGQ+PGlucHV0IHR5cGU9InN1Ym1pdCIgbmFtZT0iU3VibWl0IiB2YWx1 ZT0iU1VCTUlUIiBzdHlsZT0iLnN0eWxlNCI+PC90ZD4NCiAgICAgIDwvdHI+DQogICAgPC90YWJs ZT4NCiAgCTxwPiZuYnNwOzwvcD4NCjwvZm9ybT4NCjwvYm9keT4NCjwvaHRtbD4NCg==" # Here's a copy of the same b64 encoding without CR/LF: SampleLoginFormClassWithoutCRLF504Views0likes0CommentsFile Not Found Handler
Problem this snippet solves: This iRule will handle HTTP 404 (File Not Found) errors returned from pool members and return an embedded customized error page. Let's say you have many servers in your farm and you were told you needed to change the style of your HTTP error response pages? You've got a couple of choices. 1) Manually configure each of your servers with the new content. 2) Build a redirect lookup to issue a HTTP::redirect to a separate server with the error message, but that requires additional hardware. 3) How about embedding the content directly in your iRule? I vote for #3 and I'll show you how... This rule creates a global variable in the RULE_INIT event to contain the embedded content. Note I've included variables in there as well - this will come in handy). Then in the HTTP_REQUEST event, I store the value of the HTTP::uri in a session variable. This HTTP::uri isn't available in the HTTP_RESPONSE event or I'd just reference it there. And finally in the HTTP_RESPONSE event, I check for a 404 response, and then issue a HTTP::respond with the embedded content. The trick here is to process the content with the TCL "subst" command so that embedded variables will be converted to their values. Code : when RULE_INIT { set error_404 { <meta http-equiv="content-type" content="text/html;charset=utf-8" /> 404 Not Found &amp;amp;amp;amp;amp;lt;!-- body {font-family: arial,sans-serif} div.nav {margin-top: 1ex} div.nav A {font-size: 10pt; font-family: arial,sans-serif} span.nav {font-size: 10pt; font-family: arial,sans-serif; font-weight: bold} div.nav A,span.big {font-size: 12pt; color: #0000cc} div.nav A {font-size: 10pt; color: black} A.l:link {color: #6f6f6f} A.u:link {color: green} //--&amp;amp;amp;amp;amp;gt; <script> </script> iR ul es Ru le [HTTP::status]Error Not Found The requested URL $HTTP_URI was not found on this server. } } when HTTP_REQUEST { set HTTP_URI [HTTP::uri] } when HTTP_RESPONSE { if { [HTTP::status] == 404 } { HTTP::respond 404 content [subst $::error_404] } }420Views0likes0CommentsCredit 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.1KViews0likes1CommentHTTP Request Throttle
Problem this snippet solves: iRule to limit the number of requests clients can make within a certain amount of time. Units used are requests/minute but this could be changed to requests/sec pretty easily. Once users hit a predefined limit of requests per minute they are throttled. Clients can also be blacklisted (automatically restricted to a very low rate) or whitelisted. This rule as it is shown here has a very low limit as it only applies to certain URLs. If you remove those 'if' statements then you will need to allow many more requests as most web pages have a number of objects per page. This has not been tested against production traffic so the impact on the BIG-IP is not known. Code : # This is a complete rewrite that is CMP-friendly, see older TMOS v9 code below. ## HTTP Request Throttling ## ## This I-Rule allows only "maxReqs" HTTP requests within "timeout" interval. ## This version throttles by URI and allows IP address whitelists. IP address can be in ## the IP header or the X-Forwarded-For HTTP header. ## ## CMP compatible: Yes ## ## This rule requires: ## A default pool so that the session table can be used and to forward requests. ## ## 09/14/2014, Irule revised to use CMP compatible commands. ## - "static" is added to global variable names. ## - arrays replaced with subtables. ## ## This rule tested on: ## TMOS v11.6.0, should work on an 11.x version ## LTM ## when RULE_INIT { # This defines the maximum requests to be served within the timing interval defined by the static::timeout variable below. set static::maxReqs 4; # Timer Interval in seconds within which only static::maxReqs Requests are allowed. # (i.e: 10 req per 2 sec == 5 req per sec) # If this timer expires, it means that the limit was not reached for this interval and the request # counting starts over. # Making this timeout large increases memory usage. Making it too small negatively affects performance. set static::timeout 2; } when HTTP_REQUEST { # The iRule allows throttling for only sepecific URIs. You list the URIs_to_throttle # in a datagroup. URIs_to_throttle or Methods_to_throttle. # if you need to throttle by Method use an statement like this: # if { [class match [HTTP::uri] equals URIs_to_throttle] } # Note: a URI is everything after the hostname: e.g. /path1/login.aspx?name=user1 # if { [class match [HTTP::uri] equals URIs_to_throttle] } { # The following expects the IP addresses in multiple X-forwarded-for headers. It picks the first one. if { [HTTP::header exists X-forwarded-for] } { set client_IP_addr [getfield [lindex [HTTP::header values X-Forwarded-For] 0] "," 1] } else { set client_IP_addr [IP::client_addr] } # The matching below expects a datagroup named: Throttling_Whitelist_IPs # The Condition of the if statement is true if the IP address is NOT in the whitelist. if { not ([class match $client_IP_addr equals Throttling_Whitelist_IPs ] )} { set getcount [table lookup -notouch $client_IP_addr] if { $getcount equals "" } { table set $client_IP_addr "1" $static::timeout $static::timeout # record of this session does not exist, starting new record, request is allowed. } else { if { $getcount < $static::maxReqs } { # log local0. "Request Count for $client_IP_addr is $getcount" table incr -notouch $client_IP_addr # record of this session exists but request is allowed. } else { HTTP::respond 403 content { HTTP Request denied Your HTTP requests are being throttled. } } } } } }3.2KViews0likes7CommentsHTTP Request Throttle version 10.1 and above
Problem this snippet solves: I tested the CMP friendly version from Kirk Bauer click here and found out that it counts the number of Requests per TCP connection, but when from the same source openes multiple connections with one Request per connection then the rule does not throttle the requests. This iRule is based on subtables that utilizes the lifetime parameter of subtable objects. It offers a sliding window of seconds that can be defined and counts all GET requests that come within this sliding window from the same sourceIP, regardless of the number of TCP connection the client openes. My Rule below uses the client source IP as user identifier (userID) and counts in a slidings window the number of GET reqests. this iRule is based on the counting subtable options that came with version 10.1 and is tested with version 11.1. Here the Link to the subtable counting options: https://devcentral.f5.com/Default.aspx?tabid=63&articleType=ArticleView&articleId=2381 Basically what happens is that each user (in this case the client source IP) creates a subtable with the name and adds an object in this subtable for each GET request. Each object has a lifetime timer that removes the object after it expires. Before the new request is send to the server the number of existing objects in the subtable is counted and if a threshold is reached LTM responds with a predifined HTTP message - in this case 501 HTTP status code with a text. so... here is my iRule. enjoy: How to use this snippet: This iRule requires LTM v10. or higher. Code : when RULE_INIT { # this is the life timer of the subtable object. defines how long this object exist in the subtable set static::maxRate 10 # This defines how long is the sliding window to count the requests. This example allows 10 requests in 3 seconds set static::windowSecs 3 set static::timeout 30 } when HTTP_REQUEST { if { [HTTP::method] eq "GET" } { set getCount [table key -count -subtable [IP::client_addr]] #log local0. "getCount=$getCount" if { $getCount < $static::maxRate } { incr getCount 1 table set -subtable [IP::client_addr] $getCount "ignore" $static::timeout $static::windowSecs } else { #log local0. "This user $user has exceeded the number of requests allowed." HTTP::respond 501 content "Request blockedExceeded requests/sec limit." return } } } Tested this on version: 10.0574Views0likes2CommentsClient Cert Request by URI with OCSP Checking
Problem this snippet solves: This iRule requests a client cert for specific URIs and then validates the client cert against the client SSL profile's trusted CA cert bundle. If that succeeds, then the client cert is validated against an OCSP server (or pool of servers). Invalid certs are redirected to a URL with the Openssl verify code appended. Note: See below for a sample bigip.conf showing the profile and virtual server definitions. Warning: This is an anonymized version of an OCSP iRule that was tested in a customer implementation. I have not done any testing of the iRule since anonymizing it. So test, test, test! Code : # hooleya_auth_ssl_cc_ocsp_rule # https://devcentral.f5.com/s/Wiki/default.aspx/iRules/client_cert_request_by_uri_with_ocsp_checking # # Requires 9.4.8 and hotfix 3 for: # CR125264 - HTTP::respond should be allowed in CLIENTSSL_HANDSHAKE # CR126501 - OCSP AUTH iRules need to detect server down vs. bad cert # CR111646: Connections are no longer rejected when clients fail to send a # certificate to a virtual server with a clientssl profile configured to "request" one. # v0.9.9 - 2010-02-22 # Description: # # This iRule requests a client cert for specific URIs and then validates the client cert against the client SSL profile's # trusted CA cert bundle. If that succeeds, then the client cert is validated against an OCSP server (or pool of servers). # Invalid certs are redirected to a URL with the Openssl verify code appended. # # Configuration requirements: # # 0. This iRule will only work for 9.4.8 with hotfix 3. It cannot work for any lower LTM version. # It could be updated for 10.0.x. In 10.1, you don't need to use the session table to store the cert details, # so this iRule is probably not worth updating for 10.1. # 1. Configure the URIs to request a client cert for in a datagroup named ocsp_pages_to_require_cert_class. # 2. You might also need to customize the cert parsing based on your requirements for which headers to insert. # 3. Add this iRule to an OCSP auth profile. # 4. Ideally, configure an OCSP server pool and VIP to use in the OCSP responder field # and consider uncommenting the code in this iRule to check the state of the OCSP server pool # before attempting the OCSP validation of a client cert. # 5. Test, test test! This is an anonymized version of an OCSP iRule that was tested in a customer implementation. # I have not done any testing of the iRule since anonymizing it. when RULE_INIT { # URL to redirect clients to for failed authentication # The error code is appended to the URL in the iRule set ::auth_failure_url "https://example.com/error.asp?errCode=" # Session timeout. Length of time (in seconds) to store the client cert in the session table. set ::ocsp_session_timeout 1800 # Log debug messages? (0=none, 1=minimal, 2=verbose, 3=everything) set ::ocsp_debug 3 # Enable audit logging? (0=none, 1=unvalidated requests only, 2=all requests) set ::ocsp_audit_log_level 2 # Pages to require a client cert for (replace with datagroup post-testing) # This is now configured in the ocsp_pages_to_require_cert_class datagroup # Prefix to use when inserting the certificate details in the HTTP headers set ::header_prefix "CRT_" # SSL::sessionid returns 64 0's if the session ID doesn't exist, so set a variable to check for this set ::ocsp_null_sessionid [string repeat 0 64] } when CLIENT_ACCEPTED { # Initialise the TMM session id and variables tracking the auth status on each new connection set tmm_auth_ssl_ocsp_sid 0 set invalidate_session 0 set need_cert 0 set inserted_headers 0 # Save the client IP:port and VIP name to shorten the log lines set log_prefix "client IP:port=[IP::client_addr]:[TCP::client_port]; VIP=[virtual name]" if {$::ocsp_debug > 0}{log local0. "$log_prefix: New TCP connection to [IP::local_addr]:[TCP::local_port]"} } when CLIENTSSL_CLIENTCERT { # This event is triggered when LTM requests/requires a cert, even if the client doesn't present a cert. if {$::ocsp_debug > 0}{log local0. "$log_prefix: Cert count: [SSL::cert count], SSL sessionid: [SSL::sessionid]"} # Exit this event if we didn't request a cert if {$need_cert == 0}{ if {$::ocsp_debug > 2}{log local0. "$log_prefix: Exiting event as \$need_cert is 0"} return } # Check if client presented a cert after it was requested if {[SSL::cert count] == 0}{ # No client cert received. Use -1 to track this (0 will be used to indicate no error by SSL::verify_result) set ssl_status_code "-1" # $ssl_status_desc is only used in this rule for debug logging. set ssl_status_desc "Required client certificate not present for resource." if {$::ocsp_debug > 0}{log local0. "$log_prefix: No cert for protected resource. Invalidating session."} set invalidate_session 1 # Audit logging if {$::ocsp_audit_log_level > 0}{catch {log -noname local0. "cc_audit: $log_prefix; status_text=No cert for secured URI; URI=$requested_uri;"}} } else { # Client presented at least one cert. The actual client cert should always be first. if {$::ocsp_debug > 1}{ # Loop through each cert and log the cert subject for {set i 0} {$i < [SSL::cert count]} {incr i}{ log local0. "$log_prefix: cert $i, subject: [X509::subject [SSL::cert $i]],\ issuer: [X509::issuer [SSL::cert $i]], cert_serial=[X509::serial_number [SSL::cert $i]]" } } if {$::ocsp_debug > 2}{log local0. "$log_prefix: Received cert with SSL session ID: [SSL::sessionid]. Base64 encoded cert: [b64encode [SSL::cert 0]]"} # Save the SSL status code (defined here: http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS) set ssl_status_code [SSL::verify_result] set ssl_status_desc [X509::verify_cert_error_string [SSL::verify_result]] # Check if there was no error in validating the client cert against LTM's server cert if { $ssl_status_code == 0 }{ if {$::ocsp_debug > 0}{log local0. "$log_prefix: Certificate validation against root cert OK. status: $ssl_status_desc. Checking against OCSP."} ###################################################################################################### ##### TODO: ##### If the OCSP responder is an LTM VIP (used for load balancing multiple OCSP servers) ##### you could add a check here of the OCSP server pool before attempting the OCSP validation. ##### Just change my_ocsp_http_pool to the actual OCSP server pool name. ## Check if the OCSP server pool does not have any #if {[active_members my_ocsp_http_pool] == 0}{ # # OCSP servers are not available!! # log local0.emerg "$log_prefix: OCSP auth pool is down! Resuming SSL handshake and blocking HTTP request." # # Audit logging # if {$::ocsp_audit_log_level > 0}{ # catch {log -noname local0. "cc_audit: $log_prefix; status_text=OCSP server pool is unavailable. Blocking request."} # } # # We could send an HTTP response from this event, but it doesn't actually get sent until # # the CLIENTSSL_HANDSHAKE event anyhow. So track that this is an invalid request and set the app auth status code # # to indicate OCSP validation of the cert failed. # set invalidate_session 1 # SSL::handshake resume # return #} ##### TODO END: ###################################################################################################### # Check if there isn't already a TMM authentication OCSP session ID if {$tmm_auth_ssl_ocsp_sid == 0} { # [AUTH::start pam default_ssl_ocsp] returns an authentication session ID set tmm_auth_ssl_ocsp_sid [AUTH::start pam default_ssl_ocsp] if {$::ocsp_debug > 2}{log local0. "$log_prefix: \$tmm_auth_ssl_ocsp_sid was 0, \$tmm_auth_ssl_ocsp_sid: $tmm_auth_ssl_ocsp_sid"} if {[info exists tmm_auth_subscription]} { if {$::ocsp_debug > 1}{log local0. "$log_prefix: Subscribing to \$tmm_auth_ssl_ocsp_sid: $tmm_auth_ssl_ocsp_sid"} AUTH::subscribe $tmm_auth_ssl_ocsp_sid } } AUTH::cert_credential $tmm_auth_ssl_ocsp_sid [SSL::cert 0] AUTH::cert_issuer_credential $tmm_auth_ssl_ocsp_sid [SSL::cert issuer 0] AUTH::authenticate $tmm_auth_ssl_ocsp_sid # Hold the SSL handshake until the auth result is returned from OCSP # The AUTH::authenticate command triggers an OCSP lookup and then the AUTH_RESULT event. # In AUTH_RESULT, SSL::handshake resume triggers CLIENTSSL_HANDSHAKE. if {$::ocsp_debug > 0}{log local0. "$log_prefix: Holding SSL handshake for OCSP check"} SSL::handshake hold } else { # Client cert validation against the CA's root server cert failed. if {$::ocsp_debug > 0}{log local0. "$log_prefix: Certificate validation not ok. Status: $ssl_status_code, $ssl_status_desc"} # Audit logging if {$::ocsp_audit_log_level > 0}{catch {log -noname local0. "cc_audit: $log_prefix; \ status_text=Invalid cert for secured URI; openssl_code=$ssl_status_code; openssl_desc=$ssl_status_desc;\ cert_subject=[X509::subject [SSL::cert 0]]; cert_issuer=[X509::issuer [SSL::cert 0]];\ cert_serial=[X509::serial_number [SSL::cert 0]]; URI=$requested_uri"}} # Delete the SSL session from the session table if {$::ocsp_debug > 1}{log local0. "$log_prefix: Invalidating SSL session [SSL::sessionid]"} session delete ssl [SSL::sessionid] SSL::session invalidate set invalidate_session 1 # Release the request flow as we want to send an HTTP response to clients who don't send a valid cert if {$::ocsp_debug > 0}{log local0. "$log_prefix: Invalid cert. Releasing HTTP."} } } } when AUTH_RESULT { # AUTH::status values: # https://devcentral.f5.com/s/wiki/default.aspx/iRules/AUTH__status.html # 0 = success # 1 = failure # -1 = error # 2 = not-authed if {$::ocsp_debug > 0}{log local0. "$log_prefix: \[AUTH::status\]: [AUTH::status]; (0=success, 1=failure, -1=error, 2=not-authed)"} # Check if there is an existing TMM SSL OCSP session ID if {[info exists tmm_auth_ssl_ocsp_sid] and ($tmm_auth_ssl_ocsp_sid == [AUTH::last_event_session_id])} { # Save the auth status set tmm_auth_status [AUTH::status] # TESTING ONLY: If you want to take any response from the OCSP server as valid, # uncomment this line and set tmm_auth_status to 0. #set tmm_auth_status 0 # Check if auth was successful if {$tmm_auth_status == 0 } { # OCSP auth was successful, so resume the SSL handshake. This will trigger the CLIENTSSL_HANDSHAKE event next. if {$::ocsp_debug > 0}{log local0. "$log_prefix: Valid cert per OCSP. Resuming SSL handshake"} SSL::handshake resume } else { # OCSP auth failed # Audit logging if {$::ocsp_audit_log_level > 0}{ catch {log -noname local0. "cc_audit: status=403.13. $log_prefix; status_text=Invalid cert per OCSP for secured URI; URI=$requested_uri"} } # We could send an HTTP response from this event, but it doesn't actually get sent until # the CLIENTSSL_HANDSHAKE event anyhow. So track that this is an invalid request and set the app auth status code # to indicate OCSP validation of the cert failed. set invalidate_session 1 if {$::ocsp_debug > 0}{log local0. "$log_prefix: Invalid cert per OCSP. \[AUTH::response_data\]: [AUTH::response_data]. Resuming SSL handshake."} SSL::handshake resume } } } when CLIENTSSL_HANDSHAKE { # This event is triggered when the SSL handshake with the client completes # Log SSL cipher details if {$::ocsp_debug > 2}{log local0. "$log_prefix: Cipher name, version, bits: [SSL::cipher name], [SSL::cipher version], [SSL::cipher bits]"} # Exit this event if cert isn't required if {$need_cert == 0}{ if {$::ocsp_debug > 2}{log local0. "$log_prefix: \$need_cert is 0, exiting event."} return } # Check if OCSP auth was already successful if {[info exists tmm_auth_status] and $tmm_auth_status == 0}{ if {$::ocsp_debug > 1}{log local0. "$log_prefix: Auth succeeded, parsing cert fields and adding session table entry."} # The parsing of the cert can be customized based on the application's requirements # For this particular implementation, the customer wanted the following fields inserted into the request HTTP headers: # # Issuer # Serial number # Valid from date # Valid to date # Subject # Add the client cert fields as a list to the session table if {$::ocsp_debug > 0}{log local0. "$log_prefix: Saving client cert details in session using SSL sessionid [SSL::sessionid]."} session add ssl [SSL::sessionid] [list \ ${::header_prefix}Issuer [X509::issuer [SSL::cert 0]] \ ${::header_prefix}SerialNumber [X509::serial_number [SSL::cert 0]] \ ${::header_prefix}ValidFrom [X509::not_valid_before [SSL::cert 0]] \ ${::header_prefix}ValidUntil [X509::not_valid_after [SSL::cert 0]] \ ${::header_prefix}Subject [X509::subject [SSL::cert 0]] ] $::ocsp_session_timeout # Audit logging if {$::ocsp_audit_log_level > 1}{ catch {log -noname local0. "cc_audit: status=okay; $log_prefix; status_text=Valid cert per OCSP for secured URI (new SSL session);\ cert_subject=[X509::subject [SSL::cert 0]]; cert_issuer=[X509::issuer [SSL::cert 0]]; cert_serial=[X509::serial_number [SSL::cert 0]]; \ URI=$requested_uri"} } if {$::ocsp_debug > 1}{log local0. "$log_prefix: Auth was successful, releasing HTTP"} HTTP::release } elseif {$invalidate_session}{ if {$::ocsp_debug > 0}{log local0. "$log_prefix: No/invalid cert received, sending block response."} # Send response to client for invalid request HTTP::respond 302 Location "${::auth_failure_url}${ssl_status_code}" Connection Close Cache-Control No-Cache Pragma No-Cache HTTP::release session delete ssl [SSL::sessionid] SSL::session invalidate TCP::close } else { if {$::ocsp_debug > 2}{log local0. "$log_prefix: default case."} } } when HTTP_REQUEST { if {$::ocsp_debug > 1}{log local0. "$log_prefix: URI: [HTTP::uri], SSL session ID: [SSL::sessionid],\ session lookup llength: [llength [session lookup ssl [SSL::sessionid]]],\ string len: [string length [session lookup ssl [SSL::sessionid]]]\ User-Agent: [HTTP::header User-Agent]"} # Double check that the session is valid if {[info exists invalidate_session] and $invalidate_session == 1}{ if {$::ocsp_debug > 0}{log local0. "$log_prefix: Invalidating SSL session ID: [SSL::sessionid]"} session delete ssl [SSL::sessionid] SSL::session invalidate } # Check if request is to a page which requires a client SSL certificate if {[matchclass [string tolower [HTTP::path]] starts_with $::ocsp_pages_to_require_cert_class]}{ # Save the requested URI for logging in subsequent events set requested_uri [HTTP::uri] # Track that this is a request for a restricted URI set need_cert 1 if {$::ocsp_debug > 0}{log local0. "$log_prefix: Request to restricted path: [HTTP::path]. \$need_cert: $need_cert"} # Check if there is an existing SSL session ID and if the cert is in the session table # This condition should only be true on resumed SSL sessions. if {[SSL::sessionid] ne $::ocsp_null_sessionid and [session lookup ssl [SSL::sessionid]] ne ""}{ if {$::ocsp_debug > 0}{ log local0. "$log_prefix: Allowed request to [HTTP::host][HTTP::uri]. Inserting SSL cert details in HTTP headers." # Debug logging of each session table list item if {$::ocsp_debug > 2}{ foreach session_element [session lookup ssl [SSL::sessionid]] { log local0. "$log_prefix: $session_element" } } } # Remove any HTTP header which starts with "crt_" foreach a_header [HTTP::header names] { # Check if this header name starts with "crt_" if {[string match -nocase ${::header_prefix}* $a_header]}{ HTTP::header remove $a_header # If there is a header which starts with crt_, it is probably someone attacking the application! log local0.emerg "$log_prefix: Client with possible spoofed client cert header [HTTP::request]" } } # Insert SSL cert details in the HTTP headers HTTP::header insert [session lookup ssl [SSL::sessionid]] # Track that we've inserted the HTTP headers, so we don't do it again in HTTP_REQUEST_SEND set inserted_headers 1 if {$::ocsp_debug > 0}{log local0. "$log_prefix: Valid request"} # Audit logging if {$::ocsp_audit_log_level > 1}{ # Get the cert details from the session table for the audit logging set session_list [session lookup ssl [SSL::sessionid]] catch {log -noname local0. "cc_audit: status=okay; $log_prefix; status_text=Valid cert per OCSP for secured URI (resumed SSL session);\ cert_subject=[lindex $session_list 9]; cert_issuer=[lindex $session_list 1]; cert_serial=[lindex $session_list 3]; URI=$requested_uri"} } } else { # Hold the HTTP request until the SSL re-negotiation is complete HTTP::collect # Force renegotiation of the SSL connection with a cert requested SSL::session invalidate SSL::authenticate always SSL::authenticate depth 9 SSL::cert mode request SSL::renegotiate if {$::ocsp_debug > 0}{log local0. "$log_prefix: Restricted path, [HTTP::uri], with no client cert. Collecting HTTP and renegotiating SSL"} } } else { if {$::ocsp_debug > 1}{log local0. "$log_prefix: Request to unrestricted path: [HTTP::path]"} set need_cert 0 } } when HTTP_REQUEST_SEND { # This event is relevant only on the initial request of a secured URI (non-resumed SSL sessions). # The insertion of cert details for resumed SSL sessions is handled in HTTP_REQUEST. # Force evaluation in clientside context as HTTP_REQUEST_SEND is a serverside event clientside { if {$::ocsp_debug > 0}{log local0. "$log_prefix: \$invalidate_session: $invalidate_session,\ \$need_cert: $need_cert, \[SSL::sessionid\]: [SSL::sessionid], \[session lookup ssl \[SSL::sessionid\]\]: [session lookup ssl [SSL::sessionid]],\ URI: [clientside {HTTP::uri}]"} # Check if request was to a restricted URI and the headers weren't inserted already in HTTP_REQUEST if {$need_cert==1 and $inserted_headers==0}{ # Check if the session is still valid, there is an existing SSL session ID and that the cert is in the session table if {$invalidate_session == 0 and [SSL::sessionid] ne $::ocsp_null_sessionid and [session lookup ssl [SSL::sessionid]] ne ""}{ # Remove any HTTP header which starts with "crt_" foreach a_header [HTTP::header names] { # Check if this header name starts with "crt_" if {[string match -nocase ${::header_prefix}* $a_header]}{ HTTP::header remove $a_header # If there is a header which starts with crt_, it is probably someone attacking the application! log local0.emerg "$log_prefix: Client with possible spoofed client cert header [HTTP::host][HTTP::uri], [HTTP::header User-Agent"]" } } if {$::ocsp_debug > 0}{log local0. "$log_prefix: Inserting SSL cert details in HTTP headers."} # Insert SSL cert details from the session table in the HTTP headers HTTP::header insert [session lookup ssl [SSL::sessionid]] } else { # Client request for secured URI wasn't valid log local0. "$log_prefix: Rejecting connection for invalid request to [HTTP::host][HTTP::uri] ([IP::local_addr]:[TCP::local_port])\ with session ID: [SSL::sessionid]" # Reject the connection as we should never get here reject } } } }1.9KViews0likes3CommentsEmulate IE7 header insertion in response for IE8
Problem this snippet solves: Insert an HTTP header in responses to Internet Explorer 8 clients which tells the client to emulate Internet Explorer 7 when rendering the web application content. earton wrote in this post: http://devcentral.f5.com/Default.aspx?tabid=53&forumid=5&postid=56407&view=topic With the release of IE8 yesterday, I need to be able to automatically make users who have IE8 installed to be forced into an IE7 compatible mode. I have read some articles on Microsoft that details doing this by the use of the following commands < ?xml version="1.0" encoding="utf-8"?> < configuration> < system.webServer> < httpProtocol> < customHeaders> < clear /> < add name="X-UA-Compatible" value="IE=EmulateIE7" /> < /customHeaders> < /system.webServer> < /configuration> Is it possible to do the same thing in an iRule when a client connects to a virtual server which hosts IIS servers. Joe provided the suggestion of using an iRule to insert the corresponding HTTP header in the response. Here is an example which implements this functionality. Code : when HTTP_REQUEST { # Check if User-Agent header is IE 8 if { [HTTP::header User-Agent] contains "MSIE 8" } { set IE8Resp 1 } else { set IE8Resp 0 } } when HTTP_RESPONSE { if { $IE8Resp } { log local0. "[IP::client_addr]:[TCP::client_port] Inserting X-UA-Compatible: IE=EmulateIE7 header" HTTP::header insert "X-UA-Compatible" "IE=EmulateIE7" } }375Views0likes1CommentInsert Client Certificate In Serverside HTTP Headers
Problem this snippet solves: An example iRule that pulls certain information from a client cert and passes it along to backend server in HTTP headers. Here's one that illustrates how to use the "session" command in conjunction with SSL certificate information to allow passing of information to backend webservers. As above, I want to deliver ssl cert serial number to http server behind BIG-IP, and redirect the users who has no cert to an error page at same time. Code : # client_cert_header_insert_rule when CLIENTSSL_CLIENTCERT { # Check if client presented at least one cert if {[SSL::cert count] > 0}{ # Insert the following fields in the session table with a timeout of 7200 seconds: # Do the processing now as opposed to in HTTP_REQUEST as there # can be many HTTP requests using the same SSL session ID # # Index - item # 0 - base64 encoding of the client SSL cert # 1 - serial number of the cert # 2 - the verification status text for the client cert against the client SSL profile's root CA cert session add ssl [SSL::sessionid] [list \ [SSL::verify_result] \ [b64encode [SSL::cert 0]] \ [X509::serial_number [SSL::cert 0]] \ ] 7200 log local0. "[IP::client_addr]:[TCP::client_port]: Added session data for cert. Status:\ [X509::verify_cert_error_string [lindex [session lookup ssl [SSL::sessionid]] 0]] with key [SSL::sessionid]" } } when HTTP_REQUEST { # Check if SSL session ID is in the cache (SSL::sessionid returns 64 zeroes if it's not in v9 and a null string in v10) if {[SSL::sessionid] ne "0000000000000000000000000000000000000000000000000000000000000000" && [SSL::sessionid] ne ""}{ # Get the session table entry (a TCL list) for this session ID set session_data [session lookup ssl [SSL::sessionid]] # Check if the first element of the session table entry for this session ID is 0 (status for successful cert validation) if {[lindex $session_data 0] == 0}{ log local0. "[IP::client_addr]:[TCP::client_port]: Valid cert per session table entry. Inserting cert details in HTTP headers." # Insert cert details in the HTTP headers HTTP::header insert SSLClientCertStatus "ok" HTTP::header insert SSLClientCertb64 [lindex $session_data 1] HTTP::header insert SSLClientCertSN [lindex $session_data 2] # Exit this event in this rule return } } # If we're still in this rule, cert wasn't valid # so send HTTP 302 redirect to an error page HTTP::respond 302 Location "http://[HTTP::host]/cert_error.html" log local0. "[IP::client_addr]:[TCP::client_port]: No or invalid cert from client." }1.5KViews0likes2CommentsData Leakage Protection
Problem this snippet solves: You can limit the possibility that sensitive data can be sent to a user to prevent data leakage (DLP). By using "Regular Expression" matching we scrub out credit card or other sensitive data from server responses. Sensitive data regular expressions The basis of this iRule is to use regular expression matching to find patterns in the data flowing out of your network. General credit card regular expression from CreditCardScrubber which matches Diners, Amex, Visa, Mastercard and Discover card numbers: (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} Additional regular expression suggestions (modified from OpenDLP) Credit_Card_Track_1: \%?[Bb]\d{13,19}^[-/.\w\s]{2,26}\^\d{2}[01]\d{4} Credit_Card_Track_2: \;\d{13,19}\=(\d{3}|)(\d{4}|\=) Credit_Card_Track_Data: [1-9]\d{2}-\d{2}-\d{4}(?!\d) Mastercard: 5[1-5]\d{2}[ -]\d{4}[ -]\d{4}[ -]\d{4}(?!\d) Visa: 4\d{3}[ -]\d{4}[ -]\d{4}[ -]\d{4}(?!\d) AMEX: (34|37)\d{2}[ -]\d{6}[ -]\d{5}(?!\d) Diners_Club_1: 30[0-5]\d[ -]\d{6}[ -]\d{4}(?!\d) Diners_Club_2: (36|38)\d{2}[ -]\d{6}[ -]\d{4}(?!\d) Discover: 6011[ -]\d{4}[ -]\d{4}[ -]\d{4}(?!\d) JCB_1: 3\d{3}[ -]\d{4}[ -]\d{4}[ -]\d{4}(?!\d) JCB_2: (2131|1800)\d{11}(?!\d) Social_Security_Number: \d{3}[ -]\d{2}[ -]\d{4}(?!\d) How to use this snippet: Implementation Details This iRule requires LTM v10. or higher. Code : when RULE_INIT { # Log debug to /var/log/ltm? 1=yes, 0=no set static::dlp_debug 1 # Mask or remove sensitive response content? 1=mask, 0=remove set static::dlp_mask 1 # Regular expressions which match sensitive data we want to mask or remove # Format for the list is: # # set static::regexes [list \ # {regex1} \ # {regex2} \ # {regexN} \ # ] set static::regexes [list \ {\d{3}[- ]\d{2}[- ]\d{4}} \ {(3[4|7][0-9]{2})([ ,-]?([0-9]{5}([0-9]{1})?)){2}|(4[0-9]{3})([ ,-]?([0-9]{4})){3}|(5[1-5][0-9]{2})([ ,-]?([0-9]{4})){3}|(6011)([ ,-]?([0-9]{4})){3}} \ {another regex example wrapped in curly braces} \ ] if {$static::dlp_debug}{log local0. "\$static::regexes ([llength $static::regexes]): $static::regexes"} # Create a STREAM::expression expression from the regexes # The format is "®ex1&& ®ex2&&" set static::stream_expr "" foreach re $static::regexes { append static::stream_expr "&$re&& " } # Trim the last trailing space from the stream expression set static::stream_expr [string trimright $static::stream_expr] if {$static::dlp_debug}{log local0. "STREAM::expression: $static::stream_expr, [URI::encode $static::stream_expr]"} if {$static::dlp_debug}{ # Some test strings to check for matches on # This and the next section can be removed when you are done testing set static::strings [list \ "123-45-6789" \ "123-45 6789" \ "text123-45-6789TEXT" \ "text123-45-6789" \ "amex378282246310005" \ "378282246310005" \ "4222222222222" \ "6331101999990016" \ "4012888888881881" \ "6011000990139424" \ "3566002020360505" \ ] # Debug testing of regexes on the test strings # This and the previous section can be removed when you are done testing foreach re $static::regexes { log local0. "[string repeat - 100]" log local0. "Checking strings using regex: $re" foreach str $static::strings { log local0. "string: $str, match: [regexp -inline $re $str] " } } } } when HTTP_REQUEST { # Prevent the server from sending chunked response data 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 is not, # we need make sure the headers reflect the keep-alive status. if { [HTTP::header is_keepalive] } { HTTP::header replace "Connection" "Keep-Alive" } } # Disable the stream filter by default STREAM::disable } when HTTP_RESPONSE { # Check if the response is text if {[HTTP::header "Content-Type"] contains "text"}{ if {$static::dlp_debug}{log local0. "Enabling stream filter for this response with expression: $static::stream_expr / [URI::encode $static::stream_expr]"} STREAM::expression $static::stream_expr STREAM::enable } } # STREAM_MATCHED is triggered when the stream filter find string is found when STREAM_MATCHED { # Log the string which matched the stream profile # Comment out this logging once testing is complete so you do not log sensitive data from the iRule log local0. "[string repeat * 100]" log local0. "[IP::client_addr]:[TCP::client_port]: Matched sensitive data: [STREAM::match]" # To replace the matched strings with asterisks use the following: if {$static::dlp_mask}{ STREAM::replace "[string repeat * [string length [STREAM::match]]]" } } Tested this on version: 10.0832Views0likes1CommentHTTP POST redirect preserving POST data
Problem this snippet solves: Use Javascript in an iRule to redirect HTTP POST requests to HTTPS. When an HTTP 30x redirect it sent to a client that has sent a POST request, the user-agent transparently issues a new GET request. As a result, the original POST request payload is lost. The idea behind this iRule is when client sends a POST to an HTTP virtual server, LTM replies with an HTML page which contains a form. The form contains post-data that the client just sent. The form will be auto-submitted by Javascript via HTTPS. Here is how this iRule works: if method = post, it issues HTTP::collect, which will invoke HTTP_REQUEST_DATA then it scans POST-data and prepares new content to respond to client. the new content will include form with all INPUT field names retrieved from POST-data the "action" parameter in the form points to external server all input fields are set as hidden (so client won't see the data during the process) Javascript that submits the form automatically then iRule replies to client with HTTP::respond command with content prepared in step 2 Code : when RULE_INIT { set static::ext_url "https://10.10.71.3/test.post" } when HTTP_REQUEST { # Check if request was a POST if { [string tolower [HTTP::method]] eq "post" } { # Check if there is a Content-Length header 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_REQUEST_DATA { set content "< script type=text/javascript language=javascript> \ function s(){ document.f.submit(); } \ " foreach p [split [HTTP::payload] &] { set name [URI::decode [getfield $p = 1]] set value [URI::decode [getfield $p = 2]] set content "${content} " } set content "${content} " HTTP::respond 200 content $content }2.8KViews0likes4Comments