Forum Discussion

Gabriel_V_13146's avatar
Aug 14, 2014

SAML SLO request ignored on iRules

Hello all,

the F5 still doesn't pass the relay state on single logout (see https://devcentral.f5.com/questions/saml-logout-relaystate), I've tried to go around using an iRule (store the RelayState in a session and inject the value into the response POST form). IMHO the idea is good, but there's a little problem.

The HTTP_REQUEST event is NOT invoked on SLO (single logout POST to "/saml/idp/profile/post/sls"). I assume other events are skipped too..

Is there another way to handle/enable the events? Using BIG-IP 11.4.1 Build 635.0 Hotfix HF2

Best regards Gabriel

irule to fix SLO bug (not adding RelayState to the response)
when HTTP_REQUEST {
 set uri [HTTP::uri]
 set method [HTTP::method]
 log local0. "aether req: $method $uri"
  check if SLO request is invoked 
  if so, collect the posted content to process
 if {$method equals "POST" and $uri equals "/saml/idp/profile/post/sls"} {
   
      if { [HTTP::header Content-Length] ne "" and [HTTP::header value Content-Length] <= 1048576 } {
         set content_length [HTTP::header value Content-Length]
      } else {
           set content_length 1048576
      }
      if { $content_length > 0 } {
         HTTP::collect $content_length
      }
   }
 }


 at the SLO request, find the RelayState and store into the session
when HTTP_REQUEST_DATA {
    Do stuff with the payload
 set uri [HTTP::uri]
 set method [HTTP::method]
  if {$method equals "POST" and $uri equals "/saml/idp/profile/post/sls"} {
   set payload [HTTP::payload]
 
   set relaystate ""
    foreach x [split [ string tolower $payload] "&"] {
        if { $x starts_with "relaystate=" } {
            set relaystate [lindex [split $x "="] 1]
        }
    }
 
   log local0. $relaystate
   session add uie relaystate $relaystate
   log local0. "SAML SLO fix: stored relaystate [session lookup uie relaystate]"
   }
}

 if relaystate is stored (we assume SLO in progress)
 we will inject the realystate into the response

when HTTP_RESPONSE {
 set relaystate [session lookup uie relaystate]
 
 if {$relaystate ne ""} {
    set clen [HTTP::header Content-Length]
    log local0. "SAML SLO fix: stored relaystate $relaystate , injecting into the response"
 
    set payload [HTTP::payload]
    regsub -all { } $payload" " " newpayload  
    log local0. "new payload: $newpayload"
    session delete uie relaystate
    HTTP::payload replace 0 $clen $newpayload
    HTTP::release
 }
}
  • You can define a layered VS that apply your irule :

    1. Change the IP address of your current VS (for example: 1.1.1.1) and remove your irule

    2. Create a new VS (with published IP) and add your irule.

    3. just add

      virtual /Common/Internal_VS
      to the end of the HTTP_REQUEST event of your irule (where Internal_VS is your VS with IP 1.1.1.1)

    Thus, you will be able to manipulate REQUEST and RESPONSE (HTTP_RESPONSE) without any issues.

  • Hello,

    Can you retry by adding the following line of codes on your irule :

    when CLIENT_ACCEPTED {
        ACCESS::restrict_irule_events disable
    }
    

    This peace of irule will allow you to manage with internal APM URIs.

    Moreover, if you would be able to handle response coming from APM, you should change the HTTP_RESPONSE event by HTTP_RESPONSE_RELEASE.

    • Gabriel_V_13146's avatar
      Gabriel_V_13146
      Icon for Cirrus rankCirrus
      Hello, you were right * command "ACCESS::restrict_irule_events disable" enabled most of the events * /saml/idp/profile/post/sls fires only the HTTP_RESPONSE_RELEASE event problem stays, that in the HTTP_RESPONSE_RELEASE event we have no means (or do we?) manipulate the response. Only thing I've managed was to inject data BEFORE the response ( HTTP::payload 0 0 ".....") however trying to change anything INSIDE failed (or I just don't know how to do that). Mostly I got an APM error in access_sanitize_portal_headers.c (Line: 11767) :( Apparently - modifying response would work having the HTTP_RESPONSE event too, where we could call HTTP::collect to fill the payload.. but HTTP_RESPONSE is not called. Any hint around that? Best regards Gabriel
  • Hello,

    Can you retry by adding the following line of codes on your irule :

    when CLIENT_ACCEPTED {
        ACCESS::restrict_irule_events disable
    }
    

    This peace of irule will allow you to manage with internal APM URIs.

    Moreover, if you would be able to handle response coming from APM, you should change the HTTP_RESPONSE event by HTTP_RESPONSE_RELEASE.

    • Gabriel_V_13146's avatar
      Gabriel_V_13146
      Icon for Cirrus rankCirrus
      Hello, you were right * command "ACCESS::restrict_irule_events disable" enabled most of the events * /saml/idp/profile/post/sls fires only the HTTP_RESPONSE_RELEASE event problem stays, that in the HTTP_RESPONSE_RELEASE event we have no means (or do we?) manipulate the response. Only thing I've managed was to inject data BEFORE the response ( HTTP::payload 0 0 ".....") however trying to change anything INSIDE failed (or I just don't know how to do that). Mostly I got an APM error in access_sanitize_portal_headers.c (Line: 11767) :( Apparently - modifying response would work having the HTTP_RESPONSE event too, where we could call HTTP::collect to fill the payload.. but HTTP_RESPONSE is not called. Any hint around that? Best regards Gabriel
  • You can define a layered VS that apply your irule :

    1. Change the IP address of your current VS (for example: 1.1.1.1) and remove your irule

    2. Create a new VS (with published IP) and add your irule.

    3. just add

      virtual /Common/Internal_VS
      to the end of the HTTP_REQUEST event of your irule (where Internal_VS is your VS with IP 1.1.1.1)

    Thus, you will be able to manipulate REQUEST and RESPONSE (HTTP_RESPONSE) without any issues.

  • You can define a layered VS that apply your irule :

    1. Change the IP address of your current VS (for example: 1.1.1.1) and remove your irule

    2. Create a new VS (with published IP) and add your irule.

    3. just add

      virtual /Common/Internal_VS
      to the end of the HTTP_REQUEST event of your irule (where Internal_VS is your VS with IP 1.1.1.1)

    Thus, you will be able to manipulate REQUEST and RESPONSE (HTTP_RESPONSE) without any issues.

  • posting a complete solution

    rule saml-inject rule

     irule to store the relay state at the logout (or predefined link)
     and inject the relay state to the reply
    
     this irule is to be applied to a front layer VS
     thus intercepting all HTTP events
    
    when RULE_INIT {
     set static::virtual_SAML_server /Common/auth-test-vs
    }
    
    when HTTP_REQUEST {
      log local0. "[HTTP::host] [HTTP::method] [HTTP::uri]"
    
     set uri [HTTP::uri]
     set method [HTTP::method]
    
     if {$method equals "POST" and $uri equals "/saml/idp/profile/post/sls"} {
       
          if { [HTTP::header Content-Length] ne "" and [HTTP::header value Content-Length] <= 1048576 } {
             set content_length [HTTP::header value Content-Length]
          } else {
               set content_length 1048576
          }
          if { $content_length > 0 } {
             HTTP::collect $content_length
             log local0. "request content collected: $content_length"
          } else {
             log local0. "cannot collect the content, length: $content_length"
          }
       }
       else {
         unset uri
         unset method
       }
    
      virtual $static::virtual_SAML_server
    }
    
    
     at the SLO request, find the RelayState and store into the session
     triggered by HTTP::collect
    when HTTP_REQUEST_DATA {
     
      if { [info exists "uri"] and [info exists "method"] } {
      if {$method equals "POST" and $uri equals "/saml/idp/profile/post/sls"} {
       set payload [HTTP::payload]
     
       set relaystate ""
        foreach x [split [ string tolower $payload] "&"] {
            if { $x starts_with "relaystate=" } {
                set relaystate [lindex [split $x "="] 1]
            }
        }
     
       log local0. "found relaystate: $relaystate"
       unset payload
       HTTP::release
       }
     }
    }
    
    when HTTP_RESPONSE {
     log local0. "response"
     if { [info exists "uri"] and [info exists "method"] } {
      if {$method equals "POST" and $uri equals "/saml/idp/profile/post/sls"} {
        if { [HTTP::header Content-Length] ne "" and [HTTP::header value Content-Length] <= 1048576 } {
             set content_length [HTTP::header value Content-Length]
          } else {
               set content_length 1048576
          }
          if { $content_length > 0 } {
             HTTP::collect $content_length
             log local0. "response content collected: $content_length"
          } else {
             log local0. "cannot collect the content, length: $content_length"
          }
      }
     }
    }
    
    
    when HTTP_RESPONSE_DATA {
     log local0. "response data"
     if { [info exists "uri"] and [info exists "method"] } {
      if {$method equals "POST" and $uri equals "/saml/idp/profile/post/sls"} {
         set payload [HTTP::payload]
         set index [string first " 0 } {
         HTTP::payload replace $index 0 ""
         }
         unset payload
         unset index
         unset content_length
         unset relaystate
      }
     }
    
    }
    
    when HTTP_RESPONSE_RELEASE {
     log local0. "response release"
     HTTP::release
    }
    
    • Gabriel_V_13146's avatar
      Gabriel_V_13146
      Icon for Cirrus rankCirrus
      This workaround works only with a single SP. With multiple SPs it will be more complex.