Forum Discussion

Drewv's avatar
Drewv
Icon for Nimbostratus rankNimbostratus
Jun 12, 2015

Client Certificate Authentication: Exchange ActiveSync

Hello,

I am having issues getting Client Certificates with Exchange 2010 working properly--specifically ActiveSync.

My policy flow is: 'Client Cert Inspection' --> 'Variable Assign' (pull UPN/domain) --> 'SSO Credential Mapping'.

Inside of the clientSSL profile, I am setting the 'Require' option for Client Certificate. The SSO profile assigned to the policy is of type Kerberos.

A VIP is properly setup with the _sys_ActiveSync iRule supplied by F5.

Everything works fine, phones are able to connect, I see the Kerberos ticket and mail gets populated on the device fine. All session variables look great.

The problem is that there is a requirement to not populate the password field on the device having the Exchange/ActiveSync profile. This causes the phone to open a password input box on the phone. Because we are only using certificates for authentication we would like this to not happen. I can workaround this issue by populating any string within the input box and mail begins to get delivered properly. I have attempted to modify the iRule without success and am looking for guidance.

This is what I've tried. The session never completes and stalls on the HTTP OPTIONS method when making the initial request to the EAS service.

    when RULE_INIT {
                set static::actsync_401_http_body   "Authentication FailuredError: Authentication Failure"
                set static::actsync_503_http_body   "Service is not availableError: Service is not available"
                set static::ACCESS_LOG_PREFIX       "01490000:7:"
            }
            when HTTP_REQUEST {
                set http_path                       [string tolower [HTTP::path]]
                set f_clientless_mode               0

                if { $http_path == "/microsoft-server-activesync" } {
                }
                elseif { $http_path == "/autodiscover/autodiscover.xml" } {
                    set f_auto_discover 1
                }
                else return

                if { ! [ info exists src_ip ] } {
                    set src_ip                            [IP::remote_addr]
                }
                if { ! [ info exists PROFILE_RESTRICT_SINGLE_IP ] } {
                    set PROFILE_RESTRICT_SINGLE_IP        1
                }
                 Only allow HTTP Basic Authentication.
                set auth_info_b64enc                ""
                set http_hdr_auth                   [HTTP::header Authorization]
                regexp -nocase {Basic (.*)} $http_hdr_auth match auth_info_b64enc
                if { $auth_info_b64enc == "" } {
                    set http_hdr_auth "test"
                }

                if { $http_hdr_auth == "" } {
                    log -noname accesscontrol.local1.debug "$static::ACCESS_LOG_PREFIX Empty/invalid HTTP Basic Authorization header"
    REMOVE 401 RESPONSE
                    HTTP::respond 401 content $static::actsync_401_http_body Connection close
                    
                    return
                }

                set MRHSession_cookie               [HTTP::cookie value MRHSession]
                 Do we have valid MRHSession cookie.
                if { $MRHSession_cookie != "" } {
                    if { [ACCESS::session exists -state_allow -sid $MRHSession_cookie] } {
                        log -noname accesscontrol.local1.debug "$static::ACCESS_LOG_PREFIX HTTP *VALID* MRHSession cookie: $MRHSession_cookie"
                         Default profile access setting is false
                        if { $PROFILE_RESTRICT_SINGLE_IP == 0 } {
                            return
                        }
                        elseif { [ IP::addr $src_ip equals [ ACCESS::session data get -sid $MRHSession_cookie "session.user.clientip" ] ] } {
                            log -noname accesscontrol.local1.debug "$static::ACCESS_LOG_PREFIX source IP matched"
                            return
                        }
                        else {
                            log -noname accesscontrol.local1.debug "$static::ACCESS_LOG_PREFIX source IP does not matched"
                        }
                    }
                    else {
                        log -noname accesscontrol.local1.debug "$static::ACCESS_LOG_PREFIX HTTP *INVALID* MRHSession cookie: $MRHSession_cookie"
                    }
                    set MRHSession_cookie ""
                    HTTP::cookie remove MRHSession
                }

                set apm_username                    [ string tolower [HTTP::username] ]
                set apm_password                    [HTTP::password]

                if { $PROFILE_RESTRICT_SINGLE_IP == 0 } {
                    binary scan [md5 "$apm_password$"] H* user_hash
                } else {
                    binary scan [md5 "$apm_password$src_ip"] H* user_hash
                }
                set user_key {}
                append user_key $apm_username "." $user_hash
                unset user_hash

                set f_insert_clientless_mode    0
                set apm_cookie_list             [ ACCESS::user getsid $user_key ]
                if { [ llength $apm_cookie_list ] != 0 } {
                    set apm_cookie [ ACCESS::user getkey [ lindex $apm_cookie_list 0 ] ]
                    if { $apm_cookie != "" } {
                        HTTP::cookie insert name MRHSession value $apm_cookie
                    } else {
                        set f_insert_clientless_mode 1
                    }
                } else {
                    set f_insert_clientless_mode 1
                }

    REMOVE HEADER INSERTION
                if { $f_insert_clientless_mode == 1 } {
                    HTTP::header insert "clientless-mode" 1
                    HTTP::header insert "username" $apm_username
                    HTTP::header insert "password" $apm_password
                }
                
                unset f_insert_clientless_mode
            }
            when ACCESS_SESSION_STARTED {
                if { [ info exists user_key ] } {
                    ACCESS::session data set "session.user.uuid" $user_key
                    ACCESS::session data set "session.user.microsoft-exchange-client" 1
                    ACCESS::session data set "session.user.activesync" 1
                    if { [ info exists f_auto_discover ] && $f_auto_discover == 1 } {
                        set f_auto_discover 0
                        ACCESS::session data set "session.user.microsoft-autodiscover" 1
                    }
                }
            }
            when ACCESS_POLICY_COMPLETED {
                if { ! [ info exists user_key ] } {
                    return
                }

                set policy_result [ACCESS::policy result]
                switch $policy_result {
                "allow" {
                }
                "deny" {
                    ACCESS::respond 401 content $static::actsync_401_http_body Connection close
                    ACCESS::session remove
                }
                default {
                    ACCESS::respond 503 content $static::actsync_503_http_body Connection close
                    ACCESS::session remove
                }
                }

                unset user_key
            }

Any guidance would be appreciated. The edits shown above were suggested from another post here. I have also attempted to manually set the values in other iterations of the rule.

1 Reply

  • Drewv's avatar
    Drewv
    Icon for Nimbostratus rankNimbostratus

    This was 'solved'. We decided to include a shared secret in the devices' profile for all users. It was also possible to use an iRule to insert an Authorization header on each HTTP_REQUEST.