Forum Discussion
Moe_Jartin
Cirrus
May 26, 2010LDAP Authentication iRule... HELP
I am trying to write an iRule for an LDAP authentication profile. The irule will take the value of a cookie from every request and use it as the username AND password for which it will then validate against LDAP. I also want it to check to see if an existing session exist and allow the traffic based on the local session rather than querying LDAP for EVERY HTTP request.
I took the Code Share iRule from http://devcentral.f5.com/wiki/default.aspx/iRules/ClientAuthUsingHTMLForms.html and altered it a bit to fit my scenario. I have it working to the point that it is querying LDAP and if the cookie does not exist in the irule or if the cookie value is not valid then acces is denied. The part that is not working is the creation and checking of the local session table. It is querying LDAP for EVERY HTTP request.
There are certainly pieces to this irule that I do not understand so I am looking for help to understand how to create a session in the table and check that to see if a session exist for that user before querying LDAP.
when CLIENT_ACCEPTED {
set forceauth 1
set auth_status 2
set ckname LDSDEVKEY
set ckpass myPassword
set asid [AUTH::start pam default_ldap]
}
when HTTP_REQUEST {
if { [matchclass [HTTP::path] starts_with "/opensso"] } {
Private URI, Auth Required
if { [HTTP::header exists $ckname] } {
set ldsdevkey [HTTP::header value $ckname]
if { not ( $ldsdevkey equals "" ) } {
log local0. "LDSDEVKEY=$ldsdevkey"
retrieve the auth status from the session table
set auth_status [session lookup uie $ldsdevkey]
}
If the auth status is 0 then the user is authenticated
if { $auth_status eq 0 } {
LDSDEVKEY & Session Auth valid
set forceauth 0
}
if {$forceauth eq 1} {
set auth_username $ldsdevkey
set auth_password $ldsdevkey
AUTH::username_credential $asid $auth_username
AUTH::password_credential $asid $auth_password
AUTH::authenticate $asid
HTTP::collect
}
}
}
}
when AUTH_SUCCESS {
if {$asid eq [AUTH::last_event_session_id]} {
Now the user has authenticated lets give them an encrypted cookie with their authID
We'll also add the AUTH::status to a session entry with the authID as the key
We can then re-direct the user to the page they originally asked for
set authStatus [AUTH::status $asid]
session add uie $asid $authStatus 1800
}
}
when AUTH_FAILURE {
if {$asid eq [AUTH::last_event_session_id]} {
HTTP::respond 200 content "Authentication Failed"
}
}
when AUTH_WANTCREDENTIAL {
if {$asid eq [AUTH::last_event_session_id]} {
HTTP::respond 200 content "Authentication Credentials not provided"
}
}
when AUTH_ERROR {
if {$asid eq [AUTH::last_event_session_id]} {
HTTP::respond 200 content "Authentication Error"
}
}
7 Replies
- hoolio
Cirrostratus
I think you'd want to use HTTP::cookie to get the value for a cookie--not HTTP::header. I don't see where you're setting the cookie either. Is the application doing this?
Can you add debug logging to each major code block in HTTP_REQUEST and retest? If you want help analyzing the log output, reply here with the updated iRule and anonymized logs.
Thanks, Aaron - Moe_Jartin
Cirrus
Aaron,
Thanks again for your help. I guess it is not really a cookie. It is a custom header and, yes, the application will set this header. I will add some logging and post the results.
Joe - Moe_Jartin
Cirrus
OK, apparently I was very confused. Originally, I had tried the iRule mentioned above but never got it to work. Sorry, I was working on this a while ago and then just came back to it and forgot what I had done.
What I DO have working is an altered version of the built-in _sys_auth_ldap iRule:when HTTP_REQUEST { set ldsdevkey [HTTP::header value LDSDEVKEY] if {not [info exists tmm_auth_http_sids(ldap)]} { set tmm_auth_sid [AUTH::start pam default_ldap] set tmm_auth_http_sids(ldap) $tmm_auth_sid if {[info exists tmm_auth_subscription]} { AUTH::subscribe $tmm_auth_sid } } else { set tmm_auth_sid $tmm_auth_http_sids(ldap) } AUTH::username_credential $tmm_auth_sid $ldsdevkey AUTH::password_credential $tmm_auth_sid $ldsdevkey AUTH::authenticate $tmm_auth_sid if {not [info exists tmm_auth_http_collect_count]} { HTTP::collect set tmm_auth_http_successes 0 set tmm_auth_http_collect_count 1 } else { incr tmm_auth_http_collect_count } } when AUTH_RESULT { if {not [info exists tmm_auth_http_sids(ldap)] or \ ($tmm_auth_http_sids(ldap) != [AUTH::last_event_session_id]) or \ (not [info exists tmm_auth_http_collect_count])} { return } if {[AUTH::status] == 0} { incr tmm_auth_http_successes } If multiple auth sessions are pending and one failure results in termination and this is a failure or enough successes have now occurred if {([array size tmm_auth_http_sids] > 1) and \ ((not [info exists tmm_auth_http_sufficient_successes] or \ ($tmm_auth_http_successes >= $tmm_auth_http_sufficient_successes)))} { Abort the other auth sessions foreach {type sid} [array get tmm_auth_http_sids] { unset tmm_auth_http_sids($type) if {($type ne "ldap") and ($sid != -1)} { AUTH::abort $sid incr tmm_auth_http_collect_count -1 } } } If this is the last outstanding auth then either release or respond to this session incr tmm_auth_http_collect_count -1 if {$tmm_auth_http_collect_count == 0} { unset tmm_auth_http_collect_count if {[AUTH::status] == 0} { HTTP::release } else { HTTP::respond 401 } } }
So what I want to add to this is the "add a session when the user logs in, and check to see if a session already exist before querying LDAP" behavior from the CodeShare iRule I mentioned earlier. (http://devcentral.f5.com/wiki/default.aspx/iRules/ClientAuthUsingHTMLForms.html) I just really have NO IDEA how to do this. I am decent at HTTP iRules but this authentication is over my head. Is this possible?
Thanks again,
Joe - hoolio
Cirrostratus
Hi Joe,
If that rule is working for you and you just want to track successful auth attempts in the session table, you can try something like this. Note that I haven't tested this--I just looked for the places where the AUTH::status result was checked and added the key to the session table.
I think you'd want to prevent a request which doesn't have the header set with a value. I added a 401 response for this case, but you might want to change this. Else, if there is a key, it's checked against the session table to see if auth has already been successful.when HTTP_REQUEST { Get the key value from the header set ldsdevkey [HTTP::header value LDSDEVKEY] Do something if there is no value for the key? if {$ldsdevkey eq ""}{ HTTP::respond 401 return } Check if there is an existing session for the key if {[session lookup uie $ldsdevkey] ne ""}{ There is an existing session for this key, so don't do any auth for this request return } if {not [info exists tmm_auth_http_sids(ldap)]} { set tmm_auth_sid [AUTH::start pam default_ldap] set tmm_auth_http_sids(ldap) $tmm_auth_sid if {[info exists tmm_auth_subscription]} { AUTH::subscribe $tmm_auth_sid } } else { set tmm_auth_sid $tmm_auth_http_sids(ldap) } AUTH::username_credential $tmm_auth_sid $ldsdevkey AUTH::password_credential $tmm_auth_sid $ldsdevkey AUTH::authenticate $tmm_auth_sid if {not [info exists tmm_auth_http_collect_count]} { HTTP::collect set tmm_auth_http_successes 0 set tmm_auth_http_collect_count 1 } else { incr tmm_auth_http_collect_count } } when AUTH_RESULT { if {not [info exists tmm_auth_http_sids(ldap)] or \ ($tmm_auth_http_sids(ldap) != [AUTH::last_event_session_id]) or \ (not [info exists tmm_auth_http_collect_count])} { return } if {[AUTH::status] == 0} { Auth was successful, so add the key to the session table The value doesn't matter, so use anything session add uie $ldsdevkey 1 1800 incr tmm_auth_http_successes } If multiple auth sessions are pending and one failure results in termination and this is a failure or enough successes have now occurred if {([array size tmm_auth_http_sids] > 1) and \ ((not [info exists tmm_auth_http_sufficient_successes] or \ ($tmm_auth_http_successes >= $tmm_auth_http_sufficient_successes)))} { Abort the other auth sessions foreach {type sid} [array get tmm_auth_http_sids] { unset tmm_auth_http_sids($type) if {($type ne "ldap") and ($sid != -1)} { AUTH::abort $sid incr tmm_auth_http_collect_count -1 } } } If this is the last outstanding auth then either release or respond to this session incr tmm_auth_http_collect_count -1 if {$tmm_auth_http_collect_count == 0} { unset tmm_auth_http_collect_count if {[AUTH::status] == 0} { Auth was successful, so add the key to the session table The value doesn't matter, so use anything session add uie $ldsdevkey 1 1800 HTTP::release } else { HTTP::respond 401 } } }
Aaron - Moe_Jartin
Cirrus
Aaron,
Wow!!???!! Responding to Devcentral Forums at 1 AM?? I really appreciate it. It looks like your changes to the irule are working perfectly but, I have a couple of questions to complete my testing and validation.
1. I get a 401 response when either the LDSDEVKEY header is missing or not valid. Perfect.
2. I do not see any requests to the LDAP server on subsequent request. Unfortunately I didn't capture the intital request so I didn't see ANY queries. But when I put the old irule on I see LDAP queries on every request. So it appears that it is using the local session table. Is there a way to view the session table? A bigpipe shell command? I tried "b conn all show" but it was not there.
3. In the line where you add the session, "session add uie $ldsdevkey 1 1800", from the irule wiki, 1800 is the time in seconds that the session will remain in the table. Is this an absolute timer or an idle timer?
I will continue testing and post the final results. Thanks again for all your help.
Joe - Mants_57436
Nimbostratus
Hi guys,
I'm not sure there's something like this already :( but what I wanted to do is..for a url like www.example.com/test
I would like to add authentication to access the "/test" using LDAP is that possible with iRules? if yes example please...or is it a lot easy to do with APM? Thanks - Kevin_Stewart
Employee
You're talking about something I'd call "post-access authentication". Take a look at this section of the Wiki for SSL client certificate authentication.
https://devcentral.f5.com/wiki/iRules.SSL__authenticate.ashx
Unfortunately, neither ACA nor APM natively do post-access authentication in this way (currently), so I can potentially think of three ways to solve this:
ACA -
The iRule that implements LDAP in ACA (_sys_auth_ldap) calls AUTH::authenticate from the HTTP_REQUEST event. You could create a conditional that only calls AUTH::authenticate if the URI starts with "/test" AND the user isn't presenting a valid session token. Replace the final HTTP::release in the AUTH_RESULT event with an HTTP::respond 302 redirect back to the requested VIP and with a new session token (unique ID stored in a table).
APM -
The HTTP_REQUEST event is called before the initial access policy redirect and start, so you could probably just do an ACCESS::disable in the HTTP_REQUEST event as long as the the URI does not start with "/test".when HTTP_REQUEST { if { not ( [HTTP::uri] starts_with "/test" ) } { ACCESS::disable } }
TWO-VIP (ACA or APM)
1. User accesses site without authentication
2. user attempts to access "/test", does not have a token, so is redirected to another VIP to do authentication
3. User accesses authentication VIP, authenticates (ACA or APM), and is redirected back to originating site with a token
3. Originating site evaluates token (mapped to auth data acquired bu auth VIP) and allows access to "/test"
Recent Discussions
Related Content
DevCentral Quicklinks
* 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
Discover DevCentral Connects
