Background
With the potential of SSL certificates in Chrome being considered expired after 90 days and the inevitable downtime from not renewing a certificate in time, its time to get serious about automating the renewal and installation of certificates on all platforms. This process is well honed for popular webservers but other applications require custom solutions. Microsoft Remote Desktop Services relies heavily on trusted SSL certificates to function properly. I’ve worked on two methods of requesting and renewing free Let’s Encrypt (LE) certificates, the Certify the Web GUI utility and the POSH-Acme PowerShell module.
Environmental Considerations
There are two methods of validating an LE certificate: DNS records or a specially crafted HTTP response. I use the HTTP method because there are security issues with the API that my DNS provider offers. ACME requests must come in on port 80 to a public IP address that answers on the same DNS name as the primary CN and any SAN on the requested certificate. Since RDS Gateway servers are often already exposed to the Internet they are a good place to run this from. The methods below do not require you to keep port 80 running on the server all of the time. An ephemeral web server is spun up on port 80 at the time of renewal and immediately shut down after verification is complete. You do, however, have to have a rule allowing port 80 on your border firewall on the VIP that points to the RDS Gateway. This was an acceptable risk in my environment, YMMV – talk to your security people. You also need to remove any IIS bindings on port 80. There are ways around this but I didn’t find them reliable.
In my example I add the internal DNS names of the individual servers as SANs on the certificates. Technically this may not be required but I’ve had enough RDS cert errors over the years that I like to cover my bases. I point this out because it is only possible to do so if you are using a register-able domain name for your AD. It (they) also has to resolve publicly to the same IP as the gateway and it can’t be a .local or .lan or other made-up suffix.
Certify the Web
Certify the Web is a GUI utility that manages ACME certificate requests and deployment from several popular CAs. It also includes a service that submits renewal requests when a certificate is about to expire. There are several deployment task scripts bundled with Certify, but the built in function for applying the certificate to an RDS gateway was neither functional nor complete for my use case.
There are several quick start guides that will get you provisioning a cert quickly. I highly recommend setting up a staging account on LE to test your script. If you use the production link and something goes wrong, you could easily hit the rate limit on the certificate requests. Here are screenshots of the settings I used on the cert for this example
![](https://blog.rdorman.net/wp-content/uploads/2023/03/2023-03-29_23-58-40.jpg)
![](https://blog.rdorman.net/wp-content/uploads/2023/03/2023-03-29_23-58-58.jpg)
![](https://blog.rdorman.net/wp-content/uploads/2023/03/2023-03-29_23-57-22.jpg)
![](https://blog.rdorman.net/wp-content/uploads/2023/03/2023-03-29_23-57-52.jpg)
![](https://blog.rdorman.net/wp-content/uploads/2023/03/2023-03-29_23-58-19.jpg)
Using some examples I found along with a fair amount of trial and error I came up with this script to run as a post-deployment task.
There are a couple things I learned along the way:
- The
export-pfxcertificate
cmdlet does not work the same way on all versions of PowerShell particularly around passwords and private keys. This is why you find the interesting syntax beginning on line 16 - The cmdlets for installing the certificates on the RDS roles doesn’t like PFX files without passwords. Use the password from the script when configuring your certificate in Certify the Web. You can set the PFX password by navigating in the Certify GUI to:
- Certificate -> Advanced -> Signing & Security – Scroll to bottom
- Run the post-deployment task as
Local (as current service user)
with Network impersonation and pass result as first arg checked
Certify the Web is slick and free for personal use up to 5 certificates. Since my use case is MSP clients it doesn’t qualify for the free license; I decided to look at a free option that could be used in a commercial setting.
POSH-ACME
The POSH-ACME PowerShell module handles all parts of requesting, renewing and validating certificates. It does not have any built-in deployment hooks.
To use the self-hosted ephemeral web server, you must make a modification to the security ACL on the Windows HTTP library. The command below must be run from a Admin command line (cmd.exe) and not from a PowerShell session. This only has to be performed once on the system.
netsh http add urlacl url=http://+:80/.well-known/acme-challenge/ sddl=D:(A;;GX;;;S-1-1-0)
The script below performs the following basic tasks:
- Capture the names for the CN and SANs on the cert
- Sets an alternate config location for the POSH-ACME database
- This is done so that certificates can be managed by mutliple users
- By default, the database is stored in a hidden directory in the user’s home folder
- Request a cert and use the built in ephemeral HTTP server to validate
- Install the cert on all of the RDS roles
- Install the cert on the RDS HTML5 (Web) client
Since POSH-ACME doesn’t have a service that schedules renewals, you will need to use the Task Scheduler to run the renewal check and installation on a regular basis. The majority of the script is the same with an if function at the top that checks if the renewal returns a new cert before proceeding.
Finishing U
Test, test, test and monitor for failures… if everything above works you may never have to manually renew a cert for your RDS farm ever again.
Hi, I found your post very interesting and helpful. Maybe, with the knowledge you achieved, you could have an idea for my scenario. I don’t expect you, to solve it for me (although would be very welcomed) but maybe you could show me a track to follow. I have a scenario with multiple RDP-servers on different windows domains and different IP-ranges.
Clients that try to login trough RDP are not in the same domain as the RDP.
Do you see any chance, to secure this scenario with anything you mentioned above?
Many thanks in advance.
Oliver