Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
code share

APM Sharepoint authentication

Problem this snippet solves:

Updated version to support Webdav with windows explorer after Nicolas's comment.

APM is a great authentication service but it does it only with forms.

The default behavior is to redirect user to /my.policy to process VPE. this redirect is only supported for GET method.

Sharepoint provide 3 different access types:

  • browsing web site with a browser
  • Editing documents with Office
  • browser folder with webdav client (or editing documents with libreoffice through webdav protocol)

This irule display best authentication method for each of these access types:

  • browsers authenticate with default authentication method (form based authentication)
  • Microsoft office authenticate with Form based authentication (with support of MS-OFBA protocol)
  • Libreoffice and webdav clients authenticate with 401 basic authentication

Form based authentication (browser and Microsoft office) is compatible (validated for one customer) with SAML authentication

Editing documents is managed with a persistent cookie expiring after 5 minutes. to be shared between IE and Office, it requires :

  • cookie is persistent (expiration date instead of deleted at the end of session)
  • web site defined as "trusted sites" in IE.
How to use this snippet:

install this irule and enable it on the VS.

Tested on Version:
11.5

Code:

when RULE_INIT {
    array set static::MSOFBA {
        ReqHeader "X-FORMS_BASED_AUTH_REQUIRED"
        ReqVal "/sp-ofba-form"
        ReturnHeader "X-FORMS_BASED_AUTH_RETURN_URL"
        ReturnVal "/sp-ofba-completed"
        SizeHeader "X-FORMS_BASED_AUTH_DIALOG_SIZE"
        SizeVal "800x600"
    }
   set static::ckname "MRHSession_SP"
   set static::Basic_Realm_Text "SharePoint Authentication"
}

when HTTP_REQUEST {
   set apmsessionid [HTTP::cookie value MRHSession]
   set persist_cookie [HTTP::cookie value $static::ckname]
   set clientless_mode 0
   set form_mode 0
   # Identify User-Agents type
   if {[HTTP::header exists "X-FORMS_BASED_AUTH_ACCEPTED"] && (([HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "t") || ([HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "f"))} {
      set clientless_mode 0; set form_mode 1
   } else {
      switch -glob [string tolower [HTTP::header "User-Agent"]] {
        "*microsoft-webdav-miniredir*" { set clientless_mode 1 }
         "*microsoft data access internet publishing provider*" -
         "*office protocol discovery*" -
         "*microsoft office*" -
         "*non-browser*" -
         "msoffice 12*" { set form_mode 1 }
         "*mozilla/4.0 (compatible; ms frontpage*" {
            if { [ string range [getfield [string tolower [HTTP::header "User-Agent"]] "MS FrontPage " 2] 0 1]  > 12 } {
               set form_mode 1
            } else {
               set clientless_mode 1
            }
         }
         "*mozilla*" -
         "*opera*" { set clientless_mode 0 }
         default { set clientless_mode 1
         }
      }
   }
   if { $clientless_mode || $form_mode } {
      if { [HTTP::cookie exists "MRHSession"] } {set apmstatus [ACCESS::session exists -state_allow $apmsessionid]} else {set apmstatus 0}
      if { !($apmstatus) && [HTTP::cookie exists $static::ckname] } {set apmpersiststatus [ACCESS::session exists -state_allow $persist_cookie]} else {set apmpersiststatus 0}
      if { ($apmpersiststatus) && !($apmstatus) } {
         # Add MRHSession cookie for non browser user-agent first request and persistent cookie present
         if { [catch {HTTP::cookie insert name "MRHSession" value $persist_cookie} ] } {log local0. "[IP::client_addr]:[TCP::client_port] : TCL error on HTTP cookie insert MRHSession : URL : [HTTP::host][HTTP::path] - Headers : [HTTP::request]"} else {return}
      }
   } else { return }      
   if { $clientless_mode && !($apmstatus)} {
      if { !([HTTP::header Authorization] == "") } {
         set clientless(insert_mode) 1
         set clientless(username)    [ string tolower [HTTP::username] ]
         set clientless(password)    [HTTP::password]
         binary scan [md5 "$clientless(password)"] H* clientless(hash)
         set user_key "$clientless(username).$clientless(hash)"
         set clientless(cookie_list)             [ ACCESS::user getsid $user_key ]
         if { [ llength $clientless(cookie_list) ] != 0 } {
            set clientless(cookie) [ ACCESS::user getkey [ lindex $clientless(cookie_list) 0 ] ]
            if { $clientless(cookie) != "" } {
               HTTP::cookie insert name MRHSession value $clientless(cookie)
               set clientless(insert_mode) 0
            }
         }
         if { $clientless(insert_mode) } {
            HTTP::header insert "clientless-mode" 1
            HTTP::header insert "username" $clientless(username)
            HTTP::header insert "password" $clientless(password)
        }
        unset clientless
      } else {
        HTTP::respond 401 WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close
        return
      }
   } elseif {$form_mode && !($apmstatus) && !([HTTP::path] equals $static::MSOFBA(ReqVal))}{
      HTTP::respond 403 -version "1.1" noserver \
         $static::MSOFBA(ReqHeader) "https://[HTTP::host]$static::MSOFBA(ReqVal)" \
         $static::MSOFBA(ReturnHeader) "https://[HTTP::host]$static::MSOFBA(ReturnVal)" \
         $static::MSOFBA(SizeHeader) $static::MSOFBA(SizeVal) \
         "Connection" "Close"
      return
   }
}


when HTTP_RESPONSE {
   # Insert persistent cookie for html content type and private session
   if { [HTTP::header "Content-Type" ] contains "text/html" } {
      HTTP::cookie remove $static::ckname
      HTTP::cookie insert name $static::ckname value $apmsessionid path "/"
      HTTP::cookie expires $static::ckname 120 relative
      HTTP::cookie secure $static::ckname enable
   }
   # Insert session cookie if session was recovered from persistent cookie
   if { ([info exists "apmpersiststatus"]) && ($apmpersiststatus) } {
         HTTP::cookie insert name MRHSession value $persist_cookie path "/"
         HTTP::cookie secure MRHSession enable
   }
}

when ACCESS_SESSION_STARTED {
   if {([info exists "clientless_mode"])} {
      ACCESS::session data set session.clientless $clientless_mode
   }
   if { [ info exists user_key ] } {
      ACCESS::session data set "session.user.uuid" $user_key
   }
}

when ACCESS_POLICY_COMPLETED {
   if { ([info exists "clientless_mode"]) && ($clientless_mode) && ([ACCESS::policy result] equals "deny") } {
      ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=$static::Basic_Realm_Text" Connection close
      ACCESS::session remove
   } 
}

when ACCESS_ACL_ALLOWED {
   switch -glob [string tolower [HTTP::path]] {
      "/sp-ofba-form" {
         ACCESS::respond 302 noserver Location "https://[HTTP::host]$static::MSOFBA(ReturnVal)"
      }
      "/sp-ofba-completed" {
         ACCESS::respond 200 content {
            <html>
               <head><title>Authenticated</title></head>
               <body><b>Good Work, you are Authenticated</b></body>
            </html>
         } noserver
      }
      "*/signout.aspx" {
         # Disconnect session and redirect to APM logout Page
         ACCESS::respond 302 noserver Location "/vdesk/hangup.php3"
         return
      }
      "/_layouts/accessdenied.aspx" {
         # Disconnect session and redirect to APM Logon Page
         if {[string tolower [URI::query [HTTP::uri] loginasanotheruser]] equals "true" } {
            ACCESS::session remove
            ACCESS::respond 302 noserver Location "/"
            return
         }
      }
      default {
         # No Actions
      }
   }
}
Comments on this Snippet
Comment made 02-Jun-2016 by timon74 2
Perfect for my needs. thanks for your help
0
Comment made 04-Jun-2016 by timon74 2
I reduce the persistent cookie expiration time to 20 seconds. It permits to increase security in my case. Thanks for this really efficient Irule.
0
Comment made 07-Jun-2016 by rouanon 3
Hi. Just to make sure : Would this solve the issue of editing office documents through APM (with a rewrite profile) ? I'm trying to publish Sharepoint with APM and everything seems to be working except that editing part. Many thanks
0
Comment made 15-Jun-2016 by Nicolas COLLET 180
Hi, we need to adapt / correct this irule for managed network drive because Windows network drive used user-agent like this : Microsoft-WebDAV-MiniRedir And it's doesn't manage authentification with form mode. So it's just need to change this : Before line 24 : "*microsoft-webdav-miniredir*" - After line 22 : "*microsoft-webdav-miniredir*" { set clientless_mode 1 } After operate this modification, Network drive it's ok. Best regards
0
Comment made 23-Aug-2016 by Kai Wilke 6554

Hi Stanislas,

Thanks for sharing this iRule to Devcentral.

I'm even wondering why this iRule hasn't won the 2016 coding challenge. The ability to support MSOFBA is so much required for every SharePoint and esp. these WS-Federation based deployments. O well, but a (highly insecure) SCEP based certificate enrollment page for iPhones sounds so much cooler, isn't it? To bad that I have not found the time to vote for you... ;-)

BTW: Your last code update has broken the formating of the code. It contains now a lot of &amp and some malformated .

Cheers, Kai

0
Comment made 23-Aug-2016 by Stanislas Piron 9618

Hi Kai,

Thank you for the comment.

I rollback to the previous version. I am not able to upload new content without &amp and HTML header...

the only difference with the new version is line 113. I forgot to insert quote before and after realm name.

the line must be:

ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Connection close
0
Comment made 07-Sep-2016 by Andy from Sandy 171

Thank you for sharing, it has got me a long way to a successful configuration.

I have built up a sharepoint installation using SSO with PKI to Kerberos. So the user presents there certificate because of client ssl profile. After client cert inspection and OCSP in access profile a SSO configuration does the Kerberos lookup to pass to SharePoint.

Now I have added OWA on a separate VIP which also checks client certificate.

The iRule takes care of switching to clientless mode when OWA connects to SharePoint VIP. I have modified it to also check for the OWA request is from a known list. I have modified the access policy to bypass client cert inspection based on above change to iRule. I also found I had to switch to clientless mode when user-agent = microsoft office protocol discovery.

I don't think I have any forms based authentication so I have removed that code from the iRule.

What I would be interested in is some commentary as to how the iRule works please. Are you able to share the access policy as well please? Thank you.

0
Comment made 08-Sep-2016 by Kai Wilke 6554

Hey Stanislas,

I'm currently in the process in writing an iRule for ADFS protected SharePoints. Recycled some parts of your iRule and optimized some parts here and there. Also found some interesting tweaks for native Windows WebDav Client. Will send you the outcome once finished... ;-)

Cheers, Kai

0
Comment made 08-Sep-2016 by Stanislas Piron 9618

Hi Kai,

This irule is working on VS with following access profiles:

  • SAML SP with SAML IdP with fallback to AD Auth for non MSOFBA clients (session.clientless variable condition to choose authentication method)
  • Standard Logon Page / AD Auth
  • Standard Logon Page / AD Auth with Captcha

If you write an irule to improve APM / sharepoint compatibility, please share it.

0
Comment made 12-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

In a native ADFS-based SharePoint collaboration scenario you cannot use AD fallbacks. The SAML user accounts are most likely stored in many decentralized repositories, without a direct trust nor network access to them.

My goal was to get everything working without using the AD fallback at all. Well, I had to skip the support for outdated MS Office products and changed the handling of Microsoft WebDav Clients slightly. In addition I've restructured the code here and there to reduce the complexity and increase the performance...

when CLIENT_ACCEPTED {
    set inject_session_cookie 0
}
when HTTP_REQUEST {
    # Check if APM session cookie is present and valid
    if { ( [set sessionid [HTTP::cookie value "MRHSession"]] ne "" ) and
         ( [ACCESS::session exists -state_allow $sessionid] ) } then {
        # Allow the successfully pre authenticated request to pass
    } else {
        # Check if persistent APM session cookie is present and valid
        if { ( [set sessionid [HTTP::cookie value "MRHSession_SP"]] ne "" ) and
         ( [ACCESS::session exists -state_allow $sessionid] ) } then {
            # Restore APM session cookie value
            HTTP::cookie insert name "MRHSession" value $sessionid
            set inject_session_cookie 1 
            # Allow the successfully pre authenticated request to pass
        } else {
            # Enumerate explicit MS-OFBA authentication capabilities
            # Background: https://msdn.microsoft.com/en-us/library/office/cc313069(v=office.12).aspx
            if { ( [HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "t" ) or 
                 ( [HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "f" ) } then {
                # Explicit MSOFBA support detected. 
                set authschema "ms-ofba"
            } else {
                # Enumerate implicit MS-OFBA authentication capabilities
                switch -glob -- [string tolower [HTTP::header "User-Agent"]] "*office protocol discovery*" - \
                  "*microsoft office*" - \
                  "*microsoft data access internet publishing provider*" - \
                  "*non-browser*" - \
                  "msoffice 12*" - \
                  "*microsoft-webdav-miniredir*" - \
                  {*ms frontpage 1[23456789]*} {
                    # Implicit MSOFBA support detected.   
                    set authschema "ms-ofba"
                } "*ms frontpage*" {
                    # Legacy client detected
                    set authschema "legacy"
                } "*mozilla*" - \
                  "*opera*" {
                    # Regular web browser detected.  
                    set authschema "browser"
                } default { 
                    set authschema "legacy"
                }
            }
            if { $authschema eq "ms-ofba" } then {
                # Send a MSOFBA compatible Access Denied response 
                if { [HTTP::path] ne "/sp-msofba-form" } then {
                    HTTP::respond 403 -version "1.1" \
                                      content "Access Denied. Make sure that your client is correctly configured. See https://support.microsoft.com/en-us/kb/932118 for further information." \
                                      noserver \
                                      "Content-Type" "text/html" \
                                      "X-FORMS_BASED_AUTH_REQUIRED" "https://[getfield [HTTP::host] ":" 1]/sp-msofba-form" \
                                      "X-FORMS_BASED_AUTH_RETURN_URL" "https://[getfield [HTTP::host] ":" 1]/sp-msofba-completed" \
                                      "X-FORMS_BASED_AUTH_DIALOG_SIZE" "800x600" \
                                      "Set-Cookie" "MRHSession=deleted;path=/;secure" \
                                      "Set-Cookie" "LastMRH_Session=deleted;path=/;secure" \
                                      "Set-Cookie" "MRHSession=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure" \
                                      "Set-Cookie" "LastMRH_Session=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure"
                }
            } elseif { $authschema eq "legacy" } then {
                # Send a regular Access Denied response 
                HTTP::respond 403 content "Access denied. An unsupported client access has been detected."
            } else {
                # Let the regular web browser request pass to the APM policy
            }
        }
    }
}

when ACCESS_SESSION_STARTED {
    if { [HTTP::cookie value "SAML_Realm"] ne "" } then {
        ACCESS::session data set "session.irule.realmcookie" [HTTP::cookie value "SAML_Realm"]
    }
}
when ACCESS_POLICY_COMPLETED {
    if { [set realm_cookie [ACCESS::session data get "session.irule.setrealmcookie"]] ne "" } then {
        ACCESS::respond 302 "Location" "[ACCESS::session data get "session.server.landinguri"]" "Set-Cookie" "SAML_Realm=$realm_cookie;path=/;secure"
    } else {
        ACCESS::respond 302 "Location" "[ACCESS::session data get "session.server.landinguri"]" 
    }
}
when ACCESS_ACL_ALLOWED {
    switch -glob -- [string tolower [HTTP::path]] "/sp-msofba-form" {
        # Successfully APM authenticated request MS-OFBA request detected. Redirect to MS-OFBA return URL
        ACCESS::respond 302 noserver Location "/sp-msofba-completed"
    } "/sp-msofba-completed" {
        # Successfully APM authenticated request MS-OFBA request detected. Sending MS-OFBA return response
        ACCESS::respond 200 content "<html><head><title>Authenticated</title></head><body><b>Good Work, you are Authenticated</b></body></html>" noserver
    } "*/signout.aspx" {
        # SharePoint SignOut signature detected. Disconnect session and redirect to APM logout Page
        ACCESS::respond 302 noserver Location "/vdesk/hangup.php3"
    } "/_layouts/accessdenied.aspx" {
        # SharePoint AccessDenied signature detected.
        if { [string tolower [URI::query [HTTP::uri] loginasanotheruser]] equals "true" } then {
            # SharePoint LoginAsAnotherUser request detected. Killing the APM session an sending redirect to www-root.
            ACCESS::session remove
            ACCESS::respond 302 noserver Location "/"
            return
        }
    } default {
         # Let the authenticated request pass
    }
}
when HTTP_RESPONSE {
    if { [HTTP::header "Content-Type" ] contains "text/html" } then {
        # Insert persistent APM session cookie into HTTP response.
        HTTP::header insert "Set-Cookie" "MRHSession_SP=$sessionid;path=/;secure"
        HTTP::cookie expires "MRHSession_SP" 120 relative
    }
    if { $inject_session_cookie } then {
        # Insert APM session cookie into HTTP response.
        HTTP::header insert "Set-Cookie" "MRHSession=$sessionid;path=/;secure"
        set inject_session_cookie 0
    }
}

Note: Your Clientless_Mode support can be easily included in the provided script. You just need to insert your existing code into the elseif { $authschema eq "legacy" } then { script block and add the other iRule events. But be aware that the query to [ACCESS::user getsid $user_key] exposes some security risks, since it can be used to bypass active account lockouts.

Update: Changed the session cookie injection mechanism.

Cheers, Kai

0
Comment made 13-Sep-2016 by Stanislas Piron 9618

Hi Kai,

the use of [ACCESS::user getsid $user_key] is from F5 exchange irule. this irule is also used when enabling exchange profile.

this is based on a user key from username and password. if you think it is insecure, we can set a the variable session.max_session_timeout to 1 hour. it will force a new authentication every hour if the user changed his password or if the account is locked. this can be seamless as the authentication header is inserted in every request.

The main reason I filter on MRHSession_SP cookie only for non browser is to secure persistent cookie usage.

When managing persistent cookie for non browser, it will allow only non-browser to recover session cookie from sharepoint cookie. when user close the browser without logout, next user on a shared computer can access previous user session if persistent cookie is not expired.

if you manage the MRHSession_SP cookie for all user agents, the user will be allowed to close the browser, open again and access to sharepoint.

another reason I put ACCESS::session command only for clientless and OFBA clients is to solve performance issues.

ACCESS::session may be used carefully because it generate a pause in execution of irule waiting all other TMM answer about this session. executing this for every requests may cause latency. that's why I filtered first on user-agent, then on session status.

I understand that in my irule, there are static objects I can remove and fix OFBA urls and persist cookie name.

I agree your switch command to filter user-agents is more optimized (with frontage filter version), but I will keep irule architecture to stay generic for every client type.

I will update my irule soon with good points I found in yours.

0
Comment made 13-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

good point to allow the MRHSession_SP persistent cookie logins only to non-browsers. I've already changed my code to include this additional security mechanism (see below). The performance impact off this change shouldn't be that much, since the HTTP_RESPONSE event will inject a MRHSession cookie for the very next request.

I don't have any information that ACCESS::session will park the connection till every other TMM has been contacted. I thought its more like a [table] call where just a specific TMM (data owner) will be contacted if needed. SOL12962 does also not explain this behavior...

"Note: When you run the ACCESS::session command, iRule execution on the connection will be suspended until the operation completes only if the session database record is held by another TMM; this situation allows the current TMM to retrieve the data from the other TMM before processing the remainder of the iRule. The ACCESS:: commands are available only if your BIG-IP system is licensed for the BIG-IP APM system."

Do you have additional information on the ACCESS:: connection parking behavior?

when CLIENT_ACCEPTED {
    set inject_session_cookie 0
}
when HTTP_REQUEST {
    # Check if APM session cookie is present and valid
    if { ( [set sessionid [HTTP::cookie value "MRHSession"]] ne "" ) and
         ( [ACCESS::session exists -state_allow $sessionid] ) } then {
        # Allow the successfully pre authenticated request to pass
    } else {
        # Enumerate explicit MS-OFBA authentication capabilities
        # Background: https://msdn.microsoft.com/en-us/library/office/cc313069(v=office.12).aspx
        if { ( [HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "t" ) or 
             ( [HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "f" ) } then {
            # Explicit MSOFBA support detected. 
            set authschema "ms-ofba"
        } else {
            # Enumerate implicit MS-OFBA authentication capabilities
            switch -glob -- [string tolower [HTTP::header "User-Agent"]] "*office protocol discovery*" - \
              "*microsoft office*" - \
              "*microsoft data access internet publishing provider*" - \
              "*non-browser*" - \
              "msoffice 12*" - \
              "*microsoft-webdav-miniredir*" - \
              {*ms frontpage 1[23456789]*} {
                # Implicit MSOFBA support detected.   
                set authschema "ms-ofba"
            } "*ms frontpage*" {
                # Legacy client detected
                set authschema "legacy"
            } "*mozilla*" - \
              "*opera*" {
                # Regular web browser detected.  
                set authschema "browser"
            } default { 
                set authschema "legacy"
            }
        }
        if { not ( $authschema eq "browser" ) and  
             ( [set sessionid [HTTP::cookie value "MRHSession_SP"]] ne "" ) and
             ( [ACCESS::session exists -state_allow $sessionid] ) } then {
            # Restore APM session cookie value
            HTTP::cookie insert name "MRHSession" value $sessionid
            set inject_session_cookie 1 
            # Allow the successfully pre authenticated request to pass
        } else {
            if { $authschema eq "ms-ofba" } then {
                # Send a MSOFBA compatible Access Denied response 
                if { [HTTP::path] ne "/sp-msofba-form" } then {
                    HTTP::respond 403 -version "1.1" \
                                      content "Access Denied. Make sure that your client is correctly configured. See https://support.microsoft.com/en-us/kb/932118 for further information." \
                                      noserver \
                                      "Content-Type" "text/html" \
                                      "X-FORMS_BASED_AUTH_REQUIRED" "https://[getfield [HTTP::host] ":" 1]/sp-msofba-form" \
                                      "X-FORMS_BASED_AUTH_RETURN_URL" "https://[getfield [HTTP::host] ":" 1]/sp-msofba-completed" \
                                      "X-FORMS_BASED_AUTH_DIALOG_SIZE" "800x600" \
                                      "Set-Cookie" "MRHSession=deleted;path=/;secure" \
                                      "Set-Cookie" "LastMRH_Session=deleted;path=/;secure" \
                                      "Set-Cookie" "MRHSession=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure" \
                                      "Set-Cookie" "LastMRH_Session=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure"
                    }
            } elseif { $authschema eq "legacy" } then {
                # Send a regular Access Denied response 
                HTTP::respond 403 content "Access denied. An unsupported client access has been detected."
            } else {
                # Let the regular web browser request pass to the APM policy
            }
        }
    }
}
when ACCESS_ACL_ALLOWED {
    switch -glob -- [string tolower [HTTP::path]] "/sp-msofba-form" {
        # Successfully APM authenticated request MS-OFBA request detected. Redirect to MS-OFBA return URL
        ACCESS::respond 302 noserver Location "/sp-msofba-completed"
    } "/sp-msofba-completed" {
        # Successfully APM authenticated request MS-OFBA request detected. Sending MS-OFBA return response
        ACCESS::respond 200 content "<html><head><title>Authenticated</title></head><body><b>Good Work, you are Authenticated</b></body></html>" noserver
    } "*/signout.aspx" {
        # SharePoint SignOut signature detected. Disconnect session and redirect to APM logout Page
        ACCESS::respond 302 noserver Location "/vdesk/hangup.php3"
    } "/_layouts/accessdenied.aspx" {
        # SharePoint AccessDenied signature detected.
        if { [string tolower [URI::query [HTTP::uri] loginasanotheruser]] equals "true" } then {
            # SharePoint LoginAsAnotherUser request detected. Killing the APM session an sending redirect to www-root.
            ACCESS::session remove
            ACCESS::respond 302 noserver Location "/"
            return
        }
    } default {
         # Let the authenticated request pass
    }
}
when HTTP_RESPONSE {
    if { [HTTP::header "Content-Type" ] contains "text/html" } then {
        # Insert persistent APM session cookie into HTTP response.
        HTTP::header insert "Set-Cookie" "MRHSession_SP=$sessionid;Path=/;Secure;HttpOnly"
        HTTP::cookie expires "MRHSession_SP" 120 relative
    }
    if { $inject_session_cookie } then {
        # Insert APM session cookie into HTTP response.
        HTTP::header insert "Set-Cookie" "MRHSession=$sessionid;Path=/;Secure;HttpOnly"
        set inject_session_cookie 0
    }
}

Note: If the F5 exchange irule uses [ACCESS::user getsid $user_key] to offload authentication request, then its a even a bigger issue... :-( The problem is, that you can easily bruteforce AD accounts if the offloading cache is not protected by an account lockout mechanism. You can guess passwords even if the account is already locked out and if the correct password is found you are allowed to enter or at least get a slightly different error message. A max session timeout period of 1 hour wouldn't make it better if SmartPhones polls the mailbox 24/7, isn't it?

Cheers, Kai

0
Comment made 13-Sep-2016 by Stanislas Piron 9618

the use of [ACCESS::user getsid $user_key] is not a big issue.

in ACCESS_SESSION_STARTED, the session uuid is changed from default tmm.policyname.logonname to username."md5 of user password"

if { [ info exists user_key ] } {
  ACCESS::session data set "session.user.uuid" $user_key
}

so, when a new request comes with same username and password and without session cookie, APM will not create a new session if a previous one exists with the same username / password.

This is first created for active sync to prevent multiple access session for one user.

in the active sync, there is a option to insert in the hash the client ip address to prevent a user with multiple devices to share the same access session (this is the default behavior for active sync). this can be added in the irule.

0
Comment made 14-Sep-2016 by Stanislas Piron 9618

and I confirm I had some issues about ACCESS::session with more than 30K access session on BIGIP 10250.

BIGIP 10250 use 12 TMM process, I had some request dropped when access::session did not receive session status from all tmm.

In the first version, I used ACCESS::session to get session status, get session variable (for one customer, MRHSession_SP cookie is only sent if the user check "private computer" in the logon page).

I changed the irule to limit ACCESS::session command to optimize the code.

0
Comment made 14-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

I've performed some [clock clicks] tests, to see how the ACCESS::session connection parking behaves compared to the [table] command and to see if the per-request enumeration of the UA-header to skip the ACCESS::session command for regular APM session cookies will be a performance benefit.

Test Code

set time(ACCESS_via_TMM[TMM::cmp_unit]) [clock clicks]

set x [ACCESS::session exists -state_allow $sessionid]

lappend time(ACCESS_via_TMM[TMM::cmp_unit]) [clock clicks]

set time(TABLE_via_TMM[TMM::cmp_unit]) [clock clicks]

set y [table lookup $sessionid]

lappend  time(TABLE_via_TMM[TMM::cmp_unit]) [clock clicks]

set time(UA_Enumeration) [clock clicks]

# UA-Enumeration script block

lappend  time(TABLE_via_TMM[TMM::cmp_unit]) [clock clicks]

Results

The table below is the statistical outpout of the individual [clock clicks] timestamps. The output contains the elapsed [clock clicks] to execute the a [table lookup] and [ACCESS::session] command for each individual TMM instance as well as the elapsed [clock clicks] for the UA-Enumeration. The [table lookup] and [ACCESS::session] call where performed by using a fixed and valid APM SessionID resp. existent table data..

Image Text

It seem that [ACCESS::session] is slightly slower than [table] and both methods are slightly faster than the UA-Enumeration if the requested information is stored on the same TMM where the current connection is processed (Note: In the provided test TMM0 owns the [ACCESS::session] data and TMM1 owns the [table] data). But if the requested data is not stored on the same TMM instance, a connection parking situation will happen to ask the owning TMM instance for results. In this case the per-request UA-Enumeration will outperform the the [table lookup] and [ACCESS::session] call very much (Although, its hard to tell how much processing/idle time is required for connection parkings).

Then I've performed a second test with random and invalid SessionID values resp. random and non-existent table data, to compare the TMM-owner distribution of the [table] and [ACCESS::session] data.

Image Text

The results are clearly showing a big difference in the [table] and [ACCESS::session] TMM-owner distribution behavior. The [table] command distributes the different keys values almost evenly accross the TMM instances, but the [ACCESS::session] data is always stored on TMM0. This behavior explains very much, why you've experienced problems using the [ACCESS::session] command in the past. You've simply flooded TMM0 with cross-TMM communication and the more concurrent APM session are active (lookup takes longer) and the more TMM instances are used (more cross-TMM communication required) the more becomes TMM0 the bottle-neck...

Based on the findings I've updated my iRule to perform the UA-Enumeration on every request and to skip any [ACCESS::session] commands for regular browsers. While restructuring the code, I've found a cool method to offload the UA-Enumeration for Keep-Alive sessions by storing and comparing the last visited UA-string with a $last_ua_agent variable. And only perform a full enumeration if the user-agent has been changed. The overhead of the entire HTTP_REQUEST event is not down to ~40 clicks. You'll find my updated code below...

Note: I've to thank you very much for sharing your knowledge/experience regarding [ACCESS::session] and for sharing all the insights of your lovely MS-OFBA irule. Great stuff! ;-)

when CLIENT_ACCEPTED {

    ################################################################################
    # Initialize UA-Enumeration variable for Keep-Alive sessions
    #
    set last_ua_agent "init"

}
when HTTP_REQUEST {

    ################################################################################
    # Offload UA-Enumeration for Keep-Alive sessions
    #

    if { $last_ua_agent ne [set last_ua_agent [HTTP::header value "User-Agent"]] } then {

        ################################################################################
        # Enumerate explicit MS-OFBA authentication capabilities
        # Background: https://msdn.microsoft.com/en-us/library/office/cc313069(v=office.12).aspx
        #

        if { ( [HTTP::header value "X-FORMS_BASED_AUTH_ACCEPTED"] equals "t" ) or 
             ( [HTTP::header value "X-FORMS_BASED_AUTH_ACCEPTED"] equals "f" ) } then {

            # Explicit MSOFBA support detected. 
            set authschema "ms-ofba"

        } else {

            ################################################################################
            # Enumerate implicit MS-OFBA authentication capabilities
            # Background: https://msdn.microsoft.com/en-us/library/office/cc313069(v=office.12).aspx
            # 

            switch -glob -- [string tolower $last_ua_agent] "*office protocol discovery*" - \
              "*microsoft office*" - \
              "*microsoft data access internet publishing provider*" - \
              "*non-browser*" - \
              "msoffice 12*" - \
              "*microsoft-webdav-miniredir*" - \
              {*ms frontpage 1[23456789]*} {

                # Implicit MSOFBA support detected.   
                set authschema "ms-ofba"

            } "*ms frontpage*" {

                # Legacy client detected
                set authschema "legacy"

            } "*mozilla*" - \
              "*opera*" {

                # Regular web browser detected.  
                set authschema "browser"

            } default { 

                # Unknown user-agent detected. 
                set authschema "legacy"

            }
        }
    }

    ################################################################################
    # Apply required actions based on the browser type
    # 

    if { $authschema eq "browser" } then {

        ################################################################################
        # Browser request detected.
        # 

        # Store the current session ID for response cookie handling
        set sessionid [HTTP::cookie value "MRHSession"]
        # Enable response cookie handling to inject a persitent cookie
        set inject_session_cookie 0
        set inject_persistent_cookie 1

    } elseif { $authschema eq "ms-ofba" } then {

        ################################################################################
        # MS-OFBA compatible request detected.
        # 

        if { [ACCESS::session exists -state_allow [HTTP::cookie value "MRHSession"]] } then {
            # Deactivate response cookie handling
            set inject_session_cookie 0
            set inject_persistent_cookie 0
        } elseif { ( [set sessionid [HTTP::cookie value "MRHSession_SP"]] ne "" ) and
                   ( [ACCESS::session exists -state_allow $sessionid] ) } then {
            # Restore APM session cookie value for the current request
            HTTP::cookie insert name "MRHSession" value $sessionid
            # Enable response cookie handling to inject a session cookie
            set inject_session_cookie 1
            set inject_persistent_cookie 0
        } else {
            if { $authschema eq "ms-ofba" } then {
                # Send a MSOFBA compatible Access Denied response 
                if { [HTTP::path] ne "/sp-msofba-form" } then {
                    HTTP::respond 403 -version "1.1" \
                                      content "Access Denied. Make sure that your client is correctly configured. See https://support.microsoft.com/en-us/kb/932118 for further information." \
                                      noserver \
                                      "Content-Type" "text/html" \
                                      "X-FORMS_BASED_AUTH_REQUIRED" "https://[getfield [HTTP::host] ":" 1]/sp-msofba-form" \
                                      "X-FORMS_BASED_AUTH_RETURN_URL" "https://[getfield [HTTP::host] ":" 1]/sp-msofba-completed" \
                                      "X-FORMS_BASED_AUTH_DIALOG_SIZE" "800x600" \
                                      "Set-Cookie" "MRHSession=deleted;path=/;secure" \
                                      "Set-Cookie" "LastMRH_Session=deleted;path=/;secure" \
                                      "Set-Cookie" "MRHSession=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure" \
                                      "Set-Cookie" "LastMRH_Session=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure"
                }
            }
        }

    } elseif { $authschema eq "legacy" } then {

        ################################################################################
        # Legacy Client request detected.
        # 

        # Send a regular Access Denied response 
        HTTP::respond 403 content "Access denied. An unsupported client access has been detected."

    }
}

when ACCESS_ACL_ALLOWED {

    ################################################################################
    # Handler for MS-OFBA authentication requests and SharePoint functionality
    # 

    switch -glob -- [string tolower [HTTP::path]] "/sp-msofba-form" {
        # Successfully APM authenticated request MS-OFBA request detected. Redirect to MS-OFBA return URL
        ACCESS::respond 302 noserver Location "/sp-msofba-completed"
    } "/sp-msofba-completed" {
        # Successfully APM authenticated request MS-OFBA request detected. Sending MS-OFBA return response
        ACCESS::respond 200 content "<html><head><title>Authenticated</title></head><body><b>Good Work, you are Authenticated</b></body></html>" noserver
    } "*/signout.aspx" {
        # SharePoint SignOut signature detected. Disconnect session and redirect to APM logout Page
        ACCESS::respond 302 noserver Location "/vdesk/hangup.php3"
    } "/_layouts/accessdenied.aspx" {
        # SharePoint AccessDenied signature detected.
        if { [string tolower [URI::query [HTTP::uri] loginasanotheruser]] equals "true" } then {
            # SharePoint LoginAsAnotherUser request detected. Killing the APM session an sending redirect to www-root.
            ACCESS::session remove
            ACCESS::respond 302 noserver Location "/"
            return
        }
    } default {
         # Let the authenticated request pass to SharePoint...
    }
}
when HTTP_RESPONSE {

    ################################################################################
    # Handler for APM session and persitent cookie injection
    # 

    if { ( $inject_persistent_cookie ) and ( [HTTP::header "Content-Type" ] contains "text/html" ) } then {
        # Insert persistent APM session cookie into HTTP response.
        HTTP::header insert "Set-Cookie" "MRHSession_SP=$sessionid;Path=/;Secure;HttpOnly"
        HTTP::cookie expires "MRHSession_SP" 120 relative
    }
    if { $inject_session_cookie } then {
        # Insert APM session cookie into HTTP response.
        HTTP::header insert "Set-Cookie" "MRHSession=$sessionid;Path=/;Secure;HttpOnly"
    }
}

Note: I will dig into the [ACCESS::user getsid $user_key] later. Right now it seems that the iRule just recovers/injects the MRHSession cookie if the md5(username+password) value can be matched with an active APM session. If the password is then correctly guessed, the ACCESS_SESSION_STARTED event wouldn't become triggered. And I guess if Source_IP cookie binding is configured then APM will send a err302 to /my.policy to initiate a new APM session (instead of err401 if a wrong password is used). But I have to test this out to get sure...

Cheers, Kai

0
Comment made 14-Sep-2016 by Stanislas Piron 9618

Kai,

instead of creating a variable with previous user agent, you can check if the variable authschema exists

if { [info exists authschema] } then {
    # Do nothing, keep previous request authschema value
} elseif { ( [HTTP::header value "X-FORMS_BASED_AUTH_ACCEPTED"] equals "t" ) or 
    ( [HTTP::header value "X-FORMS_BASED_AUTH_ACCEPTED"] equals "f" ) } then {

    ################################################################################
    # Enumerate explicit MS-OFBA authentication capabilities
    # Background: https://msdn.microsoft.com/en-us/library/office/cc313069(v=office.12).aspx
    #


    # Explicit MSOFBA support detected. 
    set authschema "ms-ofba"

} else {

    ################################################################################
    # Enumerate implicit MS-OFBA authentication capabilities
    # Background: https://msdn.microsoft.com/en-us/library/office/cc313069(v=office.12).aspx
    # 

    switch -glob -- [string tolower $last_ua_agent] "*office protocol discovery*" - \
      "*microsoft office*" - \
      "*microsoft data access internet publishing provider*" - \
      "*non-browser*" - \
      "msoffice 12*" - \
      "*microsoft-webdav-miniredir*" - \
      {*ms frontpage 1[23456789]*} {

        # Implicit MSOFBA support detected.   
        set authschema "ms-ofba"

    } "*ms frontpage*" {

        # Legacy client detected
        set authschema "legacy"

    } "*mozilla*" - \
      "*opera*" {

        # Regular web browser detected.  
        set authschema "browser"

    } default { 

        # Unknown user-agent detected. 
        set authschema "legacy"

    }
}
0
Comment made 14-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

[info exists] has a rather poor performance on TCL8.4. With TCL8.5 the performance will become much better. In addition I also tried to cover the scenario, that the UA may change on a single TCP connection. But a good catch that I could combine the two [if]'s into a single one. This will save some additional cycles... Thanks! ;-)

Cheers, Kai

0
Comment made 14-Sep-2016 by Stanislas Piron 9618

Hi Kai,

I tried in version tcl 8.4 et tcl 8.5, and I confirm [info exists] is optimized in version 8.5. but $last_ua_agent ne [set last_ua_agent XXX] has also poor performance in tcl 8.4.

$ tclsh8.5
% set ua "Mozilla"
Mozilla
% set authschema "browser"
browser
% time { expr { [info exists authschema] } } 100000
0.32418845 microseconds per iteration
% time { expr { $ua != [ set ua "Mozilla"] } } 100000
0.56738861 microseconds per iteration


$ tclsh8.4
% set ua "Mozilla"
Mozilla
% set authschema "browser"
browser
% time { expr { [info exists authschema] } } 100000
0.98919 microseconds per iteration
% time { expr { $ua != [ set ua "Mozilla"] } } 100000
0.73574 microseconds per iteration

I understand the user agent can change on a single TCP connection, but the client type may not change (As I know, proxy servers does not reuse TCP connection for different client connections).

thank you very much for pointing me which part of this irule I can optimize, and share your experience about irule and APM interoperability.

I hope this feature will be included in future sharepoint iapp and deployment guide.

Next step will be to add Microsoft ADAL support with future version if F5 oauth implementation will be compatible.

0
Comment made 14-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

No please don't, I have to thank YOU for pointing me in the right direction. Your iRule simply rocks! :-)

I'm rather unsure if certain SSL-Inspection Proxy may share upstream connections between different internal clients unless "Session-Based-Authentication" support is explicitly requested. At least certain HTTP Proxy will recycle connections. But since HTTP is per RFC a stateless protocol, its always better to prepare for the worst and accept the fact that certain Proxy's may recycle connections, isn't it?

Question: Rumers (e.g. sol36322151) are going around that TMOS v12+ supports TCL 8.5 for iRules. Do you know how to enable TCL8.5? If you have some sparetime left I would be glad if you could open a support call to find out. I don't have a valid support contract in my pocket... :-(

In addition I was able to repro my concerns regarding the mentioned account lockout bypass.

Behavior with APM Profile "Restrict to Single Client IP" Option = DISABLED

1.) Access the site using a legacy client (aka. Basic Auth)

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="HelloWorld - HTTP_REQUEST"
Set-Cookie: MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/
Server: BigIP
Connection: close
Content-Length: 0

2.) Login as valid user using credential set: test:password1+

HTTP/1.1 302 Redirect
Location: /pages/default.aspx
Server: Microsoft-IIS/8.0
...
Set-Cookie: MRHSession=100f33e0307120fd8f2d0e886cf2b7d1; expires=Wed, 14 Sep 2016 15:48:19 GMT;path=/;secure;HttpOnly
Set-Cookie: LastMRH_Session=100f33e0307120fd8f2d0e886cf2b7d1; expires=Wed, 14 Sep 2016 15:48:19 GMT;path=/;secure;HttpOnly

3.) Login from a different host using credential set: test:password1-

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="HelloWorld - ACCESS_POLICY_COMPLETED"
Set-Cookie: MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/
Server: BigIP
Connection: Close
Set-Cookie: LastMRH_Session=e443275f;path=/;secure;HttpOnly
Set-Cookie: MRHSession=7884faddb7f22b02f86b38f4e443275f;path=/;secure;HttpOnly
Content-Length: 0

4.) Repeat step 3.) multiple times to lock the user account in your repository

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="HelloWorld - ACCESS_POLICY_COMPLETED"
Set-Cookie: MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/
Server: BigIP
Connection: Close
Set-Cookie: LastMRH_Session=3c9093b9;path=/;secure;HttpOnly
Set-Cookie: MRHSession=26212f5e9bec540efa77ad963c9093b9;path=/;secure;HttpOnly
Content-Length: 0

5.) Login from a different host using credential set: test:password1+ (aka. you have guessed the correct password!)

HTTP/1.1 302 Redirect
Location: /pages/default.aspx
Server: Microsoft-IIS/8.0
...
Set-Cookie: MRHSession=100f33e0307120fd8f2d0e886cf2b7d1; expires=Wed, 14 Sep 2016 15:48:19 GMT;path=/;secure;HttpOnly
Set-Cookie: LastMRH_Session=100f33e0307120fd8f2d0e886cf2b7d1; expires=Wed, 14 Sep 2016 15:48:19 GMT;path=/;secure;HttpOnly

Behavior with APM Profile "Restrict to Single Client IP" Option = ENABLED

1.) Access the site using a legacy client (aka. Basic Auth)

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="HelloWorld - HTTP_REQUEST"
Set-Cookie: MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/
Server: BigIP
Connection: close
Content-Length: 0

2.) Login as valid user using credential set: test:password1+

HTTP/1.1 302 Redirect
Location: /pages/default.aspx
Server: Microsoft-IIS/8.0
...
Set-Cookie: MRHSession=100f33e0307120fd8f2d0e886cf2b7d1; expires=Wed, 14 Sep 2016 15:48:19 GMT;path=/;secure;HttpOnly
Set-Cookie: LastMRH_Session=100f33e0307120fd8f2d0e886cf2b7d1; expires=Wed, 14 Sep 2016 15:48:19 GMT;path=/;secure;HttpOnly

3.) Login from a different host using credential set: test:password1-

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="HelloWorld - ACCESS_POLICY_COMPLETED"
Set-Cookie: MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/
Server: BigIP
Connection: Close
Set-Cookie: LastMRH_Session=e443275f;path=/;secure;HttpOnly
Set-Cookie: MRHSession=7884faddb7f22b02f86b38f4e443275f;path=/;secure;HttpOnly
Content-Length: 0

4.) Repeat step 3. multiple times to lock the user account in your repository

HTTP/1.0 401 Unauthorized
WWW-Authenticate: Basic realm="HelloWorld - ACCESS_POLICY_COMPLETED"
Set-Cookie: MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/
Server: BigIP
Connection: Close
Set-Cookie: LastMRH_Session=3c9093b9;path=/;secure;HttpOnly
Set-Cookie: MRHSession=26212f5e9bec540efa77ad963c9093b9;path=/;secure;HttpOnly
Content-Length: 0

5.) Login from a different host using credential set: test:password1+ (aka. you have guessed the correct password!)

HTTP/1.0 302 Found
Server: BigIP
Cache-Control: no-cache, no-store
Connection: Close
Content-Length: 0
Location: /vdesk/hangup.php3
Set-Cookie: LastMRH_Session=e443275f;path=/;secure;HttpOnly
Set-Cookie: MRHSession=7884faddb7f22b02f86b38f4e443275f;path=/;secure;HttpOnly

Note: As you've already pointed out, the Exchange iRule uses an optional [md5 "$apm_password$src_ip"] for per-src_ip uuie keying. But even then, it can still be used to by-pass repository account lockouts, if you sit behind the same NAT device (e.g. same conference center, cafe, etc.). The better approach would be to implement a tight account lookout before any credential caches are used.

Cheers, Kai

0
Comment made 14-Sep-2016 by Stanislas Piron 9618

Hi kai,

the exchange irule does not use the APM Profile "Restrict to Single Client IP" value.

I already created a little irule to enable the use of this value with APM exchange profile:

https://devcentral.f5.com/questions/apm-activesync-irule-and-profile_restrict_single_ip-variable

I will update the irule with the ability to limit session reuse for a single client ip like in exchange irule.

With sharepoint, it is interesting to limit session reuse per IP. with active sync, mobile phone often change client ip as they are natted with an address pool. in this case, limiting session per client IP can create lots of session for the same device.

0
Comment made 14-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

tell me what you thing about this rather simple change. Its much more secure than any IP binding... :-)

when RULE_INIT {
    set static::account_failed_auth_limit 5
    set static::account_failed_auth_window 300
    set static::account_lockout_duration 600    
}
when HTTP_REQUEST {

....

  if { !([HTTP::header Authorization] == "") } {
     set clientless(insert_mode) 1
     set username    [ string tolower [HTTP::username] ]
     if { [table lookup "$username\_lock"] ne 1 } then {
        set clientless(password)    [HTTP::password]
        binary scan [md5 "$clientless(password)"] H* clientless(hash)
        set user_key "$username.$clientless(hash)"
        set clientless(cookie_list)             [ ACCESS::user getsid $user_key ]
        if { [ llength $clientless(cookie_list) ] != 0 } {
           set clientless(cookie) [ ACCESS::user getkey [ lindex $clientless(cookie_list) 0 ] ]
           if { $clientless(cookie) != "" } {
              HTTP::cookie insert name MRHSession value $clientless(cookie)
              set clientless(insert_mode) 0
           }
        }
        if { $clientless(insert_mode) } {
           HTTP::header insert "clientless-mode" 1
           HTTP::header insert "username" $username
           HTTP::header insert "password" $clientless(password)
        }
        unset clientless
     } else {
        HTTP::respond 401 WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close
        unset clientless
        return
     }
  } else {
    HTTP::respond 401 WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close
    return
  }

...

}
when ACCESS_POLICY_COMPLETED {
   if { ([info exists "clientless_mode"]) && ($clientless_mode) && ([ACCESS::policy result] equals "deny") } {
      ACCESS::respond 401 WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close
      ACCESS::session remove
      if { $static::account_failed_auth_limit > 0 } then {
         if { [expr { [table keys -subtable "$username\_count" -count] + 1 }] >= $static::account_failed_auth_limit } {
            table set -notouch "$username\_lock" 1 indef $static::account_lockout_duration
            table delete -subtable "$username\_count" -all
         } else {
            table set -subtable "$username\_count" [clock clicks] 1 indef $static::account_failed_auth_window
         }

      }

   } 
}

Note: The provided code is recycled from one of my iRule based authentication module(s). The code is not tested in combination with your code and may contain some coding glitches...

Cheers, Kai

0
Comment made 14-Sep-2016 by Stanislas Piron 9618

since version 11.4, you can use localdb instance to store lockout users instead of table.

localdb instance allow to dynamic user creation. in locadb write action, you can allow to create unknown users.

follow this link to configure it:

https://support.f5.com/kb/en-us/products/big-ip_apm/manuals/product/apm-authentication-single-sign-on-11-6-0/13.html?sr=39912269

about your question on tcl version 8.5 since TMOS 12.0, there is nothing to do. According to SOL36322151, irule use TCL 8.6.7 since version 12.0

0
Comment made 15-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

you can't use locadb to secure the usage of [ACCESS::user getsid $user_key] within HTTP_REQUEST. Its only available within an APM Policy which is to late in the chain. Even then i think you will get much better results using iRules in combination with [table].

Furthermore, my aged development strongbox is running on TMOS 12.0.0 with TCL version 8.4.6. Specifc 8.5 commands are not shown using [info commands] nor [interp hidden] comamnds. Also any new 8.5 syntaxes (e.g. {*} aka. nipple expand) are not available and the performance of [info exists] is still suboptimal.

You may try the code below on 12.1 to see if TCL8.5 is available on those plattforms. If not, then please open a ticket for me :-)

when RULE_INIT {
        log local0.debug "TCL version : [info patchlevel]"
}

Cheers, Kai

0
Comment made 15-Sep-2016 by Stanislas Piron 9618

OK,

I did not see what did your irule. I now understand what you mean.

I think usage of [ACCESS::user getsid $user_key] with password hash is as secure as usage of session cookie.

there is the same security issue if the account is locked after the user signed on logon page.

the irule does not use username and password to authenticate but as a fingerprint to be sure this is the same user as the previous session, and reuse the same session, like session cookie does.

that's why I was thinking about insert client ip in the fingerprint as in exchange irule.

0
Comment made 15-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

without additional Account Lockouts inplace you will simply allow attacker to bypass the account lockouts of your internal repository. An IP binding (like implemented in the EAS iRules) will make it just a little harder and introduces certain side effects (opening multiple APM sessions if an IP change occours). But its by any means not a secure solution^^

Cheers, Kai

0
Comment made 15-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

if you're going to implement an account lockout then make sure you issue after "[PROFILE::access min_failure_delay]000" before sending the lockout 401 response. Otherwise it will be possible for an attacker to detect that the lockout is active.

Cheers, Kai

0
Comment made 15-Sep-2016 by Stanislas Piron 9618

Hi Kai,

[ACCESS::user getsid $user_key] in only used when another session is already authenticated. so the user provided first the right login / password.

for next requests with same login / password, there is no need to check if the password is wrong with lockout prevention. the password was right during first logon, so password is used to build fingerprint.

I understand that brute force prevention may be the next security part for the irule allowing basic auth as APM disable brute force prevention with min / max failure delay.

to enable min / max failure delay, I can add the following code before ACCESS::respond in ACCESS_POLICY_COMPLETED event:

set min "[PROFILE::access min_failure_delay]000"
set max "[PROFILE::access max_failure_delay]000"

after [expr {int(rand() * ($max + 1 - $min)) + $min}]
ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Connection close
unset min max
0
Comment made 15-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

for next requests with same login / password, there is no need to check if the password is wrong with lockout prevention. the password was right during first logon, so password is used to build fingerprint.

To protect against bruteforce attacks, its required to block further authentication attemps using the same username if a certain threshold of wrong logins has been reached. If caches are deployed to offload repository authentication (e.g. [ACCESS::user getsid $user_key] is a credential cache!), then you MUST either make sure that the lockout mechanism will also cover access to the cached credentials, or you MUST make sure that the cached credentials of a given username are gettings invalidated once the account lockout is active.

Since invalidating the cache is not practical in our scenario (it will cause the existing session to become removed), its wise to enforce the lockout in front of the cache. If both methods are ignored, the caches can be used to bruteforce a currently active account, even in the case the repository has already enforced a lockout...

after [expr {int(rand() * ($max + 1 - $min)) + $min}]

Yours is even better.. ;-)

Cheers, Kai

0
Comment made 15-Sep-2016 by Stanislas Piron 9618

Hi Kai,

I followed all your advices (except lockout, One solution instead of blocking with table can be deleting in ACCESS_POLICY_COMPLETED sessions of user with same username if VPE lockout occurs)

here is the new version :

when RULE_INIT {
   set static::Basic_Realm_Text "SharePoint Authentication"
}

when CLIENT_ACCEPTED {
    set inject_session_cookie 0
    set last_ua_agent "init"
}

when HTTP_REQUEST {
    if { ! [ info exists SP_PROFILE_RESTRICT_SINGLE_IP ] } {
        set SP_PROFILE_RESTRICT_SINGLE_IP        [PROFILE::access restrict_to_single_client_ip]
    } 
    # Identify User-Agents type
    if { $last_ua_agent equals [set last_ua_agent [HTTP::header value "User-Agent"]] } {
        # Do nothing, keep previous request authschema value
        log local0. $last_ua_agent
    } elseif {[HTTP::header exists "X-FORMS_BASED_AUTH_ACCEPTED"] && (([HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "t") || ([HTTP::header "X-FORMS_BASED_AUTH_ACCEPTED"] equals "f"))} {
        set clientless_mode 0; set form_mode 1
    } else {
        set clientless_mode 0
        set form_mode 0
        switch -glob -- [string tolower [HTTP::header "User-Agent"]] "*office protocol discovery*" - \
            "*microsoft office*" - \
            "*microsoft data access internet publishing provider*" - \
            "*non-browser*" - \
            "msoffice 12*" - \
            "*microsoft-webdav-miniredir*" - \
            {*ms frontpage 1[23456789]*} {
                # Implicit MSOFBA support detected.   
                set form_mode 1
            } "*ms frontpage*" {
                # Legacy client detected
                set clientless_mode 1
            } "*mozilla*" - \
            "*opera*" {
                # Regular web browser detected.  
                set clientless_mode 0
            } default { 
                set clientless_mode 1
            }
   }
   if { $clientless_mode || $form_mode } {
        if { ( [set sessionid [HTTP::cookie value "MRHSession"]] ne "" ) and ( [ACCESS::session exists -state_allow $sessionid] ) } then {
            # Allow the successfully pre authenticated request to pass
            return
        } else {
            # Check if persistent APM session cookie is present and valid
            if { ( [set sessionid [HTTP::cookie value "MRHSession_SP"]] ne "" ) and ( [ACCESS::session exists -state_allow $sessionid] ) } then {
                # Restore APM session cookie value
                if { [catch {HTTP::cookie insert name "MRHSession" value $sessionid} ] } {log local0. "[IP::client_addr]:[TCP::client_port] : TCL error on HTTP cookie insert MRHSession : URL : [HTTP::host][HTTP::path] - Headers : [HTTP::request]"} else {return}
                HTTP::cookie insert name "MRHSession" value $sessionid
                set inject_session_cookie 1 
                # Allow the successfully pre authenticated request to pass
                return
            }
        }
    } else {
        set sessionid [HTTP::cookie value "MRHSession"]
        return
    }      
    if { $clientless_mode } {
      if { [ string match -nocase {basic *} [HTTP::header Authorization] ] == 1 } {
         set clientless(insert_mode) 1
         set clientless(src_ip)      [IP::remote_addr]
         set clientless(username)    [ string tolower [HTTP::username] ]
         set clientless(password)    [HTTP::password]
         if { $SP_PROFILE_RESTRICT_SINGLE_IP == 0 } {
                binary scan [md5 "$clientless(password)"] H* clientless(hash)
         } else {
                binary scan [md5 "$clientless(password)$clientless(src_ip)"] H* clientless(hash)
         }
         set user_key "$clientless(username).$clientless(hash)"
         set clientless(cookie_list)             [ ACCESS::user getsid $user_key ]
         if { [ llength $clientless(cookie_list) ] != 0 } {
            set clientless(cookie) [ ACCESS::user getkey [ lindex $clientless(cookie_list) 0 ] ]
            if { $clientless(cookie) != "" } {
               HTTP::cookie insert name MRHSession value $clientless(cookie)
               set clientless(insert_mode) 0
            }
        }
        if { $clientless(insert_mode) } {
            HTTP::header insert "clientless-mode" 1
            HTTP::header insert "username" $clientless(username)
            HTTP::header insert "password" $clientless(password)
        }
        unset clientless
        } else {
            HTTP::respond 401 noserver WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Set-Cookie "MRHSession=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/" Connection close
            return
        }
    } elseif {$form_mode && [HTTP::path] ne "/sp-msofba-form"}{
        HTTP::respond 403 -version "1.1" \
            content "Access Denied. Make sure that your client is correctly configured. See https://support.microsoft.com/en-us/kb/932118 for further information." \
            noserver \
            "Content-Type" "text/html" \
            "X-FORMS_BASED_AUTH_REQUIRED" "https://[getfield [HTTP::host] ":" 1]/sp-msofba-form" \
            "X-FORMS_BASED_AUTH_RETURN_URL" "https://[getfield [HTTP::host] ":" 1]/sp-msofba-completed" \
            "X-FORMS_BASED_AUTH_DIALOG_SIZE" "800x600" \
            "Set-Cookie" "MRHSession=deleted;path=/;secure" \
            "Set-Cookie" "LastMRH_Session=deleted;path=/;secure" \
            "Set-Cookie" "MRHSession=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure" \
            "Set-Cookie" "LastMRH_Session=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;secure"
        return
    }
}


when HTTP_RESPONSE {
    # Insert persistent cookie for html content type and private session
    if { [HTTP::header "Content-Type" ] contains "text/html" } {
        HTTP::cookie remove MRHSession_SP
        HTTP::cookie insert name MRHSession_SP value $sessionid path "/"
        HTTP::cookie expires MRHSession_SP 120 relative
        HTTP::cookie secure MRHSession_SP enable
    }
    # Insert session cookie if session was recovered from persistent cookie
    if { ([info exists "inject_session_cookie"]) && ($inject_session_cookie) } {
        HTTP::cookie insert name MRHSession value $sessionid path "/"
        HTTP::cookie secure MRHSession enable
    }
}

when ACCESS_SESSION_STARTED {
    if {([info exists "clientless_mode"])} {
        ACCESS::session data set session.clientless $clientless_mode
    }
    if { [ info exists user_key ] } {
        ACCESS::session data set "session.user.uuid" $user_key
    }
}

when ACCESS_POLICY_COMPLETED {
    if { ([info exists "clientless_mode"]) && ($clientless_mode) && ([ACCESS::policy result] equals "deny") } {
        set min "[PROFILE::access min_failure_delay]000"
        set max "[PROFILE::access max_failure_delay]000"
        after [expr {int(rand() * ($max + 1 - $min)) + $min}]
        ACCESS::respond 401 noserver WWW-Authenticate "Basic realm=\"$static::Basic_Realm_Text\"" Connection close
        unset min max
        ACCESS::session remove
    } 
}

when ACCESS_ACL_ALLOWED {
    switch -glob [string tolower [HTTP::path]] {
        "/sp-msofba-form" {
            ACCESS::respond 302 noserver Location "/sp-msofba-completed"
        }
        "/sp-msofba-completed" {
            ACCESS::respond 200 content {
                <html>
                <head><title>Authenticated</title></head>
                <body><b>Good Work, you are Authenticated</b></body>
                </html>
            } noserver
        }
        "*/signout.aspx" {
            # Disconnect session and redirect to APM logout Page
            ACCESS::respond 302 noserver Location "/vdesk/hangup.php3"
            return
        }
        "/_layouts/accessdenied.aspx" {
            # Disconnect session and redirect to APM Logon Page
            if {[string tolower [URI::query [HTTP::uri] loginasanotheruser]] equals "true" } {
                ACCESS::session remove
                ACCESS::respond 302 noserver Location "/"
                return
            }
        }
        default {
            # No Actions
        }
    }
}
0
Comment made 16-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

I have some feedback for you...

Line 11-13: Make this option a static::variable. 
Line 15: Unify the syntax within your script to either [HTTP::header MyHeader] or [HTTP::header value MyHeader].
Line 17: Leftover log line?
Line 18: The HTTP::header exists makes much sense. I've updated my own script to include this.
Line 19: I don't see a reason to use two independent variables for forms and client_less. Using a multivalue would make things easier. You may use 0 for browser, 1 for MSOFBA and 2 for Clientless. In this case you could apply certain script blocks for ==1 ==2 or even >0
Line 20,21: Make sure that each switch script block sets both variables. In this case you don't need set them to 0 at the beginning. It will safe some cycles.
Line 51: Remove this line. HTTP::header insert should not throw an error and line 52 will nevertheless insert the cookie for you.
Line 53: Keep in mind that you should reset this variable once a cookie has been issued. Currently it will issues a cookie on consecutive requests. Alternatively set this variable on each request depending on the enumerated client type.
Line 68: Remove the == 0 and flip the order of the [if]. It may save your some additional cycles....
Line 111: I would make sure that only browser are allowed to issue persistent cookie. I guess there is no reason to inject persistent cookie for non-browser clients.
Line 112: A SharePoint wouldn't use a cookie name of MRHSession_SP, so you don't need to remove it.
Line 113-114: Combine this two commands using a HTTP::header insert it will safe some cycles. Also try to issue a HttpOnly header. It will work with Office clients.
Line 135-137: There is no need to random-delay the 401 within ACCESS_POLICY_COMPLETED. Its already auto-random-delayed. The delay would be required only for 401 responses within the HTTP_REQUEST event.
Line 118: There is no need for the [info exists]. The variable is set during CLIENT_ACCEPTED and the not removed.

Deleting VPE session: Sounds interesting. How will you enumerate the sssions with an identical username?

Cheers, Kai

0
Comment made 18-Sep-2016 by Stanislas Piron 9618

Hi Kai,

here are my answers:

  • Line 11-13: Make this option a static::variable. : No, if it is static::variable, it won't change when changing Access profile since the irule is reload (static::variable goal is to be set in RULE_INIT).
  • Line 15: Unify the syntax within your script to either [HTTP::header MyHeader] or [HTTP::header value MyHeader]. : I agree. I will change it.
  • Line 17: Leftover log line? : debug log i forgot to remove
  • Line 18: The HTTP::header exists makes much sense. I've updated my own script to include this.
  • Line 19: I don't see a reason to use two independent variables for forms and client_less. Using a multivalue would make things easier. You may use 0 for browser, 1 for MSOFBA and 2 for Clientless. In this case you could apply certain script blocks for ==1 ==2 or even >0 : I agree, but I must change it and manage in ACCESS_SESSION_STARTED, time was missing
  • Line 20,21: Make sure that each switch script block sets both variables. In this case you don't need set them to 0 at the beginning. It will safe some cycles. I will change to one variable
  • Line 51: Remove this line. HTTP::header insert should not throw an error and line 52 will nevertheless insert the cookie for you. Cookie insert,as most of HTTP commands, can throw an error if previous HTTP response command was executed before (another irule or LTM Policy)
  • Line 53: Keep in mind that you should reset this variable once a cookie has been issued. Currently it will issues a cookie on consecutive requests. Alternatively set this variable on each request depending on the enumerated client type.
  • Line 68: Remove the == 0 and flip the order of the [if]. It may save your some additional cycles.... : OK, I got it from F5 exchange irule
  • Line 111: I would make sure that only browser are allowed to issue persistent cookie. I guess there is no reason to inject persistent cookie for non-browser clients.
  • Line 112: A SharePoint wouldn't use a cookie name of MRHSession_SP, so you don't need to remove it. : Right
  • Line 113-114: Combine this two commands using a HTTP::header insert it will safe some cycles. Also try to issue a HttpOnly header. It will work with Office clients. I agree, I will do it
  • Line 135-137: There is no need to random-delay the 401 within ACCESS_POLICY_COMPLETED. Its already auto-random-delayed. The delay would be required only for 401 responses within the HTTP_REQUEST event. I tested it an in clientless mode and the auto random delay was not working on version 12.0, I will try again
  • Line 118: There is no need for the [info exists]. The variable is set during CLIENT_ACCEPTED and the not removed. : Right

And Lines 100 - 101: same cookie remove than lines 102 - 103. lines 100 and 101 will be removed.

0
Comment made 20-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

Line 11-13: Personally I would make the setting independent of the APM profile configuration. This would allow to distinct between regular browser and MSOFBA clients (not restricted) and clientless access (Single-IP restricted). Furthermore I would recoment to always use the Single-IP mode for clientless access to reduce the security risks associated with the [ACCESS::user getsid $user_key] syntax.

Line 51: Okay, I got the intention behind. Maybe you should issue a if { [catch {HTTP::payload replace 0 0 {}}] } then { return } at the beginning of the HTTP_REQUEST/HTTP_RESPONSE events to cover every write access on already responded HTTP requests/responses.

Line 135-137: Also tested on TMOS v12.0 and it has worked like a charm for me.

Additional Note: I've identified a problem within the MSOFBA authentication. If the APM session of an MSOFBA client is removed (e.g. session timeout) the MSOFBA client will not be able to re-authenticate using MSOFBA a second time. Only a fresh persistent cookie will allow him to save pending document changes, which is unfortunately not very intuitive for the end user. I've already developed a mechanism to restore/create a minimalistic APM session on-the-fly based on selected session meta-data (e.g. Username, Language-Settings, SSO settings) utilizing an AES encrypted MRHSession_R session cookie. The code already works like a charm and allows me to deploy strict APM Policy session inactivity timeouts (e.g. 900 seconds), while providing a solid backdoor for long-living MSOFBA client sessions (e.g. 1-2 day(s)) without wasting any APM concurrent user CALs. Will perform additional tests and then post my latest iRule for you...

Cheers, Kai

0
Comment made 23-Sep-2016 by Stanislas Piron 9618

Hi Kai,

I just tested to kill a session for a MSOFBA authenticated user.

When trying to save the document, Office client (In my test, Excel 2011 for Mac) display a message with a "reconnect" button. when I clicked on it, the document was uploaded on the server (as the user was authenticated on the SAML IdP, I was not prompted for user credentials).

I think this is not a problem but the expected behavior when session expires.

0
Comment made 23-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

every Windows based Office client I've tested so far does not perform MSOFBA a second time. It receives the err403 and then just stops to follow the MSOFBA headers... :-(

Cheers, Kai

0
Comment made 23-Sep-2016 by Stanislas Piron 9618

I just tried with Excel 2016 for windows. Excel displays the error message with connection button. when I click, the office browser open then close because of already saml session opened.

The irule used when I try is the one of this thread, not the last one in comments.

0
Comment made 24-Sep-2016 by Kai Wilke 6554

Hi Stanislas,

Just tried your iRule and mentioned problem remains.

I'm currently testing with Excel14 (aka. Office 2010), but will test some newer Office versions next week, to get a felling which specific versions are afffected.

Cheers, Kai

0
Comment made 05-Dec-2016 by domokos 68

Hello,

I have used the irule and hit some limitations. I turned on a lot of logging and I see the following behavior. If I use IE I see the 2 cookies inserted (I have also a VPE that does SAML auth followed by Kerberos SSO and the access policy uses persistent cookies). I click on the link to open the document in WORD and word fires. It always has the MRH cookie but not the _SP one. The variable apmstatus is set to 1 and apmpersiststatus is set to 0. I guess it never tries to insert the _SP one because the MRH cookie is always there. So it seems that word shares the cookie of IE. Now when I use Firefox I see in the FF session the 2 cookies but when word comes up it does not use any of them. So I get a redirect inside WORD to go to the SAML iDP to authenticate. This comes from the fact that the cookie that FF has is not used by word, so I wonder if you have any ideas on how to link the word session to FF. I cannot find anything in common that would link the 2 of them.

Thanks in advance Carol

0
Comment made 06-Dec-2016 by Stanislas Piron 9618

Hi,

Firefox does not write persistent cookies on the same folder than Microsoft products, and I'm not sure the cookie storage format is the same between applications. re-authentication is required when editing document from FF.

in the irule, there is no link between apmpersiststatus and _SP cookie.

0
Comment made 16-Jan-2017 by asaf 01 0

Hey Kai , can you please publish the final version . another thing , you need to disable the protected mode in IE.

0
Comment made 02-Feb-2017 by Daphne Won (F5)

Everyone, I like to engage the folks here that have deployed OFBA iRules to support authentication access from native Office apps to Sharepoint on-prem. Can you email me to see what authentication methods you need, what version of the Office clients you need to support, and what versions of the Sharepoints you have deployed for? You can email me at d.won@f5.com.

0
Comment made 22-May-2017 by ryanm99 23

Hello Everyone,

I am attempting to set up the iRule that was shared here to allow Microsoft Office Mobile apps to access SharePoint through APM. I am currently running version 13.0.0. When I attempt to access the environment, I get the following in the APM log file:

May 19 22:34:17 DV-BIGIP01 err tmm1[19118]: 01490514:3: (null):Common:00000000: Access encountered error: ERR_ARG. File: ../modules/hudfilter/access/access.c, Function: access_sanitize_portal_headers, Line: 15499
May 19 22:34:17 DV-BIGIP01 err tmm1[19118]: 01490514:3: (null):Common:00000000: Access encountered error: ERR_ARG. File: ../modules/hudfilter/access/access.c, Function: access_forward_request_to_portal, Line: 15578
May 19 22:34:17 DV-BIGIP01 err tmm1[19118]: 01490514:3: (null):Common:00000000: Access encountered error: ERR_ARG. File: ../modules/hudfilter/access/access.c, Function: access_process_state_client_enforce_session, Line: 7261
May 19 22:34:17 DV-BIGIP01 err tmm1[19118]: 01490514:3: (null):Common:00000000: Access encountered error: ERR_ARG. File: ../modules/hudfilter/access/access.c, Function: hud_access_handler, Line: 2735

Does anyone have any ideas on something I could try? On the mobile app side it just spins at connecting for a while and eventually times out. Thanks!​text

0
Comment made 23-Jan-2018 by sergio.baza.alonso 74

Hello all,

This iRule is working great for me in IE, Firefox and Chrome but I'm having problems with Edge. Everytime I try to open a Microsoft Office document I'm prompted for credentials.

Does someone know how to fix this?

Kind regards

0
Comment made 23-Jan-2018 by Nicolas COLLET 180

Hello,

It's possible to try the new version of this irule :

Best regards

0
Comment made 23-Jan-2018 by sergio.baza.alonso 74

Hello,

With the new version I'm still having the issue :(

Kind regards

0
Comment made 23-Jan-2018 by sergio.baza.alonso 74

Actually I'm using the iRule v2.

What I've noticed, (putting logs in the iRule) is that the IE and the Edge uses the same user agent so the behaviour shouldn't be different

EDGE: Jan 23 10:36:54 F5 info tmm1[18133]: Rule /Common/iRule_edicion_documentos : UA: microsoft office excel 2014 (16.0.8431) windows nt 10.0

IE: Jan 23 10:39:40 F5 info tmm[18133]: Rule /Common/iRule_edicion_documentos : UA: microsoft office excel 2014 (16.0.8431) windows nt 10.0

Does anybody has an idea of why is this happening?

Kind regards

0
Comment made 23-Jan-2018 by Stanislas Piron 9618

What is the exact behavior? You says you are prompted... but what kind of prompt?

OFBA? Basic auth pop-up?

With other browsers, is there any OFBA prompt?

0
Comment made 24-Jan-2018 by sergio.baza.alonso 74

Hello, I have made some screenshots.

This is my login page:

Image Text

I put my credentials and browse to a file, this one for example:

Image Text

I click in the file and the Excel App opens giving this error with a JS script https://host.com/public/include/js/web_host.js

Image Text

After that my login page appears in the Excel file

Image Text

If you put the credentials you can open the file.

This is happening with Edge browser but not with internet explorer.

Thanks for your support

Kind regards

0
Comment made 24-Jan-2018 by asaf 01 0

check that you getting the cookies- i had an issues that the time + timezone was not sync and the cookies was expired .

      HTTP::cookie remove $static::ckname
      HTTP::cookie insert name $static::ckname value $apmsessionid path "/"
      HTTP::cookie expires $static::ckname 120 relative
      HTTP::cookie secure $static::ckname enable
0
Comment made 24-Jan-2018 by Stanislas Piron 9618

it's not an irule issue but may be a browser behavior change.

0
Comment made 29-Jan-2018 by JoeTheFifth 301

Here is one for you guys: i don't use adfs. I use a forms login page to authenticate users in an ldap then I get the username and do a kerberos sso. This works fine with sharepoint and Office web apps but opening office documents does not work. i have the login pop. I will need to take a look a look at the irule and try ti adapt it. if you have any hints please shoot :-)

0
Comment made 29-Jan-2018 by Stanislas Piron 9618

Which login pop up?

If this is a form inside a browser it means the code works fine.

If you expected a transparent authentication because of browser auth, it works only with internet explorer, and only if the url is in trusted sites.

Authentication is stored in a persistent cookie written on disk by browser, but office only read cookies in ie directories.

0
Comment made 14-Feb-2018 by Pål Andre Ropstad 20

Hi Stanislas,

Thank you so much for your provided iRule. This is working great. Our only problem is getting it to work with Safari on Mac OS with Office for Mac (currently testing 2016). What happens is just like you describe. When your authenticated with Safari, and then try to open a Office document within SharePoint, this trigger the Office product (word, Excel, Powerpoint). But when the office application opens this trigger the access policy/authentication process within a browser - just like you describe. In reference to your last post - does that mean that this will not work with Safari on Mac?

Are there any customization that can be done to get it to work?

Thanks again.

Sincereley, Pål Andrè

0
Comment made 14-Feb-2018 by Stanislas Piron 9618

@Pal: I guess you misunderstand how it works.

This irule allow to support clientless authentication according to browser / office compatibility.

Without this irule, office apps (even libreoffice) can't edit online documents. This irule allow to use such applications to edit documents online by changing authentication method.

A persistent cookie is also inserted to allow office apps to download documents without reauthentication if both browser and app accept to share cookies. Until now, only Microsoft products allow such configuration (Explorer, Edge, Office).

If Apps can't share cookies, there is no solution.

0
Comment made 16-Feb-2018 by Pål Andre Ropstad 20

Hello Stanislas,

Thanks for your reply. I did not know that only microsoft products allowed the sharing of Cookies.

So then i understand that the form authentication that triggers from Safari/Office for Mac when accessing documents on SharePoint is expected. But when i complete the login/authentication I recieve the message: "Good work, you are authenticated". But it does not trigger the download/access to the requested document/resource. If I try to open the document one more time nothing happens.

I understand that this might not be related to the iRule at all. This could be a LTM/APM/Sharepoint 2016/Safari/Office for Mac or OS X issue. But does that make any sense to you? Appreciate any help!

And another question: Should I use Persistent Cookie on the access profile itself in addition to your iRule?

Thanks again!

0
Comment made 16-Feb-2018 by Stanislas Piron 9618

the goal of the persistent cookie is to support cookie share between IE and office.

this feature is also done by this irule with a 2 minutes expiration time cookie to make it more secure.

I don't ever work on this code but on the v2 available here

0
Comment made 1 month ago by abdul rahman 61

I applied the rule but its asking multiple time login. Once am authenticated via APM and I access the file on sharepoint, when we try to open it, we re redirected to APM login page again.

0
Comment made 1 month ago by abdul rahman 61

Image TextImage Text

0
Comment made 1 month ago by abdul rahman 61

applied this irule to get rid of multiple time login

when HTTP_REQUEST { # selectively disable HTTP processing for specific request methods switch [HTTP::method] { "MOVE" - "COPY" - "LOCK" - "UNLOCK" - "OPTIONS" - "PROPFIND" - "PROPPATCH" - "MKCOL" { HTTP::disable } } }

0
Comment made 1 month ago by Kai Wilke 6554

Hi Sergio,

I ran into the same JS-Errors. Certain Office Versions may have problems when loading the APM login pages and may report a JS error in the https://host.com/public/include/js/web_host.js file.

To fix the problem you could add a custom JS-Script function to your header.inc file which hooks the affected JS-Functions and then simply suppresses to error with an try-catch exemption...

Affected JS Functions:

DefaultExternalWebHostImpl.prototype.setWeblogonCallbacks = function (weblogonAutosubmitCall, challengeAutosubmitCall) {
    if (this.hasProp('WebLogonAutosubmitCall')) {
        window.external.WebLogonAutosubmitCall = weblogonAutosubmitCall;
    }

    if (this.hasProp('WebLogonChallengeAutosubmitCall')) {
        window.external.WebLogonChallengeAutosubmitCall = challengeAutosubmitCall;
    }
}

DefaultExternalWebHostImpl.prototype.hasWebLogonResetSession = function (){
    return this.hasProp('WebLogonResetSession');
}

Position to insert:

Image Text

Code to insert:

<script language="JavaScript">
var _catch_1 = DefaultExternalWebHostImpl.prototype.setWeblogonCallbacks;
DefaultExternalWebHostImpl.prototype.setWeblogonCallbacks = function() {
    try {
        _catch_1.apply(this, arguments);
    }
    catch (err) {
        // suppress error
    }
};
var _catch_2 = DefaultExternalWebHostImpl.prototype.hasWebLogonResetSession;
DefaultExternalWebHostImpl.prototype.hasWebLogonResetSession = function() {
    try {
        _catch_2.apply(this, arguments);
    }
    catch (err) {
        // suppress error
    }
};
</script>

Cheers, Kai

0
Comment made 1 month ago by Stanislas Piron 9618

Hi Abdul,

Please don’t use the code you posted here.... it disable all the irule behavior...

This code works without disabling APM for lots of users without issue.. maybe your access access policy have some misconfiguration.

0
Comment made 3 weeks ago by JoeTheFifth 301

Thanks for the JS error fix Kai. I was going to start digging into this as this was one of the last things annoying me with my policy profile nd then saw your post.

Cheers !

0