Forum Discussion

Moe_Jartin's avatar
May 26, 2010

LDAP 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

  • 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
  • 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

     

  • 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

  • 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

  • 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

     

  • 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
  • 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"