F5 BIG-IP Access Policy Manager (APM) - Google Authenticator and Microsoft Authenticator
Introduction
In our walkthrough we are refreshing an existing time-based one-time password (TOTP) deployment Two-Factor Authentication With Google Authenticator And APM
In our walkthrough we are following the below assumptions,
- Secret key is generated outside of F5 and saved to Active Directory (AD) user attribute.
- F5 APM should be able to query AD user attribute (for example, in our case it's called serialNumber).
- We have two separate portals,
- One portal for Token generation and QR scanning.
- One portal for Application access.
Lab guide
Phase 1: Token Generation
- User logs in to the token generation portal and authenticates with AD credentials.
- F5 APM authenticates the user with AD and query the attribute for the secret key.
- F5 APM presnets the QR code for the user to be scanned whether by Google or Microsoft Authenticators.
Phase 2: Token verification
- User Access the application and authenticates with AD credentials.
- Once user is successfully authenticated, the user is prompted to provide the one-time password (OTP).
- Once F5 verifies the provided OTP, the user is allowed to access the application.
Verification iRule
when ACCESS_POLICY_AGENT_EVENT {
if { [ACCESS::policy agent_id] eq "ga_code_verify" } {
### Google Authenticator verification settings ###
# lock the user out after x attempts for a period of x seconds
set static::lockout_attempts 3
set static::lockout_period 30
# logon page session variable name for code attempt form field
set static::ga_code_form_field "ga_code_attempt"
# key (shared secret) storage method: ldap, ad, or datagroup
set static::ga_key_storage "ad"
# LDAP attribute for key if storing in LDAP (optional)
set static::ga_key_ldap_attr "google_auth_key"
# Active Directory attribute for key if storing in AD (optional)
set static::ga_key_ad_attr [ACCESS::session data get "session.ad.last.attr.serialNumber"]
# datagroup name if storing key in a datagroup (optional)
set static::ga_key_dg "google_auth_keys"
#####################################
### DO NOT MODIFY BELOW THIS LINE ###
#####################################
# set lockout table
set static::lockout_state_table "[virtual name]_lockout_status"
# set variables from APM logon page
set username [ACCESS::session data get session.logon.last.username]
set ga_code_attempt [ACCESS::session data get session.logon.last.$static::ga_code_form_field]
# retrieve key from specified storage
set ga_key ""
switch $static::ga_key_storage {
ldap {
set ga_key [ACCESS::session data get session.ldap.last.attr.$static::ga_key_ldap_attr]
}
ad {
set ga_key [ACCESS::session data get "session.ad.last.attr.serialNumber"]
}
datagroup {
set ga_key [class lookup $username $static::ga_key_dg]
}
}
# increment the number of login attempts for the user
set prev_attempts [table incr -notouch -subtable $static::lockout_state_table $username]
table timeout -subtable $static::lockout_state_table $username $static::lockout_period
# verification result value:
# 0 = successful
# 1 = failed
# 2 = no key found
# 3 = invalid key length
# 4 = user locked out
# make sure that the user isn't locked out before calculating GA code
if { $prev_attempts <= $static::lockout_attempts } {
# check that a valid key was retrieved, then proceed
#Update the key length based on the organization requirements
if { [string length $ga_key] == 16 } {
# begin - Base32 decode to binary
# Base32 alphabet (see RFC 4648)
array set static::b32_alphabet {
A 0 B 1 C 2 D 3
E 4 F 5 G 6 H 7
I 8 J 9 K 10 L 11
M 12 N 13 O 14 P 15
Q 16 R 17 S 18 T 19
U 20 V 21 W 22 X 23
Y 24 Z 25 2 26 3 27
4 28 5 29 6 30 7 31
}
set ga_key [string toupper $ga_key]
set l [string length $ga_key]
set n 0
set j 0
set ga_key_bin ""
for { set i 0 } { $i < $l } { incr i } {
set n [expr $n << 5]
set n [expr $n + $static::b32_alphabet([string index $ga_key $i])]
set j [incr j 5]
if { $j >= 8 } {
set j [incr j -8]
append ga_key_bin [format %c [expr ($n & (0xFF << $j)) >> $j]]
}
}
# end - Base32 decode to binary
# begin - HMAC-SHA1 calculation of Google Auth token
set time [binary format W* [expr [clock seconds] / 30]]
set ipad ""
set opad ""
for { set j 0 } { $j < [string length $ga_key_bin] } { incr j } {
binary scan $ga_key_bin @${j}H2 k
set o [expr 0x$k ^ 0x5C]
set i [expr 0x$k ^ 0x36]
append ipad [format %c $i]
append opad [format %c $o]
}
while { $j < 64 } {
append ipad 6
append opad \\
incr j
}
binary scan [sha1 $opad[sha1 ${ipad}${time}]] H* token
# end - HMAC-SHA1 calculation of Google Auth hex token
# begin - extract code from Google Auth hex token
set offset [expr ([scan [string index $token end] %x] & 0x0F) << 1]
set ga_code [expr (0x[string range $token $offset [expr $offset + 7]] & 0x7FFFFFFF) % 1000000]
set ga_code [format %06d $ga_code]
# end - extract code from Google Auth hex token
if { $ga_code_attempt eq $ga_code } {
# code verification successful
set ga_result 0
} else {
# code verification failed
set ga_result 1
}
} elseif { [string length $ga_key] > 0 } {
# invalid key length, greater than 0, but not length not equal to 16 chars
set ga_result 3
} else {
# could not retrieve user's key
set ga_result 2
}
} else {
# user locked out due to too many failed attempts
set ga_result 4
}
# set code verification result in session variable
ACCESS::session data set session.custom.ga_result $ga_result
}
}
Related Content
- Two-Factor Authentication With Google Authenticator And APM
- Demystifying Time-based OTP
- Google Authenticator Token Verification iRule For APM
- APM Google Authenticator HTTP API
- Google Authenticator Verification iRule (TMOS v11.1+ optimized)
- UDF Lab
- Configuring MFA OTP
Note, Scan the Article photo for more BIG-IP Access Policy Manager (APM) info.
Hello mmahdy!
To overcome "Secret key is generated outside of F5" you can try my approach with all-in-one solution
- momahdyEmployee
HiVladimir_Akhmarov , Yes I saw your project prior to working on this article. it looks great (Y)
In this article the main approach is to work with customers who rely on local secret generations and assign them to the users AD accounts, that's why F5 APM query that attribute from AD and verify the token based on it.
Thanks for your comment ^^
- liborjNimbostratus
hello Vladimir_Akhmarov does your solution work with the F5 management console login?
we are looking for some kind of MFA but struggling to get a straight direction from F5. We only need that for the admins to login to the F5 management UI. One time password or code would work too.
thank you for sharing
Libor
Hello liborj
If I understand you correctly, you are looking for APM System Authentication
You could deploy my APM 2FA policy from Implementation Guide and configure BIG-IP to use it as per article from the link above
- dupapaNimbostratus
Hello mmahdy!
Thanks for your sharing of those technical details!
I have some questions realted to your technical details:
- is the ga_code_submit another Logon page or even External Logon page
- if the TOTP validation fails, the APM evaluation process will terminate with Deny, which is ok for demo purpose 🙂 However, if I have to allow max three TOTP attempts, how can we realize it without restarting a brand new APM evaluation process?
that is, when the times of failuare is less than 3, the APM evaluation process should flow to the ga_code_submit again rather than the Deny ending.
for instance, the built-in OTP Verify with VPE supports max 3 attempts without terminating the ongoing APM evaluation process.
- momahdyEmployee
dupapa Thanks a lot for your comment, regarding your questions,
1- Yes, Another logon page.
2- For this to be acheived, we can put the validation within a macro and allow for multiple repeats, I will try this out in the lab and update the article with the results.
The OTP generate and verify relies on token being generated at F5, in some environments, there's a need for secret key used for generation and validation to be external to F5, from there the need for such approach was made. So, it's almost like we are creating a custom OTP validate plugin, will try out the macro approach and let you know.
Thanks again,Mohamed
- dupapaNimbostratus
momahdy I'm looking forward to your new updatings. Although I tried to put the TOTP onboarding and verification in a Macro as I did for the built-in OTP Verify (F5 Help Desk suggested it to me last year), my TOTP didn't work with a invalid TOTP code, while it works well when the TOTP code is valid. I've actually registered a support case with F5, https://my.f5.com/manage/s/case/500Hs000021KOOAIA4/max-logon-attempts-allowed-with-my-own-timebased-onetimepassword-totp It'll be great if you could help me with it