Forum Discussion

dirken's avatar
dirken
Icon for Nimbostratus rankNimbostratus
Jul 23, 2019

APM Client Side Basic Authentication against AD

A client machine coming in via SOAP is only able to do BASIC authentication and has to be authenticated against Active Directory.

Any idea, how to do this? Cannot find anything on the web.

5 Replies

  • Hi

     

    Have you tried using the HTTP 401 Response object? This can be configured to send a BASIC auth challenge back to the client.

     

    If your client can't handle the redirect to /my.policy then you can use clientless mode to proxy the auth attempt.

     

    • dirken's avatar
      dirken
      Icon for Nimbostratus rankNimbostratus

      Hi Iaine, sorry, was offline a few days...

       

      As I understand it, the built-in 401 in VPE presents the user with a mask to enter the credentials.

      In my case, there is no user, it is a scheduled SOAP script. Also I have no idea what you mean by 'clientless mode' to proxy the auth attempt.

       

      Here's what I am actually doing in my iRule

      when HTTP_REQUEST...

      a) check for the authorization header. If not existent, send 401. If existent, decode the base64 string (b64decode) and put it in "creds".

      b) check for the credentials format (username@domain:password, domain\\username:password or username:password)

      c) put domain (if existent), username and password into variables $domain, $usr, $pass

      When doing a bit of 'log local0. "User $usr loggin in" etc. I see the username, password and domain are collected correctly and variables are set. Fine!

      when ACCESS_POLICY_AGENT_EVENT...

      a) trigger an event "get_credentials_from_auth_header" in VPE

      b) ACCESS::session data set session.logon.last.username $usr (and so on for domain and password)

       

      After this I use those credentials for AD query, AD Auth, SSO etc. in VPE - or at least, I would, if it worked.

       

      Error: APM message: no such variable $usr => obviously the policy is running and triggering the iRule event before the variable is set during the HTTP_REQUEST. Funny enough, I get both log messages: first the "User $usr logged in..." message with the correct username from the HTTP_REQUEST routine and the next line is "no such variable $usr" from the ACCESS_POLICY_AGENT_EVENT routine.

       

      I then changed config, deleted the iRule trigger from VPE and moved the 'ACCESS::session data set' commands as the last statement of the HTTP_REQUEST routine. No more error messages, but still session.logon.last.username (and others) are still empty, which can be seen in some logs I write via VPE.

       

      So somehow, the order of the events is not working but I cannot find any overall documentation of the event order for LTM/APM. Under https://devcentral.f5.com/s/articles/http-event-order-access-policy-manager it states that the APM only fires directly after the HTTP_REQUEST, but unfortunately exact graphics there are blurred and the links to the full size graphics do not work anymore (after DevCentral moved).

       

      Also thought to put in some time of wait timer, but I see no way to do this in APM other than an iRule trigger, but not sure how to do it even in an iRule.

       

      So, summing up, I am totally lost about the correct event order in LTM/APM, when fires what, and how the hell do I put I collect correctly form an HTTP header into some APM login variable.

  • Hi

     

    This is clientless mode - https://support.f5.com/csp/article/K80934060#link_06 - if a client can't follow redirects for example then you can proxy the initial auth request and replay it all on the device.

     

    If you need to do this in code then something like this might work....or at least provide a starting point. This will look at a HTTP Request and if an APM session doesn't exist (based on the MRHSession cookie value) then the code will either send the client a 401 asking for Basic auth details or, if the Basic Auth header is present it will store these as variables. Then, when the APM policy starts, these HTTP variables are stored as APM variables so that they can be used by APM Auth objects.

     

    Hope this makes sense

     

    when HTTP_REQUEST {
            if { [HTTP::cookie exists "MRHSession"] } {
            set apmstatus [ACCESS::session exists -state_allow [HTTP::cookie value MRHSession]]} 
            else {set apmstatus 0}  
     
            if { !($apmstatus)} {
     
            	if { [ string match -nocase {basic *} [HTTP::header Authorization] ] == 1 } {
                    set usr [ string tolower [HTTP::username] ]
                    set pass [HTTP::password]
                    } else {
    				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 {
    	if { !($apmstatus)} {
    		ACCESS::session data set "session.logon.last.username" $usr
    		ACCESS::session data set "session.logon.last.password" $pass
    		ACCESS::session data set "session.logon.last.domain" "domain.local"
    		}
    }

    Hope this makes sense

    • dirken's avatar
      dirken
      Icon for Nimbostratus rankNimbostratus

      Hi iaine, you're the best! 🙂

      Not only does it make sense, it works straight away.

       

      Your code is infinitely more elegant than mine, but I still do not understand why my session variable assignment did not work.

      when HTTP_REQUEST {
      	if { [HTTP::header exists Authorization] } {
      		catch { 
       
      			# credentials come base64 encoded in the Authorization header
       
      			set creds [b64decode [findstr [HTTP::header Authorization] "Basic " 6]]
       
      			# credentials will be in one of three formats
      			# - domain\user:password
      			# - user@domain:password format
      			# - user:password
       
      			if { $creds contains "\\" } {
      				set domain [findstr $creds "" 0 "\\"]
      				set usr [findstr $creds "\\" 1 ":"]
      				log local0. "CMPWBS iRule: Auth format: <domain>\\<user>:<password> - domain:$domain user:$usr"
      			} elseif { $creds contains "@" } {
      				set domain [findstr $creds "@" 1 ":"]
      				set usr [findstr $creds "" 0 "@"]
      				log local0. "CMPWBS iRule: Auth format: <user>@<domain>:<password> - domain:$domain user:$usr"
      			} elseif { $creds contains ":" } {
      				set domain ""
      				set usr [findstr $creds "" 0 ":"]
      				log local0. "CMPWBS iRule: Auth format: <user>:<password> - user:$usr"
      			} else {
      				log local0. "CMPWBS request coming in w/ wrong auth format"
      				return
      			}
      			set pass [findstr $creds ":" 1 "\r"]
      		}
      	} else {
      		HTTP::respond 401 WWW-Authenticate "Basic realm=\"CMP WBS\""
      	}
      }
       
      when ACCESS_POLICY_AGENT_EVENT {
          if { [ACCESS::policy agent_id] eq "get_credentials_from_auth_header" } {
      		ACCESS::session data set session.logon.last.username $usr
      		ACCESS::session data set session.logon.last.password $pass
      		ACCESS::session data set session.logon.last.domain $domain
      	}
      }

      In the meantime I found out that

      • the auth string is always in the format <user>:<password>
      • HTTP::user and HTTP::password is far easier than manually extracting stuff with 'findstr'
      • looking for an existing session is probably a good idea

       

      However, the rough idea is not so different from yours and when I put in some 'log local0.' into the HTTP_REQUEST I saw that username and password extraction worked fine. Still, when assigning the variable content of $usr to session.logon.last.username etc. the variable did not exist, and this is the part I still do not fully understand.

       

      I mean, the stuff works now and I do not want to unduly try your patience. 🙂

      I would, however, really love to understand what the inner workings are here. I am pretty sure it has something to do with correlating the variables to the right session, something solved by the cookie. It's all very foggy to me, though.

       

      Cheers

      Dirk

  • Hi Dirk

     

    It's because the ACCESS_POLICY_AGENT_EVENT occurs in a different context. **Taken from the APM Operation Guide** The agent runs in the context of TMM to the renderer rather than client to BIG-IP APM.

     

    This means that the HTTP_REQ variables weren't available by the time you had called them. If you had used them in ACCESS_SESSION_STARTED then you'd probably have been fine. To prove this, log HTTP::URI in HTTP_REQ and ACCESS_POLICY_AGENT_EVENT