CUGC Blogs

Extending NLS with Dynamic Beacons

By Eltjo van Gulik posted 01-02-2020 06:47 AM

  
A short while back, in November, Citrix premiered the Network Location Service (NLS) for the Citrix Gateway Service (CGS) functionality of Citrix Cloud.

Without the NLS, providing external connectivity via the Citrix Gateway Service is an all or nothing solution:

NLS External Connectivity

External Connectivity

We can either enable external connectivity for all users or not at all.

And for this reason, the NLS feature for CGS offers a way to differentiate between internal and external users. This leads to the possibility to negate the all or nothing approach and offers some more flexibility with CGS.

Although the NLS functionality is a welcome one, it was met with skepticism in the field:

NLS_skepticism.png

On the contrary to Citrix Storefront, for instance, the NLS feature does not use beacons, and, at least in the tech preview, you need to manually define which locations are internal based on their external IP address (more on that later).

Because this involves manual work, we decided to add some rudimentary beacon support. Please note that this is just a Proof of Concept and please read the disclaimer with the caveats at the end of the blogpost.

Also, before you start, please review the Citrix Network Location Service documentation here: https://docs.citrix.com/en-us/citrix-workspace/workspace-network-location.html

Please also note, that at the time of this writing, the NLS feature is still in Technical Preview and should only be used in test environments!

Now that is out of the way, let’s see if we can extend the NLS functionality with the mentioned beacon support.

For this to work, the PowerShell script needs to be run on the connecting client, before the client connects to the VDA.

In order to determine if the connecting user is internal or external, we first have to define an internal beacon. This beacon should ideally be a highly available host and should only be accessible via the internal network. We will use this beacon to determine if the client has an internal connection to the network, and should, therefore, be routed directly to the VDA without using the CGS.

Again, the beacon must only be accessible via the local network and should respond to ICMP echo pings.

The beacon is defined as $internalBeacon. We could, of course, have hardcoded the beacon address, or we could even read the address (or multiple addresses for that matter) from an XML, CSV or JSON file for instance.

$internalBeacon="cloudconnector.internal.local"

We can test if we can reach the internal beacon with the Test-NetConnection cmdlet:

if((Test-NetConnection $internalBeacon).PingSucceeded -eq $true){

    Write-Debug "Internal beacon reachable"
    $internalConnectivity=$true
}


When we established that we are internal and set $internalConnectivity=$true, based on the beacons reachability, next we fetch the external (or WAN) IP address. This is because Citrix uses the external IP address to define the network locations (or sites in the script).

try{

    #retrieve the external IP address

    $externalIP=(Invoke-WebRequest -uri "http://ifconfig.me/ip").Content;

    Write-Debug $externalIP

    #retrieve additional information

    $externalIPInfo=Invoke-RestMethod -Uri ('http://ipinfo.io/'+(Invoke-WebRequest -uri "http://ifconfig.me/ip").Content)

    Write-Debug $externalIPInfo
    }

Ok, so far so good. Now that we have an internal beacon and the external IP address, we can check if we can reach the aforementioned beacon. If we can reach the beacon, we need to find out if there already is an existing site that the external IP address belongs to. For this we utilize the function IS-InSubnet() together with the Get-NLSSite cmdlet to cycle through all the configured NLS sites and check if the external IP address matches any of the subnets configured in any of the NLS Sites:

If($internalConnectivity) {
    # Get all NLS Sites and check if there is an existing entry for this IP
    $allNLSSites = Get-NLSSite

    $nlsExists = $false

    # Iterate through all sites and check if current IP is included in existing NLS Sites
    foreach($nlsSite in $allNLSSites) {
        $ipv4Ranges = $nlsSite.ipv4Ranges

           foreach($ipv4Range in $ipv4Ranges) {
                if((IS-InSubnet -ipaddress $externalIP -Cidr $ipv4Range)){
                    $nlsExists = $true
               }
       }
}

If there is no location yet for the current location of the client, we can add a new NLS location site based on the external IP address, location and time zone.

if(-not $nlsExists){
    New-NLSSite -name "$($externalIP)" -tags @("CustomNLS") -timezone "$($externalIPInfo.timezone)" -ipv4Ranges @("$($externalIP)/32") -longitude $externalIPInfo.loc.Split(",")[1] -latitude $externalIPInfo.loc.Split(",")[0]
  }

Please note that for the Tech Preview, the location and the time zone are not used and are hardcoded in the nls.psm1 script that Citrix provided for the NLS Technical Preview.

To accommodate the fact that we might want to have statically defined NLS sites as well as dynamically defined sites, we can use the $globalNLSSites array. In this array, we can add NLS sites that are static (or predefined if you will), and should for that reason be left alone.

In this case, we only have one Statically defined NLS site called HeadOffice.

$globalNLSSites = @("HeadOffice")

In order to make the NLS sites dynamic, we also need to be able to remove the NLS site if the client in the specified location is no longer able to reach the internal beacon. To remove the location from the NLS Site configuration we can use the Remove-NLSSite cmdlet:

# If NOT InternalConnectivity check if NLS Site for current IP exists and delete site if not in pre-defined NLS Site list
    if(-not $internalConnectivity) {
    # Get all NLS Sites (excluding global NLS Sites) and check if there is an existing entry for this IP
    $allNLSSites = Get-NLSSite | Where-Object {$globalNLSSites -notcontains $_.name}

    # Iterate through all sites and check if current IP is included in existing NLS Sites
    foreach($nlsSite in $allNLSSites) {
         $ipv4Ranges = $nlsSite.ipv4Ranges

            foreach($ipv4Range in $ipv4Ranges) {
            # If current IP is included in existing NLS Site, remove NLS Site
                if((IS-InSubnet -ipaddress $externalIP -Cidr $ipv4Range)){
                    (Get-NLSSite | Where-Object { $_.name -eq $nlsSite.name }) | Remove-NLSSite
                }
            }
    }
}

Time to put it all together. Below is a link to the script in its entirety. Make sure the nls.psm1 PowerShell module that Citrix supplies with the NLS Technical Preview is located in the same folder as this script:

https://github.com/eltjovangulik/PowerShell/blob/master/dynamicnls/dynamicnls.ps1

This script uses the function IS-InSubnet() by Srinivasa Rao Tumarada and I would like to thank my colleague Daan Bennekom for re-coding my initial mess into something readable and workable.

Disclaimer: This script is not perfect; instead it is just a rudimentary solution, and should only be regarded as it is: a proof of concept. There are a number of caveats with this solution.

For instance, there is no fallback when to CGS a client is presumed internal and for some reason, the VDA is not reachable via the internal network.
Secondly, the script will fail if a client had a DirectAccess connection at the time the beacon check was made. When, at the moment of a reconnect, no DA connection was available, the launches to the VDA will fail.

The other way around is less problematic. If the beacon, for some reason, wasn't reachable when the check was made, the connection will flow via the CGS instead of directly via the internal network.

In addition, if the location that the client belongs to has multiple endpoints, where not all endpoints have internal connectivity to the beacon, things will go bad.
And lastly, it is not (I repeat NOT) recommended having the Secure Client details in a script that is run on an endpoint, so that these credentials are up for grabs.

Thank you very much for reading. If you have any comments or questions, please reach out to me on Twitter!

Eltjo van Gulik
@eltjovg