Inhaltsverzeichnis
Zielsetzung
Einer meiner Windows Server 2016 mit dem Namen WS-RDS1 war ursprünglich mein RDS-System für die Einwahl von außen. Nach einigen Experimenten lief der Server nicht mehr stabil. Kurzerhand hatte ich einen weiteren Server WS-RDS2 installiert und als alleinigen Endpunkt für die RDP-Einwahl definiert. Seitdem hatte WS-RDS1 außer der Ausführung von 2 Scriptaufgaben (1x pro Tag) nichts mehr zu tun.
Im Rahmen meiner Umstellung auf Windows Server 2019 wird es Zeit, diese Altlast zu bereinigen. Leider hat Microsoft mit Windows Server 2019 die Rolle RemoteDesktopService verschlechtert. Daher habe ich überlegt, wie ich das zum einen selber testen kann und zum anderen aber keine wertvolle Funktionalität verliere. Die Lösung ist einfach: Ich installiere mit WS-RDS1 auf Windows Server 2019 eine zusätzliche Farm zu meinem WS-RDS2.
Zusätzlich möchte ich gerne zu meiner bestehenden RDS-RemoteApp-Verbindung für den Zugriff von außen auch einen HTML5-WebClient bereitstellen. Und diese Funktion wird der neue WS-RDS1 übernehmen.
Das Umstellungsszenario ist denkbar einfach: die beiden Scripte habe ich bereits auf einen anderen Server verschoben. WS-RDS1 kann also einfach entfernt und neu installiert werden. Die neue Installation erbt den Namen und die IPv4. So spare ich mir Anpassungen im Bereich Monitoring, Backup und Firewall.
Entfernung von WS-RDS1
Ich prüfe noch einmal, ob auf dem System Daten oder Dienste liegen, die ich mitnehmen muss. Aber auf WS-RDS1 (alt) gibt es nichts mehr. Es kann also losgehen.
Ich pausiere das Monitoring:
Dann beende ich die VM aus im Hyper-V-Host WS-HV1 und entferne die alte VHDX-Festplatte:
Die Datei entferne ich vom Datenträger und kopiere die vorbereitete VHDX-Datei mit Windows Server 2019 rein:
Neuinstallation von WS-RDS1
Die neue VHDX „installiere“ ich in die VM. Zusätzlich passe ich die „Hardware“ etwas an:
Nach dem Start der VM wird das Betriebssystem im OOBE-Modus vorbereitet:
Ich definiere ein initiales Kennwort für den Administrator, konfiguriere die IPv4-Einstellungen und benenne das System um:
Vor dem DomainJoin lösche ich das alte Konto im ActiveDirectory:
Nun lasse ich das System in mein ActiveDirectory. Vor dem Neustart platziere ich das AD-Computerkonto in der richtigen Organisationseinheit. So wirken alle Gruppenrichtlinien direkt nach dem Neustart:
Jetzt fehlen noch die Windows Updates. Diese installiere brav im Hintergrund. Im WSUS meldet sich das System als Windows Server 2019:
Nach dem Update wird ein Neustart verlangt. Der ist schnell erledigt..
Nun fehlt noch das Backup. Auch bei diesem System setze ich auf die SystemState-Sicherung mit meinem Script. Dieses importiere ich als Scripttask:
Und wie schon mehrfach gezeigt passe ich den ausführenden Account mit meinem gMSA-Tool an:
Das System ist einsatzbereit und wartet auf seine Aufgabe.
Der Server soll eine eigene RDS-Instanz bereitstellen. Daher werde ich die 4 Rollen: RD-ConnectionBroker, RD-Webservice, RD-SessionHost und RD-Gateway installieren. Den Zugriff auf die SessionCollection möchte ich mit HTML5 abbilden.
Das Setup der Instanz gleicht dem der vorherigen Betriebssysteme. Es wird über den Servermanager gestartet. Da der Vorgang aber einen Neustart erwartet werde ich die Installation von einem anderen Server ausführen. Hier wähle ich meinen neuen WS-MON. Auch dieser läuft schon mit Windows Server 2019. Im Servermanager füge ich den WS-RDS1 hinzu:
Und dann geht es mit der Rolleninstallation los:
Das Setup läuft und ist nach wenigen Minuten erfolgreich abgeschlossen:
Für die verschiedenen Funktionen benötige ich ein internes Zertifikat. Dieses beantrage ich mit meinem PowerShell-Tool von meiner internen Windows-PKI:
Warum ich kein öffentliches Zertifikat verwende? Der Server wird selber kein „Tageslicht“ sehen. Seine Webdienste leite ich durch einen WAP (Web Application Proxy). Nur dieser bekommt das vertrauenswürdige Zertifikat installiert…
Nun kann die Bereitstellung beginnen:
Noch ist kein Gateway installiert:
Den Lizenzserver installiere ich später, wenn die Lösung funktioniert. Bis dahin genügt mir der Evaluierungszeitraum:
Das ist die interessante Funktion:
In diesem Dialog gibt es keine Veränderung. Für jede Komponente müssen die Zertifikate installiert werden:
Das wiederhole ich für alle anderen Komponenten. Hier sehen wir das Ergebnis:
Jetzt erstelle ich eine Session-Collection. Früher haben wir das einfach Farm genannt. Gemeint sind SessionHosts, die alle die gleiche Funktion anbieten. Mitglied ist der WS-RDS1 selber:
Hier ist eine meiner GPO gegen eine neue Collection am Wirken. Diese mag der RDS-Broker nicht:
Dennoch wurde sie erstellt:
Die Webseite möchte ich mit dem FQDN rdsweb.ws.its intern aufrufen können. Dazu ist ein neuer DNS-Record erforderlich:
Es wird Zeit für einen Test. Ich rufe die RD-Website von meinem Client aus auf. Leider ohne Erfolg:
Die Ursache ist schnell gefunden: Meine Firewall kennt den neuen Service noch nicht und blockt erwartungsgemäß:
Also schreibe ich die IPv4-Adresse des neuen Servers in die passende Gruppe und versuche es erneut:
Die Webseite lässt sich nun fehlerfrei aufrufen:
Nach der Anmeldung wird die SessionCollection angezeigt:
Und über die RDP-Datei gelange ich auf meinen neuen Server. Natürlich noch ohne SingleSignOn:
Das Grundgerüst steht.
Erweiterung auf den HTML5-WebClient
So eine Collection habe ich aber schon. Ich möchte den Zugriff über HTML5 im Browser ermöglichen. Im Servermanager stand vorhin dazu ein passender Hinweis:
Ich folge dem Link und gelange auf eine MSDocs-Seite. Gleich zu Beginn wird ein Hinweise angezeigt:
Der Webclient kann wohl nicht mit einem WebApplicationProxy konfiguriert werden. Manchmal weiß ich echt nicht, wie Microsoft sowas veröffentlichen kann. Es nerft. Ich probiere es dennoch aus. Kein Support muss ja nicht zwingend bedeuten, dass es nicht funktioniert. Und Support will ich von denen eh keinen haben…
Die Anleitung zeigt, wie das Setup über die PowerShell ausgeführt wird. Die relevanten Befehle habe ich hier zusammengestellt. Dazu habe ich einige Vereinfachungen geschrieben:
Install-Module -Name PowerShellGet -Force # PowerShell neustarten Install-Module -Name RDWebClientManagement Install-RDWebClientPackage Get-RDWebClientPackage $BrokerCertThumprint = (Get-RDCertificate -Role RDRedirector).Thumbprint Get-ChildItem -Path "Cert:\LocalMachine\My\$BrokerCertThumprint" | Export-Certificate -FilePath 'C:\Broker.cer' | Out-Null Import-RDWebClientBrokerCert -Path 'C:\Broker.cer' Publish-RDWebClientPackage -Type Production -Latest
Dem Webclient muss das richtige Zertifikat zugewiesen werden. Dieses muss man aber vorher exportieren. Das Setup benötigt eine Internetverbindung. In der Anleitung gibt es zwar auch eine Offline-Variante, aber die lief bei mir nicht. Daher habe ich den Server für das Setup in der Firewall freigeschaltet. So lief es problemlos durch.
Dann teste ich den Webclient mal von intern. Im Browser muss diese Adresse aufgerufen werden: https://rdsweb.ws.its/rdweb/webclient/. Die Anmeldemaske sieht vielversprechend aus:
Auch die SessionCollection wird angezeigt. Und beim Start werden Verbindungsoptionen abgefragt:
Der Start dauert einige Sekunden:
Doch statt einem Desktop erhalte ich eine Fehlermeldung:
Mmh, das sieht mir nach einem Block in der Firewall aus. Ein Blick in die Logfiles meiner PFSense gibt mir Recht:
Da muss noch ein Port freigeschaltet werden. Das hole ich fix nach. Für jeden Port bzw. jede Applikation habe ich in meiner PFSense-Firewall eine Alias-Gruppe erstellt. Damit kann ich Anwendungen sehr einfach freigeben:
Für diese Anwendung erweitere ich den bestehenden PortAlias „Ports_RDS“:
Und jetzt nehme ich die IP-Adresse vom neuen Server in die AliasGruppe „ServerIn_RDS“ auf:
Aktuell steht der Server WS-RDS1 noch im Servernetz. Dieses kann mein Client so nicht erreichen. Also erstelle ich eine
zusätzliche Ausnahme für den Zugriff:
Es wird Zeit für einen weiteren Testlauf. Leider erscheint die gleiche Fehlermeldung. In der Firewall wird aber keine Verbindung mehr blockiert. Was ist da los? In der Anleitung von Microsoft finde ich den passenden Eintrag:
Mmh, ich meinte, dass für Windows Server 2019 kein RD-Gateway mehr nötig wäre. Und weiter unten in der Anleitung finde ich den passenden Abschnitt:
OK, hier muss noch etwas verbogen werden. Die erforderlichen Befehle werden in der Anleitung beschrieben:
$BrokerCertThumprint = (Get-RDCertificate -Role RDRedirector).Thumbprint $BrokerCertThumprint netsh http show sslcert netsh http add sslcert ipport=0.0.0.0:3392 certhash="$BrokerCertThumprint" certstorename="Remote Desktop" appid="
Leider ergibt eine Vorprüfung, dass bei mir die Einstellungen bereits vorhanden sind:
Da auch ein Versuch aus dem gleichen Netzwerksegment fehlschlägt installiere ich die Rolle RD-Gateway nach:
Noch belasse ich es bei dem internen Zertifikat.
Die Konfiguration braucht einen weiteren Testlauf. Das Ergebnis ist aber unverändert. In der Anleitung von Microsoft steht, der RD-Gatewayservice muss mit einem öffentlichen Zertifikat ausgestattet sein. OK, für meinen alten RDS-Service habe ich noch eins. Das importiere ich im nächsten Schritt. Unterschiedliche Zertifikate für RD-Web und RD-Gateway sind nicht erlaubt. Daher ändere ich beide ab:
Damit nun aber die Namensauflösung passt, ändere ich den DNS-Record für rds.ws-ist.de auf meinem DC:
Aber selbst jetzt kommt diese Fehlermeldung! Logfiles mit Fehlern oder passende Eventlogs suche ich vergebens. Vielleicht sind es die Gruppenrichtlinien, die ich auf das System anwende? Ich deaktiviere alle GPOs, starte das System neu und versuche es wieder. Das Problem bleibt aber bestehen
Die Ursache liegt woanders. Es muss eine Einschränkung sein. Im Netzwerk kann sie nicht liegen, da ist nun alles freigeschaltet. Ist es ein Authentifizierungsproblem? Ich hatte vor einiger Zeit NTLMv2 aus meiner AD-Infrastruktur verbannt. Auf meinen Domain Controllern habe ich für genau diesen Fall das Logging aktiviert. Mit einem PowerShell-Script kann ich die Eventlogs aller DCs prüfen:
Das ist ein Volltreffer: Der Domain Controller blockiert die Authentifizierung, die offenbar mit NTLM forciert wird… In der GPO, mit der ich NTLM deaktiviert habe, gibt es auch eine Ausnahme-Einstellung: Hier trage ich den Server WS-RDS1 mit ein:
Doch das Problem besteht weiter. Da es aber nicht meine erste RDS-Infrastruktur ist, fällt mir die Lösung (etwas spät) ein: Das RD-Gateway benötigt ein öffentliches Zertifikat. Na klar! In der Anleitung stand es ja auch drin… Aber bevor ich eines kaufe, erstelle ich ein öffentlich gültiges Testzertifikat für 30 Tage bei PSW. Der Request ist leicht erstellt und das Zertifikat ist einsatzbereit:
Den Namen habe ich auf eine öffentliche Subdomain rdsweb.ws-its.de erstellt. Diese konfiguriere ich noch fix bei meinem Provider.
Nun installiere ich das Zertifikat im RD-Gateway:
Für die interne Namensauflösung verwende ich eine neue DNS-Zone mit dem Namen rdsweb.ws-its.de, die ohne Hostname direkt auf die IPv4 des Servers WS-RDS1 zeigt:
Das Ergebnis kann einfach geprüft werden: Der externe Name löst intern auf die IPv4 des Servers auf:
Nun muss aber auch das RD-Gateway von diesem Namen erfahren. Dazu starte ich den Bereitstellungsassistenten im Servermanager:
So. Jetzt kann wieder getestet werden. Nach der Anmeldung kommt statt der Fehlermeldung die Konfiguration der lokalen Ressourcen:
Und die Session wird im Browser angezeigt:
So hab ich mir das vorgestellt!
Der interne Zugriff funktioniert. Ich möchte die Ressource aber nach extern veröffentlichen. Und wie eingangs erwähnt verwende ich bisher einen Web Application Proxy im Clusterverbund (2x WAP und 2x ADFS auf insgesamt 4 Servern). Laut Microsoft wird dieses Szenario eigentlich nicht unterstützt. Ich will es dennoch einmal versuchen.
Warum so kompliziert? Ganz einfach: Ich habe von meinem Internet-Provider nur eine IPv4-Adresse erhalten. Und an dieser kann ich den Port 443 für https nur einmal anbinden. Da ich aber mehrere Webanwendungen nach extern veröffentlichen möchte (Exchange, RDS, VPN, Monitoring, …) leite ich die Verbindungen am Router auf den WAP-Cluster weiter. Dieser analysiert die SNI und redirected im Hintergrund auf die internen Server.
Es ist immer noch zu kompliziert? Stimmt. Hier kann Visio helfen. Zur Erläuterung:
mein WS-Gate1 ist der Internet-Router. Dieser hat eine externe, feste IPv4 und eine interne IPv4 in meine externe DMZ
WS-PFS1 ist meine Firewall. Diese verbindet die externe DMZ mit dem Servernetzwerk. Auf der PFSense läuft ein HA-Proxy. Dieser bekommt den externen Traffic vom Internet für HTTPS weitergeleitet. Dieser Reverse-Proxy leitet die Verbindungen an beide WAP-Server (WS-RA1 und WS-RA2) weiter.
Die WAP-Systeme analysieren nun den SNI der Verbindungsabfrage (das ist der Servername in der URL) und leiten die Verbindung an das passende Zielsystem weiter.
Der Web Application Proxy ist der Endpunkt für die Client-Kommunikation von extern. Daher benötigt er das gleiche Zertifikat wie der interne Service. Ich installiere also auf beiden WAP-Servern das öffentliche Zertifikat für rdsweb.ws-its.de:
Nun kann die neue Anwendung veröffentlicht werden:
Das war einfach:
Es wird Zeit für einen Test. Dafür verbinde ich mein Notebook mit meinem Handy und rufe die RDS-Website von außen auf:
Bis hier sieht es gut aus. Doch die Verbindung wird nicht aufgebaut:
In den WAP-Servern finde ich keine Hinweise. Laut der Firewall wird der Traffic korrekt zugestellt. Das RD-Gateway mag also keine Unterbrechung des Traffics… Der Hinweis aus dem Microsoft-Artikel stimmt also.
Die Konstruktion mit den beiden WAP-Servern hinter dem HA-Proxy ist historisch gewachsen: Zuerst stellten die WAP-Server einen NLB-Cluster mit einer einzelnen IPv4 bereit, Diese konnte ich in meinem Router als Ziel für eingehende HTTPS-Verbindungen auf Port 443 definieren. Da ein Micosoft NLB aber immer schon etwas buggy war, hatte ich dann den HAProxy eingestellt. Dieser arbeitete also als reiner LoadBalancer.
Aber mit dem HA-Proxy kann auch eine Vorfilterung und eine Steuerungslogik eingesetzt werden. Vielleicht funktioniert diese mit einem RDS-Webclient?
Aktuell schlägt externer HTTPS-Traffic auf dem HA-Proxy-Frontend auf. Dieses leitet weiter an WS-RA1 und WS-RA2 – meine beiden Backend-Systeme (WAP):
Zuerst benötige ich ein neues Backend mit der IP-Adresse meines WS-RDS1. Dieses ist mit der WebGUI recht schnell aufgebaut:
Nun kann ich das Frontend editieren:
Hier muss eine ACL angelegt werden. Mit dieser wird ein Filter auf den SNI definiert und eine Aktion, wenn die Bedingung des Filters erfüllt ist:
Im Detail arbeitet der HA-Proxy also nach diesem Muster:
Eingehender Traffic wird nach dem SNI (Server Name Indication) untersucht.
Entspricht der SNI dem Muster „rdsweb.ws-ist.de“, dann wird das Backend „RDSWEB“ gewählt.
Ist der SNI anders, dann wird das Default Backend „HTTPS“ gewählt. Das ist der WAP-Cluster mit WS-RA1 und WS-RA2
Soweit sollte nun alles passen. Es wird Zeit für einen weiteren Test von extern. Also verbinde ich mein Notebook wieder mit meinem Handy und wähle mich von außen auf das Portal des RDS-Webclients:
Es funktioniert jetzt auch von extern! Der Traffic wird ja vom HA-Proxy nicht geöffnet bzw. terminiert. Das RD-Gateway ist direkt erreichbar:
Im Dashboard von der PFSense ist der Datenstrom gut sichtbar:
Finetuning und Absicherung
Ich kann mich über das Webportal auf meine Infrastruktur aufschalten. Schön wären jetzt noch einige Anwendungen auf dem RDS-Server. Eigentlich hatte ich hierfür meine erste RDS-Farm mit einer RemoteApp-Sammlung vorgesehen. Auf dem neuen Server könnte ich die gleichen Anwendungen installieren.
Aber warum eigentlich? Ich könnte diese RemoteApps auch in die WebSession integrieren. Dafür muss ich nur den RD-Webfeed eintragen. Dieser versteckt sich immer noch in der alten Systemsteuerung. Alternativ kann auch die GPO verwendet werden:
Eine Anmeldung später werden die RemoteApps integriert:
Ich starte testweise meine Outlook-RemoteApp:
Das sieht gut aus:
Vielleicht habt ihr euch schon gefragt, warum ich hier so offen mit meinen Konfigurationen umgehe? Ein Angreifer könnte darin eine Schwachstelle erkennen und durch z.B. diesen neuen HTML5-Webclient in meine Infrastruktur eindringen.
Da möchte ich 2 Punkte dagegenhalten: Zum ersten ist Security by Obscurity nicht wirklich sinnvoll. Ein Angreifer kann öffentlich erreichbare Endpunkte analysieren und die gleichen Erkenntnisse gewinnen. Und zum zweiten belasse ich die Absicherung nicht bei einem Kennwort. Den Zugriff von extern gewähre ich nur nach erfolgreicher Zweifaktor-Authentifizierung!
Als Anbieter verwende ich DUO, dass mittlerweile von Cisco übernommen wurde. DUO bietet eine freie Lizenz für bis zu 10 Accounts an. Ideal für mich, wenn ich benötige natürlich weniger.
Die Authentifizierung wird nach der Anmeldung am Zielsystem durch eine Erweiterung an den DUO-Server gesendet. Mein Smartphone hält mit der App permanent die Verbindung mit diesem Server und wird via Push-Notification zur Bestätigung aufgefordert. Eine feine Sache!
Für die Integration muss ich bei DUO eine Application erstellen:
Der Katalog bietet reichlich Auswahl:
Ich entscheide mich für das „Microsoft RD Web“-Template. Dazu gibt es auf der Seite einige Hinweise für die Bereitstellung:
OK, ich prüfe die Voraussetzungen:
Jetzt kann ich die MSI-Datei installieren. Die 3 Informationen liefert die Anweisung im DUO-Dashboard gleich mit:
Die erforderliche Firewall-Ausnahme zum DUO-Server existiert bereits:
Es sollte also alles funktionieren. Oder?
Das funktioniert so nicht! Der Name der Application klingt auch eher nach dem RDWeb-Portal. Und so ist es leider auch:
Auf der Support-Seite von DUO steht weiter unten, dass der HTML5-Client nicht unterstützt wird. War ja klar. Also entferne ich die Installation:
Ich versuche es mit der klassischen „Microsoft RDP“-Application:
Das Setup ist fast identisch mit dem ersten:
Nun muss die Anmeldung vollständig sein, damit der zweite Faktor angefordert wird. Aber es ist immer noch besser als nur ein Passwort. Und auch das muss erst einmal gefunden werden!
Jetzt wird das Smartphone kontaktiert.:
Der Webclient wird nur aktiv, wenn man im Browser die richtige URL einträgt. Die mag ich mir nicht merken. Ebenso mag ich sie nicht tippen. Also stelle ich im IIS auf meinem neuen WS-RDS1 die http-Umleitung ein
Nun genügt die URL https://<FQDN> für die Verbindung.
Über die PowerShell können einige Optionen definiert werden. Die klingen ganz gut:
Der Server wird in die Datensicherung integriert. Ein Testlauf erstellte eine vollständige Sicherung der VM im laufenden Betrieb.
Ebenso aktiviere ich das Monitoring des Systems in meinem PRTG.
Die Windows Updates laufen dank der GPO-Konfiguration automatisch an.
Nachdem die Lösung funktioniert möchte ich die Absicherung weiter verbessern. Aktuell würde ein Angreifer nach der erfolgreichen Anmeldung am Server direkt im Servernetzwerk 192.168.100.0/24 rauskommen. Danach nützt mir meine Firewall zwischen den Clientnetzen und dem Servernetzwerk nichts mehr.
RDS-Server werden im Gegensatz zu anderen Servern von Endanwendern verwendet. Daher gehören sie in ein eigenes Netzwerksegment oder in das Clientnetzwerk. Ich entscheide mich für mein Clientnetz 192.168.110.0/24.
Zuerst ändere ich die IPv4-Konfiguration des Servers WS-RDS1:
Da es eine virtuelle Maschine ist, wird die Anpassung des Netzwerkadapters recht einfach ausfallen: Ich ändere den vSwitch und die VLAN-ID im Hyper-V-Manager:
Aber auch die PFSense muss über den Wechsel informiert werden. Der HA-Proxy prüft permanent die Verfügbarkeit. Er vermisst den Server bereits:
Die Anpassung nehme ich im Backend des HA-Proxy vor:
Die Firewall besteht bei mir aus etlichen Alias-Einträgen, die in diversen Regeln angewendet werden. Diese müssen nun ebenfalls angepasst werden:
Weitere Aliase warten auf die Editierung:
Zuletzt muss auch der DNS-Record in meinen DNS-Servern angepasst werden:
Ein finaler Test von intern und extern war erfolgreich!
Zusammenfassung
Die Umstellung des Servers WS-RDS1 von Windows Server 2016 auf Windows Server 2019 ist abgeschlossen. Das 30 Tage gültige Testzertifikat wird später noch gegen ein gekauftes Zertifikat ersetzt.
Und auch die Erweiterung um den HTML5-Webclient verlief fast ohne Probleme. So macht eine Migration Spass!
Wie üblich gibt es hier diesen Beitrag auch als PDF.
Stay tuned!
Hier geht es zum Übersichtsbeitrag meiner Serie “Migration auf Windows Server 2019”.