authentication
5 TopicsAPM Full Step Up Authentication
Problem this snippet solves: By default, APM is not able to handle several authentication during a session. Once you are logged in, it’s finished, you can’t ask for authentication again. Since v12.1.0, we can see a new feature in EA called “Step-up Authentication” and the introduction of subroutines that is currently limited to ldap authentication or a confirm box. The irule and configuration below allow the administrator to define 2 levels of authentication based on URIs. The concept can be extended to have multiple authentication levels. This concept can be extended to define several Level of authentication. You can also change the element that trigger the additionnal authentication process. How to use this snippet: Installation irule To make it works, you need to install the irule on the Virtual Server that publish your application with APM authentication. datagroup You need to create a datagroup of string type. This dg must contains http path that need an additional authentication step. The dg is named loa3_uri in the irule example. access profile If you already have an existing access profile, you will need to modify it and include some additionnal configuration in your VPE. If you have no access profile, you can starts building your own based on the description we provide below. Scenarios 1) User try to reach strong uri after first authentication process In this scenario, the user first authenticate using a standard authentication mecanism. Once authenticated, if the user request content that is behing strong uris, the user restart an authentication process in the "Strong Auth" and "Already Auth" branch of the VPE. 2) User try to reach strong uri during the first authentication process If the user try to access a strong uri on its first attempt, he will need to complete the full authentication process. Then, he can access every part of the web application without any additional prompt. Special considerations Client certificate Authentication You may need to use Client certificate authentication as a primary factor or second factor. We highly recommend to use "SSl on-demand authentication" if you need it as primary factor. Client Certificate is not supported as a second factor, you need to use SSl on-demand authentication. WebSSO When first authentication has already been allowed and the user try to access a protected uri, the system will invite the user to complete the new authentication (second factor). This process will restart a webSSO action on the backend. Basic, NTLM and Kerberos webSSO have been tested with success. Configuring the Visual Policy Editor The printscreen below is a minimal Visual Policy Editor used to make Step up Authentication works properly : Strong Auth The strong Auth block is an "Empty Action" with two branch. The branch named "Strong" contains the following condition : expr { [mcget {session.server.landinguri}] starts_with "/strong" || [mcget {session.custom.last.strong}] == 1 } We check that the uri starts with strong (used in scenario 1) or if a custom variable is set to 1 (second scenario) Already Auth This is an empty action with two branch. The branch named "yes" contains the following expression : expr { [mcget {session.custom.last.authresult}] contains "true" } 2-factor Ending session.custom.last.authtype variable must be set to 1 session.policy.result.redirect.url must be changed. The session.server.landinguri contains the true origin uri. To set this variable, you must use the tcl script below : proc urldecode str { variable map variable alphanumeric a-zA-Z0-9 for {set i 0} {$i <= 256} {incr i} { set c [format %c $i] if {![string match \[$alphanumeric\] $c]} { set map($c) %[format %.2x $i] } } array set map { " " + \n %0d%0a } set str [string map [list + { } "\\" "\\\\"] $str] regsub -all -- {%([A-Fa-f0-9][A-Fa-f0-9])} $str {\\u00\1} str return [subst -novar -nocommand $str] } set decoded_uri [urldecode [string range [mcget {session.server.landinguri}] [expr { [string last = [mcget {session.server.landinguri}]] + 1 }] end]] return $decoded_uri Full strong Ending session.custom.last.authtype variable must be set to 1 Standard Ending session.custom.last.authtype variable must be set to 0 Session variables The following variables can be used in the 2-factor section of the Visual Policy Editor : session.custom.last.username session.custom.last.password Features 2-step authentication Retrieve username and password from first authentication Encrypt Session1 cookie to avoid session Hijacking External links Github : https://github.com/e-XpertSolutions/f5 Code : when RULE_INIT { # to be changed prior to any publishing set passphrase "hEuoYjmFUpB4PcpO3bUdQtLP4ic7jjm" } when HTTP_REQUEST { if { [HTTP::cookie exists MRHSession] and [ACCESS::session exists -state_allow -sid [HTTP::cookie MRHSession]] } { set strong_auth [ACCESS::session data get session.custom.last.authtype] if { [class match [HTTP::path] starts_with loa3_uri] and $strong_auth == 0 } { HTTP::cookie encrypt "MRHSession" $passphrase HTTP::respond 302 noserver "Location" "/strong?return_url=[URI::encode [HTTP::uri]]" "Cache-Control" "no-cache, must-revalidate" Set-Cookie "MRHSession=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" Set-Cookie "LastMRH_Session=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT;path=/" Set-Cookie "Session1=[HTTP::cookie MRHSession];path=/" } } } when ACCESS_SESSION_STARTED { # decrypt Session1 cookie value set decrypted [HTTP::cookie decrypt "Session1" $passphrase] if { [HTTP::cookie exists Session1] and [ACCESS::session exists -state_allow -sid $decrypted] } { ## section : retrieve session variables from the first session ACCESS::session data set session.custom.last.username [ACCESS::session data get session.logon.last.username -sid $decrypted] ACCESS::session data set session.custom.last.password [ACCESS::session data get session.logon.last.password -sid $decrypted] ## End section ACCESS::session data set session.custom.last.authresult "true" # remove the first created session during standard authentication to avoid multiple active sessions ACCESS::session remove -sid $decrypted } elseif { [class match [HTTP::path] starts_with loa3_uri] } { ACCESS::session data set session.custom.last.strong 1 } } Tested this on version: 11.51.3KViews0likes7CommentsForm Based Authentication with external SOAP web services
Problem this snippet solves: 1- You need to authenticate users against an external authentication system relying on SOAP calls. 2- The session identifier must be provided by an external third party system. How to use this snippet: Installation Files You need to upload an html login page using ifiles. You need to upload the SOAP body of the external web service using ifiles. irule You need to install the irule on your Virtual Server you need to protect. Variables set static::holdtime 3600 # session timeout set static::login_url "/login" # login url set static::sideband_vs "VS_EXTERNAL_AUTH_PROVIDER" # Virtual Server that publish the web service Features Version 1.0 Form based login (provided by a custom ifile) Authentication against an external SOAP web service Manage Session timeout Backlog Improve logging Allow 2-factor authentication (Challenge) Encrypt Session cookie Provide internal mecanism to generate a session cookie accept Basic Authentication External links Github : https://github.com/e-XpertSolutions/f5 Code : when RULE_INIT { set static::holdtime 3600 set static::login_url "/login" set static::sideband_vs "VS_EXTERNAL_AUTH_PROVIDER" } when HTTP_REQUEST { if { [HTTP::cookie exists SessionCook] and [table lookup -subtable "active_sessions" [HTTP::cookie SessionCook]] != "" } { return } else { if { [HTTP::path] eq $static::login_url } { if { [HTTP::method] eq "POST" } { if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= 1048576}{ set content_length [HTTP::header "Content-Length"] } else { set content_length 1048576 } if { $content_length > 0} { HTTP::collect $content_length } } else { HTTP::respond 200 content [ifile get login.html] "Cache-Control" "no-cache, must-revalidate" "Content-Type" "text/html" } } else { HTTP::respond 302 noserver "Location" $static::login_url "Cache-Control" "no-cache, must-revalidate" Set-Cookie "SessionCook=$result;domain=[HTTP::host];path=/" } } } when HTTP_REQUEST_DATA { set payload [HTTP::payload] set username "" set password "" regexp {Login1\%3AtxtUserName\=(.*)\&Login1\%3AtxtPassword\=(.*)\&Login1\%3AbtnSubmit\=(.*)} $payload -> username password garbage if {[catch {connect -timeout 1000 -idle 30 -status conn_status $static::sideband_vs} conn_id] == 0 && $conn_id ne ""}{ log local0. "Connect returns: $conn_id and conn status: $conn_status" } else { log local0. "Connection could not be established to sideband_virtual_server" } set content [subst -nocommands -nobackslashes [ifile get soap_body]] set length [string length $content] set data "POST /apppath/webservicename.asmx HTTP/1.1\r\nHost: www.hostname.com\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: $length\r\nSOAPAction: http://schemas.microsoft.com/sqlserver/2004/SOAP\r\n\r\n$content" set send_bytes [send -timeout 1000 -status send_status $conn_id $data] set recv_data [recv -timeout 1000 $conn_id] # parse response to retrieve the authentication result, it gives 0 if authentication failed or a session_id if it succeed regexp { (.*) (.*)} $recv_data -> result garbage unset content unset length unset data unset recv_data close $conn_id # add a custom alert notification to the login page if { $result == 0 } { set alert " Invalid credentials. " HTTP::respond 200 content [subst -nocommands -nobackslashes [ifile get login.html]] "Cache-Control" "no-cache, must-revalidate" "Content-Type" "text/html" Set-Cookie "SessionCook=deleted;expires=Thu, 01-Jan-1970 00:00:10 GMT;domain=[HTTP::host];path=/" } else { HTTP::respond 302 noserver "Location" "/" "Cache-Control" "no-cache, must-revalidate" Set-Cookie "SessionCook=$result;domain=[HTTP::host];path=/" # save the cookie value in a cache for fast checking table add -subtable "active_sessions" $result $username indef $static::holdtime } } Tested this on version: 11.5450Views0likes1CommentSubsequent Form Based SSO
Problem this snippet solves: After performing a successful APM Form Based SSO it can happen that the backend website will expire the user session while the APM session is still active. When this happens, the user will see the logon page of the backend website and needs to login again. This code snippet will try to detect that the user is being redirected to the backend website login page and will perform a Form Based SSO again by using the credentials from the active APM session. How to use this snippet: When using this code snippet, make sure you set the below shown variables to match your environment. set static::start_uri set static::form_action You should also use the Variable Assign agent in the VPE to set the APM session variable session.custom.form_based.password with the users password. This password will be used to perform the subsequent Form Based SSO. Use the following custom expression: return [mcget -secure {session.logon.last.password}] . Code : when RULE_INIT { set static::start_uri "login.html" set static::form_action "/F5/form_based_login/login.php" set static::form_html { Your browser does not support JavaScript, press the Continue button once to proceed. } set static::form_html [string map "form_action $static::form_action" $static::form_html] } when HTTP_REQUEST { if { [HTTP::cookie exists MRHSession] and [ACCESS::session exists -state_allow -sid [HTTP::cookie MRHSession]] } { set active_session 1 if { [HTTP::method] equals "POST" && [HTTP::uri] equals $static::form_action } { set collect_length 2048 if { [HTTP::header Content-Length] eq "" } { set collect_length $collect_length } elseif { [HTTP::header Content-Length] == 0 } { unset collect_length } elseif { [HTTP::header Content-Length] > $collect_length } { set collect_length $collect_length } else { set collect_length [HTTP::header Content-Length] } if { [info exists collect_length] } { HTTP::collect $collect_length } } } } when HTTP_REQUEST_DATA { # the session.custom.form_based.password variable needs to be set via a variable assign agent in the VPE. set username [ACCESS::session data get session.logon.last.username] set password [ACCESS::session data get session.custom.form_based.password] HTTP::payload replace 0 [HTTP::payload length] "username=$username&password=$password" } when HTTP_RESPONSE { if { [info exists active_session] } { if { [HTTP::header "Location"] equals $static::start_uri } { if { [ACCESS::session data get session.custom.first_redirect] == 1 } { # this is the subsequent redirect which is not covered by APM Form Based SSO HTTP::respond 200 content $static::form_html } else { # this is the initial redirect which is covered by APM Form Based SSO ACCESS::session data set session.custom.first_redirect 1 } } unset active_session } } Tested this on version: 13.0351Views0likes2CommentsSWG, Kerberos Auth and identify users by credentials
Problem this snippet solves: When using SWG and NTLM Auth it's possible to identify users by IP address or credentials. However, when using Kerberos Auth it isn't possible to identify users by credentials. This iRule enables the 'identify users by credentials' feature for SWG and Kerberos Auth. The 'identify by credentials' method adds security to environments that use shared systems (like terminal servers) where multiple users are hidden behind the same IP address. How to use this snippet: How to use Configure an APM Explicit Forwarding Proxy Configuration. Call it for example: /Common/vs_proxy_kerberos. Follow the instructions provided by AskF5, but make sure that this virtual server doesn't listen on any VLAN or tunnel. This will be an internal virtual server. Create a datagroup of type Address and add IP addresses of shared systems to it. Call it for example: /Common/data_group_ip. Edit the f5.swg_kerberos_identify_by_credentials iRule variables in the RULE_INIT event to match your configuration. Configure a second virtual server that holds the f5.swg_kerberos_identify_by_credentials iRule. Call it for example: /Common/vs_proxy_kerberos_front. This virtual server will accept proxy traffic from clients and route it to the internal virtual server. How it works The iRule will check if the HTTP request contains a Proxy-Authorization header. If the request contains a Proxy-Authorization header it will take a part of the Base64 Kerberos ticket and maps it to an internal IP address. This internal IP address will be used as a SNAT address. The internal virtual server will use the standard Kerberos Auth 'identification by IP' method to authenticate this session. The iRule uses tables to map Kerberos tickets to IP addresses. Please note that single users can sometimes use different Kerberos tickets. This results in a single user consuming more than one session. Like NTLM Auth, the 'identify by credentials' method adds extra overhead in the communication between the client and the proxy, because the client is forced to send a Proxy-Authorization header with each request. Disclaimer This iRule has been tested in a lab environment only Code : https://github.com/nvansluis/f5.swg_kerberos_identify_by_credentials676Views0likes2CommentsAkamai G20 Header Authentication
Problem this snippet solves: iRule for detection and authentication of Akamai G2O headers. Requires TMOS 11.1 or above as this leverages "CRYPTO::sign". This code block is the basics needed to make a decisions about requests that may or may not contain Akamai G2O headers. Code : when HTTP_REQUEST { #Requires TMOS 11.1 or above for support for "CRYPTO::sign" #This code block detects if the Akamai authentication headers are there #if so it then does the caculations based on the shared secret #finally it compares the output and logs a match if {[HTTP::header exists "X-Akamai-G2O-Auth-Data"] && [HTTP::header exists "X-Akamai-G2O-Auth-Sign"]} { #set shared secret here set secret_key "pass" set data "[HTTP::header value "X-Akamai-G2O-Auth-Data"][HTTP::path]" set signature "[HTTP::header value "X-Akamai-G2O-Auth-Sign"]" set signed_data [b64encode [CRYPTO::sign -alg hmac-md5 -key $secret_key $data]] if { $signed_data eq $signature } { log local0. "Signatures match" } } } Tested this on version: 11.1850Views0likes1Comment