Forum Discussion
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.
- iaine
Nacreous
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
Nimbostratus
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.
- iaine
Nacreous
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
Nimbostratus
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
- iaine
Nacreous
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
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com