Suppress MFA for a period of time

Problem this snippet solves:

This code snippet can be used if you want to suppress MFA for a period of time. This solution uses an encrypted persistent cookie, that will be set at a successful MFA logon. Upon subsequent logons the browser will send the persistent cookie (when not expired) and the cookie will be validated. When the cookie is valid, the 2nd authentication factor will be skipped.

How to use this snippet:

Create an Access Policy which uses MFA. For example see the following picture.

Note the following VPE agents in the example above: 'iRule Event - Check Cookie', 'Suppress MFA' and 'Variable Assign - Set Cookie'. These events need to be in place to cooperate with the iRule in this code snippet.


iRule Event - Check Cookie



Empty Event - SuppressMFA


Add a new Branch Rule named 'Yes' and add the following expression:


expr { [mcget {session.custom.suppressmfa.skip}] == 1 }



Variable Assign - Set Cookie


Code :

when RULE_INIT {
    # change passphrase below before any publishing
    # set seconds after which the peristent cookie expires
    array set static::suppress_mfa {
        passphrase "hEuoYjmFUpB4PcpO3bUdQtLP4ic7jjm"
        cookie "SuppressMFA"
        seconds 86400
    }
}

when ACCESS_SESSION_STARTED {
    # store hash from cookie in APM variable
    if { [HTTP::cookie exists $static::suppress_mfa(cookie)] } {
        set hash [HTTP::cookie decrypt $static::suppress_mfa(cookie) $static::suppress_mfa(passphrase)]
        ACCESS::session data set session.custom.suppressmfa.hash $hash
    }
}

when ACCESS_POLICY_COMPLETED {
    # if cookie should be set, create hash and store it into a APM variable
    if { [ACCESS::session data get session.custom.suppressmfa.setcookie] == 1 } {
        set username [ACCESS::session data get session.logon.last.username]
        set UA [ACCESS::session data get session.user.agent]
        set hash [b64encode [md5 "c:$username:$UA"]]
        ACCESS::session data set session.custom.suppressmfa.hash $hash
    }
}

when ACCESS_POLICY_AGENT_EVENT {
    # check if hash from cookie matches current session hash (username and user-agent)
    switch [ACCESS::policy agent_id] {
        "checkcookie" {
            set username [ACCESS::session data get session.logon.last.username]
            set UA [ACCESS::session data get session.user.agent]
            set hash [b64encode [md5 "c:$username:$UA"]]
            if { $hash equals [ACCESS::session data get session.custom.suppressmfa.hash] } {
                ACCESS::session data set session.custom.suppressmfa.skip 1
            }
        }
    }
}

when HTTP_RESPONSE {
    # if cookie should be set, insert an encrypted cookie containing the hash (username and user-agent)
    if { [ACCESS::session data get session.custom.suppressmfa.setcookie] == 1 } {
        HTTP::cookie insert name $static::suppress_mfa(cookie) value [ACCESS::session data get session.custom.suppressmfa.hash]
        HTTP::cookie expires $static::suppress_mfa(cookie) $static::suppress_mfa(seconds) relative
        HTTP::cookie encrypt $static::suppress_mfa(cookie) $static::suppress_mfa(passphrase)
        HTTP::cookie path $static::suppress_mfa(cookie) "/"
        HTTP::cookie secure $static::suppress_mfa(cookie) enable
        ACCESS::session data set session.custom.suppressmfa.setcookie 0
    }
}

Tested this on version:

13.0
Published Jul 16, 2019
Version 1.0
  • I just tested the original iRule as shared with the community and it seems to be working fine in 14.1.4. One thing I notice in your version is that you removed the conditional 'set cookie' from the HTTP_RESPONSE part. The original version only sets a cookie when an user has successfully performed the MFA. Your version sets the cookie every time the HTTP_RESPONSE is being triggered. This doesn't seem right to me.

  • Niels thank you for your response.

    I have put my entire iRule here. I just entered the HTTP_RESPONSE originally because that is what I am having problems with. I am not sure what you mean by saying I removed the conditional 'set cookie'. I have the

    if { [ACCESS::session data get session.custom.suppressmfa.setauthtable] == 1 } . I also have the line in there that sets it to ACCESS::session data set session.custom.suppressmfa.setauthtable 0 after the first response. Really the only thing that I have added was that I am creating a table called tab_amia [IP::client_addr] and added a value "Authed" and added that to the cookie check in the when ACCESS_POLICY_AGENT_EVENT. All this seems to be working accept for the actual cookie creation

    when RULE_INIT {
        # set the cookie encryption passphrase
        # set the cookie name
        # set the encrypted value in the cookie
        # set seconds after which the peristent cookie expires
    	# To see debug logs set to 1, turn off with 0. Logs can be viewed in /var/log/apm or in the TMUI under System -> Logs -> Local Traffic
    	set static::AMIADEV_Cookie_debug 1
        array set static::suppress_mfa {
            passphrase "pw for decryption"
            cookie "AMIA_MFA"
            value "amia"
            seconds "300"
        }
    }
    when ACCESS_SESSION_STARTED {
        # store hash from cookie in APM variable
        if { [HTTP::cookie exists $static::suppress_mfa(cookie)] } {
    		log local0. "amia cookie exists for [IP::client_addr]"
        set hash [HTTP::cookie decrypt $static::suppress_mfa(cookie) $static::suppress_mfa(passphrase)]
    		if {$static::AMIADEV_Cookie_debug } {log local0. "cookie decrypted $hash"}
        ACCESS::session data set session.custom.suppressmfa.hash $hash
        }
        else {
        table delete tab_amia:[IP::client_addr]
    		if {$static::AMIADEV_Cookie_debug } {log local0. "no cookie found"}
    		if {$static::AMIADEV_Cookie_debug } {log local0. "cookie name expected $static::suppress_mfa(cookie)"}
        }
    }
    when ACCESS_POLICY_AGENT_EVENT {
        # check if hash from cookie matches encrypted value
        switch [ACCESS::policy agent_id] {
            "checkauthed" {
    			set checked [table lookup tab_amia:[IP::client_addr]] 
                if { [ACCESS::session data get session.custom.suppressmfa.hash] equals $static::suppress_mfa(value) and $checked contains "Authed" } {
                    ACCESS::session data set session.custom.suppressmfa.skip 1 
                }
            }
        }
    }
    when HTTP_RESPONSE {
        # if table shoud be set then take record of the ClientIP and set encrytped cookie
       if { [ACCESS::session data get session.custom.suppressmfa.setauthtable] == 1 } {
    		table set tab_amia:[IP::client_addr] Authed $static::suppress_mfa(seconds)
    		set taba [table lookup tab_amia:[IP::client_addr]]
    			if {$static::AMIADEV_Cookie_debug } {log local0. "$taba"}
    		HTTP::cookie insert name $static::suppress_mfa(cookie) value $static::suppress_mfa(value) path "/"
    		HTTP::cookie expires $static::suppress_mfa(cookie) $static::suppress_mfa(seconds) relative
    		HTTP::cookie secure $static::suppress_mfa(cookie) enable
    	    HTTP::cookie httponly $static::suppress_mfa(cookie) enable
    	    HTTP::cookie encrypt $static::suppress_mfa(cookie) $static::suppress_mfa(passphrase)
    		HTTP::header "Cache-Control" "max-age=$static::suppress_mfa(seconds)"
    		HTTP::close
    		ACCESS::session data set session.custom.suppressmfa.setauthtable 0
    		}
        }	
  • I was able to get this fixed with the help of a colleague. No matter what we tried in the response it always worked for the first person but no one after. We took the when HTTP_RESPONSE out completely and added a when ACCESS_POLICY_COMPLETES.

    when ACCESS_POLICY_COMPLETED {
     
        if { [ACCESS::session data get session.custom.suppressmfa.setauthtable] == 1 }{
        	set sessionauth [ACCESS::session data get session.custom.suppressmfa.setauthtable]
    			if {$static::AMIADEV_Cookie_debug } {log local0. "AMIA set auth table is $sessionauth"}
    		table set tab_amia:[IP::client_addr] Authed $static::suppress_mfa(seconds)
    		set taba [table lookup tab_amia:[IP::client_addr]]
    			if {$static::AMIADEV_Cookie_debug } {log local0. "$taba"}
            HTTP::cookie insert name $static::suppress_mfa(cookie) value $static::suppress_mfa(value) path "/"
                if {$static::AMIADEV_Cookie_debug } {log local0. "cookie $static::suppress_mfa(cookie) set for $static::suppress_mfa(seconds)"}
            HTTP::cookie expires $static::suppress_mfa(cookie) $static::suppress_mfa(seconds) relative
                if {$static::AMIADEV_Cookie_debug } {log local0. "cookie expires in $static::suppress_mfa(seconds)"}
            HTTP::cookie secure $static::suppress_mfa(cookie) enable
            HTTP::cookie httponly $static::suppress_mfa(cookie) enable
            HTTP::cookie encrypt $static::suppress_mfa(cookie) $static::suppress_mfa(passphrase)
            ACCESS::respond 302 noserver "Location" [ACCESS::session data get session.policy.result.start_uri] "Cache-Control" "no-cache, must-revalidate" Set-Cookie "$static::suppress_mfa(cookie)=[HTTP::cookie $static::suppress_mfa(cookie)];path=/;secure;httponly;Max-age=$static::suppress_mfa(seconds)"
                if {$static::AMIADEV_Cookie_debug } {log local0. "policy completed"}
            foreach aHeader [HTTP::header names] {
    			if {$static::AMIADEV_Cookie_debug } {log local0. "$aHeader: [HTTP::header value $aHeader]"}}
            unset sessionauth
    		unset taba
            }
     }
  • Harri's avatar
    Harri
    Icon for Nimbostratus rankNimbostratus

    Totally overkill to play with cookies. Just use session table with the table-command to add username to table when mfa is first successful and inspect the table in the beginning of the policy in irule agent to set up a suppress variable.