Forum Discussion

Grey1's avatar
Grey1
Icon for Nimbostratus rankNimbostratus
Jun 04, 2013

authentication iRule

Below is an iRule which is a customized version of the standard _sys_radius_auth iRule. The purpose of the modification is to not have to make a RADIUS call for every object i.e. the result of the authentication request is stored for an hour to take load off the RADIUS servers.

 

 

Unfortunately, this rule is generating a lot of logging messages to the point of us experiencing I/O Swap thrash. The message is the following:

 

May 30 08:26:37 local/tmm2 err tmm2[6301]: 01220001:3: TCL error: [RULENAME_REDACTED] - can't read "tmm_auth_http_collect_count": no such variable (reading value of variable to increment) invoked from within "incr tmm_auth_http_collect_count -1"

 

 

Can anyone assist and diagnose why we are getting this message? The "incr tmm_auth_http_collect_count -1" statement happens within the AUTH_RESULT event. The message seems to indicate that tmm_auth_http_collect_count is never instantiated. The only way this could happen is if the else statement enveloping the "set tmm_auth_http_collect_count 1" doesnt execute. However, if the else statement doesnt execute, then there is no call to auth::authenticate which to me would mean that the AUTH_RESULT event wouldn't fire.

 

 

 

 

when HTTP_REQUEST {

 

Save the username and password for reference in the AUTH_event

 

set user [HTTP::username]

 

set pass [HTTP::password]

 

set pass_hash [b64encode [md5 $pass]]

 

set key "$user-[virtual]-[IP::remote_addr]"

 

set tbl "[virtual]-radius-auth"

 

 

if {not [info exists tmm_auth_http_sids(radius)]} {

 

set tmm_auth_sid [AUTH::start pam default_radius]

 

set tmm_auth_http_sids(radius) $tmm_auth_sid

 

if {[info exists tmm_auth_subscription]} {

 

AUTH::subscribe $tmm_auth_sid

 

}

 

} else {

 

set tmm_auth_sid $tmm_auth_http_sids(radius)

 

}

 

 

 

Check if username/password are already in the session table

 

if {($user ne "") && \

 

([session lookup uie $key] eq $pass_hash)}{

 

 

Auth was already successful on previous request

 

 

 

log "HTTP_REQUEST auth found in session table for $key"

 

 

} else {

 

log "HTTP_REQUEST auth not found in session table for $key"

 

 

AUTH::username_credential $tmm_auth_sid $user

 

AUTH::password_credential $tmm_auth_sid $pass

 

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(radius)] or \

 

($tmm_auth_http_sids(radius) != [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 "radius") 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 } {

 

 

log "AUTH_RESULT addig to session table for $key"

 

Add the username and password to the session table for one hour

 

session add uie $key $pass_hash 600

 

 

add a session entry with a indefinite timeout, but with a 600sec lifetime (force the record at lifetime)

 

table add -subtable $tbl $key $pass_hash indefinite 60

 

 

HTTP::release

 

} else {

 

HTTP::respond 401

 

}

 

}

 

}

 

 

 

6 Replies

  • A few things:

     

     

    1. First, can we see the rest of the logs to see the whole sequence?

     

     

    2. Try putting the offending statement inside an if info exists clause.

     

     

    3. While not really an issue, it doesn't appear that you're doing anything with the data that you set with the table command.
  • what are tcp profile (virtual server)'s and auth profile's idle timeouts?

     

    is auth profile's idle timeout shorter than tcp profile's idle timeout? have you tried to set auth profile's idle timeout longer than tcp profile's idle timeout?

     

  • Grey1's avatar
    Grey1
    Icon for Nimbostratus rankNimbostratus
    @Kevin

     

     

    A few things:

     

    1. First, can we see the rest of the logs to see the whole sequence?

     

    The log is currently inundated with the message I indicated. The only other messages are ones saying that logging is being suppressed.

     

    2. Try putting the offending statement inside an if info exists clause.

     

    This may work, but the offending statement is from the canned _sys_auth_radius iRule and this iRule doesnt seem to generate these log messages. What about my modifications is causing this issue?

     

    3. While not really an issue, it doesn't appear that you're doing anything with the data that you set with the table command.

     

    The table command is commented out. It was used for debugging/testing :D

     

     

    @nitass

     

    The TCP profile timeout is 60 minutes. The auth profile timeout is 300 seconds. Can you elaborate on where you are going with this line of thinking? My understanding is that once the connection has been "authed", that connection is allowed for the duration of its life.

     

     

    Thanks!
  • Grey1's avatar
    Grey1
    Icon for Nimbostratus rankNimbostratus
    Another line of thinking I investigated was the fact that in my iRule vs the canned _sys_auth_radius iRule, I wrap the Auth::authenticate call in an if statement, but do the Auth:: start and other related commands no matter what. My line of thinking was that MAYBE by virtue of doing an Auth::start, I automatically ensure that the AUTH_RESULT event will be fired for this connection. However, if the if/else statement results in the Auth::Authenticate call never happening, then the tmm_auth_http_collect_count variable would never be created. However, I tried to fix this by wrapping the initialization of the auth call (i.e. auth::Start etc) within the if statement to no avail.
  • The TCP profile timeout is 60 minutes. The auth profile timeout is 300 seconds. Can you elaborate on where you are going with this line of thinking?i have seen it is an issue in the past. it happens when auth profile idle timeout is shorter than tcp idle timeout.

     

     

    ID244379 - TCL error: Rule _sys_auth_ldap - ... no such variable
  • Please try this minor modification:

    
    when RULE_INIT {
    set static::DEBUGLOG 1
    set static::TABLENAME "RADUSERS"
    set static::TIMEOUT 60
    }
    when HTTP_REQUEST {
    set key "[HTTP::username][b64encode [HTTP::password][HTTP::header User-Agent]]"
    if { $static::DEBUGLOG } { log local0. "key = [substr $key 0 30]" }
    if { [table lookup -subtable $static::TABLENAME $key] eq "" } {
    if { $static::DEBUGLOG } { log local0. "not authed yet" }
    if {not [info exists tmm_auth_http_sids(radius)]} {
    set tmm_auth_sid [AUTH::start pam default_radius]
    set tmm_auth_http_sids(radius) $tmm_auth_sid
    if {[info exists tmm_auth_subscription]} {
    AUTH::subscribe $tmm_auth_sid
    }
    } else {
    set tmm_auth_sid $tmm_auth_http_sids(radius)
    }
    
    AUTH::username_credential $tmm_auth_sid [HTTP::username]
    AUTH::password_credential $tmm_auth_sid [HTTP::password]
    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
    }
    } else {
    if { $static::DEBUGLOG } { log local0. "found existing auth" }
    }
    }
    when AUTH_RESULT {
    if {not [info exists tmm_auth_http_sids(radius)] or ($tmm_auth_http_sids(radius) != [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 { $static::DEBUGLOG } { log local0. "adding authed user to table: [substr $key 0 30]" }
    table add -subtable $static::TABLENAME $key $static::TIMEOUT
    }
     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 "radius") 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
    }
    }
    }