Yubikey 2Factor Authentication with BIG-IP LTM
Problem this snippet solves:
This solution using Yubico's YubiKey OTP generator technology along with their YubiCloud service and BIG-IP LTM to perform Two-Factor Authentication for an application. Please reference the tech tip for details.
NOTE: Built on version 11.4, though the only 11.4 dependency is the nonce generator proc that can be rolled into the CLIENT_ACCEPTED event for earlier versions. The non-proc version is of the CLIENT_ACCEPTED event is shown in the tech tip reference above.
How to use this snippet:
Code :
# Required Data-groups ltm data-group internal authorized_users { records { jason { data /md5sum of password here/ } jrahm { data /md5sum of password here/ } } type string } ltm data-group internal yubikey_users { records { jrahm { data /yubikey serial number here/ } } type string } # Source Code - NOTE: You'll need your own YubiCloud API Client ID And Secret Key to plug into the static variables ### PROC FOR NONCE GENERATOR ### proc randomNumberGenerator {length {chars "0123456789"}} { set range [expr {[string length $chars]-1}] set txt "" for {set i 0} {$i < $length} {incr i} { set pos [expr {int(rand()*$range)}] append txt [string range $chars $pos $pos] } return $txt } when RULE_INIT { #for yubico auth array set static::modhex_alphabet { 0 c 1 b 2 d 3 e 4 f 5 g 6 h 7 i 8 j 9 k A l B n C r D t E u F v } set static::yubico_client_id "xxxxx" set static::yubico_secret_key "xxxxx" } when CLIENT_ACCEPTED { set attempts 0 #for form auth set forceauth 1 set auth_status 2 set aeskey "AES 128 63544a5e7178677b45366b41405f2dab" set ckname BIGIP_AUTH #for yubico auth set yubico_server [RESOLV::lookup @24.217.0.5 -a "api2.yubico.com"] set nonce [call randomNumberGenerator 25] } when HTTP_REQUEST { if { not ([HTTP::path] starts_with "/Login_form") } { if { [HTTP::cookie exists $ckname] } { set cookie_payload [HTTP::cookie value $ckname] set decryptedCookie [AES::decrypt $aeskey [b64decode $cookie_payload ]] if { not ( $decryptedCookie equals "" ) } { # retrieve the auth status from the session table set auth_status [session lookup uie $decryptedCookie] } # If the auth status is 0 then the user is authenticated if { $auth_status eq 0 } { #Cookie Decrypted & Session Auth valid set forceauth 0 } } if {$forceauth eq 1} { set orig_uri [HTTP::uri] HTTP::redirect "/Login_form?req=$orig_uri" } } else { # If the user is re-directed to the login form then serve the login form from the BigIP if { [HTTP::path] starts_with "/Login_form" && [HTTP::method] equals "GET" } { # Retrieve the login form from ifile HTTP::respond 200 content [ifile get loginformlong] "Content-Type" "text/html" return } elseif { [HTTP::path] starts_with "/Login_form" && [HTTP::method] equals "POST" } { # Process the login form and auth the user HTTP::collect [HTTP::header Content-Length] } } } when HTTP_REQUEST_DATA { set namevals [split [HTTP::payload] "&"] # Break out the POST data for username and password values for {set i 0} {$i < [llength $namevals]} {incr i} { set params [split [lindex $namevals $i] "="] if { [lindex $params 0] equals "username" } { set auth_user [lindex $params 1] } if { [lindex $params 0] equals "password" } { set auth_pw [lindex $params 1] } if { [lindex $params 0] equals "otp" } { set auth_otp [lindex $params 1] } } #validate basic authentication (username/password) before trying OTP validation binary scan [md5 $auth_pw] H* password if { $password eq [class lookup $auth_user authorized_users] } { #start OTP validation here set yubikey_modhex 1 if { $auth_user ne "" } { set yubikey_serial [string trimleft [class lookup $auth_user yubikey_users] 0] if { $yubikey_serial eq "" } { HTTP::respond 200 content "No Yubikey on file for username $auth_user. Contact Support.
" return } if { [string is integer -strict $yubikey_serial] } { set yubikey_serial [split [format %012X $yubikey_serial] ""] set yubikey_modhex "" foreach index $yubikey_serial { append yubikey_modhex $static::modhex_alphabet($index) } } } if { $yubikey_modhex equals [string range $auth_otp 0 11] } { ## Build GET request to yubico ## set params "id=$static::yubico_client_id&nonce=$nonce&otp=$auth_otp" set signature [string map { "+" "%2B" } [b64encode [CRYPTO::sign -alg hmac-sha1 -key [b64decode $static::yubico_secret_key] $params]]] set yubico_get_request "GET /wsapi/2.0/verify?$params&h=$signature HTTP/1.1\r\n" append yubico_get_request "Host: api2.yubico.com\r\n" append yubico_get_request "Accept: */*\r\n\r\n" ## Create connection and send request set conn [connect -timeout 1000 -idle 30 $yubico_server:80] send -timeout 1000 -status send_status $conn $yubico_get_request ## Store Response from yubico set yubico_response [recv -timeout 1000 -status recv_info $conn] #set hash [getfield [getfield $yubico_response "\r\n" 9] "=" 2] #set timestamp [getfield [getfield $yubico_response "\r\n" 10] "=" 2] set otp_r [getfield [getfield $yubico_response "\r\n" 11] "=" 2] set nonce_r [getfield [getfield $yubico_response "\r\n" 12] "=" 2] #set sl [getfield [getfield $yubico_response "\r\n" 13] "=" 2] set status [getfield [getfield $yubico_response "\r\n" 14] "=" 2] if { not ( ($status eq "OK") && ($auth_otp eq $otp_r) && ($nonce eq $nonce_r)) } { HTTP::respond 200 content "Yubico Authentication Failed. Status=$status
" } else { session add uie $auth_user 0 1800 set encrypted_user [b64encode [AES::encrypt $aeskey $auth_user]] set authcookie [format "%s=%s; path=/; " $ckname $encrypted_user] HTTP::respond 302 Location $orig_uri "Set-Cookie" $authcookie } } else { if { $attempts < 3 } { HTTP::redirect "/Login_form?req=$orig_uri" incr attempts } else { HTTP::respond 200 content "You have exceeded the acceptable login attempts. Please try again later." } } } else { if { $attempts < 3 } { HTTP::redirect "/Login_form?req=$orig_uri" incr attempts } else { HTTP::respond 200 content "You have exceeded the acceptable login attempts. Please try again later." } } }
Published Mar 18, 2015
Version 1.0JRahm
Admin
Joined January 20, 2005
JRahm
Admin
Joined January 20, 2005
No CommentsBe the first to comment