Send OTP via sendgrid(email) API
Problem this snippet solves: This is an example of how to send an OTP generated by the Big-IP via email using the sendgrid api. Preparations Register a free Sendgrid account: https://sendgrid.com/ Generate an API key: https://app.sendgrid.com/settings/api_keys Create an iruleLX workspace or just import it from my github (pay attention to naming conventions): https://github.com/alexnimo/F5/blob/master/iRules_LX/sendgrid/sendgrid_ilx.tgz Configure AD resource Preface Most of the code and examples in this tutorial are just cut and paste from different places with slight modifications. I've decided to try it out after trying the twilio tutorial by Niels van Sluis: https://devcentral.f5.com/codeshare/send-an-one-time-password-otp-via-the-twilio-sms-gateway-1132. Afterward, I wanted to better understand the irule LX architecture and usage, so I watched this great tutorial by Artiom: https://www.youtube.com/watch?v=7yRP2fPCxIs&index=1&list=PLRL802iBI7n-6y8_eNawyeUZ8E0YwWro8 And this is my first attempt to write a tutorial... Configurations In case you're configuring from scratch, you need to install sendgrid plugin, otherwise its already included in the tgz file. To install the plugin: cd /var/ilx/workspaces/Common/ilx_sendgrid/extensions/sendgrid/node_modules npm install --save @sendgrid/mail The index.js includes 2 options to send an email. If you want to use the first option. Simple email, then uncomment before //Use this method to send regular email and comment before //Use this method to send email with dynamic template There are no additional configurations to be done. If you want to send an email with a dynamic template(customized), the second(default) option then first you need to create the template via the sendgrid UI: https://sendgrid.com/dynamic_templates You can make different customizations to your email. I've just used an example template which can be found on their github: https://github.com/sendgrid/email-templates/blob/master/paste-templates/password-reset.html and played with it just to understand how it works. Here is an ugly modified template with the variables that will be presented in the email: https://github.com/alexnimo/F5/blob/master/iRules_LX/sendgrid/otp_template.html More information can be found here: https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/ https://sendgrid.com/docs/ui/sending-email/using-handlebars/ I've generated and used the code from the documentation: https://dynamic-templates.api-docs.io/3.0/mail-send-with-dynamic-transactional-templates/v3-mail-send If the default option is used(dynamic templates) then you must include the template id in the variable template_id Hope I haven't forgot anything.... How to use this snippet: Code variables: var api_key = The api key generated in sendgrid Everything else can be customized. template_id = Template ID created in sendgrid. Code : 'use strict'; // Import the f5-nodejs module. var f5 = require('f5-nodejs'); // Import the sendgrid module. const sgMail = require('@sendgrid/mail'); //Set API key var api_key = "PASTE API KEY HERE" sgMail.setApiKey(api_key); // Create a new rpc server for listening to TCL iRule calls. var ilx = new f5.ILXServer(); //Construct email ilx.addMethod('ilx_sendmail', function(tclArgs, funcResp) { console.log("Method Invoked, Args:", tclArgs.params()); const userEmail = tclArgs.params()[2]; var emailText = 'Dear' + tclArgs.params()[0] + ' Your OTP is: ' + tclArgs.params()[1]; /* //Use this method to send regular email const msg = { to: userEmail, from: 'OTP_Admin@otptest.com', subject: 'Please See your OTP', text: emailText, // html: 'and easy to do anywhere, even with Node.js', }; //Send email sgMail.send(msg, (error, result) => { if (error) { console.log("Error sending email, the issue is:" + error) funcResp.reply(error); } else { return result funcResp.reply("email sent"); } }); */ //Use this method to send email with dynamic template var http = require("https"); var options = { "method": "POST", "hostname": "api.sendgrid.com", "port": null, "path": "/v3/mail/send", "headers": { "authorization": "Bearer " + api_key, "content-type": "application/json" } }; var req = http.request(options, function (res) { var chunks = []; res.on("data", function (chunk) { chunks.push(chunk); }); res.on("end", function () { var body = Buffer.concat(chunks); console.log(body.toString()); }); }); req.write(JSON.stringify({ personalizations: [ { to: [ { email: userEmail , name: tclArgs.params()[0] } ], dynamic_template_data: { user: tclArgs.params()[0], otp: tclArgs.params()[1], partysize: 4, english: true } } ], from: { email: 'OTP_Admin@otptest.com', name: 'OTP Admin' }, reply_to: { email: 'OTP_Admin@otptest.com', name: 'OTP Admin' }, template_id: 'PASTE TEMPLATE ID HERE' })); req.end(); funcResp.reply("email sent"); }); ilx.listen(); Tested this on version: 13.02.8KViews0likes1CommentComplete MFA solution with GA stored in Active Directory
Problem this snippet solves: All modern business applications require Multi-Factor Authentication (MFA) to be used for remote access by employees. There are many vendors on market selling enterprise MFA solutions that may be utilised with F5 BIG-IP Access Policy Manager (APM). Those solutions are complex and allow customers to create flexible policies which allow them to decide when and whom will be authorised to access protected applications. But what about those customers which have no needs for using complex enterprise solutions or does not have adequate budget for such spendings? How to use this snippet: For those customers I would like to present my One-Time Password (OTP) application which requires BIG-IP LTM/APM/iRulesLX. Shared secret value is stored in Active Directory and QR code is generated in user's browser. All you need after implementing this application on your BIG-IP is. to ask your users to get any OTP-compatible mobile application, like Google Authenticator or Microsoft Authenticator Please see https://github.com/akhmarov/f5_otp/ for instructions UPDATE 1: New version now support APM 15.1+ Modern Customization UPDATE 2: Added trusted device support UPDATE 3: Added multi-tenancy support Tested this on version: 15.11.4KViews1like8CommentsBlock Log4j with use of IOCs
Problem this snippet solves: iRule that helps to mitigate the Log4j vulnerability with use of public available IOCs. Currently the following IOCs can be used: cert-agid.gov.it (Contains scan IP's): https://cert-agid.gov.it/download/log4shell-iocs.txt NLD Police: https://thanksforallthefish.nl/log4j_blocklist.txt These IOCs combined will result in about 25191 IP addresses being blocked. The plan is to add some more IOCs soon. Last update: 27 December 2021 How to use this snippet: This solution makes use of iRulesLX. So first of all you need to provision iRulesLX on your BIG-IP. Then proceed to add the LX Workspace, iRule and Extension. Create LX Workpace: log4j_ioc Add iRule: log4j_ioc_irule Add Extension: log4j_ioc_extension (index.js) Add LX Plugin: log4j_ioc_plugin (from Workspace log4j_ioc) Install the required NodeJS modules. Use SSH to login to your BIG-IP and install the https and lokijs modules. # cd /var/ilx/workspaces/Common/log4j_ioc/extensions/log4j_ioc_extension # nmp install https lokijs --save Tested this on version: 15.1717Views3likes0CommentsUse iRules LX to write to the file system
Problem this snippet solves: Whist you can log to /var/log/ltm from an iRule, the TCL implementation on TMOS has its file reading and writing capabilities disabled. However it is still possible to write to a non-logging file to the file system using iRules LX, albeit, to the iRules LX workspace. A second script would be required to move the file to where you require it. This demo writes a file test.txt containing a client IP address to the iRules LX workspace How to use this snippet: First you need an iRule that passes the client IP address into iRules LX (you'll want to put the LX call in a catch for a prod environment) when CLIENT_ACCEPTED { set rpcHandle [ILX::init "fs-pl" "fs-ex"] set result [ILX::call $rpcHandle "writeFile" "[IP::client_addr]"] log local0. "$result" } Then a simple iRules LX script that will write out a test file with the value of the IP you passed in. It will write out the file to the extension folder which will vary.. it will also change slightly each time it executes For example, my file was located here: /var/sdm/plugin_store/plugins/:Common:fs-pl_83750_4/extensions/fs-ex/test.txt The next time, it was located here: /var/sdm/plugin_store/plugins/:Common:fs-pl_83750_5/extensions/fs-ex/test.txt Code : var f5 = require('f5-nodejs'); var ilx = new f5.ILXServer(); var fs = require('fs'); function writeFile(req, res) { var ip = req.params()[0]; fs.writeFile('test.txt', ip, function(err) { if (err) { res.reply(err); } else { res.reply('file written successfully'); } }); } ilx.addMethod('writeFile', writeFile); ilx.listen(); Tested this on version: No Version Found599Views2likes0CommentsiRule LX (Node.js) development environment
Problem this snippet solves: Write iRulesLX (Node.js) on your own system and save them into some source control repo (git). How to use this snippet: Download VSCode (Visual Studio Code) and install Download Node.js v6.9.1 (used by TMOS 13.1.0 and above) Create a new folder for your project and run the following npm commands to setup the environment You will get a file names package.json will all your project details in it. Code : npm init npm install --save-dev typescript npm install --save-dev jest npm install --save-dev ts-jest npm install --save-dev nock npm install --save-dev debug npm install --save-dev @types/jest npm install --save-dev @types/node Tested this on version: 13.0502Views2likes0CommentsDynamic DNS registration of APM SSL VPN Client to AWS route53
Problem this snippet solves: This iRules LX code dynamically registers a clients obtained IP from APM when they initiate a VPN tunnel to an AWS route 53 domain. How to use this snippet: Import attached tgz(zip will need to be re-packaged to tgz) to a new iRules LX workspace, create an iRule LX plugin, and attached the editRecord iRule to your virtual server. You can also manually create a new workspace and add the iRules LX iRule and extension with the code below. You will also have to install the aws-sdk module using npm. Eric Flores has a good write-up on how to manage npm, https://devcentral.f5.com/articles/getting-started-with-irules-lx-part-4-npm-best-practices-20426. iRule Code: when CLIENT_ACCEPTED { ACCESS::restrict_irule_events disable } when HTTP_REQUEST { if { [ACCESS::policy result] eq "allow" && [HTTP::uri] starts_with "/myvpn?sess="} { after 5000 { set apmSessionId [ACCESS::session data get session.user.sessionid] set apmProfileName [ACCESS::session data get session.access.profile] set apmPart [ACCESS::session data get session.partition_id] set DNSName "<your zone with trailing dot>" set action "UPSERT" set name "<your hostname>" if {not ($name ends_with ".[string trimright ${DNSName} {.}]")}{ log -noname local1.err "${apmProfileName}:${apmPart}:${apmSessionId}: proper hostname was not provided ($name): 01490000: A Record will not be updated for this session." event disable all return } set TTL 1200 set ip [ACCESS::session data get "session.assigned.clientip"] if {[catch {IP::addr $ip mask 255.255.255.255}]}{ log -noname local1.err "${apmProfileName}:${apmPart}:${apmSessionId}: proper IP addressed was not assigned ($ip): 01490000: A Record will not be updated for this session." event disable all return } set RPC_HANDLE [ILX::init route53] if {[catch {set rpc_response [ILX::call $RPC_HANDLE route53_nodejs $DNSName $action $name $TTL $ip]}]}{ set rpc_response "Check LTM log for execution errors from sdmd. A Record may not have been updated for this session." } log -noname local1. "${apmProfileName}:${apmPart}:${apmSessionId}: Client connected: 01490000: Params sent = $DNSName $action $name $TTL $ip\r\nRPC response = $rpc_response" } } } when ACCESS_SESSION_CLOSED { set apmSessionId [ACCESS::session data get session.user.sessionid] set apmProfileName [ACCESS::session data get session.access.profile] set apmPart [ACCESS::session data get session.partition_id] set DNSName "<your zone with trailing dot>" set action "DELETE" set name "<your hostname>" if {not ($name ends_with ".[string trimright ${DNSName} {.}]")}{ log -noname local1.err "${apmProfileName}:${apmPart}:${apmSessionId}: proper hostname was not provided ($name): 01490000: A Record will not be removed for this session." event disable all return } set TTL 1200 set ip [ACCESS::session data get "session.assigned.clientip"] if {[catch {IP::addr $ip mask 255.255.255.255}]}{ log -noname local1.err "${apmProfileName}:${apmPart}:${apmSessionId}: proper IP addressed was not assigned ($ip): 01490000: A Record will not be removed for this session." event disable all return } set RPC_HANDLE [ILX::init route53] if {[catch {set rpc_response [ILX::call $RPC_HANDLE route53_nodejs $DNSName $action $name $TTL $ip]}]}{ set rpc_response "Check LTM log for execution errors from sdmd. A Record may not have been removed for this session." } log -noname local1. "${apmProfileName}:${apmPart}:${apmSessionId}: Client disconnected: 01490000: Params sent = $DNSName $action $name $TTL $ip\r\nRPC response = $rpc_response" } node.js code: var AWS = require('aws-sdk'); var f5 = require('f5-nodejs'); /* AWS Config */ //AWS.config.update({accessKeyId: "<your access key>", secretAccessKey: "<your secret>"}); /* get the Route53 library */ var route53 = new AWS.Route53(); /* 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(); /* Add a method and expect DNSName, action, name, TTL, and ip parameters and reply with response */ ilx.addMethod('route53_nodejs', function(req, response) { var DNSName = req.params()[0]; var params = { DNSName: DNSName }; var action = req.params()[1]; var name = req.params()[2]; var TTL = req.params()[3]; var ip = req.params()[4]; route53.listHostedZonesByName(params, function(err,data) { if (err) { //console.log(err, err.stack); response.reply(err.toString()); } else if (data.HostedZones[0].Name !== params.DNSName) { response.reply(params.DNSName + " is not a zone defined in Route53"); } else { var zoneId = data.HostedZones[0].Id; var recParams = { "HostedZoneId": zoneId, "ChangeBatch": { "Changes": [ { "Action": action, "ResourceRecordSet": { "Name": name, "Type": "A", "TTL": TTL, "ResourceRecords": [ { "Value": ip } ] } } ] } }; /* edit records using aws sdk */ route53.changeResourceRecordSets(recParams, function(err,data) { if (err) {response.reply(err.toString());} else if (data.ChangeInfo.Status === "PENDING") {response.reply("Record is being updated");} else {response.reply(data);} }); } }); }); This is also on my github, https://github.com/bepsoccer/iRulesLX Code : 75409 Tested this on version: 12.1480Views0likes0Comments