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
Christ Follower, Husband, Father, Technologist. I love community and I especially love THIS community. My background is networking, but I've dabbled in all the F5 iStuff, I'm a recovering Perl guy, and am very much a python enthusiast. Learning alongside all of you in this accelerating industry toward modern apps and architectures.JRahm
Admin
Christ Follower, Husband, Father, Technologist. I love community and I especially love THIS community. My background is networking, but I've dabbled in all the F5 iStuff, I'm a recovering Perl guy, and am very much a python enthusiast. Learning alongside all of you in this accelerating industry toward modern apps and architectures.No CommentsBe the first to comment
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)