From 355b121c79e643d8b382f273cfb262acb475b355 Mon Sep 17 00:00:00 2001 From: Curd Becker <12437061+curdbecker@users.noreply.github.com> Date: Fri, 1 May 2026 17:15:57 +0200 Subject: [PATCH] Add deployment plugin for Windows RDP via OpenSSH (#6925) * Add deployment plugin for Windows RDP via OpenSSH --- acme.sh | 19 +++++ deploy/windows_rdp.sh | 158 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 deploy/windows_rdp.sh diff --git a/acme.sh b/acme.sh index 0f97bcea..6c191c92 100755 --- a/acme.sh +++ b/acme.sh @@ -1057,6 +1057,25 @@ _digest() { } +#Usage: certpath hashalg +#Output certificate fingerprint without colons +_fingerprint() { + cert="$1" + alg="$2" + if [ -z "$alg" ]; then + _usage "Usage: _fingerprint certpath hashalg" + return 1 + fi + + if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then + # openssl prints "SHA1 Fingerprint=AA:BB:CC:..."; strip prefix and colons. + ${ACME_OPENSSL_BIN:-openssl} x509 -in "$cert" -noout -fingerprint -"$alg" | sed 's/.*=//; s/://g' + else + _err "$alg is not supported yet" + return 1 + fi +} + #Usage: hashalg secret_hex [outputhex] #Output binary hmac _hmac() { diff --git a/deploy/windows_rdp.sh b/deploy/windows_rdp.sh new file mode 100644 index 00000000..e708e9a7 --- /dev/null +++ b/deploy/windows_rdp.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env sh + +# install a certificate on a Windows host over OpenSSH and bind it to the Remote +# Desktop listener (RDP-Tcp). +# +# One ssh invocation does the whole job: +# * the PFX is built locally, base64'd, and embedded as a string literal +# inside a generated PowerShell script; +# * the script is piped to `powershell.exe -Command -` over ssh. No scp, +# no temp files on the Windows host. +# +# First run: +# export DEPLOY_WIN_RDP_HOST=winserver.example.com +# acme.sh --deploy -d winserver.example.com --deploy-hook windows_rdp +# +# Available variables: +# DEPLOY_WIN_RDP_HOST required SSH host +# DEPLOY_WIN_RDP_USER optional SSH user, must be a local administrator (can also by set via ssh_config) +# DEPLOY_WIN_RDP_PORT optional SSH port, default 22 +# DEPLOY_WIN_RDP_SSH_OPTS optional extra ssh options, e.g. +# "-i /root/.ssh/win_id_ed25519 -o StrictHostKeyChecking=yes" +# DEPLOY_WIN_RDP_LISTENER optional RDP listener name, default RDP-Tcp +# DEPLOY_WIN_RDP_RESTART optional "1" to restart TermService after install. +# Active RDP sessions will drop! + +windows_rdp_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if ! _exists "ssh"; then + _err "ssh is required but was not found in PATH." + return 1 + fi + + # ---- configuration ------------------------------------------------------ + _getdeployconf DEPLOY_WIN_RDP_HOST + _getdeployconf DEPLOY_WIN_RDP_USER + _getdeployconf DEPLOY_WIN_RDP_PORT + _getdeployconf DEPLOY_WIN_RDP_SSH_OPTS + _getdeployconf DEPLOY_WIN_RDP_LISTENER + _getdeployconf DEPLOY_WIN_RDP_RESTART + + if [ -z "$DEPLOY_WIN_RDP_HOST" ]; then + _err "DEPLOY_WIN_RDP_HOST must be set." + return 1 + fi + + _savedeployconf DEPLOY_WIN_RDP_HOST "$DEPLOY_WIN_RDP_HOST" + [ -n "$DEPLOY_WIN_RDP_USER" ] && _savedeployconf DEPLOY_WIN_RDP_USER "$DEPLOY_WIN_RDP_USER" + [ -n "$DEPLOY_WIN_RDP_PORT" ] && _savedeployconf DEPLOY_WIN_RDP_PORT "$DEPLOY_WIN_RDP_PORT" + [ -n "$DEPLOY_WIN_RDP_SSH_OPTS" ] && _savedeployconf DEPLOY_WIN_RDP_SSH_OPTS "$DEPLOY_WIN_RDP_SSH_OPTS" + [ -n "$DEPLOY_WIN_RDP_LISTENER" ] && _savedeployconf DEPLOY_WIN_RDP_LISTENER "$DEPLOY_WIN_RDP_LISTENER" + [ -n "$DEPLOY_WIN_RDP_RESTART" ] && _savedeployconf DEPLOY_WIN_RDP_RESTART "$DEPLOY_WIN_RDP_RESTART" + + _port="${DEPLOY_WIN_RDP_PORT:-22}" + _listener="${DEPLOY_WIN_RDP_LISTENER:-RDP-Tcp}" + if [ -n "$DEPLOY_WIN_RDP_USER" ]; then + _target="$DEPLOY_WIN_RDP_USER@$DEPLOY_WIN_RDP_HOST" + else + _target="$DEPLOY_WIN_RDP_HOST" + fi + _pfx_pass="acme" + + # ---- build thumbprint + PFX locally ------------------------------------ + _thumb="$(_fingerprint "$_ccert" 'sha1')" + if [ -z "$_thumb" ]; then + _err "Failed to compute certificate thumbprint." + return 1 + fi + _debug "Thumbprint: $_thumb" + + _debug "Building PFX at $_pfx_file" + _pfx_file="$(_mktemp)" + if ! _toPkcs "$_pfx_file" "$_ckey" "$_ccert" "$_cca" "$_pfx_pass"; then + _err "Failed to build PFX archive." + rm -f "$_pfx_file" + return 1 + fi + _pfx_b64=$(_base64 "multiline" <"$_pfx_file") + rm -f "$_pfx_file" + + # ---- build installer script -------------------------------------------- + if [ "$DEPLOY_WIN_RDP_RESTART" = "1" ]; then + _restart_ps='Restart-Service -Name TermService -Force' + else + _restart_ps='# New RdP connections will pick up the new cert automatically.' + fi + + # Escape every literal `$` with `\$` so the shell does not expand it. + # Values substituted from shell: $_pfx_b64, $_pfx_pass, $_thumb, $_listener. + _ps1=$( + cat <