Forum Discussion

sajmethod's avatar
sajmethod
Icon for Altostratus rankAltostratus
Jul 11, 2023

Hi I am looking for some help with an irule that can call out to an api

I been pulled into a project  that is replacing a mobile app.  For the project  they need  f5 to do the following :

  •  listen for a http request  on a vs and extract the bearer token fro that request
  • append the token to an api query and send it to another application 
  • parse the json response for query and capture the username sent back
  • Replace header values in the original request and send it to the backend servers on the vs.

Basically replacing the bearer token with other headers. This is what I came up with as a skelton but having trouble on how to make an api call from an irule and parse the json response.  Also the vendor mentioned that a solution with these steps have been implemented at other clients who use Netscaler. I am waiting for tech documentation on the netscaler solution to see if can help me out. 

when when HTTP_REQUEST  {
if {[HTTP::uri match "/Auth/OAuth2/CREATESESSION"] and [string tolower [HTTP::header Authorization]] contains "bearer"} {

#Extract the bearer token from the auth header an dsave it to a variable
set bearer_token [string map {bearer ""} [string to lower[HTTP::header value Authorization]]

#Send an api query to https://TBD/api/scim/Me?attributes=userName that appends the bearer token to the end
set api_endpoint "https://TBD/api/scim/Me?attributes="
append api_endpoint $bearer_token
??? --> how to call a api from irule

#Parse the json response and retrieve the username.
??? --> set euid [parse json for username key pair ]

#Rewrite the original HTTP request replace the User ID in the header with the new user ID and update the Authorization header to static basic header with auth credentials.
HTTP::header replace Authorization ""
HTTP::header replace User-ID $euid
HTTP::header replace User-IDType "SYSTEMLOGIN"

}
}

 

  • irules is not good at parsing JSON key value in HTTP Payload, Initiating HTTPS connections on irules' sideband is also cumbersome

    I suggest you use irules and cooperate with iRulesLX to initiate a sideband https connection using node.js,Node.js can use its built-in JSON library to handle JSON, which is very advantageous

  • xuwen's avatar
    xuwen
    Icon for Cumulonimbus rankCumulonimbus

    irules is not good at parsing JSON key value in HTTP Payload, Initiating HTTPS connections on irules' sideband is also cumbersome

    I suggest you use irules and cooperate with iRulesLX to initiate a sideband https connection using node.js,Node.js can use its built-in JSON library to handle JSON, which is very advantageous

    • sajmethod's avatar
      sajmethod
      Icon for Altostratus rankAltostratus

      Thank for the suggestion . I don't have much experience with irulesLX or node .js for that matter.  I will look into this.  I was also wondering  if a per request policy with an http connector my be a more optimized solution. I can trigger the access policy based on request. I have not used http connectors  so the flow is still fuzzy in my mind. 

    • sajmethod's avatar
      sajmethod
      Icon for Altostratus rankAltostratus

       I do not know how big the reponse JSON  will be yet, per example it does not look like a lot of lines.  I will take a look at the sideband request as I have not used that but the all the responses are pointing to sideband solution. I will look through the link you provided to get a better understandng of it and see if it can solve the problem. 

  • As juergen said, I'd script a procedure that calls API via a sideband connection.
    You can use this as a starting point: (it's a working code that I'm running on a customer) 

    edit : if you want to debug it, i'd recommend logging $payload value to understand better what the api is returning 
    from there you can use string operators syntax to match the intended KV pair and estract the value

     

     

    when RULE_INIT {
      set static::calls_timeout 5
      set static::first_recv_timeout 50
      set static::first_recv_tries 25
    }
    
    proc apicall {ip port uri} {
      set api_res ""
      set retries 0	
      set api_req "GET $uri HTTP/1.1\r\nUser-Agent: F5 API service\r\nHost: TBD\r\n\r\n"
    
      set api_conn [connect -timeout $static::calls_timeout -status conn_status $ip:$port] 
      if {[send -timeout $static::calls_timeout $api_conn $api_req]} {
        while {$retries < $static::first_recv_tries} {
          set api_res [recv -timeout $static::first_recv_timeout -status recv_status $api_conn]
    
          if {$api_res ne ""} {
            if {[string match -nocase "*Content-Length: *" $api_res]} {
              set header_len [expr {[string first "\r\n\r\n" $api_res] + 4}]
              set payload_len [findstr [string tolower $api_res] "content-length: " 16 "\r"]
              set api_res_len [string length $api_res]
              if {$payload_len ne "" and $payload_len > 0 and [expr {$header_len + $payload_len}] != $api_res_len} {
                set api_res "$api_res[recv -peek -timeout $static::calls_timeout [expr {$header_len + $payload_len - $api_res_len}] $api_conn]"
                set api_res_len [string length $api_res]
              }
            }
            set payload [string range $api_res $header_len $api_res_len]
    		
    		#the two lines below are used to match a string in response. In this example i want to retrieve VALUE from key KEY, formatted as {"KEY":"VALUE"} in response 
            set search_start [string first "\{" $payload]
            set api_res_ret [string trim [string map {"\{\"KEY\":" ""} [string range $payload $search_start [expr {[string first "," $payload $search_start] - 1}]]] "\""]
            
    		break
    		
          } else {
            set retries [expr {$retries + 1}]
            log local0.debug "No response after $retries retries, T= [expr {$retries * $static::first_recv_timeout}]ms."}
            set api_res_ret "FAILED" 
          }
        }
      }
    
      close $api_conn
      return $api_res_ret
    }
    
    when HTTP_REQUEST {
       # < your code here >
       # set token [HTTP::header Authorization]
       # set uri "/api/scim/Me?attributes=$token"
       
       set key [call apicall $api_ip $api_port $uri]
       if {$key eq "FAILED"}{ 
         # api call failed, do something 
       } else {
       
       # < your code here >
    HTTP::header replace Authorization ""
    HTTP::header replace User-ID $euid
    HTTP::header replace User-IDType "SYSTEMLOGIN"
     
       }
    }

     

     

    • xuwen's avatar
      xuwen
      Icon for Cumulonimbus rankCumulonimbus

      Personally, it is recommended to use irules in conjunction with iRulesLX. Using iRulesLX to initiate HTTPS sideband connections and handle JSON is simple, while the official TCL super HTTPS sideband connection code is too long, which affects device performance.

      Your code appears to be an http sideband, not an encrypted https sideband

      • Thank you xuwen for poiting this out. 
        You're right, it's implemented unencrypted in my environment. I must have missed the requirement for SSL. 

    • sajmethod's avatar
      sajmethod
      Icon for Altostratus rankAltostratus

      Thanks , I will try this out  in my test environment and see how it works. 

  • I started with Ca-valli solution  because i was more comfortable with it but  with all the extra irules ( sideband, helper sideband) I needed  to incorporate it seemed bulky and seemed difficult to document and  maintain by others. I decided to go with the irulelx  as the json parsing was easier and the whole setup once I understood how it works is easier to maintain. Unfortunately the app group has not tested it yet. I will update this post once they get around to testing it. 

    Thanks for all the help on this guys!