Forum Discussion

Tero_Toikkanen_'s avatar
Tero_Toikkanen_
Icon for Nimbostratus rankNimbostratus
Oct 18, 2018
Solved

APM authentication for a sessionless and clientless API

We are trying to use APM 13.1.1 to replace a TMG for authenticating API calls. While searching for existing solutions, the closest one I found is the iRule by Stanislas Piron provided for a similar case https://devcentral.f5.com/questions/clientless-mode-and-401-for-poor-client-48409 , but there's still too much session-oriented stuff there.

 

The problem is that the APM should be completely transparent to both the server and the requestor (not calling it a client, as it's not a full client). It should only show itself when an LDAP authentication fails, and then it should only send back a 401 reply. Using the iRule and access policy above, the cookies and headers we get are confusing the API server. Testing with curl (not using -b, so cookies should not be stored), we get a 401 reply when the session is established, and subsequent request pass through OK as long as the session is there. That means that the APM changes the first server request by inserting cookies and/or headers. On the client side I see the LastMRHSession and MRHSession cookies in the first reply, but not after that.

 

So the question is: how to configure APM to authenticate each and every request without changing the traffic in any way? As there's no client to handle the cookies, they should not even be sent out. Session tracking is unnecessary, so sessions should be deleted right after the API call is answered.

 

  • After extensive testing, and diving deep into HTTPS and APM event orders (https://devcentral.f5.com/questions/irule-event-order-https-ssl-client-server-side and https://devcentral.f5.com/articles/http-event-order-access-policy-manager) I managed to get APM working like I wanted it to. Briefly, here's what the APM combined with an iRule does:

    • Authenticate every request via LDAP using the credentials supplied in the Authorization header
    • Remove the APM session once the server response is ready
    • Remove the APM cookies before sending the server response back to the requestor (still not calling it a client)

    As mentioned, the starting point was the iRule and policy here https://devcentral.f5.com/questions/clientless-mode-and-401-for-poor-client-48409

    This is the combined iRule I came up with:

    This is essentially the iRule by Stanislas Piron
    when HTTP_REQUEST {
        set apmsessionid [HTTP::cookie value MRHSession]
        Check for existing APM session cookie
        if { [HTTP::cookie exists "MRHSession"] } {set apmstatus [ACCESS::session exists -state_allow $apmsessionid]} else {set apmstatus 0}
        If no APM status is found
        if { !($apmstatus)} {
            Find Authorization-header
            if { [ string match -nocase {basic *} [HTTP::header Authorization] ] == 1 } {
                Set clientless-mode, username and password for APM
                set clientless(insert_mode) 1
                set clientless(username)    [ string tolower [HTTP::username] ]
                set clientless(password)    [HTTP::password]
                Create an md5 hash of the password
                binary scan [md5 "$clientless(password)"] H* clientless(hash)
                Use combination of username and password hash as the user_key
                set user_key "$clientless(username).$clientless(hash)"
                set clientless(cookie_list)             [ ACCESS::user getsid $user_key ]
                if { [ llength $clientless(cookie_list) ] != 0 } {
                    set clientless(cookie) [ ACCESS::user getkey [ lindex $clientless(cookie_list) 0 ] ]
                if { $clientless(cookie) != "" } {
                        HTTP::cookie insert name MRHSession value $clientless(cookie)
                        set clientless(insert_mode) 0
                    }
                }
            if { $clientless(insert_mode) } {
                Set variables in headers for APM
                HTTP::header insert "clientless-mode" 1
                HTTP::header insert "username" $clientless(username)
                HTTP::header insert "password" $clientless(password)
            }
            unset clientless
          } else {
            If there is no auth header, respond with 401
            HTTP::respond 401 noserver WWW-Authenticate "Basic realm=\"[HTTP::host] Authentication\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close
            return
          }
        }
    }
    
    when ACCESS_SESSION_STARTED {
        Use the user_key as APM uuid
        if { [info exists user_key] } then {
            ACCESS::session data set {session.user.uuid} $user_key
        }
    }
    
    The rest I came up with on my own
    when ACCESS_ACL_ALLOWED {
        If APM auth is successfull, perform URL manipulation.
        I removed the rules, but this is apparently the right place to perform pool select
        based on URL when using APM. Pool selects under HTTP_REQUEST event may be
        overridden by APM.
    }
    
    when ACCESS_POLICY_COMPLETED {
        If auth fails, remove the APM session
        if { ([ACCESS::policy result] equals "deny") } {
        set host [ACCESS::session data get "session.network.name"]
            ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=\"$host Authentication\"" Connection close
            ACCESS::session remove
        }
    }
    
    when HTTP_REQUEST_SEND {
        Remove successfully authenticated sessions only when the HTTP request has been
        sent to the server. Not 100% sure this is the best place to do the removal, but
        it seems to work. This might also require some additional processing to make
        sure we are removing the right session...
        ACCESS::session remove
    }
    
    when HTTP_RESPONSE_RELEASE {
        Remove APM session cookies from the server response
        HTTP::cookie remove "MRHSession"
        HTTP::cookie remove "LastMRH_Session"
    }
    

    As a sidenote, the problem of getting an initial 401 responce, which I mentioned in the question, was APM overriding the pool selects we had in place before APM processing. APM actually doesn't send the session cookies to the server (I verified this using an iRule to count the cookies), so there was no issue on the server side in terms of transparency. We just needed to get rid of the client side cookies and remove the APM session at the right point in time.

    It still remains to be seen how efficient this approach is, but at least it works. Production tests in the near future will show that. At least the original iRule could be streamlined a bit, as we know the requests coming in will never contain an APM session cookie, so that check is a waste of time.

3 Replies

  • Hello Tero,

     

    I have the same issue you described, we are still tring to migrate TMG API calls to APM using the 13.1.1, and the only solution that works by now its using the Stanislas Piron Clientless iRule , but like you said its oriented on the session, its match the session ID to an Username/Password request.

     

    I wish there was an solution provided by F5 to this situations, where we do not have an client request and we do dont want an 302 redirect to My.Policy for system/api calls.

     

    Last month i requested an RFE for this but there is no ETA, this its awful because in this days we are on v14 of the Bigip and still we do not have an solution for this type of implementation on the APM Module...

     

  • After extensive testing, and diving deep into HTTPS and APM event orders (https://devcentral.f5.com/questions/irule-event-order-https-ssl-client-server-side and https://devcentral.f5.com/articles/http-event-order-access-policy-manager) I managed to get APM working like I wanted it to. Briefly, here's what the APM combined with an iRule does:

    • Authenticate every request via LDAP using the credentials supplied in the Authorization header
    • Remove the APM session once the server response is ready
    • Remove the APM cookies before sending the server response back to the requestor (still not calling it a client)

    As mentioned, the starting point was the iRule and policy here https://devcentral.f5.com/questions/clientless-mode-and-401-for-poor-client-48409

    This is the combined iRule I came up with:

    This is essentially the iRule by Stanislas Piron
    when HTTP_REQUEST {
        set apmsessionid [HTTP::cookie value MRHSession]
        Check for existing APM session cookie
        if { [HTTP::cookie exists "MRHSession"] } {set apmstatus [ACCESS::session exists -state_allow $apmsessionid]} else {set apmstatus 0}
        If no APM status is found
        if { !($apmstatus)} {
            Find Authorization-header
            if { [ string match -nocase {basic *} [HTTP::header Authorization] ] == 1 } {
                Set clientless-mode, username and password for APM
                set clientless(insert_mode) 1
                set clientless(username)    [ string tolower [HTTP::username] ]
                set clientless(password)    [HTTP::password]
                Create an md5 hash of the password
                binary scan [md5 "$clientless(password)"] H* clientless(hash)
                Use combination of username and password hash as the user_key
                set user_key "$clientless(username).$clientless(hash)"
                set clientless(cookie_list)             [ ACCESS::user getsid $user_key ]
                if { [ llength $clientless(cookie_list) ] != 0 } {
                    set clientless(cookie) [ ACCESS::user getkey [ lindex $clientless(cookie_list) 0 ] ]
                if { $clientless(cookie) != "" } {
                        HTTP::cookie insert name MRHSession value $clientless(cookie)
                        set clientless(insert_mode) 0
                    }
                }
            if { $clientless(insert_mode) } {
                Set variables in headers for APM
                HTTP::header insert "clientless-mode" 1
                HTTP::header insert "username" $clientless(username)
                HTTP::header insert "password" $clientless(password)
            }
            unset clientless
          } else {
            If there is no auth header, respond with 401
            HTTP::respond 401 noserver WWW-Authenticate "Basic realm=\"[HTTP::host] Authentication\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close
            return
          }
        }
    }
    
    when ACCESS_SESSION_STARTED {
        Use the user_key as APM uuid
        if { [info exists user_key] } then {
            ACCESS::session data set {session.user.uuid} $user_key
        }
    }
    
    The rest I came up with on my own
    when ACCESS_ACL_ALLOWED {
        If APM auth is successfull, perform URL manipulation.
        I removed the rules, but this is apparently the right place to perform pool select
        based on URL when using APM. Pool selects under HTTP_REQUEST event may be
        overridden by APM.
    }
    
    when ACCESS_POLICY_COMPLETED {
        If auth fails, remove the APM session
        if { ([ACCESS::policy result] equals "deny") } {
        set host [ACCESS::session data get "session.network.name"]
            ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=\"$host Authentication\"" Connection close
            ACCESS::session remove
        }
    }
    
    when HTTP_REQUEST_SEND {
        Remove successfully authenticated sessions only when the HTTP request has been
        sent to the server. Not 100% sure this is the best place to do the removal, but
        it seems to work. This might also require some additional processing to make
        sure we are removing the right session...
        ACCESS::session remove
    }
    
    when HTTP_RESPONSE_RELEASE {
        Remove APM session cookies from the server response
        HTTP::cookie remove "MRHSession"
        HTTP::cookie remove "LastMRH_Session"
    }
    

    As a sidenote, the problem of getting an initial 401 responce, which I mentioned in the question, was APM overriding the pool selects we had in place before APM processing. APM actually doesn't send the session cookies to the server (I verified this using an iRule to count the cookies), so there was no issue on the server side in terms of transparency. We just needed to get rid of the client side cookies and remove the APM session at the right point in time.

    It still remains to be seen how efficient this approach is, but at least it works. Production tests in the near future will show that. At least the original iRule could be streamlined a bit, as we know the requests coming in will never contain an APM session cookie, so that check is a waste of time.

    • e0013192's avatar
      e0013192
      Icon for Altostratus rankAltostratus

      How can I use this iRule so that it is only invoked if the HTTP::url starts_with "/api". I don't want this iRule to run unless the HTTP uri starts_with "/api"