Securing VMware Horizon UAGs with Let’s Encrypt SSL certificates

20151023 - 1Securing your Horizon Universal Access Gateway (UAG) with a genuine SSL certificate from a recognised vendor is an important process. It enables your users to be sure they’re connecting to the correct VDI infrastructure, and that the communications between their endpoint and remote desktop are secure.

However, SSL certificates are often not cheap and replacing them can be an administrative burden.

In this post, I will show you how to leverage Let’s Encrypt to provide free SSL certificates, and how the renewal and replacement process can be automated.

Let’s Encrypt

Let’s Encrypt is a not-for-profit certificate authority offering free SSL certs valid up to ninety days. Users can renew their certificates any time during this period.

The aim of the service is to reduce complexity and the management overhead of acquiring and installing SSL certificates on servers. To facilitate this, the Automated Certificate Management Environment (ACME) protocol is used to validate domain ownership. This is typically done by placing a file in a web server’s root directory or the creation of a TXT record in the domain’s DNS zone. Third-party add-ons can be used to interface with well-known web and DNS providers such as Amazon Web Services Route 53, which I will be using this in this post.

Getting Started

What we’ll need:

AWS Configuration

To enable Posh-ACME to communicate with AWS, we need to create an API key and secret.

Login into the AWS Console and navigate to Route 53. In the following screenshot you can see I have two hosted zones:

Route 53

Click on Hosted Zones and make a note of your Hosted Zone ID(s):

Hosted Zone ID(s)

Navigate to the AWS IAM and select Users, then click Add User. Give the user a name, select Programmatic access and then click Next

Under Set Permissions, click Attach Existing Policies Directly, then click Create Policy. A new window will open. Click the JSON tab and paste in the following:

"Version": "2012-10-17",
"Statement": [
"Effect": "Allow",
"Action": [
"Resource": [
"Effect" : "Allow",
"Action" : [
"Resource" : [

view raw


hosted with ❤ by GitHub

Remember to replace YOURHOSTEDZONEID with the actual ID of your zone(s).

Click Review Policy, and then give it a name:

Click Create Policy.

Switch back to the original IAM window and click the refresh icon. Filter the policies to find the one you just created and check to select it, then click Next.

Click Next again, followed by Create User. The success screen will appear:

Make a note of the Access Key ID and the Secret Access Key, as you will need these later on.

Replacing the Certificates

The last step in the process is our script. It takes two parameters. The first is the external/public hostname of your UAG(s). This hostname often points to load-balanced address and is backed by more than one UAG.

The second parameter is the list of UAGs you wish to upload the certificate to. The certificate is only applied to the Internet interface, as it is common for the admin interface (on port 9443) to use a different certificate which is typically supplied from an internal CA.

Uploads freshly minted certs to Horizon UAG
Generates an SSL certificate from Let's Encrypt, then connects to each UAG using the REST API and applies the certificate to the Internet interface. The Admin interface (port 9443) is unaffected.
The public DNS name of the UAG
Comma-seperated list of UAGs
Update-UAGCerts.ps1 <DNS name> <UAGs>
Author: Mark Brookfield (@virtualhobbit)
if (!$dnsName) {
Write-Error "No DNS name supplied – aborting"
if (!$uags) {
Write-Error "No UAGs supplied – aborting"
# Define Lets Encrypt parameters
$psMod = "Posh-ACME"
$dnsPlugin = "Route53"
$r53Params = @{R53AccessKey='YOURACCESSKEY'; R53SecretKey='YOURSECRETKEY'}
$email = ""
# Define UAG credentials
$user = 'admin'
Write-Host "Please enter the UAG admin password. Please note this must be the same for all UAGs."
$pass = Read-Host AsSecureString "Admin password" Force
$pass = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass))
$creds = "$($user):$($pass)"
# Encode credentials
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($creds))
if (!(Get-InstalledModule Name $psMod)) {
# Install the Posh-ACME module
Install-Module Name $psMod Scope CurrentUser Force
# Set Let's Encrypt server
Set-PAServer LE_PROD
# Order the certificate
New-PACertificate $dnsName AcceptTOS DnsPlugin $dnsPlugin PluginArgs $r53Params Contact $email Verbose Force
$newCert = Get-PACertificate
# Convert private key to one-liner
$privKey = [IO.File]::ReadAllText($newCert.KeyFile)
$privKeyReplace = $privKey.Replace("`n",'\n')
# Convert SSL certificate to one-liner
$cert = [IO.File]::ReadAllText($newCert.FullChainFile)
$certReplace = $cert.Replace("`n",'\n')
# Create JSON body
$json = '{"privateKeyPem":"' + $privKeyReplace + '","certChainPem":"' + $certReplace + '"}'
# Define API parameters
$params = @{
Headers = @{ 'Authorization' = "Basic $encodedCreds" }
Method = 'PUT'
Body = $json
ContentType = 'application/json'
ForEach ($uag in $uags){
# Define the URI
$Uri = "https://" + $uag + ':9443/rest/v1/config/certs/ssl'
# Display UAG
Write-Host "UAG is: " $uag
# Connect to each UAG and replace SSL certificate and private key
Invoke-RestMethod $uri @params

Don’t forget to replace the following variables:

  • $r53Params
  • $email

I hope this was useful. Please reach out on Twitter (virtualhobbit) if you have any issues, or more importantly if you can improve the process/script!

Happy minting!

2 thoughts on “Securing VMware Horizon UAGs with Let’s Encrypt SSL certificates

  1. Pingback: Newsletter: December 27, 2019 – Notes from MWhite

  2. Pingback: Newsletter: January 4, 2020 – Notes from MWhite

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.