Dynamic 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.1
Updated Jun 06, 2023
Version 2.0

Was this article helpful?

No CommentsBe the first to comment