Bryce Jenkins

Systems Administrator

Automator

DIY Enthusiast

Human

Bryce Jenkins

Systems Administrator

Automator

DIY Enthusiast

Human

Blog Post

Deploying IKEv2 VPN to Windows with PowerShell

April 21, 2020 Uncategorized
Deploying IKEv2 VPN to Windows with PowerShell

The History

As a result of the COVID-19 Global Pandemic and resultant mass shift to remote workers being the standard instead of the exception, many organizations rushed to deploy VPN solutions to their users devices. My company was no different, and we helped rapidly convert a number of our clients to a remote workforce quickly.

After looking back at the IKEv2 deployment we did, I was able to do some research and analyze some other VPN deployment scripts to put together a script that can be leveraged in Datto RMM (my works chosen RMM platform).

We have standardized to WatchGuard Firewalls at work, so this is all designed around their implementation of IKEv2. This will not detail configuring Datto or WatchGuard at this time.

The Script

Here in it’s entirety is the script. Below that I will dive into how each piece fits into the bigger picture and thoughts on how this script can be improved.

$client = $env:ClientName
$vpnName = "Johnsons Farm Products","Big Bubba's BBQ Sauce"
$vpnCert = "path\to\johnsonsVPN.crt","path\to\bubbaVPN.crt"
$vpnSuffix = "corp.jfp.com","bigbubba.local"
$vpnTarget = "x.x.x.x","y.y.y.y"

switch ($client) {
    "Client 1 Friendly Name" {
        [string]$vpnName = $vpnName[0]
        [string]$vpnCert = $vpnCert[0]
        [string]$vpnSuffix = $vpnSuffix[0]
        [string]$vpnTarget = $vpnTarget[0]
    }
    "Client 2 Friendly Name" {
        [string]$vpnName = $vpnName[1]
        [string]$vpnCert = $vpnCert[1]
        [string]$vpnSuffix = $vpnSuffix[1]
        [string]$vpnTarget = $vpnTarget[1]
    }
}

Import-Certificate -FilePath $vpnCert -CertStoreLocation Cert:\LocalMachine\Root
Write-Output "Certificate Imported - $vpnCert"
Add-VpnConnection -Name $vpnName -ServerAddress $vpnTarget -AllUserConnection -TunnelType 'IKEv2' -EncryptionLevel 'Required' -AuthenticationMethod Eap -RememberCredential -DnsSuffix $vpnSuffix
Write-Output "Added VPN"
Set-VpnConnectionIPsecConfiguration -ConnectionName $vpnName -AuthenticationTransformConstants 'SHA256128' -CipherTransformConstants 'AES256' -DHGroup 'Group14' -EncryptionMethod 'AES256' -IntegrityCheckMethod 'SHA256' -PfsGroup 'None' -Force
Write-Output "Configured VPN"

The Breakdown

The overriding design concern of this script was to allow myself or my colleagues to easily, clearly, and confidently deploy an IKEv2 Tunnel across any of our clients.

To ensure this was met, I needed to let the script and RMM tool do all the heavy lifting so that even the most novice Tier 1 could deploy the VPN configuration to a client endpoint.

Datto RMM operates on the concept of Components for their automation. At the end of the day a Component is ultimately just a script in a supported language. It also allows you to define input variables in a number of formats. My current favorites are to use Boolean and Selection, as these allow you to provide a discrete option set to your technicians while giving you full control of how the script handles each option.

Input Variables in Datto RMM are accessed by your script using the environment variable flag: $env

So our variable declarations are where we configure our various options. For those of you new to coding, keeping data in the proper place is a very important foundational piece of the puzzle. Any program or script with bad data management practices is usually asking for a big headache.

$client = $env:ClientName
$vpnName = "Johnsons Farm Products","Big Bubba's BBQ Sauce"
$vpnCert = "path\to\johnsonsVPN.crt","path\to\bubbaVPN.crt"
$vpnSuffix = "corp.jfp.com","bigbubba.local"
$vpnTarget = "x.x.x.x","y.y.y.y"

The $client variable allows us to collect our Datto RMM input Variable and will be used to control what data is used to configure the VPN tunnel from the next 4 variables. Speaking of them, let’s take a peek at these arrays.

Each array contains the appropriate information the VPN configuration needs that differs from client to client. $vpnName contains the Friendly Name of the company, like my favorite made up companies, Johnsons Farm Products and Big Bubba’s BBQ Sauce. Then we reference the path to the certificate file we got from our WatchGuard with the $vpnCert array. In Datto you can add files to a component that are loaded into the environment when it is run, making it easy to access static assets that your scripts may require.

We ran into a number of DNS issues during our rollout and the fix was to specify the proper DNS Suffix for the VPN connection, so I use the $vpnSuffix array to store the FQDN’s of the appropriate client domains. And lastly the $vpnTarget array contains the External IP’s of the VPN Endpoints.

Next we can look at our switch statement to see how we handle this data in preparation to running the generic cmdlets to configure the VPN.

switch ($client) {
    "Client 1 Friendly Name" {
        [string]$vpnName = $vpnName[0]
        [string]$vpnCert = $vpnCert[0]
        [string]$vpnSuffix = $vpnSuffix[0]
        [string]$vpnTarget = $vpnTarget[0]
    }
    "Client 2 Friendly Name" {
        [string]$vpnName = $vpnName[1]
        [string]$vpnCert = $vpnCert[1]
        [string]$vpnSuffix = $vpnSuffix[1]
        [string]$vpnTarget = $vpnTarget[1]
    }
}

When the script runs, it compares the value of $client against each of the cases in the switch(){} block.

You might be wondering why we duplicate some effort by having $client be a distinct variable from $vpnName. This is for brevity on the RMM side. It allows me to utilize internal shorthand for clients while maintaining proper client naming for their VPN configuration.

Once the switch determines which code block to execute, each code block redefines the variables that were the Arrays to strings equal to the proper value from themselves. Casting the variables like: [string]$vpnName ensures that whatever we set after the = is of the String Type, which is what our cmdlets parameters will require. This shows how keeping your data and variable consistently organized will allow for scalability and easy understanding of a script. By assigning the variables to $vpnName[0] or [1] we select the first or second (respectively) elements in each array.

So $vpnName[0] would evaluate to Johnsons Farm Products with a type of [string]. If you want to easily understand this behavior yourself, use this run the following through PowerShell line by line.

$arrayVariable = "Item Number 1",548
[string]$item2 = $arrayVariable[1]
Write-Host $arrayVariable[1]
Write-Host $item2
$arrayVariable[1].GetType()
$item2.GetType()

This shows how you access arrays from the Zero Index, which means the first item in the object is referenced with the number 0. It also demonstrates casting types, as you can see that arrayVariable[1] is of the Integer type, where $item2 is of the String type.

And lastly in the script, we have the actual actions to be performed.

Import-Certificate -FilePath $vpnCert -CertStoreLocation Cert:\LocalMachine\Root
Write-Output "Certificate Imported - $vpnCert"
Add-VpnConnection -Name $vpnName -ServerAddress $vpnTarget -AllUserConnection -TunnelType 'IKEv2' -EncryptionLevel 'Required' -AuthenticationMethod Eap -RememberCredential -DnsSuffix $vpnSuffix
Write-Output "Added VPN"
Set-VpnConnectionIPsecConfiguration -ConnectionName $vpnName -AuthenticationTransformConstants 'SHA256128' -CipherTransformConstants 'AES256' -DHGroup 'Group14' -EncryptionMethod 'AES256' -IntegrityCheckMethod 'SHA256' -PfsGroup 'None' -Force
Write-Output "Configured VPN"

Here we use the Import-Certificate cmdlet with the $vpnCert variable to import the VPN Certificate into the Local Machine Trusted Root. We then use Add-VpnConnection to create the VPN connection with the proper $vpnName ,$vpnTarget and $vpnSuffix and the appropriate Tunnel settings for our endpoint. Finally, we modify the IPSec Configuration of the tunnel with Set-VpnConnectionIPsecConfiguration targeting the tunnel with $vpnName. You’ll notice I include some Write-Output lines, I use this to report back into Datto RMM what has happened in a simplified form.

And that’s that for the script. Some things I know I would like to add down the line would be error correction and duplication detection to ensure the script exits gracefully on systems where it is already deployed or collects errors in a meaningful way to allow troubleshooting that system.

Taggs:
Write a comment