Forum Discussion

eric_haupt1's avatar
eric_haupt1
Icon for Nimbostratus rankNimbostratus
Nov 01, 2018

APM - dynamically enabling access policy when 401 back from server is detected

I'm working a new fronting of an F5 APM instance in front of Sharepoint 2013. We are looking to expand our ability to offer Sharepoint across a large corporate network that has multiple domains. There will be no domain trusts so you can imagine Sharepoint on one domain and users could be on any number of domains, however all users will have a PKI certificate and account on the Sharepoint domain. We will simply be having F5 take a client cert and process Kerberos on the back-end to allow "off-domain" users to access "on-domain" sharepoint from any remote location - pretty standard stuff.

 

I have a technical issue with the placement of this APM: The sharepoint team also offers "anonymous" access to certain data locations within this Sharepoint instance. So we will have both credentialed access and anon access through the same F5 VIP; probably not the best way to do things, but this is what they gave me to deal with.

 

Now, normally I can simply use an irule to perform URI inspection to ACCESS::disable and ACCESS::enable based on URI path when I have to offer anon and credentialed access. The issue with this particular SP instance is that the anon resources are all over the place - no reasonable way for me to mask out based on URIs.

 

What I've envisioned is keeping the service anon until the SP IIS front-end sends back a 401, then dynamically enabling APM and performing an access policy evaluation. If the policy passes, the user will have access to resources per the users AD settings; if the user doesn't have access - APM will deny based on AD checks within the policy after performing a client cert parsing.

 

Anyone have a suggestion on how to implement something like this?

 

5 Replies

  • You can find this code in a previous thread

     

    when HTTP_REQUEST {
         store the host header for the initial /start_policy redirect
        set uri [HTTP::uri]
        set logout_req 0
        set apm_cookie [HTTP::cookie value MRHSession]
        if { ( [ACCESS::session exists -state_allow $apm_cookie] ) \
                 or ( [HTTP::uri] starts_with "/my.policy" ) } {
             initial redirect to /my.policy (starts access policy evaluation) - or a normal post-policy request
            set apm_req 1
            return
        } elseif { ( [HTTP::uri] starts_with "/start_policy" ) } {
             initial redirect to /start_policy (starts access policy evaluation)
             Remove the not established previous sessions
            ACCESS::session remove
            ACCESS::session create -timeout 1800 -lifetime 0
            ACCESS::session data set session.server.landinguri [findstr [HTTP::uri] "/start_policy?url=" 18]
            set apm_req 1
            return
        } else {
             APM session disabled until logon process is started
            ACCESS::disable
            set apm_req 0
            return
        }
    }
    when ACCESS_SESSION_STARTED {
         store the initial (redirect URI) until it's needed
        ACCESS::session data set session.server.landinguri [findstr [HTTP::uri] "/start_policy?url" 18]
    }
    
    when HTTP_RESPONSE { 
        log local0. "apm_req was $apm_req"  
         capture the redirect to authenticate
        if {  ([HTTP::status] eq "401") and ($apm_req eq 0) } {
             initiate access policy processing
            log local0. "apm_req was $apm_req so redirecting"
             HTTP::respond 302 Location "/start_policy?url=$uri"
        }
    }
  • You can also try this code...

     

    when HTTP_REQUEST {
         store the host header for the initial /start_policy redirect
        set uri_path [HTTP::path]
        set uri_query [HTTP::query]
        set apm_sessionid [HTTP::cookie value MRHSession]
        set apm_sessionid_401 [HTTP::cookie value MRHSession_401]
        set inject_401_cookie 0
        set delete_401_cookie 0
        if { ( [URI::query [HTTP::uri] forceauth] == 1 ) } {
             initial redirect to /start_policy (starts access policy evaluation)
             Remove the not established previous sessions
            ACCESS::session remove
            ACCESS::session create -timeout 1800 -lifetime 0
            HTTP::uri "$uri_path[expr {[string length $uri_query] == 12 ? "" : "?[string map {"forceauth=1\&" "" "\&forceauth=1" "" "forceauth=1" ""} $uri_query]"}]"
            set apm_req 1
        } elseif { $apm_sessionid_401 equals $apm_sessionid} {
            set apm_req 1
        } elseif { $apm_sessionid_401 equals ""} {
             APM session disabled until logon process is started
            ACCESS::disable
            set apm_req 0
        } elseif {[ACCESS::session exists -state_allow $apm_sessionid] } {
             normal post-policy request with cookie not existing
            set apm_req 1
            set inject_401_cookie 1
        } else {
             APM session disabled until logon process is started
            ACCESS::disable
            set apm_req 0
            set delete_401_cookie 1
        }
    }
    
    when HTTP_RESPONSE { 
        log local0. "apm_req was $apm_req"  
         capture the redirect to authenticate
        if {  ([HTTP::status] eq "401") and ($apm_req eq 0) } {
             initiate access policy processing
            log local0. "apm_req was $apm_req so redirecting"
             HTTP::respond 307 noserver Location $uri_path?[join "$uri_query forceauth=1" &] Connection Close
        }
        if {$inject_401_cookie} {
            HTTP::cookie insert name MRHSession_401 value $apm_sessionid path "/"
            HTTP::cookie secure MRHSession_401 enable
        } elseif {$delete_401_cookie} {
            HTTP::cookie insert name MRHSession_401 value deleted path "/"
            HTTP::cookie expires absolute 0
            HTTP::cookie secure MRHSession_401 enable
        }
    }

    The goal of this version is to prevent session ID check on every request.

     

  • We are testing this and it is working well for us so far. Thanks.

    when HTTP_REQUEST {
        set var_uri [HTTP::uri]
        set var_apm_cookie [HTTP::cookie value MRHSession]
    
        if { ( [ACCESS::session exists -state_allow $var_apm_cookie] ) \
                 or ( [HTTP::uri] starts_with "/my.policy" ) } {
            set var_apm_required 1
            return
    
        } elseif { ( [HTTP::uri] starts_with "/start_policy" ) } {
            ACCESS::session remove
            ACCESS::session create -timeout 1800 -lifetime 0
            ACCESS::session data set session.server.landinguri [findstr [HTTP::uri] "/start_policy?url=" 18]
            set var_apm_required 1
            return
    
        } else {
            ACCESS::disable
            set var_apm_required 0
            return
        }
    }
    
    when ACCESS_SESSION_STARTED {
        ACCESS::session data set session.server.landinguri [findstr [HTTP::uri] "/start_policy?url" 18]
    }
    
    when HTTP_RESPONSE { 
        if {  ([HTTP::status] eq "401") and ($var_apm_required eq 0) } {
             HTTP::respond 302 Location "/start_policy?url=$var_uri"
        }
    }
    
    • eric_haupt1's avatar
      eric_haupt1
      Icon for Nimbostratus rankNimbostratus

      We rolled this out in production without issue thus far. Thanks Stanislas.

       

  • Added some additional features: setting a datagroup check based on IP address to bypass APM, and setting the ability to perform user-agent checks to also bypass. We've been leaning on this fairly heavily for months and it works well.

    `       when HTTP_REQUEST {
            set var_uri [HTTP::uri]
            set var_apm_cookie [HTTP::cookie value MRHSession]
    
            if { ( [class match [IP::client_addr] equals datagroup-apm-bypass] ) } {
                 Permanently disable APM for client addresses within datagroup
                ACCESS::disable
                set var_apm_req 1
                return
    
            } elseif { ( [string tolower [HTTP::header "User-Agent"]] contains "*infopath*" ) \
                    or ( [string tolower [HTTP::header "User-Agent"]] contains "*onenote*" ) } {
                 Permanently disable APM for these user-agents
                ACCESS::disable    
                set var_apm_req 1
                return
    
            } elseif { ( [ACCESS::session exists -state_allow $var_apm_cookie] ) \
                    or ( [HTTP::uri] starts_with "/my.policy" ) } {
                 initial redirect to /my.policy (starts access policy evaluation)
                set var_apm_req 1
                return
    
            } elseif { ( [HTTP::uri] starts_with "/start_policy" ) } {
                 initial redirect to /start_policy (starts access policy evaluation)
                ACCESS::session remove
                ACCESS::session create -timeout 1800 -lifetime 0
                ACCESS::session data set session.server.landinguri [findstr [HTTP::uri] "/start_policy?url=" 18]
                set var_apm_req 1
                return
    
            } else {
                 APM session disabled until logon process is started
                ACCESS::disable
                set var_apm_req 0
                return
            }
        }
        when ACCESS_SESSION_STARTED {
             store the initial (redirect URI) until it's needed
            ACCESS::session data set session.server.landinguri [findstr [HTTP::uri] "/start_policy?url" 18]
        }
        when HTTP_RESPONSE { 
            if { ([HTTP::status] eq "401") and ($var_apm_req eq 0) } {
                HTTP::respond 302 Location "/start_policy?url=$var_uri"
            }
        }`