For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

APM session caching for Web API's

Problem this snippet solves:

Background.

Originally the web was personal. You logged on to a site with your browser, worked and quit. A session could last many hours.

Currently, we are using the Web for remote calls, B2B processes and distributed systems. These are normally a single POST with basic authentication. This is inefficient when a session must be created for each transaction.

The Problem.

You can add authentication to a web call by using APM. This authenticates you by creating a Session and setting a cookie. This is done by returning a 302 to /my.policy

But what if you are using a client that isn't a browser? It can't follow redirects and also it can't log out of the session. The client can use a header "clientless-mode: 1" that enable sessions to start without the 302. But there is a session created for every API call, which is a huge overhead and the sessions continue until they time out.

The Solution.

We need to reuse existing sessions (session caching) There was an iRule published to do this. Sorry I haven't been able to find the author. If it's you, please contact me for credit. I have expanded on it.

How to use this snippet:

Add this iRule to the APM VIP

Code :

# Updated 2017-11-22  Extra information printed on a non 2XX response
# Session caching iRule for nonbrowser webcalls.

#  John Huttley 2018 based on others work. no copyright asserted.

# This is intended for clients that connect to a server providing an API.
# in normal mode this would cause a new session to be created and then left to expire (the client can't call hangup)
# This is a lot of work.
# The solution is to make a key for each client. The uniqueness of the key controls how many sessions are running.
# It would be perfectly possible to set the key to "FredandWilma" and have all clients use the same session.
# The session has nothing to do with the web API uri, unless we make it that way.

# 1. Client connects with Basic Auth matching the AP Authentication, we get the user name and password.
# 2. Use the Client IP, the HTTP::path username and password to generate a  key, user_key
# 3: All existing MRHSession Cookies are removed from the request.
# 4. Use the user_key to see if a session already exists.

# yes, session exists:  A New MRHSession cookie is inserted into the request -> APM. The client isn't involved.
# no, no session found, add the clientless-mode  header   -> APM
# Say Again, we never read any MRHSession cookies, even the ones we set ourselves. We don't care what the client does with them.
#  ===>>We delete client all MRHSession cookies. <<===
# All state is retrieved from APM  from the user_key hash.

# The request should not return a 302 redirect to /my.policy. If it does, clientless mode isn't working. Print the headers in any non 2XX response status.

when RULE_INIT {
    set static::missing_auth_header   "soap:ClientMissing Authorization header"
    set static::ACCESS_LOG_PREFIX     "01490000:7:"
}
when HTTP_REQUEST {
    log -noname local0. ""
#    log -noname local0. "Irule Started"

#Save request headers, used if the response is not a 2XX
array set request_headers {}
foreach aHeader [HTTP::header names] {
set request_headers($aHeader) [HTTP::header values $aHeader]
}



    # Only allow HTTP Basic Authentication.
    set auth_info_b64enc                ""
    set http_hdr_auth                   [HTTP::header Authorization]
    regexp -nocase {Basic (.*)} $http_hdr_auth match auth_info_b64enc
    if { $auth_info_b64enc == "" } {
        set http_hdr_auth ""
    }

    if { $http_hdr_auth == "" } {
        log -noname accesscontrol.local1.debug "$static::ACCESS_LOG_PREFIX Empty/invalid HTTP Basic Authorization header"
        HTTP::respond 401 content $static::missing_auth_header Connection close
        return
    }

    set username                    [ HTTP::username ]
    set password                    [ HTTP::password ]
#    log  -noname local0. "user: $username   Pass: $password"
    # Building MD5 hash data. Here starts with password to avoid storing raw password data.
    set hash_data                       $password
    # Append source IP address
    append hash_data                    [ IP::remote_addr ]
    # Append URI
    append hash_data                    [ HTTP::path ]
    # Calculate MD5 hash and hex encode it.
    binary scan [md5 $hash_data ] H* user_hash
    # Finalize the user key with adding username. This is also for readability.
    set user_key {}
    append user_key $username "." $user_hash
    unset user_hash hash_data

    set f_insert_clientless_mode    1

# remove all existing MRHSession Cookies. We generate them new and /never/ need them from the client.

   foreach acookie [HTTP::cookie names] {
if { $acookie == "MRHSession" } {
 #           log -noname local0. "removed MRHSession Cookie: [HTTP::cookie value $acookie]"
HTTP::cookie remove $acookie
}
  }


    # Given the user_key (hash) get the list of hashed session_ids
# ACCESS::user getsid 
#Returns the list of created external SIDs which is associated with the specified key

##### This function is badly named and the description is wrong. ACCESS::user getsid   returns a list of SID /hashes/ which then need to be processed by ACCESS::user getkey


set sid_hash_list             [ ACCESS::user getsid $user_key ]

#    log -noname local0. "sid_hash_list: $sid_hash_list "

    # We just need one session... example of one sid_hash: d89a62f4985b724a61e1fc9965e202b5
    if { [ llength $sid_hash_list ] != 0 } {


#ACCESS::user getkey 
#Returns the original SID for specified hash of SID

set sid [ ACCESS::user getkey [ lindex $sid_hash_list 0 ] ]
        if { $sid == "" } {
#            log -noname local0. "sid_hash gave empty sid. bad, Goes to clientless mode."
        }

        if { $sid!= "" } {
#            log  -noname local0.  "Session ID  -  $sid"

              HTTP::cookie insert name MRHSession value $sid
              set f_insert_clientless_mode  0
        }
    }
    unset sid_hash_list


    if { $f_insert_clientless_mode == 1 } {
        HTTP::header insert clientless-mode 1
        log -noname local0. "Started Clientless Mode"
    }
    unset f_insert_clientless_mode
}

when ACCESS_SESSION_STARTED {
#    log -noname local0. "Access Session Started"

    if { ! [ info exists user_key ] } {
        # Something horribly wrong
        log local0. "ERROR: user_key  not set."
        return
    }

    ACCESS::session data set session.user.uuid $user_key
    ACCESS::session data set session.logon.last.username $username
    ACCESS::session data set -secure session.logon.last.password $password
    unset username password
}


when ACCESS_POLICY_COMPLETED {
#    log -noname local0. "Access Session Completed"
    if { ! [ info exists user_key ] } {
        # Something horribly wrong
        log local0. "ERROR: user_key  not set."
        return
    }

    set policy_result [ACCESS::policy result]
#    log -noname local0. "Access Session Result: $policy_result"

    switch $policy_result {
    "allow" {
    }
    "deny" {
        ACCESS::respond 403 content "
  
    
      
        soap:Client
        The requested operation was denied. Please consult with your administrator.Your support ID is: [ ACCESS::session data get session.user.sessionid ]
     
  
" Connection close
        ACCESS::session remove
    }
    }
    unset user_key policy_result
}

when HTTP_RESPONSE {
    if {! [HTTP::status] starts_with "2" } {
        log -noname local0. ""
        log -noname local0. "WARNING: Got a [HTTP::status] Response, Clientless-mode not working. SID: $sid, "
        log -noname local0.  "===Request Headers BEGIN==="
foreach {header value} [array get request_headers] {
           log -noname local0. "$header  $value"
         }

         log -noname local0.  "===Request Headers END==="

log -noname local0.  "===Response Headers BEGIN==="
        foreach aHeader [HTTP::header names] {
           log -noname local0. "$aHeader: [HTTP::header value $aHeader]"
            }
log -noname local0.  "===Response Headers END==="
    }
}

Tested this on version:

12.0
Published Apr 09, 2018
Version 1.0
No CommentsBe the first to comment