APM Cookbook: Single Sign On (SSO) using Kerberos
To get the APM Cookbook series moving along, I’ve decided to help out by documenting the common APM solutions I help customers and partners with on a regular basis. Kerberos SSO is nothing new, but seems to stump people who have never used Kerberos before. Getting Kerberos SSO to work with APM is straight forward once you have the Active Directory components configured. Overview I have a pre-configured web service (IIS 7.5/Sharepoint 2010) that is configured for Windows Authentication, which will send a “Negotiate” in the header of the “401 Request for Authorization”. Make sure the web service is configured to send the correct header before starting the APM configuration by accessing the website directly and viewing the headers using browser tools. In my example, I used the Sharepoint 2010/2013 iApp to build the LTM configuration. I’m using a single pool member, sp1.f5.demo (10.10.30.2) listening on HTTP and the Virtual Server listening on HTTPS performing SSL offload. Step 1 - Create a delegation account on your domain 1.1 Open Active Directory Users and Computers administrative tool and create a new user account. User logon name: host/apm-kcd.f5.demo User logon name (pre-Windows 2000): apm-kcd Set the password and not expire 1.2 Alter the account and set the servicePrincipcalName. Run setspn from the command line: setspn –A host/apm-kcd.f5.demo apm-kcd A delegation tab will now be available for this user. Step 2 - Configure the SPN 2.1 Open Active Directory Users and Computers administrative tool and select the user account created in the previous step. Edit the Properties for this user Select the Delegation tab Select: Trust this user for delegation to specified services only Select: Use any authentication protocol Select Add, to add services. Select Users or Computers… Enter the host name, in my example I will be adding HTTP service for sp1.f5.demo (SP1). Select Check Names and OK Select the http Service Type and OK 2.2 Make sure there are no duplicate SPNs and run setspn –x from the command line. Step 3 - Check Forward and Reverse DNS DNS is critical and a missing PTR is common error I find when troubleshooting Kerberos SSO problems. From the BIG-IP command line test forward and reverse records exist for the web service using dig: # dig sp1.f5.demo ;; QUESTION SECTION: ;sp1.f5.demo. IN A ;; ANSWER SECTION: sp1.f5.demo. 1200 IN A 10.10.30.2 # dig -x 10.10.30.2 ;; QUESTION SECTION: ;2.30.10.10.in-addr.arpa. IN PTR ;; ANSWER SECTION: 2.30.10.10.in-addr.arpa. 1200 IN PTR sp1.f5.demo. Step 4 - Create the APM Configuration In this example I will use a Logon Page to capture the user credentials that will be authenticated against Active Directory and mapped to the SSO variables for the Kerberos SSO. 4.1 Configure AAA Server for Authentication Access Policy >> AAA Servers >> Active Directory >> “Create” Supply the following: Name: f5.demo_ad_aaa Domain Name: f5.demo Domain Controller: (Optional – BIG-IP will use DNS to discover if left blank) Admin Name and Password Select “Finished" to save. 4.2 Configure Kerberos SSO Access Policy >> SSO Configurations >> Kerberos >> “Create” Supply the following: Name: f5.demo_kerberos_sso Username Source: session.sso.token.last.username User Realm Source: session.ad.last.actualdomain Kerberos Realm: F5.DEMO Account Name: apm-kcd (from Step 1) Account Password & Confirm Account Password (from Step1) Select “Finished” to save. 4.3 Create an Access Profile and Policy We can now bring it all together using the Visual Policy Editor (VPE). Access Policy >> Access Profiles >> Access Profile List >> “Create” Supply the following: Name: intranet.f5.demo_sso_ap SSO Configuration: f5.demo_kerberos_sso Languages: English (en) Use the default settings for all other settings. Select “Finished” to save. 4.4 Edit the Access Policy in the VPE Access Policy >> Access Profiles >> Access Profile List >> “Edit” (intranet.f5.demo_sso_ap) On the fallback branch after the Start object, add a Logon Page object. Leave the defaults and “Save”. On the fallback branch after the Logon Page object, add an AD Auth object. Select the Server Select “Save” when your done. On the Successful branch after the AD Auth object, add a SSO Credential Mapping object. Leave the defaults and “Save”. On the fallback branch after the SSO Credential Mapping, change Deny ending to Allow. The finished policy should look similar to this: Don't forget to “Apply Access Policy”. Step 5 – Attach the APM Policy to the Virtual Server and Test 5.1 Edit the Virtual Server Local Traffic >> Virtual Servers >> Virtual Server List >> intranet.f5.demo_vs Scroll down to the Access Policy section and select the Access Profile. Select “Update” to save. 5.2 Test Open a browser, access the Virtual Server URL (https://intranet.f5.demo in my example), authenticate and verify the client is automatically logged on (SSO) to the web service. To verify Kerberos SSO has worked correctly, check /var/log/apm on APM by turning on debug. You should see log events similar to the ones below when the BIG-IP has fetched a Kerberos Ticket. info websso.1[9041]: 014d0011:6: 33186a8c: Websso Kerberos authentication for user 'test.user' using config '/Common/f5.demo_kerberos_sso' debug websso.1[9041]: 014d0018:7: sid:33186a8c ctx:0x917e4a0 server address = ::ffff:10.10.30.2 debug websso.1[9041]: 014d0021:7: sid:33186a8c ctx:0x917e4a0 SPN = HTTP/sp1.f5.demo@F5.DEMO debug websso.1[9041]: 014d0023:7: S4U ======> ctx: 33186a8c, sid: 0x917e4a0, user: test.user@F5.DEMO, SPN: HTTP/sp1.f5.demo@F5.DEMO debug websso.1[9041]: 014d0001:7: Getting UCC:test.user@F5.DEMO@F5.DEMO, lifetime:36000 debug websso.1[9041]: 014d0001:7: fetched new TGT, total active TGTs:1 debug websso.1[9041]: 014d0001:7: TGT: client=apm-kcd@F5.DEMO server=krbtgt/F5.DEMO@F5.DEMO expiration=Tue Apr 29 08:33:42 2014 flags=40600000 debug websso.1[9041]: 014d0001:7: TGT expires:1398724422 CC count:0 debug websso.1[9041]: 014d0001:7: Initialized UCC:test.user@F5.DEMO@F5.DEMO, lifetime:36000 kcc:0x92601e8 debug websso.1[9041]: 014d0001:7: UCCmap.size = 1, UCClist.size = 1 debug websso.1[9041]: 014d0001:7: S4U ======> - NO cached S4U2Proxy ticket for user: test.user@F5.DEMO server: HTTP/sp1.f5.demo@F5.DEMO - trying to fetch debug websso.1[9041]: 014d0001:7: S4U ======> - NO cached S4U2Self ticket for user: test.user@F5.DEMO - trying to fetch debug websso.1[9041]: 014d0001:7: S4U ======> - fetched S4U2Self ticket for user: test.user@F5.DEMO debug websso.1[9041]: 014d0001:7: S4U ======> trying to fetch S4U2Proxy ticket for user: test.user@F5.DEMO server: HTTP/sp1.f5.demo@F5.DEMO debug websso.1[9041]: 014d0001:7: S4U ======> fetched S4U2Proxy ticket for user: test.user@F5.DEMO server: HTTP/sp1.f5.demo@F5.DEMO debug websso.1[9041]: 014d0001:7: S4U ======> OK! Conclusion Like I said in the beginning, once you know how Kerberos SSO works with APM, it’s a piece of cake!8.1KViews1like28CommentsAPM Cookbook: Modify LDAP Attribute Values using iRulesLX
Introduction Access Policy Manager (APM) does not have the ability to modify LDAP attribute values using the native features of the product. In the past I’ve used some creative unsupported solutions to modify LDAP attribute values, but with the release of BIG-IP 12.1 and iRulesLX, you can now modify LDAP attribute values, in a safe and in supported manner. 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 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. For this solution I’ve opted to use the ldapjs package. The iRulesLX requires the following session variable to be set for the LDAP Modify to execute: Distinguished Name (DN): session.ad.last.attr.dn Attribute Name (e.g. carLicense): session.ldap.modify.attribute Attribute Value: session.ldap.modify.value This guide also assumes you have a basic level of understanding and troubleshooting at a Local Traffic Manager (LTM) level and you BIG-IP Self IP, VLANs, Routes, etc.. are all configured and working as expected. Step 1 – iRule and iRulesLX Configuration 1.1 Create a new iRulesLX workspace Local Traffic >> iRules >> LX Workspaces >> “Create” Supply the following: Name: ldap_modify_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: ldap_modify_apm_event 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::ldap_debug 0 } when ACCESS_POLICY_AGENT_EVENT { if { [ACCESS::policy agent_id] eq "ldap_modify" } { # Get the APM session data set dn [ACCESS::session data get session.ad.last.attr.dn] set ldap_attribute [ACCESS::session data get session.ldap.modify.attribute] set ldap_value [ACCESS::session data get session.ldap.modify.value] # Basic Error Handling - Don't execute Node.JS if LDAP attribute name or value is null if { (([string trim $ldap_attribute] eq "") or ([string trim $ldap_value] eq "")) } { ACCESS::session data set session.ldap.modify.result 255 } else { # Initialise the iRulesLX extension set rpc_handle [ILX::init ldap_modify_extension] if { $static::ldap_debug == 1 }{ log local0. "rpc_handle: $rpc_handle" } # Pass the LDAP Attribute and Value to Node.JS and save the iRulesLX response set rpc_response [ILX::call $rpc_handle ldap_modify $dn $ldap_attribute $ldap_value] if { $static::ldap_debug == 1 }{ log local0. "rpc_response: $rpc_response" } ACCESS::session data set session.ldap.modify.result $rpc_response } } } 1.3 Add an Extension Select “Add extenstion” and supply the following: Name: ldap_modify_extension Select OK Cut / Paste the following Node.JS and replace the default index.js. Select “Save File” to save. // Author: Brett Smith @f5 // index.js for ldap_modify_apm_events // Debug logging control. // 0 = debug off, 1 = debug level 1, 2 = debug level 2 var debug = 1; // Includes var f5 = require('f5-nodejs'); var ldap = require('ldapjs'); // 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(); // Unbind LDAP Connection function ldap_unbind(client){ client.unbind(function(err) { if (err) { if (debug >= 1) { console.log('Error Unbinding.'); } } else { if (debug >= 1) { console.log('Unbind Successful.'); } } }); } // LDAP Modify method, requires DN, LDAP Attribute Name and Value ilx.addMethod('ldap_modify', function(ldap_data, response) { // LDAP Server Settings var bind_url = 'ldaps://10.1.30.101:636'; var bind_dn = 'CN=LDAP Admin,CN=Users,DC=f5,DC=demo'; var bind_pw = 'Password123'; // DN, LDAP Attribute Name and Value from iRule var ldap_dn = ldap_data.params()[0]; var ldap_attribute = ldap_data.params()[1]; var ldap_value = ldap_data.params()[2]; if (debug >= 2) { console.log('dn: ' + ldap_dn + ',attr: ' + ldap_attribute + ',val: ' + ldap_value); } var ldap_modification = {}; ldap_modification[ldap_attribute] = ldap_value; var ldap_change = new ldap.Change({ operation: 'replace', modification: ldap_modification }); if (debug >= 1) { console.log('Creating LDAP Client.'); } // Create LDAP Client var ldap_client = ldap.createClient({ url: bind_url, tlsOptions: { 'rejectUnauthorized': false } // Ignore Invalid Certificate - Self Signed etc.. }); // Bind to the LDAP Server ldap_client.bind(bind_dn, bind_pw, function(err) { if (err) { if (debug >= 1) { console.log('Error Binding to: ' + bind_url); } response.reply('1'); // Bind Failed return; } else { if (debug >= 1) { console.log('LDAP Bind Successful.'); } // LDAP Modify ldap_client.modify(ldap_dn, ldap_change, function(err) { if (err) { if (debug >= 1) { console.log('LDAP Modify Failed.'); } ldap_unbind(ldap_client); response.reply('2'); // Modify Failed } else { if (debug >= 1) { console.log('LDAP Modify Successful.'); } ldap_unbind(ldap_client); response.reply('0'); // No Error } }); } }); }); You will need to modify the bind_url, bind_dn, and bind_pw variables to match your LDAP server settings. 1.4 Install the ldapjs package SSH to the BIG-IP as root cd /var/ilx/workspaces/Common/ldap_modify_workspace/extensions/ldap_modify_extension npm install ldapjs -save You should expect the following output from above command: [root@big-ip1:Active:Standalone] ldap_modify_extension # npm install ldapjs -save ldapjs@1.0.0 node_modules/ldapjs ├── assert-plus@0.1.5 ├── dashdash@1.10.1 ├── asn1@0.2.3 ├── ldap-filter@0.2.2 ├── once@1.3.2 (wrappy@1.0.2) ├── vasync@1.6.3 ├── backoff@2.4.1 (precond@0.2.3) ├── verror@1.6.0 (extsprintf@1.2.0) ├── dtrace-provider@0.6.0 (nan@2.4.0) └── bunyan@1.5.1 (safe-json-stringify@1.0.3, mv@2.1.1) 1.5 Create a new iRulesLX plugin Local Traffic >> iRules >> LX Plugin >> “Create” Supply the following: Name: ldap_modify_plugin From Workspace: ldap_modify_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[6415]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:24396 big-ip1 info sdmd[6415]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:24397 big-ip1 info sdmd[6415]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:24398 big-ip1 info sdmd[6415]: 018e000b:6: Extension /Common/ldap_modify_plugin:ldap_modify_extension started, pid:24399 Step 2 – Create a test Access Policy 2.1 Create an Access Profile and Policy We can now bring it all together using the Visual Policy Editor (VPE). In this test example, I will not be using a password just for simplicity. Access Policy >> Access Profiles >> Access Profile List >> “Create” Supply the following: Name: ldap_modify_ap Profile Type: LTM-APM Profile Scope: Profile Languages: English (en) Use the default settings for all other settings. Select “Finished” to save. 2.2 Edit the Access Policy in the VPE Access Policy >> Access Profiles >> Access Profile List >> “Edit” (ldap_modify_ap) On the fallback branch after the Start object, add a Logon Page object. Change the second field to: Type: text Post Variable Name: attribute Session Variable Name: attribute Read Only: No Add a third field: Type: text Post Variable Name: value Session Variable Name: value 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 #2” and “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 Query object. This step verifies the username is correct against Active Directory/LDAP, returns the Distinguished Name (DN) and stores the value in session.ad.last.attr.dn which will be used by the iRulesLX. Supply the following: Server: Select your LDAP or AD Server SearchFilter: sAMAccountName=%{session.logon.last.username} Select Add new entry Required Attributes: dn Under Branch Rules, delete the default and add a new one, by selecting Add Branch Rule. Update the Branch Rule settings: Name: AD Query Passed Expression (Advanced): expr { [mcget {session.ad.last.queryresult}] == 1 } Select “Finished”, then “Save” when your done. On the AD Query Passed branch after the AD Query object, add a Variable Assign object. This step assigns the Attribute Name to session.ldap.modify.attribute and the Attribute Value entered on the Logon Page to session.ldap.modify.value. Supply the following: Name: Assign LDAP Variables Add the Variable assignments by selecting Add new entry >> change. Variable Assign 1: Custom Variable (Unsecure): session.ldap.modify.attribute Session Variable: session.logon.last.attribute Variable Assign 2: Custom Variable (Secure): session.ldap.modify.value Session Variable: session.logon.last.value Select “Finished”, then “Save” when your done.Leave the “Branch Rules” as the default. On the fallback branch after the Assign LDAP Variables object, add a iRule object. Supply the following: Name: LDAP Modify ID: ldap_modify Under Branch Rules, add a new one, by selecting Add Branch Rule. Update the Branch Rule settings: Name: LDAP Modify Successful Expression (Advanced): expr { [mcget {session.ldap.modify.result}] == "0" } Select “Finished”, then “Save” when your done. The finished policy should look similar to this: As this is just a test policy I used to test my Node.JS and to show how the LDAP Modify works, I will not have a pool member attached to the virtual server, I have just left the branch endings as Deny. In a real word scenario, you would not allow a user to change any LDAP Attributes and Values. Apply the test Access Policy (ldap_modify_ap) to a HTTPS virtual server for testing and the iRuleLX under the Resources section. Step 3 - OK, let’s give this a test! To test, just open a browser to the HTTPS virtual server you created, and supply a Username, Attribute and Value to be modified. In my example, I want to change the Value of the carLicense attribute to test456. Prior to me hitting the Logon button, I did a ldapsearch from the command line of the BIG-IP: ldapsearch -x -h 10.1.30.101 -D "cn=LDAP Admin,cn=users,dc=f5,dc=demo" -b "dc=f5,dc=demo" -w 'Password123' '(sAMAccountName=test.user)' | grep carLicense carLicense: abc123 Post submission, I performed the same ldapsearch and the carLicense value has changed. It works! ldapsearch -x -h 10.1.30.101 -D "cn=LDAP Admin,cn=users,dc=f5,dc=demo" -b "dc=f5,dc=demo" -w 'Password123' '(sAMAccountName=test.user)' | grep carLicense carLicense: test456 Below is some basic debug log from the Node.JS: big-ip1 info sdmd[6415]: 018e0017:6: pid[24399] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] Creating LDAP Client. big-ip1 info sdmd[6415]: 018e0017:6: pid[24399] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] LDAP Bind Successful. big-ip1 info sdmd[6415]: 018e0017:6: pid[24399] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] LDAP Modify Successful. big-ip1 info sdmd[6415]: 018e0017:6: pid[24399] plugin[/Common/ldap_modify_plugin.ldap_modify_extension] Unbind Successful. Conclusion You can now modify LDAP attribute values, in safe and in supported manner with iRulesLX. Think of the possibilities! For an added bonus, you can add addtional branch rules to the iRule Event - LDAP Modify, as the Node.JS returns the following error codes: 1 - LDAP Bind Failed 2 - LDAP Modified Failed I would also recommend using Macros. Please note, this my own work and has not been formally tested by F5 Networks.2.8KViews0likes8CommentsAPM Cookbook: SAML IdP Chaining
As an APM subject mater expert at F5 I often find myself in situations where a customer or colleague needs an example of a particular configuration. While most of these requests are easily handled with a call or WebEx I'm a firm believer in sharing knowledge through documentation.. and I don't like getting calls at 3 AM. If you're like me you grew up with the O'Reilly Cookbook series which served as a great reference document for various development or server configuration tasks. My goal is to create a similar reference resource here on DevCentral for those one-off scenarios where a visual example may help your complete your task. For the first APM Cookbook series I'll discuss SAML IdP chaining. Overview Security Assertion Markup Language, more commonly known as SAML, is a popular federated authentication method that provides web based single sign-on. One of the key security advantages to SAML is the reduction in username/password combinations that a user has remember... or in my experience as a security engineer the number of passwords written on a post-it note stuck to their monitor. There are two major services in a SAML environment: IdP - Identity Provider SP - Service Provider The identity provider is the SAML service that authenticates the user and passes an assertion to then service providers proving the user's identity. F5's APM performs both IdP and SP services and allows customers to easily deploy federated authentication in their environment. In more complex scenarios you may run across a requirement where multiple SAML IdPs need to be chained together. This comes up from time to time when customers have contractors that utilize federated authentication for authorization to corporate resources. Example For our configuration we have the Globex Corporation that uses APM to authenticate uses to Office 365. Globex hire contractors from Acme Corp. who authenticate using the Acme Corp. ADFS environment. However, since Office 365 is configured to authenticate against the Globex APM we need to convert the Acme Corp. SAML assertion into a Globex SAML assertion, which is known as IdP chaining. The step ladder for this process is shown below: 1. User requests https://outlook.com/globex.com 2 - 3. Office 365 redirects user to idp.globex.com 3 - 4. idp.globex.com determines user is a contractor and redirect user to sts.acme.com 5 - 8. User authenticates using Acme credentials and is then redirect back to idp.globex.com 9. idp.globex.com consumes the Acme SAML assertion and creates a Globex SAML assertion 10. User is redirected back to Office 365 11 - 12. Office 365 consumes the Globex SAML assertion and displays the user's mail Configuration To configure your APM SAML IdP to accept incoming assertion from sts.acme.com we need to create an external SP connector. Under the Access Policy -> SAML -> BIG-IP as SP configuration section: 1. Create a new SAML SP Service 2. Export the SP metadata and configure sts.acme.com accordingly (follow your IdP vendor's documentation) 3. Click the External IdP Connectors menu at the top 4. Click the dropdown arrow on the create button and choose From Metadata (import the metadata from sts.acme.com) 5. Bind the Local SP service to the external IdP connector Now that idp.globex.com and sts.acme.com are configured to trust one another we need to configure the APM IdP to consume the sts.acme.com SAML assertion. The IdP's Visual Policy Editor should look similar to the image below: 1. The Decision Box asks the user what company they're with. This is a simple example but more elaborate home realm discovery techniques can be used. 2. The SAML Auth box is configured to consume the sts.acme.com assertion 3. Since we no longer have a login form on the IdP we need to set a few APM session variables: session.logon.last.username = Session Variable session.saml.last.identity session.logon.last.logonname = Session Variable session.saml.last.identity 4. Create an Advanced Resource Assign that matches your existing IdP Advance Resource Assign. Conclusion This particular post was a little longwinded due to the steps required but overall is a fairly simple configuration. So the next time someone asks if your F5 can do IdP chaining you can confidently reply "Yes and I know how to do that".3.9KViews1like6CommentsAPM Cookbook: On-Demand VPN for iOS Devices
Overview I’ve been using On-Demand VPN for a number of years as it’s a secure and easy way to access applications while on the road with the added benefit of conserving device battery life when idle. On-Demand VPN is a simple concept. You define a domain or list of domains in the “Connect On Demand” settings. The Edge Client looks at the DNS requests made by the App (e.g Safari) and when the FQDN matches one of the defined domains, it fires up the VPN in the background and authenticates using a X.509 Client Certificate. On-Demand VPN requires the installation of a X.509 Client Certificate on the iOS device, which can be provisioned using a number of tools. This article will not go into these tools, but I will give you a few ideas how to get the X.509 certificate on the device. Some of the methods I have used in the past: Mobile Device Management (MDM) configured to use Simple Certificate Enrollment Protocol (SCEP) iPhone Configuration Utility (iPCU) Email the .p12 or .pfx file When using X.509 Client Certificates, I highly recommend adding the User Principal Name (UPN), e.g. test.user@f5.demo to the Subject Alternative Name (SAN) extension. This allows APM to identify the user of the device and can be used for: Single Sign On (SSO) using Kerberos or SAML Active Directory / LDAP queries to determine group membership etc. Logging and Reporting In a large scale environment I recommend using MDM, SCEP, Certificate Authority (CA) and an OCSP Responder to verify the revocation status. This article will cover how to configure the APM to authenticate the user and establish the VPN tunnel automatically using the On-Demand feature. My Example In my working example, I have a pre-configured CA called “CA1” joined to the domain “f5.demo”. The iOS device has the client certificate (Test User) installed with a UPN “test.user@f5.demo” in the SAN extension. This certificate has been issued by CA1 and is trusted by the iOS device. I have also installed the F5 Edge Client via the App Store. Step 1 – Import the SSL Certificates 1.1 Import the SSL Certificate/Key that will be used for SSL VPN connection System >> File Management >> SSL Certificate List Please follow SOL14620: Managing SSL certificates for BIG-IP systems My configuration looks like this: 1.2 Import Certificate Authority Certificate that is signing the Client Certificates Repeat Step 1.1 but this time you need to import the CA Certificate (without the Key) that is signing the User Certificates. It should look something similar to this: Step 2 – Create a Client SSL Profile Local Traffic >> Profiles >> SSL >> Client >> “Create” Supply the following: Name: vpn.f5.demo_ssl_profile Parent Profile: clientssl Certificate: vpn.f5.demo Key: vpn.f5.demo Trusted Certificate Authorities: CA1 Select “Finished” to save. Step 3 – Create the APM Configuration In this example I will use an On-Demand Certificate Authentication to validate the client certificate and some TCL to extract the UPN from the SAN extension. After successful authentication, a VPN tunnel is then assigned and established. You can expand on this configuration by adding an OCSP Responder to check if the certificate has been revoked. Simply modify the Names, IP addresses, DNS etc. to suit your environment. 3.1 Create a Webtop Access Policy >> Webtop >> Webtop List >> “Create” Supply the following: Name: vpn_webtop Type: Full Select “Finished” to save. 3.2 Create a Connectivity Profile Access Policy >> Secure Connectivity >> Connectivity Profile >> “Create” Supply the following: Name: vpn_cp Parent Profile: connectivity Select “Finished” to save. 3.3 Create a Lease Pool Access Policy >> Network Access >> Lease Pools >> IPv4 Lease Pools >> “Create” Supply the following: Name: vpn_lease_pool_10.123.123.0 Start IP Adddress: 10.123.123.1 End IP Address: 10.123.123.254 Select “Add” Select “Finished” to save. 3.4 Create a Network Access Access Policy >> Network Access >> Network Access List >> “Create” Supply the following: Name: vpn_na Caption: SSL VPN Select “Update” to save. Under “Network Settings”. Supply the following: IPV4 Lease Pool: vpn_lease_pool_10.123.123.0 Traffic Options: Use split tunneling for traffic (select appropriate option for your environment) IP Address: 10.0.0.0 Mask: 255.0.0.0 Select “Add” Select “Update” to save. Under “DNS/Hosts” Supply the following: IPV4 Primary Name Server: 10.10.20.1 DNS Default Domain Suffix: f5.demo Select “Update” to save. 3.5 Create an Access Policy We can now bring it all together using the Visual Policy Editor (VPE). Access Policy >> Access Profiles >> Access Profile List >> “Create” Supply the following: Name: vpn.f5.demo_ap Languages: English (en) Use the default settings for all other settings. Select “Finished” to save. 3.6 Edit the Access Policy in the VPE Access Policy >> Access Profiles >> Access Profile List >> “Edit” (vpn.f5.demo_ap) On the fallback branch after the Start object, add anOn-Demand Cert Auth object. Leave the defaults and “Save”. On the Successful branch after the On-Demand Cert Auth object, add a Variable Assign object. Supply the following: Name: Extract UPN Add the following variable assignments by selecting “Add new entry” >> “change”. Variable Assignment 1: Custom Variable (Unsecure): session.logon.last.domain Custom Expression: set upn [mcget {session.logon.last.upn}]; if {[string first "@" $upn] >= 0} { return [string range $upn [expr { [string first "@" $upn] + 1 } ] end ]; } else { return ""; } Variable Assignment 2: Custom Variable (Unsecure): session.logon.last.username Custom Expression: set upn [mcget {session.logon.last.upn}]; if {[string first "@" $upn] >= 0} { return [string range $upn 0 [expr { [string first "@" $upn] - 1 } ] ]; } else { return $upn; } Variable Assignment 3: Custom Variable (Unsecure): session.logon.last.upn Custom Expression: set e_fields [split [mcget {session.ssl.cert.x509extension}] "\n"]; foreach qq $e_fields { if {[string first "othername:UPN" $qq] >= 0} { return [string range $qq [expr { [string first "<" $qq] + 1 } ] [expr { [string first ">" $qq] - 1 } ] ]; } } return ""; Select “Finished”, then “Save” when your done. The VPE should look similar to this: On the fallback branch after the Extract UPN object, add a Full Resource Assign object. Supply the following: Name: SSL VPN Select “Add new entry” >> “Add/Delete” Add the Webtop: /Common/vpn_webtop (from step 3.1) Add the Network Access: /Common/vpn_na (from step 3.4) Select “Update” then “Save”. On the fallback branch after the SSL VPN object, change Deny ending to Allow. The finished policy should look similar to this: Step 4 – Create the Virtual Server for SSL VPN In this step, you need to create a HTTPS Virtual Server (VS) for the Edge Client to establish the connection. I also recommend creating a DTLS VS in addition to the HTTPS VS. This configuration is well documented in the APM Manual and I will not go through the DTLS VS creation. 4.1 Create the HTTP VS Local Traffic >> Virtual Servers >> Virtual Server List >> “Create” Supply the following: Name: vpn.f5.demo_https_vs Destination: 10.10.20.100 Service Port: HTTPS HTTP Profile: http SSL Profile (Client): vpn.f5.demo_ssl_profile (from step 2) SNAT Pool: Auto Map Access Profile: vpn.f5.demo_ap (from step 3.5) Connectivity Profile: vpn_cp (from step 3.2) Select “Finished" to save. Step 5 – Configure the Edge Client on the iOS device and Test 5.1 Create a new Edge Client Configuration Open the Edge Client and select “Settings” then “Add Configuration…” Supply the following: Description: On Demand VPN Server: vpn.f5.demo Use Certificate: On Certificate: Test User Connect On Demand: On Select Domain List Add Domain… In my example: demo.vpn Don’t forget to Save 5.2 Test Open Safari and access a URL that will cause the VPN trigger. In my example http://intranet.demo.vpn, a Sharepoint site. The VPN will start up when it sees the DNS query for intranet.demo.vpn and the data will flow across the VPN as per the Network Access settings. Conclusion On-Demand VPN with APM is a simple way to enable the workforce by providing a secure and easy to use remote access solution while out of the office.1.5KViews0likes14CommentsAPM 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.7KViews0likes6Comments