2fa
14 TopicsAPM Cookbook: Two-Factor Authentication using YubiKey OTP with iRulesLX.
Introduction It’s been a number of years since I penned my first DC article: Two-Factor Authentication using YubiKey, YubiCloud and APM. A lot has changed over the years, BIG-IP versions and features, new YubiKey models and the YubiCloud Validation API has changed significantly rendering my older article obsolete. This article is a rewrite of the original with a number of improvements, such as: No need for HTTP Auth agent No need to Reverse Proxy the HTTP connection to the YubiCloud API “yub” NPM package used with iRulesLX. This does all the hard work for us, such as signing the message, validating the response and decoding the YubiKey serial. HMAC-SHA1 signed message Signed response validation VPE improvements to protect AD Account Lockouts YubiKey 2-Factor Authentication Process with APM The authentication process can be broken down into a few simple steps which is illustrated below and explained in more detail. Step 1 – The user is presented with a login page. The login page in my example asks for a Username, Password and YubiKey OTP. After entering your username and password, you simply plug in the YubiKey to the USB port and press the button. The YubiKey will generate the unique OTP followed by the enter key. Step 2 & 3 – APM sends the YubiKey OTP to the YubiCloud validation service. If the YubiCloud API returns “Status=OK”, the signature and the nonce is verified, then we know the YubiKey OTP is valid. This is performed by the “yub” NPM package using iRulesLX. Step 4 & 5 – Check to make sure the user has been provisioned a YubiKey and the Serial number assigned to that user matches. I store the 8-digit YubiKey serial number to an Active Directory attribute: “employeeID”. Obviously you can use any attribute field you like or you can modify the policy to query a data group. Step 6 & 7 – The Username and Password is verified by Active Directory/LDAP or what ever is your preference. Step 8 - On success, grant the user access to the resource. An explanation of the validation protocol can be found here: https://developers.yubico.com/yubikey-val/Validation_Protocol_V2.0.html. The “yub” NPM module uses this API and simplifies the validation and signing process. Before we get started I have a pre-configured Active Directory 2012 R2 server which I will be using as my LDAP server with an IP address of 10.1.30.101. My BIG-IP is running TMOS 12.1.2 and the iRules Language eXtension has been licensed and provisioned. Make sure your BIG-IP has internet access to download the required Node.JS packages. This guide also assumes you have a basic level of understanding and troubleshooting at a Local Traffic Manager (LTM) level and your BIG-IP Self IP, VLANs, Routes, etc.. are all configured and working as expected. You have obtained a Client ID and API Key from: https://upgrade.yubico.com/getapikey/ to validate the YubiKey OTP. Step 1 – iRule and iRuleLX Configuration 1.1 Create a new iRulesLX workspace Local Traffic >> iRules >> LX Workspaces >> “Create” Supply the following: Name: yubikey_auth_workspace Select “Finished" to save. You will now have any empty workspace, ready to cut/paste the TCL iRule and Node.JS code. 1.2 Add the iRule Select “Add iRule” and supply the following: Name: yubikey_auth_apm_event_irulelx Select OK Cut / Paste the following iRule into the workspace editor on the right hand side. Select “Save File” to save. # Author: Brett Smith @f5 when RULE_INIT { # Debug logging control. # 0 = debug logging off, 1 = debug logging on. set static::yubikey_debug 0 } when ACCESS_POLICY_AGENT_EVENT { if { [ACCESS::policy agent_id] eq "yubikey_auth" } { # Get the YubiKey OTP from APM session data set yubiotp [ACCESS::session data get session.logon.last.yubiotp] if { $static::yubikey_debug == 1 }{ log local0. "YubiKey OTP: $yubiotp" } # Basic error handling - don't execute Node.JS if session.logon.last.yubiotp is null if { ([string trim $yubiotp] eq "") } { # The YubiKey OTP is not valid ACCESS::session data set session.yubikey.valid 0 if { $static::yubikey_debug == 1 }{ log local0. "YubiKey OTP is not valid!" } } else { # Initialise the iRulesLX extension set rpc_handle [ILX::init yubikey_auth_extension] # Need to change the default RPC timeout from 3 sec to 30 sec to # allow for the HTTPS request to the Yubico API set timeout 30000 # Pass the YubiKey OTP to Node.JS and save the iRulesLX response set rpc_response [ILX::call $rpc_handle -timeout $timeout yubikey_auth $yubiotp] if { $static::yubikey_debug == 1 }{ log local0. "rpc_response: $rpc_response" } # Loop through each key/value pair returned from "yub.verify" foreach {key value} $rpc_response { # Assign the key/value pair to an APM session variable so it # can be referenced in the Access Policy ACCESS::session data set session.yubikey.$key $value if { $static::yubikey_debug == 1 }{ log local0. "$key $value" } } } } } 1.3 Add the Extension Select “Add extenstion” and supply the following: Name: yubikey_auth_extension Select OK Cut / Paste the following Node.JS and replace the default index.js. Select “Save File” to save. Update the “client_id” and “secret_key” variables with your Yubico Client ID and API Key. // Author: Brett Smith @f5 // index.js for yubikey_auth_apm_event_lx // Includes var f5 = require('f5-nodejs'); var yub = require('yub'); // Create a new rpc server for listening to TCL iRule calls. var ilx = new f5.ILXServer(); // Start listening for ILX::call and ILX::notify events. ilx.listen(); // YubiKey Auth ilx.addMethod('yubikey_auth', function(yubiotp, response) { // Get a Yubico Client ID and API Key from here: https://upgrade.yubico.com/getapikey/ var client_id = 'XXXX'; var secret_key = 'XXXXXXXXXXXXXXX'; // Initialise the yub library yub.init(client_id, secret_key); // Attempt to verify the OTP yub.verify(yubiotp.params()[0], function(err,data) { if (err) { console.log('Error: YubiKey OTP Verify Failed!'); response.reply('valid 0'); } else { response.reply(data); } }); }); 1.4 Install the “yub” package SSH to the BIG-IP as root cd /var/ilx/workspaces/Common/yubikey_auth_workspace/extensions/yubikey_auth_extension npm install yub -save You should expect the following output from the above command: [root@big-ip1:Active:Standalone] ldap_modify_extension # npm install yub -save yub@0.11.1 node_modules/yub 1.5 Create a the iRulesLX plugin Local Traffic >> iRules >> LX Plugin >> “Create” Supply the following: Name: yubikey_auth_plugin From Workspace: yubikey_auth_workspace Select “Finished" to save. If you look in /var/log/ltm, you will see the extension start a process per TMM for the iRuleLX plugin. big-ip1 info sdmd[16339]: 018e000b:6: Extension /Common/yubikey_auth_plugin:yubikey_auth_extension started, pid:975 big-ip1 info sdmd[16339]: 018e000b:6: Extension /Common/yubikey_auth_plugin:yubikey_auth_extension started, pid:976 big-ip1 info sdmd[16339]: 018e000b:6: Extension /Common/yubikey_auth_plugin:yubikey_auth_extension started, pid:977 big-ip1 info sdmd[16339]: 018e000b:6: Extension /Common/yubikey_auth_plugin:yubikey_auth_extension started, pid:978 Step 2 – APM Configuration 2.1 Create a new Authentication Server or reuse an existing server 2.1.1 Access Policy >> AAA Servers >> Active Directory >> “Create” Supply the following: Name: f5.demo_ad_aaa (something sensible) Domain Name: f5.demo (Domain Name) Server Connection: Direct or Use Pool depending on your setup. Domain Controller: <FQDN> or (AD server) or leave blank and APM will use DNS. Admin Name and Password Select “Finished" to save. 2.2 Create an Access Profile and Policy 2.2.1 Access Policy >> Access Profiles >> Access Profiles List >> “Create” Supply the following: Name: yubikey_otp_2fa_iruleslx_ap Profile Type: All Profile Scope: Profile Languages: English (en) Use the default settings for all other settings. Select “Finished" to save. 2.2.2 Access Policy >> Access Profiles >> Access Profiles List >> “Edit” On the “fallback” branch after the “Start” object, add a “Logon Page” object. Add a third field: Type: text Post Variable Name: yubiotp Session Variable Name: yubiotp Read Only: No In the “Customization” section further down the page, set the “Form Header Text” to what ever you like and change “Logon Page Input Field #3” to something meaningful, see my example below for inspiration. Leave the “Branch Rules” as the default. Don’t forget to “Save”. 2.2.3 On the “fallback” branch after the “Logon Page” object, add an “iRule Event” object. This step verifies the YubiKey OTP by passing “session.logon.last.yubiotp” from the ”Logon Page” to the iRuleLX created in Step 1. Supply the following Properties: Name: YubiKey Auth ID: yubikey_auth 2.2.3.1 Under “Branch Rules”, add a new one, by selecting “Add Branch Rule”. Update the Branch Rule settings: Name: YubiKey OTP Valid Expression (Advanced): expr { [mcget {session.yubikey.valid}] == "1" } Select “Finished”, then “Save” when your done. 2.2.4 On the “YubiKey OTP Valid” branch after the “YubiKey Auth” object, add an “AD Query” object. This step checks if the user has a YubiKey provisioned in their Active Directory account and the Serial number assigned to that user matches. I’ve added the serial number of the YubiKey to the “employeeID” attribute in Active Directory for each user. I used the “employeeID” attribute for simplicity, but I would recommend creating a custom AD attribute for the YubiKey serial number. Supply the following Properties: Name: YubiKey Serial Match Server: /Common/f5.demo_ad_aaa (select your AD Server) SearchFilter: sAMAccountName=%{session.logon.last.username} Required Attributes: employeeID 2.2.4.1 Under “Branch Rules”, delete the default and add a new one, by selecting “Add Branch Rule”. Update the Branch Rule settings: Name: Not Provisioned Expression (Advanced): expr { [mcget {session.ad.last.attr.employeeID}] == "" } Select “Finished. 2.2.4.2 Add another Branch Rule by selecting “Add Branch Rule”. Update the Branch Rule settings: Name: Match Found Expression (Advanced): expr { [mcget {session.yubikey.serial}] eq [string trim [mcget {session.ad.last.attr.employeeID}] 0] } Select “Finished”, then “Save” when your done. 2.2.5 On the “Match Found” branch after the “YubiKey Serial Match” object, add an “AD Auth” object. This step verifies the username and password is correct against Active Directory. Supply the following Properties: Name: AD Auth AAA Server: /Common/f5.demo_ad_aaa (select your AD Server) Leave the “Branch Rules” as the default. Select “Save” when your done. 2.2.6 On the “Successful” branch after the “AD Auth” object, change the branch end from “ Deny” to “Allow”. This competes the Access Policy. It should resemble something similar to this: Step 3 – Virtual Server Configuration Attach the Access Policy (yubikey_otp_2fa_iruleslx_apldap_modify_ap) to a HTTPS virtual server. Attach the iRuleLX (yubikey_auth_apm_event_irulelx) under the Resources section. Conclusion This is another great example how you can easily add a 2nd factor of authentication to any application using the power of Access Policy Manager (APM). F5 provides a 10 Concurrent User trial version of APM with every BIG-IP licensed with LTM. APM is one of my favourite pieces of technology, it amazes me every day what I can create with this flexible tool. Why not give it a try today.2.7KViews0likes6CommentsF5 APM with Microsoft Authenticator
Hi team, has anyone tested Microsoft Authenticator with F5 APM for 2FA? Lot of articles are there for google Authenticator, but none for Microsoft Authenticator, pls let me know or point me in right direction if anyone has ever tested it??2.5KViews0likes17CommentsSend an One Time Password (OTP) via the MessageBird SMS gateway
Problem this snippet solves: This snippet makes it possible to send an One Time Password (OTP) via the MessageBird SMS gateway. This snippet uses iRuleLX and the node.js messagebird package to interact with the MessageBird API. How to use this snippet: Prepare the BIG-IP Provision the BIG-IP with iRuleLX. Create LX Workspace: messagebird Add iRule: messagebird_irule Add Extension: messagebird_extension Add LX Plugin: messagebird_plugin -> From Workspace: messagebird Install the node.js messagebird module # cd /var/ilx/workspaces/Common/messagebird/extensions/messagebird_extension # npm install messagebird --save messagebird@2.1.4 node_modules/messagebird # irule To make it works, you need to install the irule on the Virtual Server that publish your application with APM authentication. 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. Configuring the Visual Policy Editor The printscreen below is a minimal Visual Policy Editor used to make MessageBird OTP Authentication works properly : For a larger version of this image please download here. Irule Event – MessageBird This is an irule event with the ID set to ‘MessageBird’. This will trigger the messagebird_irule to come into action. MessageBird Status This is an empty action with two branches. The branch named "successful" contains the following expression : expr { [mcget {session.custom.messagebird.status}] contains "successful" } Message Box This is a message box that will inform the user that there was a failure sending the One Time Password. messagebird_irule ### ### Name : messagebird_irule ### Author : Niels van Sluis, <niels@van-sluis.nl> ### Version: 20180721.001 ### Date : 2018-07-21 ### when ACCESS_POLICY_AGENT_EVENT { if { [ACCESS::policy agent_id ] eq "MessageBird" } { # Set MessageBird access key set accessKey "<ACCESS_KEY>" # Set user-friendly message which will be send prior to the OTP itself set message "Your OTP is: " # Set username used for logging purposes only set username "[ACCESS::session data get session.logon.last.username]" # Set OTP generated by BIG-IP APM which will be added to the SMS message. set generatedOTP "[ACCESS::session data get session.otp.assigned.val]" # Set phonenumber to send the SMS to set telephoneNumber "[ACCESS::session data get session.ad.last.attr.telephoneNumber]" # Set the sender of the message. This can be a telephone number (including country code) or an alphanumeric string. # In case of an alphanumeric string, the maximum length is 11 characters. set sender "MessageBird" if {[info exists username] && ($username eq "")} { log local0. "Error: username variable is empty; no OTP sent." return } if {[info exists generatedOTP] && ($generatedOTP eq "")} { log local0. "Error: generatedOTP variable is empty; no OTP sent for user $username." return } if {([info exists telephoneNumber] && $telephoneNumber eq "")} { log local0. "Error: telephoneNumber variable is empty; no OTP sent for user $username." return } set rpc_handle [ ILX::init messagebird_plugin messagebird_extension ] if {[ catch { ILX::call $rpc_handle sendOTP $generatedOTP $telephoneNumber $sender $message $accessKey } result ] } { log local0. "sendOTP failed for telephoneNumber: $telephoneNumber, ILX failure: $result" return } log local0. "MessageBird status for user $username ($telephoneNumber): $result" ACCESS::session data set session.custom.messagebird.status $result } } Code : /** *** Name : messagebird_extension *** Author : Niels van Sluis, *** Version: 20180721.001 *** Date : 2018-07-21 **/ // Import the f5-nodejs module. var f5 = require('f5-nodejs'); // Create a new rpc server for listening to TCL iRule calls. var ilx = new f5.ILXServer(); ilx.addMethod('sendOTP', function(req, res) { var generatedOTP = req.params()[0]; var telephoneNumber = req.params()[1]; var sender = req.params()[2]; var message = req.params()[3]; var accessKey = req.params()[4]; var params = { 'originator': sender, 'recipients': [ telephoneNumber ], 'body': message + generatedOTP }; var messagebird = require('messagebird')(accessKey); messagebird.messages.create(params, function (err, response) { if (err) { //console.log(err); return res.reply('failed'); } //console.log(response); return res.reply('successful'); }); }); // Start listening for ILX::call and ILX::notify events. ilx.listen(); Tested this on version: 13.02.4KViews0likes3CommentsTwo-Factor Authentication using YubiKey, YubiCloud and APM
Introduction What is a YubiKey? The YubiKey is an innovative USB-key that simplifies the process of logging in with strong two factor authentication. With a simple touch on the device, it generates a One-Time Password (OTP) on any computer and platform without any client software needed. How it YubiKey works By touching the integrated button, the YubiKey sends a time-variant, secure login code as if it was typed in from a keyboard. The unique passcode is verified by a YubiKey compliant web service or software application, in our case APM. For more details see: http://www.yubico.com/yubikey What is YubiCloud? The YubiCloud is a free cloud based YubiKey (OTP) validation service, for easy integration of strong two-factor authentication with your web site or online service. i.e APM For more details see: http://www.yubico.com/yubicloud YubiKey 2-Factor Authentication Process with APM The authentication process can be broken down into a few simple steps which is illustrated below and explained in more detail. Step 1 – The user is presented with a login page. The login page in my example asks for a Username, Password and YubiKey OTP. After entering your username and password, you simply plug in the YubiKey to the USB port and press the button. The YubiKey will generate the unique OTP followed by the enter key. Step 2 & 3 – The Username and Password is verified by Active Directory. On success move to Step 4. Step 4 & 5 – Check to make sure the user has been assigned a YubiKey. I store the 8-digit YubiKey serial number to an Active Directory attribute: “employeeID”. Obviously you can use any attribute field you like or a data group. Step 6 – Make sure the OTP generated matches the YubiKey serial number for that user. YubiKey OTP uses Modified Hexadecimal output, known as MODHEX. The MODHEX alphabet is explained on the Yubico forum: http://forum.yubico.com/viewtopic.php?f=6&t=96. I created an iRule which takes a number (YubiKey Serial) and converts it to MODHEX and saves this to an APM session variable. Next compare the OTP value from step 1 to the MODHEX serial number, if the string exists, then we are good to go. Step 7, 8 & 9 – Send the YubiKey OTP to the YubiCloud validation service. If the YubiCloud API returns “Status=OK” then we can allow the user to access to the resource. An explanation of the validation protocol can be found here: http://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20. In my example setup, I’m not signing or validating the response, this could be done with an iRule, something for my next post. BIG-IP APM Configuration The APM configuration is straight forward and assumes: You have an existing directory server with users configured and you can add the YubiKey serial number to an LDAP/AD attribute. Your YubiKey is registered with the YubiCloud service. You have obtained an Auth ID and API key from: https://upgrade.yubico.com/getapikey/ to validate the YubiKey. You have use a BIG-IP before and have suitable LTM knowledge. This configuration was created using BIG-IP v11.2.0. Lets get into the APM configuration. Authentication Servers Active Directory Access Policy >> AAA Servers >> Active Directory >> “Create” Supply the following: Name: dc1.test.lab (something sensible) Domain Name: test.lab (Domain Name) Domain Controller: <FQDN> or (AD server) Admin Name and Password Select “Finished" to save. YubiCloud API The HTTP AAA Server object doesn’t support HTTPS connections, so we need to create a Virtual Server to proxy the traffic from the BIG-IP to the YubiCloud API. The Virtual Server will listen on HTTP, but we will apply a Server SSL profile creating a HTTP –> HTTPS proxy. Local Traffic >> Nodes >> Node List >> “Create” Supply the following: Name: api.yubico.com Address: api.yubico.com Health Monitor Select “Finished" to save. Local Traffic >> Pools >> Pool List >> “Create” Supply the following: Name: pool_yubicloud Description: YubiCloud Proxy Pool (optional) Health Monitor (optional) Address: api.yubico.com Service Port: HTTPS Select “Finished" to save. Local Traffic >> Virtual Servers >> Virtual Server List >> “Create” Supply the following: Name: vs_yubicloud Description: YubiCloud Proxy VS (optional) Destination: <IP Address> Service Port: HTTP HTTP Profile: http SSL Profile (Server): clientssl SNAT Pool: Auto Map Default Pool: pool_yubicloud Select “Finished" to save. Access Policy >> AAA Servers >> HTTP Supply the following: Name: aaa_yubicloud Form Method: GET Form Action: http://IP_address_of_vs_yubicloud/wsapi/verify Form Parameter For User Name: id Form Parameter For Password: otp Successful Logon Detection Match Type: By Specific String in Response Successful Logon Detection Match Value: status=OK The Form Action uses the Virtual Server IP configured above instead of the YubiCloud server FQDN. You need to supply at least the id and otp parameters for an unsigned response. The id parameter is your Auth ID for the YubiCloud service and otp is the token code generated by the YubiKey. Select “Finished" to save. MODHEX Encode iRule Local Traffic >> iRules >> iRules List >> “Create” Copy and paste the following iRule which will convert the YubiKey serial number to Modified Hexadecimal (MODHEX) format. In my example, I’m retrieving the YubiKey serial associated with the user from Active Directory using the “employeeID” attribute. This iRule will need to be modified if the serial number has been stored in alternate source, such as a data group or a custom LDAP/AD attribute. when ACCESS_POLICY_AGENT_EVENT { if { [ACCESS::policy agent_id] eq "irule_apm_yubikey_modhex_encode" } { # yubikey serial number set yubikey_serial [ACCESS::session data get session.ad.last.attr.employeeID] # modhex alphabet array set 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 } # remove leading zeros from serial number set yubikey_serial [string trimleft $yubikey_serial 0] # convert serial to hex and pad with zeros set yubikey_serial [format %012X $yubikey_serial] # split the string set yubikey_serial [split $yubikey_serial ""] # for each HEX change to MODHEX using alphabet set yubikey_modhex "" foreach index $yubikey_serial { append yubikey_modhex $modhex_alphabet($index) } ACCESS::session data set session.custom.yubikey.modhex $yubikey_modhex } } Access Profile & Policy Access Policy >> Access Profiles >> Access Profiles List >> “Create” Supply the following: Name: profile_yubicloud Language: English (en) Use the default settings for all other settings. Select “Finished" to save. Access Policy >> Access Profiles >> Access Profiles List >> “Edit” On the “fallback” branch after the “Start” object, add a “Logon Page” object. Add a third field: Type: text Post Variable Name: yubiotp Session Variable Name: yubiotp Read Only: No In the “Customization” section further down the page, set the “Form Header Text” to what ever you like and change “Logon Page Input Field #3” to something meaningful, see my example below for inspiration. Leave the “Branch Rules” as the default. Don’t forget to “Save”. On the “fallback” branch after the “Logon Page” object, add an “AD Auth” object. This step verifies the username and password is correct against Active Directory. Supply the following Properties: Name: AD Auth AAA Server: /Common/dc1.test.lab (select your AD Server) Leave the “Branch Rules” as the default. Select “Save” when your done. On the “Successful” branch after the “AD Auth” object, add an “AD Query” object. This step checks if the user has a YubiKey provisioned in their Active Directory account. I’ve added the serial number of the YubiKey to the “employeeID” attribute in Active Directory for each user. I used the “employeeID” attribute for simplicity, but I would recommend creating a custom AD attribute for the YubiKey serial number. Supply the following Properties: Name: YubiKey Provisioned SearchFilter: sAMAccountName=%{session.logon.last.username} Under “Branch Rules”, delete the default and add a new one, by selecting “Add Branch Rule”. Update the Branch Rule settings: Name: Yes Expression (Advanced): expr { [mcget {session.ad.last.attr.employeeID}] != "" } Select “Finished”, then “Save” when your done. On the “Yes” branch after the “YubiKey Provisioned” object, add an “iRule Event” object. This step passes “session.ad.last.attr.employeeID” (YubiKey serial) from the successful AD Query to the iRule called “irule_apm_yubikey_modhex_encode”. The iRule returns a Modified Hexadecimal (MODHEX) encoded string and stores this to “session.custom.yubikey.modhex”. We need the YubiKey serial in the MODHEX format so we can compare this to the original YubiKey OTP entered on the Logon Page. To learn more about how/why Yubico uses this format, visit the Yubico Forum: http://forum.yubico.com/viewtopic.php?f=6&t=96 Supply the following Properties: Name: YubiKey MODHEX Encode ID: irule_apm_yubikey_modhex_encode Now we compare the MODHEX formatted YubiKey serial to the YubiKey OTP entered on the Logon Page. This step verifies the user is using their YubiKey and not someone else's. The first 12 characters of the YubiKey OTP is the serial of that YubiKey, this makes is very easy to verify using the “string first” TCL command. Under “Branch Rules” add a new one, by selecting “Add Branch Rule”. Update the Branch Rule settings: Name: YubiKey Serial Match Expression (Advanced): expr { [string first [mcget {session.custom.yubikey.modhex}] [mcget {session.logon.last.yubiotp}]] == 0 } Select “Finished”, then “Save” when your done. On the “YubiKey Serial Match” branch after the “YubiKey MODHEX Encode” object, add a “Variable Assign” object. This step assigns your YubiCloud Auth ID to to “session.logon.last.username” and the YubiKey OTP entered on the Logon Page to “session.logon.last.password”. These session variables are then assigned to the id and otp HTTP parameters used in the HTTP Auth AAA object “aaa_yubicloud” created earlier. If you don’t quite follow how this works, just believe me and just do it! Supply the following Properties: Name: Assign YubiCloud Variables Add the Variable assignments by selecting “Add new entry” >> “change”. Variable Assign 1: Custom Variable (Unsecure): session.logon.last.username Custom Expression: expr {"1111"} (change “1111” to your YubiCloud Auth ID) Variable Assign 2: Custom Variable (Secure): session.logon.last.password Custom Expression: expr { [mcget {session.logon.last.yubiotp}] } Select “Finished”, then “Save” when your done.Leave the “Branch Rules” as the default. On the “fallback” branch after the “Assign YubiCloud Variables” object, add a “HTTP Auth” object. This step sends a HTTP Get to the YubiCloud API (https://api.yubico.com/wsapi/verify) to verify the YubiKey OTP entered on the Logon Page. Supply the following Properties: Name: YubiCloud Auth AAA Server: /Common/aaa_yubicloud Leave the “Branch Rules” as the default. Select “Save” when you’re done. This competes the Access Policy. It should resemble something similar to this: Next attached the “Access Profile” and “iRule” to a Virtual Server to test. Conclusion This is another great example how you can easily add a 2nd factor of authentication to any application using the power of Access Policy Manager (APM). F5 provides a 10 Concurrent User trial version of APM with every BIG-IP licensed with LTM. APM is one of my favourite pieces of technology, it amazes me every day what I can create with this flexible tool. Why not give it a try today.1.7KViews0likes4CommentsPost of the Week: Two-Factor Auth and SSO with BIG-IP
In this Lightboard Post of the Week, I answer a question about 2FA and SSO with AD/RSA on BIG-IP by creating a SSO Credential Mapping policy agent in the Visual Policy Editor, that takes the username and password from the logon page, and maps them to variables to be used for SSO services. Special thanks to senthil147for the question and a new 2018 MVP, MrPlastic (Lee Sutcliffe, which I flubbed) for the great answer. Posted Question on DevCentral: 2FA Authentication with SSO on APM ps1.2KViews0likes1CommentRadius External Monitor (Python)
Problem this snippet solves: Note: This script works and is usable however currently the only issue is that for some unknown reason it fails authentication on valid credentials, but since it still gets a valid response from the Radius server it will still mark the pool member as up. This only occurs via a configured monitor using either variable or arguments, running the script via command line with the same arguments works. This python script is an external monitor for radius that checks for any radius response in order to mark a pool member as up. If the connection times out the script does not output any response and as such will result in the member being marked as down. The script strips of routing domain tags and the IPv6 padding of the Node IPs. How to use this snippet: Installation: Import as an External Monitor Program File and name it however you want. Get and copy python modules Six (six py) and Py-Radius (radius py) then copy them to a directory with suitable permissions such as the /config/eav directory. If you're running python 2.6, which you probably will, then the radius module will need to be modified as per Code Mods section in the code below. Monitor Configuration: Set up a monitor with at the very least a valid SECRET, if you're looking to simply test whether the Radius server is alive and responding then the user account does not need to be valid. Valid Environment Variables are as follows: DEBUG = 0, 1, 2 (Default: 0) MOD_PATH = Directory containing the Python modules (Default: /config/eav) SECRET = Radius server secret (Default: NoSecret) USER = User account (Default: radius_monitor) PASSWD = Account Password (Default: NoPassword) Notes: The configured environment variables can overridden using Arguments with TESTME as the first argument followed by the variables you want to override in the same format as above, eg. KEY=Value. This is usedful for troubleshooting and can be used to override the F5 supplied NODE_IP and NODE_PORT variables. Log file location: /var/log/monitors Debug level 2 outputs environment variables, command line arguments and radius module into the log file. Code : #!/usr/bin/env python # -*- coding: utf-8 -*- # # Filename : radius_mon.py # Author : ATennent # Version : 1.1 # Date : 2018/09/14 # Python ver: 2.6+ # F5 version: 12.0+ # ========== Installation # Import this script via GUI: # System > File Management > External Monitor Program File List > Import... # Name it however you want. # Get, modify and copy the following modules: # ========== Required modules # -- six -- # https://pypi.org/project/six/ # Copy six.py into /config/eav # # -- py-radius -- # https://pypi.org/project/py-radius/ # Copy radius.py into /config/eav # If running python2.6 modify radius.py as per "Code mods -- python.py --" section below # ========== Notes # NB: Any and all outputs from EAV scripts equate to a POSITIVE result by the F5 monitor # so to make the result of the script to be a NEGATIVE result, ie. a DOWN state, we need # to ensure that the script does not output anything when the attempt results in a time out. # ========== Environment Variables # NODE_IP - Supplied by F5 monitor # NODE_PORT - Supplied by F5 monitor # MOD_PATH - Path to location of Python modules six.py and radius.py, default: /config/eav # SECRET - Radius server secret # USER - Username for test account # PASSWD - Password for user account # DEBUG - Enable/Disable Debugging # ========== Additional Environment Variables (supplied at execution) # MON_TMPL_NAME - Monitor Name, eg. '/Common/radius_monitor' # RUN_I - Run file, eg. '/Common/radius_mon.py' # SECURITY_FIPS140_COMPLIANCE - 'true'/'false' # PATH - Colon delimited and not editable. # ARGS_I - Copy of command line Arguments # NODE_NAME - Node Name, eg, '/Common/SNGPCTXWEB01' # MON_INST_LOG_NAME - Pool member's monitor log, eg. '/var/log/monitors/Common_radius_monitor-Common_MyNode-1812.log' # SECURITY_COMMONCRITERIA - 'true'/'false' # ========== Code mods -- python.py -- # Dictionary comprehension fixes: # https://stackoverflow.com/questions/1747817/create-a-dictionary-with-list-comprehension-in-python#1747827 # LINE:193; replace with #ATTR_NAMES = dict((v.lower(), k) for (k, v) in ATTRS.items()) # # LINE:100; replace with #CODE_NAMES = dict((v.lower(), k) for (k, v) in CODES.items()) # # Logger.NullHandler fix: # https://stackoverflow.com/questions/33175763/how-to-use-logging-nullhandler-in-python-2-6#34939479 # LINE:64-65; replace with #LOGGER = logging.getLogger(__name__) #try: # Python 2.7+ # LOGGER.addHandler(logging.NullHandler()) #except AttributeError: # class NullHandler(logging.Handler): # def emit(self, record): # pass # LOGGER.addHandler(NullHandler()) # ========== Imports/Modules from sys import path from sys import argv from sys import stdout from os import environ import logging.handlers import re if environ.get('MOD_PATH'): path.append(environ.get('MOD_PATH')) else: path.append('/config/eav') import radius # ========== Dictionary Defaults opts_dict = {'NODE_IP': '', 'NODE_PORT': '', 'SECRET': 'NoSecret', 'USER': 'radius_monitor', 'PASSWD': 'NoPassword', 'DEBUG': '0', } # ========== TEST w/ command line try: if argv[3] == 'TESTME': environ['NODE_IP'] = argv[1] environ['NODE_PORT'] = argv[2] for cmd_opt in argv[3:]: if 'DEBUG' in cmd_opt: environ['DEBUG'] = cmd_opt.split('=')[1] elif 'SECRET' in cmd_opt: environ['SECRET'] = cmd_opt.split('=')[1] elif 'USER' in cmd_opt: environ['USER'] = cmd_opt.split('=')[1] elif 'PASSWD' in cmd_opt: environ['PASSWD'] = cmd_opt.split('=')[1] else: continue except: pass # ========== Logging Config try: if int(environ.get('DEBUG')) == 0: log_file = '/dev/null' elif int(environ.get('DEBUG')) <=2: log_file = environ.get('MON_INST_LOG_NAME') else: log_file = '/dev/null' except: # DEBUG not supplied as ENV variable environ['DEBUG'] = opts_dict['DEBUG'] log_file = '/dev/null' if int(environ.get('DEBUG')) == 2: logging.basicConfig( filename=log_file, level=logging.DEBUG, format='%(asctime)s %(name)s:%(levelname)s %(message)s') else: logging.basicConfig( filename=log_file, level=logging.INFO, format='%(asctime)s %(name)s:%(levelname)s %(message)s') log = logging.getLogger('radius_mon') syslog_handler = logging.handlers.SysLogHandler(address='/dev/log', facility=0) log.addHandler(syslog_handler) if int(environ.get('DEBUG')) == 0: log.setLevel(logging.INFO) opts_dict['DEBUG'] = environ.get('DEBUG') elif int(environ.get('DEBUG')) == 1: log.setLevel(logging.DEBUG) opts_dict['DEBUG'] = environ.get('DEBUG') elif int(environ.get('DEBUG')) == 2: log.setLevel(logging.DEBUG) opts_dict['DEBUG'] = environ.get('DEBUG') log.debug('VERBOSE Debug Enabled, CMD line and ENV variables will be dumped to file.') else: log.error('Bad DEBUG value!') # ========== CMD Line and ENV Dump if int(opts_dict['DEBUG']) == 2: log.debug('=========== CMD ARGS') log.debug(argv[0:]) log.debug('=========== ENV VARS') log.debug(environ) # ========== Update Dictionary if environ.get('NODE_IP'): opts_dict['NODE_IP'] = environ.get('NODE_IP') log.debug('Set NODE_IP, %s', opts_dict['NODE_IP']) else: log.error('NODE_IP not supplied as ENV variable') log.error('Exiting..') exit(0) if environ.get('NODE_PORT'): opts_dict['NODE_PORT'] = environ.get('NODE_PORT') log.debug('Set NODE_PORT, %s', opts_dict['NODE_PORT']) else: log.error('NODE_PORT not supplied as ENV variable') log.error('Exiting..') exit(0) if environ.get('SECRET'): opts_dict['SECRET'] = environ.get('SECRET') log.debug('Set SECRET, %s', opts_dict['SECRET']) else: log.debug('SECRET not supplied as ENV variable') if environ.get('USER'): opts_dict['USER'] = environ.get('USER') log.debug('Set USER, %s', opts_dict['USER']) else: log.debug('USER not supplied as ENV variable') if environ.get('PASSWD'): opts_dict['PASSWD'] = environ.get('PASSWD') log.debug('Set PASSWD, %s', opts_dict['PASSWD']) else: log.debug('PASSWD not supplied as ENV variable') # ========== Clean up NODE_IP for IPv4 if '::ffff:' in opts_dict['NODE_IP']: opts_dict['NODE_IP'] = re.sub(r'::ffff:','', opts_dict['NODE_IP']) log.debug('Stripped IPv6 notation from IPv4 address') # ========== Strip routing domain from NODE_IP if '%' in opts_dict['NODE_IP']: opts_dict['NODE_IP'] = re.sub(r'%\d{1,4}$', '', opts_dict['NODE_IP']) log.debug('Stripped routing domain from IP address') # ========== Dictionary debug if int(opts_dict['DEBUG']) == 2: log.debug('----- KEY, VALUE pairs:') for key in opts_dict.keys(): log.debug('%s, %s', key, opts_dict[key]) log.debug('-----') # ========== Main r = radius.Radius(opts_dict['SECRET'], host=opts_dict['NODE_IP'], port=int(opts_dict['NODE_PORT'])) try: if r.authenticate(opts_dict['USER'], opts_dict['PASSWD']): log.debug('Service UP, Authentication attempt Successful') stdout.write('UP') else: log.debug('Service UP, Authentication attempt Failure') stdout.write('UP') except radius.ChallengeResponse: log.debug('Service UP, Authentication attempt Challenge Response') stdout.write('UP') except (radius.NoResponse, radius.SocketError): # This includes bad SECRET keys, the radius RFC states that Invalid responses from the radius # server are to be silently dropped which, from a client perspective, will result ina timeout. # This cannot be changed without modifying radius.py to instead raise an Exception. log.debug('No Response from %s:%s', opts_dict['NODE_IP'], int(opts_dict['NODE_PORT'])) exit(0) except Exception as msg: log.error('Exception due to %s, marking as DOWN', msg) exit(0) Tested this on version: 12.01KViews1like0CommentsOutlook Anywhere 2 Factor Authentication
Hello, since there is no native support for 2FA by Outlook Anywhere I'm wondering if it's possible to set up 2FA with SAML. For example, Outlook is connecting and authenticated by the NTLM Auth object. After this AD query finds users mobile number from AD, SAML is triggered and a iRule sends a SMS with a "magic link". The user has to open this link on his smartphone and the session is allowed. The link refers to the BigIP as SAML service provider. Something like Ping Identity does without external service provider and mobile app where you have to confirm your ID by sliding over. What do you think? Is this possible or have someone did a scenario like this already? Cheers546Views0likes4CommentsF5 SOC Malware Summary Report: Neverquest
#F5SOC #malware #2FA #infosec The good news is that compromising #2FA requires twice the work. The bad news? Malware can do it. That malware is a serious problem, particularly for organizations that deal with money, is no surprise. Malware is one of the primary tools used by fraudsters to commit, well, fraud. In 2013, the number of cyberattacks involving malware designed to steal financial data rose by 27.6% to reach 28.4 million according to noted security experts at Kaspersky. Organizations felt the result; 36% of financial institutions admit to experiencing ACH/wire fraud (2013 Faces of Fraud Survey). To protect against automated transactions originating from infected devices, organizations often employ two-factor authentication (2FA) that leverages OTP (one time passwords) or TAN (transaction authorization numbers) via a secondary channel such as SMS to (more) confidently verify identity. 2FA systems that use a secondary channel (a device separate from the system on which the transaction is initiated) are more secure, naturally, than those that transmit the second factor over a channel that can be used from the initiating system, a la an e-mail. While 2FA that use two disparate systems are, in fact, more secure, they are not foolproof, as malware like Neverquest has shown. Neverquest activity has been seen actively in the wild, meaning despite the need to compromise two client devices - usually a PC/laptop and a smartphone - it has been successful at manipulating victims into doing so. The primary infection occurs via the PC, which is a lot less difficult these days thanks to the prevalence of infected sites. But the secondary infection requires the victim to knowingly install an app on their phone, which means convincing them they should do so. This is where script injection comes in handy. Malware of this ilk modify the web app by injecting a script that changes the behavior and/or look of the page. Even the most savvy browsers are unlikely to be aware of such changes as they occur "under the hood" at the real, official site. Nothing about the URI or host changes, which means all appears as normal. The only way to detect such injections is to have prior knowledge of what the page should look like - down to the code level. The trust a victim has for the banking site is later exploited with a popup indicating they should provide their phone number and download an app. As it appears to be a valid request coming from their financial institution, victims may very well be tricked into doing so. And then Neverquest has what it needs - access to SMS messages over which OTP and/or TAN are transmitted. The attacker can then initiate automated transactions and confirm them by intercepting the SMS messages. Voila. Fraud complete. We (as in the corporate We) rely on our F5 SOC (Security Operations Center) team to analyze malware to understand how they compromise systems and enable miscreants to carry out their fraudulent goals. In July, the F5 SOC completed its analysis of Neverquest and has made its detailed results available. You can download the full technical analysis here on DevCentral. We've also made available a summary analysis that provides an overview of the malware, how it works, and how its risk can be mitigated. You can get that summary here on DevCentral as well. The F5 SOC will continue to diligently research, analyze and document malware in support of our security efforts and as a service to the broader community. We hope you find it useful and informative, and look forward to sharing more analysis in the future. You can get the summary analysis here, and the full technical analysis here. Additional Resources: F5 SOC on DevCentral F5 WebSafe Service F5 Web Fraud Protection Reference Architecture314Views0likes0Comments