One time passwords, or OTP, are used (as the name indicates) for a single session or transaction.  The plus side is a more secure deployment, the downside is two-fold—first, most solutions involve a token system, which is costly in management, dollars, and complexity, and second, people are lousy at remembering things, so a delivery system for that OTP is necessary.  The exercise in this tech tip is to employ BIG-IP APM to generate the OTP and pass it to the user via an SMS Gateway, eliminating the need for a token creating server/security appliance while reducing cost and complexity.

Getting Started

This guide was developed by F5er Per Boe utilizing the newly released BIG-IP version 10.2.1.  The “-secure” option for the mcget command is new in this version and is required in one of the steps for this solution.  Also, this solution uses the Clickatell SMS Gateway to deliver the OTPs.  Their API is documented at http://www.clickatell.com/downloads/http/Clickatell_HTTP.pdfOther gateway providers with a web-based API could easily be substituted.  Also, there are steps at the tail end of this guide to utilize the BIG-IP’s built-in mail capabilities to email the OTP during testing in lieu of SMS.  The process in delivering the OTP is shown in Figure 1.

First a request is made to the BIG-IP APM.  The policy is configured to authenticate the user’s phone number in Active Directory, and if successful, generate a OTP and pass along to the SMS via the HTTP API.  The user will then use the OTP to enter into the form updated by APM before allowing the user through to the server resources.

BIG-IP APM Configuration

Before configuring the policy, an access profile needs to be created, as do a couple authentication servers.  First, let’s look at the authentication servers

Authentication Servers

To create servers used by BIG-IP APM, navigate to Access Policy->AAA Servers and then click create.  This profile is simple, supply your domain server, domain name, and admin username and password as shown in Figure 2.

The other authentication server is for the SMS Gateway, and since it is an HTTP API we’re using, we need the HTTP type server as shown in Figure 3.

Note that the hidden form values highlighted in red will come from your Clickatell account information.  Also note that the form method is GET, the form action references the Clickatell API interface, and that the match type is set to look for a specific string.  The Clickatell SMS Gateway expects the following format:

https://api.clickatell.com/http/sendmsg?api_id=xxxx&user=xxxx&password=xxxx&to=xxxx&text=xxxx

Finally, successful logon detection value highlighted in red at the bottom of Figure 3 should be modified to response code returned from SMS Gateway.  Now that the authentication servers are configured, let’s take a look at the access profile and create the policy.

Access Profile & Policy

Before we can create the policy, we need an access profile, shown below  in Figure 4 with all default settings.

Now that that is done, we click on Edit under the Access Policy column highlighted in red in Figure 5.

The default policy is bare bones, or as some call it, empty.  We’ll work our way through the objects, taking screen captures as we go and making notes as necessary.  To add an object, just click the “+” sign after the Start flag.  The first object we’ll add is a Logon Page as shown in Figure 6.  No modifications are necessary here, so you can just click save.

Next, we’ll configure the Active Directory authentication, so we’ll add an AD Auth object.  Only setting here in Figure 7 is selecting the server we created earlier.

Following the AD Auth object, we need to add an AD Query object on the AD Auth successful branch as shown in Figures 8 and 9.  The server is selected in the properties tab, and then we create an expression in the branch rules tab.  To create the expression, click change, and then select the Advanced tab.

The expression used in this AD Query branch rule:

expr { [mcget {session.ad.last.attr.mobile}] != "" }

Next we add an iRule Event object to the AD Query OK branch that will generate the one time password and provide logging.  Figure 10 Shows the iRule Event object configuration.

 

The iRule referenced by this event is below.  The logging is there for troubleshooting purposes, and should probably be disabled in production.

   1: when  ACCESS_POLICY_AGENT_EVENT {
   2:     expr srand([clock clicks])
   3:     set otp [string range [format "%08d" [expr int(rand() * 1e9)]] 1 6 ]
   4:     set mail [ACCESS::session data get "session.ad.last.attr.mail"]
   5:     set mobile [ACCESS::session data get "session.ad.last.attr.mobile"]
   6:     set logstring mail,$mail,otp,$otp,mobile,$mobile
   7:     ACCESS::session data set session.user.otp.pw $otp
   8:     ACCESS::session data set session.user.otp.mobile $mobile
   9:     ACCESS::session data set session.user.otp.username [ACCESS::session data get "session.logon.last.username"]
  10:     log local0.alert "Event [ACCESS::policy agent_id] Log $logstring"
  11: }
  12:  
  13: when ACCESS_POLICY_COMPLETED {
  14:     log local0.alert "Result: [ACCESS::policy result]"
  15: }

On the fallback path of the iRule Event object, add a Variable Assign object as show in Figure 10b.  Note that the first assignment should be set to secure as indicated in image with the [S].

 

The expressions in Figure 10b are:

session.logon.last.password = expr { [mcget {session.user.otp.pw}]}

session.logon.last.username = expr { [mcget {session.user.otp.mobile}]}

On the fallback path of the AD Query object, add a Message Box object as shown in Figure 11 to alert the user if no mobile number is configured in Active Directory.

On the fallback path of the Event OTP object, we need to add the HTTP Auth object.  This is where the SMS Gateway we configured in the authentication server is referenced.  It is shown in Figure 12.

On the fallback path of the HTTP Auth object, we need to add a Message Box as shown in Figure 13 to communicate the error to the client.

On the Successful branch of the HTTP Auth object, we need to add a Variable Assign object to store the username.  A simple expression and a unique name for this variable object is all that is changed.  This is shown in Figure 14.

On the fallback branch of the Username Variable Assign object, we’ll configure the OTP Logon page, which requires a Logon Page object (shown in Figure 15).  I haven’t mentioned it yet, but the name field of all these objects isn’t a required change, but adding information specific to the object helps with readability.  On this form, only one entry field is required, the one time password, so the second password field (enabled by default) is set to none and the initial username field is changed to password.  The Input field below is changed to reflect the type of logon to better queue the user.

Finally, we’ll finish off with an Empty Action object where we’ll insert an expression to verify the OTP.  The name is configured in properties and the expression in the branch rules, as shown in Figures 16 and 17.  Again, you’ll want to click advanced on the branch rules to enter the expression.

The expression used in the branch rules above is:

expr { [mcget {session.user.otp.pw}] == [mcget -secure {session.logon.last.otp}] }

Note again that the –secure option is only available in version 10.2.1 forward.  Now that we’re done adding objects to the policy, one final step is to click on the Deny following the OK branch of the OTP Verify Empty Action object and change it from Deny to Allow.  Figure 18 shows how it should look in the visual policy editor window.

 

 

Now that the policy is completed, we can attach the access profile to the virtual server and test it out, as can be seen in Figures 19 and 20 below.

 

 

 

 

Email Option

If during testing you’d rather send emails than utilize the SMS Gateway, then configure your BIG-IP for mail support (Solution 3664), keep the Logging object, lose the HTTP Auth object, and configure the system with this script to listen for the messages sent to /var/log/ltm from the configured Logging object:

#!/bin/bash
while true
do
   tail -n0 -f /var/log/ltm | while read line
   do
      var2=`echo $line | grep otp | awk -F'[,]' '{ print $2 }'`
      var3=`echo $line | grep otp | awk -F'[,]' '{ print $3 }'`
      var4=`echo $line | grep otp | awk -F'[,]' '{ print $4 }'`
      if [ "$var3" = "otp" -a -n "$var4" ]; then
        echo Sending pin $var4 to $var2
        echo One Time Password is $var4 | mail -s $var4 $var2
      fi
   done
done

The log messages look like this:

Jan 26 13:37:24 local/bigip1 notice apd[4118]: 01490113:5: b94f603a: session.user.otp.log is mail,user1@home.local,otp,609819,mobile,12345678

The output from the script as configured looks like this:

[root@bigip1:Active] config # ./otp_mail.sh
Sending pin 239272 to user1@home.local

Conclusion

The BIG-IP APM is an incredibly powerful tool to add to the LTM toolbox.  Whether using the mail system or an SMS gateway, you can take a bite out of your infrastructure complexity by using this solution to eliminate the need for a token management service.  Many thanks again to F5er Per Boe for this excellent solution!

Comments on this Article
Comment made 31-Mar-2011 by clemaf 0
I made the OTP lab, but i only used the mailed token option. The Vpn connection its working properly but i´m not getting the token generation, i think the attribute "attr.mail" it's not been read by the Big-Ip from the AD object account.
I need some assistance to fix this issue.
Waiting for some response.
Claudio Lema.
0
Comment made 26-Apr-2011 by Owe@Xait 0
You must add the iRule to the resource list in your virtual server to successfully be able to trigger the iRule.
0
Comment made 26-Apr-2011 by Owe@Xait 0
To support international mobile numbers (e.g. + or 00 prefix), one cannot use the expr function in "Variable Assign HTTP" as this will lead to octal conversion of the number or dropping the + sign. Instead of "expr { [mcget {session.user.otp.mobile}]}", simply "mcget {session.user.otp.mobile}" will work.
0
Comment made 11-May-2011 by EmBee 206
Hi, this is really great and works fine!. Now I am looking for a solution for the following situation: sometimes we have to grant access to a third party maintenance guy to solve a problem to let's say a server. I would like to grant him access only once or give him an SMS code that would work for only the next 24 hours. ( We always forget to close his account after 24 hours. )
Could you give some code suggestions to get that working?
thanks!
0
Comment made 27-Nov-2011 by Adi 0
I have followed the tutorial to the letter, but still managed to stuff up.

I can confirm that AD auth and AD query are working correctly (i.e. user successfully is logging in and mobile attribute from AD is accessed). However, once I pass the AD auth stage I get "Can't connect to SMS gateway".

Thus far I have verified the following:
- can telnet to api.clickatell.com on port 443 and 80 from LTP box
- when I remove mobile details from AD I get "No number found"

The only difference in my configuration is HTTP Auth... I can't save settings when using "https://api.clickatell.com/http/sendmsg". HTTP works fine.

Are there are any important steps missing from the tutorial?

please help!
0
Comment made 28-Nov-2011 by Jason Rahm
I'll reach out to the dev of this solution.
0
Comment made 03-Dec-2011 by Adi 0
Now I'm getting "No end curly brace for session variable in form based parameter".

Does anyone know why this is being logged? The configuration is exactly the same as shown above.
0
Comment made 19-Dec-2011 by Jason Rahm
I updated Figure 18, it was missing the variable assign after the iRule event.
0
Comment made 20-Jan-2012 by SupportBT 0
Hello Adi,

Did you find a way to resolve your issue (No end curly brace for session variable in form based parameter)?

If so, how did you fix it ?

Thanks,
0
Comment made 20-Jan-2012 by secu-id 0
i'm getting "No end curly brace for session variable in form based parameter" too.

Do you have any idea for the problem?
0
Comment made 09-Apr-2012 by Ferg 24
Hi,

I would like to add some additional info I have experienced during the implementation of the email based OTP design. The build I have implemented is based on a version of the F5 Tutorial provided with a slight difference. My client is not using a sms gateway or email server to send emails to the user but a mixture of both: the principle is still the same for the email based OTP.

My build works like this, the F5 points to an smtp server as a relay server, which sends an email to mysmsserviceonline@telco.com to send the text message to the user.

I followed the instructions to setup mail relay from the guide

http://support.f5.com/kb/en-us/solutions/public/3000/600/sol3664.html
ltm01 ~ # cat /etc/postfix/main.cf | grep relay
relayhost = [smtp.server.com]

To provide accountability and auditable for my client, I created a custom log, using the guide below:

https://devcentral.f5.com/Tutorials/TechTips/tabid/63/articleType/ArticleView/articleId/1084377/Writing-to-and-rotating-custom-log-files.aspx

OTP EMAIL Scipt

I amended the script to my needs,

#!/bin/bash
while true
do
tail -n0 -f /var/log/customlog | while read line
do
var2=`echo "$line" | grep -i otp | awk -F'[,]' '{ print $2 }'`
var3=`echo "$line" | grep -i otp | awk -F'[,]' '{ print $3 }'`
var4=`echo "$line" | grep -i otp | awk -F'[,]' '{ print $4 }'`
## Mobile number from AD
var6=`echo "$line" | grep -i otp | awk -F'[,]' '{ print $6 }'`
## Strips whitespaces from mobile number
var6=`echo "$var6" | sed 's/ //g'`
if [ "$var3" = "otp" -a -n "$var4" ]; then
## I was required to amend header to lock down who was requiring access by using '-- -f ', email address pulled from AD
echo One Time Password is $var4 | mail $var6@telcosmsgateway.com -- -f user@myclient.com
fi
done
done

I had several issues with the script being called; basically, if I ran it manually it would work however the script wouldn’t get called automatically. I tried several options before I came up with my solution; one suggestion was to use user_alerf config file to call my program, this worked in a fashion, but the delay between when it was called was too great for the APM session.

https://devcentral.f5.com/Community/GroupDetails/tabid/1082223/asg/44/aft/1178752/showtab/groupforums/Default.aspx#1227184
https://devcentral.f5.com/Tutorials/TechTips/tabid/63/articleType/ArticleView/articleId/256/Custom-SNMP-Traps.aspx

To get round my issue I came up with two custom scripts to ensure the script ruan in the background. The first would run every 5 minutes to check the script is still running and restart if necessary and the other would restart the script at 4:05am.

The reason for the second script was I encountered some issues with the log file rollover; the script was still running but would not process requests. I believe the issue was due to the customlog being tarred and so the ‘while true’ was no longer valid.

I tested this by manually deleting the log and testing. It held true, I had to manually restart syslog-ns to make the script write to the log again.

These are the scripts I used:

OTPEmailCheck.sh

#!/bin/bash
RUNNING=`ps -ef | grep OTPEmail.sh | grep -v grep | awk '{print $2}'`

echo $RUNNING

# If the variable RUNNING has not been define i.e. is empy then run
if [[ -z $RUNNING ]]; then
/config/OTPEmail.sh &
echo "script stated"
else
echo "already running"
fi

OTPEmailRestart.sh

#!/bin/bash

RUNNING=`ps -ef | grep OTPEmail.sh | grep -v grep | awk '{print $2}'`
echo $RUNNING
if [[ -z $RUNNING ]]; then
echo "OTPEmail.sh is not running. OTPEmailCheck.sh will start program within 5mins"
else
KILL=`kill -9 $RUNNING`
echo $KILL
/config/OTPEmail.sh &
echo "OTPEmail.sh was restated"
fi

Crontab

5 * * * * /bin/bash /root/scripts/OTPEmailCheck.sh
5 4 * * * /bin/bash /root/scripts/OTPEmailRestart.sh

After that it worked as desired.

Hope this helps someone else who’s having issues

Ferg.
0
Comment made 11-Apr-2012 by Aung Thurein 1
There are views that tcl rand() srand() functions are not secure enough to use it for OTP generation.

In that case, [AES::key ] can be used for random number generation.

set otp [string range [expr 0x[string range [AES::key 128] 24 end]] 3 end]

Regards,
- Aung
0
Comment made 11-Apr-2012 by Evan 11
The TCL manual states that rand() and srand() are not cryptographically secure, and should not be used for OTP or secret key generation:

"The generator algorithm is a simple linear congruential generator that is not cryptographically secure. Each result from rand completely determines all future results from subsequent calls to rand, so rand should not be used to generate a sequence of secrets, such as one-time passwords. The seed of the generator is initialized from the internal clock of the machine or may be set with the srand function." http://www.tcl.tk/man/tcl8.4/TclCmd/expr.htm#M38

The article below mentions both the AES::key option and also the CRYPTO::keygen function (in 11.1) as ways to generate a more random and secure value.

https://devcentral.f5.com/Community/GroupDetails/tabid/1082223/asg/39/aft/2161610/showtab/groupforums/Default.aspx#2261273
0
Comment made 18-Apr-2012 by Kristoffer.O 1
I took Aung's advice and made som adjustment to the code. This version wil return a alphanumeric string with a length of 6.

!!! ONLY WORKS ON LTM-11.1.0 AND UP !!!

when ACCESS_POLICY_AGENT_EVENT {
expr srand([clock clicks])
set tmpKey [CRYPTO::keygen -alg random -len 128 -passphrase [AES::key 128] -rounds 2]
set otp [string toupper [string range [b64encode $tmpKey] 0 5]]
set mail [ACCESS::session data get "session.ad.last.attr.mail"]
set mobile [ACCESS::session data get "session.ad.last.attr.mobile"]
set logstring mail,$mail,otp,$otp,mobile,$mobile
ACCESS::session data set session.user.otp.pw $otp
ACCESS::session data set session.user.otp.mobile $mobile
ACCESS::session data set session.user.otp.username [ACCESS::session data get "session.logon.last.username"]
log local0.alert "Event [ACCESS::policy agent_id] Log $logstring"
}

when ACCESS_POLICY_COMPLETED {
log local0.alert "Result: [ACCESS::policy result]"
}

Kristoffer
0
Comment made 06-Nov-2012 by zafer 134
Hello Kristoffer,

i tired you rule for alfanumeric but when i copy your in LTM i got error mesage. (version 11.2.1) and i have another question about otp password time. How can i set expire time for password. i want set life time for that password 3 minutes.
0
Comment made 12-Nov-2012 by AJ 0
Hi,
I am trying to implement the OTP generation using the iRule but its giving error, could anyone please let me know how I can make it work for 11.1.0 HF4


Errors:
+++++++++++++++++++++++++++++++++++
01070151:3: Rule [/Common/OTP_iRule] error:
line 2: [use curly braces to avoid double substitution] [srand([clock clicks])]
line 3: [wrong # args] [set tmpKey [CRYPTO::keygen -alg random -len 128 -passphrase ]AES::key 128[ -rounds 2]]
line 3: [undefined procedure: -rounds] [-rounds 2]
line 4: [wrong # args] [set otp [string toupper ]string range [b64encode $tmpKey] 0 5[]]
line 4: [wrong # args] [string toupper ]
+++++++++++++++++++++++++++++++
Thanks
Arry.
0
Comment made 13-Nov-2012 by Kristoffer.O 1
Hi AJ and zafer

Looks like the comment form malformed the code.
I just tried this code in 11.2.1



when ACCESS_POLICY_AGENT_EVENT {
expr srand([clock clicks])
set tmpKey [CRYPTO::keygen -alg random -len 128 -passphrase [AES::key 128] -rounds 2]
set otp [string toupper [string range [b64encode $tmpKey] 0 5]]
set mail [ACCESS::session data get "session.ad.last.attr.mail"]
set mobile [ACCESS::session data get "session.ad.last.attr.mobile"]
set logstring mail,$mail,otp,$otp,mobile,$mobile
ACCESS::session data set session.user.otp.pw $otp
ACCESS::session data set session.user.otp.mobile $mobile
ACCESS::session data set session.user.otp.username [ACCESS::session data get "session.logon.last.username"]
log local0.alert "Event [ACCESS::policy agent_id] Log $logstring"
}

0
Comment made 15-Nov-2012 by AJ 0
Awesome thanks Kristoffer. I am able to add the iRule now.
I have come across a different problem now, after implementing the total setup, I am getting stuck between AD Query & iRule Event.
User is getting authenticated but the page is getting stuck and the session is eventually closing.

Below are the logs:

2012-11-16 15:58:59 Received User-Agent header: Mozilla%2f4.0%20(compatible%3b%20MSIE%207.0%3b%20Windows%20NT%206.1%3b%20WOW64%3b%20Trident%2f5.0%3b%20SLCC2%3b%20.NET%20CLR%202.0.50727%3b%20.NET%20CLR%203.5.30729%3b%20.NET%20CLR%203.0.30729%3b%20Media%20Center%20PC%206.0%3b%20InfoPath.3%3b%20.NET4.0C%3b%20.NET4.0E). Common

2012-11-16 15:58:59 Received client info - Type: IE Version: 9 Platform: Win7 CPU: WOW64 UI Mode: Full Javascript Support: 1 ActiveX Support: 1 Plugin Support: 0 Common

2012-11-16 15:58:59 New session from client IP 10.242.18.157 (ST=/CC=/C=) at VIP 10.242.22.128 Listener /Common/POC_SharePoint_HTTP Common

2012-11-16 15:59:17 Username 'sptest1' Common

2012-11-16 16:04:26 \N: Session deleted due to user inactivity or errors. Common

2012-11-16 16:04:26 IP Cleanup: Failed to read rtdom_id err: ERR_OK Common

2012-11-16 16:05:00 Session statistics - bytes in: 2324, bytes out: 6577 Common

++++++++++++++++++++++++++++++++++++++++++++++

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on external, link-type EN10MB (Ethernet), capture size 96 bytes
15:24:56.697236 IP 10.242.22.5.44922 > 10.242.19.100.kerberos: v5
15:24:56.702915 IP 10.242.19.100.kerberos > 10.242.22.5.44922:
15:24:56.724561 IP 10.242.22.5.46779 > 10.242.19.100.kerberos: v5
15:24:56.730017 IP 10.242.19.100.kerberos > 10.242.22.5.46779: v5
15:24:56.736273 IP 10.242.22.5.58474 > 10.242.19.100.ldap: S 4094267117:4094267117(0) win 4380 0,nop,nop,timestamp 128825429 0,sackOK,eol>
15:24:56.738441 IP 10.242.19.100.ldap > 10.242.22.5.58474: S 2894698054:2894698054(0) ack 4094267118 win 8192 8,sackOK,timestamp 4279416 128825429>
15:24:56.738658 IP 10.242.22.5.58474 > 10.242.19.100.ldap: . ack 1 win 4380
15:24:56.738699 IP 10.242.22.5.58474 > 10.242.19.100.ldap: P 1:15(14) ack 1 win 4380
15:24:57.938405 IP 10.242.22.5.58474 > 10.242.19.100.ldap: P 1:15(14) ack 1 win 4380
15:25:00.138325 IP 10.242.22.5.58474 > 10.242.19.100.ldap: P 1:15(14) ack 1 win 4380
15:25:04.338394 IP 10.242.22.5.58474 > 10.242.19.100.ldap: P 1:15(14) ack 1 win 4380
15:25:12.538342 IP 10.242.22.5.58474 > 10.242.19.100.ldap: P 1:15(14) ack 1 win 4380
0
Comment made 20-Nov-2012 by zafer 134
Hello Kristoffer

i fixed the rule it works on 11.2.1

when ACCESS_POLICY_AGENT_EVENT {
expr srand([clock clicks])
set tmpKey [CRYPTO::keygen -alg random -len 128 -passphrase [AES::key 128] -rounds 2]
set otp [string range [b64encode $tmpKey] 0 8[]]
set mail [ACCESS::session data get "session.ad.last.attr.mail"]
set mobile [ACCESS::session data get "session.ad.last.attr.mobile"]
set logstring mail,$mail,otp,$otp,mobile,$mobile
ACCESS::session data set session.user.otp.pw $otp
ACCESS::session data set session.user.otp.mobile $mobile
ACCESS::session data set session.user.otp.username [ACCESS::session data get "session.logon.last.username"]
log local0.alert "Event [ACCESS::policy agent_id] Log $logstring"
}

when ACCESS_POLICY_COMPLETED {
log local0.alert "Result: [ACCESS::policy result]"
}


-----
regards
Zafer Berber

0
Comment made 06-Jul-2015 by JWhitesPro 307
I must be missing something...where in this workflow does the actual mobile address and OTP get sent to the sms gateway? The http auth doesn't have any options and I never see anywhere where they set any variables = to the 'to' and 'text' fields....
0
Comment made 06-Jul-2015 by JWhitesPro 307
nevermind...wasn't thinking clearly...apm passes the session.user and session.password i assume..
0
Comment made 11-Sep-2015 by jefp 241
A question about this, I have tried to set it up but when creating my AAA HTTP server I get this error: 01071346:3: In AAA HTTP server (/cegeka/otp_test), Using Http auth agent against SSL backend is not allowed, please, create a layered virtual server with serverssl profile any idea what this means?
0
Comment made 1 month ago by HenriT 0

Any idea will this work on 12.1?

0