umbau 1.3.12

This commit is contained in:
2026-09-03 08:42:04 +02:00
parent 2741910a72
commit b2c72de069
4 changed files with 201 additions and 110 deletions

View File

@ -59,5 +59,10 @@ guacd_log_level: "info"
# guacd_tls_min_protocol genau darauf setzen statt pauschal auf TLSv1. # guacd_tls_min_protocol genau darauf setzen statt pauschal auf TLSv1.
guacd_tls_legacy_compat: false guacd_tls_legacy_compat: false
guacd_tls_min_protocol: "TLSv1" guacd_tls_min_protocol: "TLSv1"
# Obergrenze, leer = keine. Wird gebraucht, wenn das Ziel bei einem
# ClientHello mit angebotenem TLS 1.3 einfach auflegt (SCHANNEL schickt dann
# ein TCP-RST statt einer sauberen handshake_failure-Warnung) -- dann
# "TLSv1.2" eintragen. Das Diagnoseskript nennt den noetigen Wert.
guacd_tls_max_protocol: ""
guacd_tls_security_level: 0 guacd_tls_security_level: 0
guacd_tls_unsafe_legacy_renegotiation: true guacd_tls_unsafe_legacy_renegotiation: true

View File

@ -271,6 +271,21 @@
create: true create: true
notify: restart guacd notify: restart guacd
- name: Falsch benannte Protokollierstufe aus guacd.conf entfernen
# BUGFIX (3.9.): hier stand zuerst "log_level = ...". Diesen Parameter
# kennt guacd.conf NICHT -- er heisst "max_log_level" (guacd.conf(5):
# pid_file, max_log_level, bind_host, bind_port, server_certificate,
# server_key). guacd bricht bei einem unbekannten Schluessel den Start
# komplett ab ("Parse error at line N: Invalid parameter or section name"),
# es gibt also keine stille Fehlbedienung -- der Dienst startete gar nicht
# mehr. Diese Aufraeumzeile entfernt die falsche Zeile auf Hosts, auf denen
# die fehlerhafte Fassung bereits ausgerollt wurde.
ansible.builtin.lineinfile:
path: /etc/guacamole/guacd.conf
regexp: '^\s*log_level\s*='
state: absent
notify: restart guacd
- name: guacd-Protokollierstufe setzen - name: guacd-Protokollierstufe setzen
# Auf "info" (Standard) protokolliert guacd bereits, ob die # Auf "info" (Standard) protokolliert guacd bereits, ob die
# Zertifikatspruefung wegen ignore-cert uebersprungen wurde # Zertifikatspruefung wegen ignore-cert uebersprungen wurde
@ -280,8 +295,8 @@
# guacd_log_level voruebergehend auf "debug" setzen (siehe defaults). # guacd_log_level voruebergehend auf "debug" setzen (siehe defaults).
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: /etc/guacamole/guacd.conf path: /etc/guacamole/guacd.conf
regexp: '^log_level' regexp: '^\s*max_log_level\s*='
line: "log_level = {{ guacd_log_level }}" line: "max_log_level = {{ guacd_log_level }}"
create: true create: true
notify: restart guacd notify: restart guacd

View File

@ -33,6 +33,9 @@ system_default = system_default_sect
[system_default_sect] [system_default_sect]
MinProtocol = {{ guacd_tls_min_protocol }} MinProtocol = {{ guacd_tls_min_protocol }}
{% if guacd_tls_max_protocol | default('', true) | length > 0 %}
MaxProtocol = {{ guacd_tls_max_protocol }}
{% endif %}
CipherString = DEFAULT@SECLEVEL={{ guacd_tls_security_level }} CipherString = DEFAULT@SECLEVEL={{ guacd_tls_security_level }}
{% if guacd_tls_unsafe_legacy_renegotiation %} {% if guacd_tls_unsafe_legacy_renegotiation %}
# Manche aeltere Windows-/SCHANNEL-Staende koennen die in OpenSSL 3 # Manche aeltere Windows-/SCHANNEL-Staende koennen die in OpenSSL 3

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Stellt genau den TLS-Verbindungsaufbau nach, an dem guacd/FreeRDP scheitert. """Stellt genau den TLS-Verbindungsaufbau nach, an dem guacd/FreeRDP scheitert,
und ermittelt die MILDESTE Lockerung, die ihn gelingen laesst.
Hintergrund (Live-Test 3.9.): Hintergrund (Live-Test 3.9.):
@ -14,27 +15,28 @@ Dass es NICHT am Zertifikat liegt, zeigt das guacd-Journal selbst: guacd
protokolliert beim Pruefen eines Zertifikats immer eine der beiden Zeilen protokolliert beim Pruefen eines Zertifikats immer eine der beiden Zeilen
"Certificate validation bypassed" (ignore-cert greift) oder "Certificate "Certificate validation bypassed" (ignore-cert greift) oder "Certificate
validation failed". Steht KEINE von beiden im Journal, wurde der validation failed". Steht KEINE von beiden im Journal, wurde der
Zertifikats-Rueckruf nie erreicht -- der Handshake ist also schon vorher Zertifikats-Rueckruf nie erreicht -- der Handshake ist schon vorher
gescheitert, bei der Aushandlung von Protokollversion und Cipher. Die gescheitert, bei der Aushandlung von Protokollversion, Cipher-Suite und
Hosteinstellung "Zertifikat ignorieren" kann daran nichts aendern. Signaturverfahren. Die Hosteinstellung "Zertifikat ignorieren" kann daran
nichts aendern.
Genau das prueft dieses Skript. Es macht, was FreeRDP macht: Was dieses Skript macht -- dasselbe wie FreeRDP:
1. TCP zu <ziel>:3389 1. TCP zu <ziel>:3389
2. X.224-Verbindungsanfrage mit RDP_NEG_REQ (MS-RDPBCGR 2.2.1.1) -- 2. X.224-Verbindungsanfrage mit RDP_NEG_REQ (MS-RDPBCGR 2.2.1.1). Das ist
das ist die RDP-eigene Vorstufe, ohne die kein RDP-Server TLS spricht. die RDP-eigene Vorstufe, ohne die kein RDP-Server TLS spricht -- deshalb
Deshalb funktioniert "openssl s_client -connect ziel:3389" hier auch taugt "openssl s_client -connect ziel:3389" hier NICHT als Gegentest:
nicht: es faengt sofort mit ClientHello an und laeuft ins Leere. es faengt sofort mit dem ClientHello an und laeuft ins Leere.
3. Auswerten, welche Sicherheitsstufe der Server auswaehlt 3. Auswerten, welche Sicherheitsstufe der Server waehlt.
4. TLS-Handshake auf derselben Verbindung -- und zwar MEHRFACH, mit 4. TLS-Handshake auf derselben Verbindung, mehrfach, mit aufsteigend
verschiedenen OpenSSL-Vorgaben: staerkerer Lockerung der OpenSSL-Vorgaben -- von "unveraendert" bis
a) System-Standard: exakt das, was guacd/FreeRDP auf diesem Host "alles erlaubt". Die Zertifikatspruefung ist ueberall AUS (wie
bekommen. Debian 12 setzt in /etc/ssl/openssl.cnf ignore-cert), damit kein Ergebnis daran haengen kann.
MinProtocol = TLSv1.2 und CipherString = DEFAULT@SECLEVEL=2.
b) Alles erlaubt (SECLEVEL=0, ab TLS 1.0) Die Reihenfolge ist Absicht: gemeldet wird die ERSTE (= mildeste) Variante,
c/d) TLS 1.0 bzw. TLS 1.2 einzeln erzwungen die funktioniert, zusammen mit den passenden Ansible-Variablen. Auf einem
Der Vergleich sagt, ob die OpenSSL-Vorgaben des JUMPHOSTS die Ursache Jumphost pauschal auf SECLEVEL=0 zu gehen, wenn SECLEVEL=1 genuegt, waere
sind (a scheitert, b klappt) oder das Ziel selbst (alles scheitert). unnoetig.
Braucht nichts ausser Python 3 -- kein X11, kein xfreerdp, kein nmap. Braucht nichts ausser Python 3 -- kein X11, kein xfreerdp, kein nmap.
Aufruf: python3 scripts/diagnose_rdp_tls.py 10.0.0.12 Aufruf: python3 scripts/diagnose_rdp_tls.py 10.0.0.12
@ -46,6 +48,12 @@ import socket
import ssl import ssl
import struct import struct
import sys import sys
import warnings
# ssl.TLSVersion.TLSv1/TLSv1_1 sind in neueren Python-Fassungen als veraltet
# markiert. Hier werden sie absichtlich benutzt -- um festzustellen, OB das
# Ziel sie noch spricht. Die Warnungen wuerden die Ausgabe nur zumuellen.
warnings.filterwarnings("ignore", category=DeprecationWarning, module=__name__)
# MS-RDPBCGR 2.2.1.1.1 -- requestedProtocols # MS-RDPBCGR 2.2.1.1.1 -- requestedProtocols
PROTOCOL_RDP = 0x00000000 # "Native RDP", ohne TLS PROTOCOL_RDP = 0x00000000 # "Native RDP", ohne TLS
@ -72,6 +80,85 @@ _FAILURE_CODES = {
0x06: "SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER", 0x06: "SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER",
} }
_VERSIONS = {
"TLSv1": ssl.TLSVersion.TLSv1,
"TLSv1.1": ssl.TLSVersion.TLSv1_1,
"TLSv1.2": ssl.TLSVersion.TLSv1_2,
"TLSv1.3": ssl.TLSVersion.TLSv1_3,
}
class Variante:
"""Eine zu testende OpenSSL-Einstellung.
`ansible` ist None, wenn die Variante keine Empfehlung darstellt
(System-Standard, oder reine Versionstests zur Information).
"""
def __init__(self, key, label, *, ciphers=None, min_version=None,
max_version=None, ansible=None, kandidat=False):
self.key = key
self.label = label
self.ciphers = ciphers
self.min_version = min_version
self.max_version = max_version
self.ansible = ansible
self.kandidat = kandidat # kommt als Empfehlung in Frage?
def context(self) -> ssl.SSLContext:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# Wie guacd mit ignore-cert: keinerlei Pruefung. Damit ist
# ausgeschlossen, dass ein Ergebnis an der Zertifikatspruefung haengt.
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
if self.ciphers:
ctx.set_ciphers(self.ciphers)
if self.min_version:
ctx.minimum_version = _VERSIONS[self.min_version]
if self.max_version:
ctx.maximum_version = _VERSIONS[self.max_version]
return ctx
# Reihenfolge = aufsteigende Staerke der Lockerung. Die erste funktionierende
# Variante mit kandidat=True ist die Empfehlung.
VARIANTEN = [
Variante("system", "System-Standard (genau das, was guacd/FreeRDP heute bekommt)"),
Variante("max12", "unveraendert streng, aber hoechstens TLS 1.2",
max_version="TLSv1.2", kandidat=True,
ansible={"guacd_tls_security_level": 2, "guacd_tls_min_protocol": "TLSv1.2",
"guacd_tls_max_protocol": "TLSv1.2"}),
Variante("sec1", "SECLEVEL=1 (SHA-1-Signaturen wieder erlaubt), bis TLS 1.3",
ciphers="DEFAULT@SECLEVEL=1", kandidat=True,
ansible={"guacd_tls_security_level": 1, "guacd_tls_min_protocol": "TLSv1.2",
"guacd_tls_max_protocol": ""}),
Variante("sec1_max12", "SECLEVEL=1 und hoechstens TLS 1.2",
ciphers="DEFAULT@SECLEVEL=1", max_version="TLSv1.2", kandidat=True,
ansible={"guacd_tls_security_level": 1, "guacd_tls_min_protocol": "TLSv1.2",
"guacd_tls_max_protocol": "TLSv1.2"}),
Variante("sec0_max12", "SECLEVEL=0, ab TLS 1.2, hoechstens TLS 1.2",
ciphers="DEFAULT@SECLEVEL=0", min_version="TLSv1.2", max_version="TLSv1.2",
kandidat=True,
ansible={"guacd_tls_security_level": 0, "guacd_tls_min_protocol": "TLSv1.2",
"guacd_tls_max_protocol": "TLSv1.2"}),
Variante("relaxed", "alles erlaubt: ALL@SECLEVEL=0, ab TLS 1.0",
ciphers="ALL@SECLEVEL=0", min_version="TLSv1", kandidat=True,
ansible={"guacd_tls_security_level": 0, "guacd_tls_min_protocol": "TLSv1",
"guacd_tls_max_protocol": ""}),
]
# Nur zur Information: welche Versionen spricht das Ziel ueberhaupt?
VERSIONSTESTS = [
Variante(f"nur {name}", f"nur {name}", ciphers="ALL@SECLEVEL=0",
min_version=name, max_version=name)
for name in ("TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3")
]
def _x224_connection_request(requested: int) -> bytes: def _x224_connection_request(requested: int) -> bytes:
"""TPKT + X.224 Connection Request mit angehaengtem RDP_NEG_REQ.""" """TPKT + X.224 Connection Request mit angehaengtem RDP_NEG_REQ."""
@ -81,14 +168,6 @@ def _x224_connection_request(requested: int) -> bytes:
return struct.pack(">BBH", 0x03, 0x00, 4 + len(x224)) + x224 return struct.pack(">BBH", 0x03, 0x00, 4 + len(x224)) + x224
def _read_tpkt(sock: socket.socket) -> bytes:
header = _recv_exact(sock, 4)
if header[0] != 0x03:
raise ValueError(f"Keine TPKT-Antwort (erstes Byte 0x{header[0]:02x}) -- spricht dort wirklich RDP?")
(length,) = struct.unpack(">H", header[2:4])
return header + _recv_exact(sock, length - 4)
def _recv_exact(sock: socket.socket, count: int) -> bytes: def _recv_exact(sock: socket.socket, count: int) -> bytes:
buf = b"" buf = b""
while len(buf) < count: while len(buf) < count:
@ -99,11 +178,18 @@ def _recv_exact(sock: socket.socket, count: int) -> bytes:
return buf return buf
def _read_tpkt(sock: socket.socket) -> bytes:
header = _recv_exact(sock, 4)
if header[0] != 0x03:
raise ValueError(f"Keine TPKT-Antwort (erstes Byte 0x{header[0]:02x}) -- spricht dort wirklich RDP?")
(length,) = struct.unpack(">H", header[2:4])
return header + _recv_exact(sock, length - 4)
def negotiate(sock: socket.socket, requested: int) -> int: def negotiate(sock: socket.socket, requested: int) -> int:
"""Fuehrt die X.224-Aushandlung durch, gibt das gewaehlte Protokoll zurueck.""" """X.224-Aushandlung; gibt das vom Server gewaehlte Protokoll zurueck."""
sock.sendall(_x224_connection_request(requested)) sock.sendall(_x224_connection_request(requested))
response = _read_tpkt(sock) body = _read_tpkt(sock)[4:]
body = response[4:]
if len(body) < 7: if len(body) < 7:
raise ValueError("X.224-Antwort zu kurz") raise ValueError("X.224-Antwort zu kurz")
payload = body[7:] payload = body[7:]
@ -113,56 +199,33 @@ def negotiate(sock: socket.socket, requested: int) -> int:
kind = payload[0] kind = payload[0]
if kind == 0x03: if kind == 0x03:
(code,) = struct.unpack("<I", payload[4:8]) (code,) = struct.unpack("<I", payload[4:8])
raise ValueError( raise ValueError("Der Server LEHNT die gewuenschte Sicherheitsstufe ab: "
"Der Server LEHNT die gewuenschte Sicherheitsstufe ab: " + _FAILURE_CODES.get(code, f"unbekannter failureCode 0x{code:02x}"))
+ _FAILURE_CODES.get(code, f"unbekannter failureCode 0x{code:02x}")
)
if kind != 0x02: if kind != 0x02:
raise ValueError(f"Unerwartete Antwortart 0x{kind:02x} in der X.224-Antwort") raise ValueError(f"Unerwartete Antwortart 0x{kind:02x} in der X.224-Antwort")
(selected,) = struct.unpack("<I", payload[4:8]) (selected,) = struct.unpack("<I", payload[4:8])
return selected return selected
def _context(variant: str) -> ssl.SSLContext: def try_handshake(host, port, requested, variante: Variante, timeout):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# Wie guacd mit ignore-cert: keinerlei Pruefung. Damit ist ausgeschlossen,
# dass ein Ergebnis hier an der Zertifikatspruefung haengt.
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
if variant == "system":
return ctx # erbt /etc/ssl/openssl.cnf -- genau wie guacd
if variant == "relaxed":
ctx.minimum_version = ssl.TLSVersion.TLSv1
ctx.set_ciphers("ALL:@SECLEVEL=0")
return ctx
if variant in ("tls1.0", "tls1.1", "tls1.2", "tls1.3"):
version = {
"tls1.0": ssl.TLSVersion.TLSv1,
"tls1.1": ssl.TLSVersion.TLSv1_1,
"tls1.2": ssl.TLSVersion.TLSv1_2,
"tls1.3": ssl.TLSVersion.TLSv1_3,
}[variant]
ctx.set_ciphers("ALL:@SECLEVEL=0")
ctx.minimum_version = version
ctx.maximum_version = version
return ctx
raise ValueError(variant)
def try_handshake(host: str, port: int, requested: int, variant: str, timeout: float):
"""Gibt (erfolg, text) zurueck."""
try: try:
with socket.create_connection((host, port), timeout=timeout) as sock: with socket.create_connection((host, port), timeout=timeout) as sock:
selected = negotiate(sock, requested) selected = negotiate(sock, requested)
if selected == PROTOCOL_RDP and requested != PROTOCOL_RDP: if selected == PROTOCOL_RDP and requested != PROTOCOL_RDP:
return (False, "Server waehlt 'Native RDP' -- er bietet auf dieser Stufe gar kein TLS an") return (False, "Server waehlt 'Native RDP' -- kein TLS auf dieser Stufe")
ctx = _context(variant) with variante.context().wrap_socket(sock, server_hostname=host) as tls:
with ctx.wrap_socket(sock, server_hostname=host) as tls:
cert = tls.getpeercert(binary_form=True) cert = tls.getpeercert(binary_form=True)
info = f"{tls.version()}, {tls.cipher()[0]}, Zertifikat {len(cert)} Byte" return (True, f"{tls.version()}, {tls.cipher()[0]}, Zertifikat {len(cert)} Byte")
return (True, info)
except ssl.SSLError as exc: except ssl.SSLError as exc:
return (False, f"{type(exc).__name__}: {exc.reason if getattr(exc, 'reason', None) else exc}") grund = getattr(exc, "reason", None) or exc
return (False, f"{type(exc).__name__}: {grund}")
except ConnectionResetError:
# Wichtig zu unterscheiden: KEIN lokaler OpenSSL-Abbruch, sondern die
# Gegenstelle legt auf. SCHANNEL (Windows) schickt ein TCP-RST statt
# einer sauberen handshake_failure-Warnung, wenn im ClientHello nichts
# Verwendbares steht -- also ein Aushandlungsproblem, kein Netzfehler.
return (False, "Connection reset by peer -- das Ziel hat aufgelegt "
"(nichts Passendes im ClientHello gefunden)")
except (OSError, ValueError) as exc: except (OSError, ValueError) as exc:
return (False, f"{type(exc).__name__}: {exc}") return (False, f"{type(exc).__name__}: {exc}")
@ -174,20 +237,16 @@ def main() -> int:
parser.add_argument("--timeout", type=float, default=10.0) parser.add_argument("--timeout", type=float, default=10.0)
args = parser.parse_args() args = parser.parse_args()
import ssl as _ssl print(f"Jumphost: {ssl.OPENSSL_VERSION}")
print(f"Jumphost: {_ssl.OPENSSL_VERSION}")
try: try:
with open("/etc/ssl/openssl.cnf", encoding="utf-8") as fh: with open("/etc/ssl/openssl.cnf", encoding="utf-8") as fh:
relevant = [ relevant = [line.strip() for line in fh
line.strip() for line in fh if line.strip().startswith(("MinProtocol", "MaxProtocol", "CipherString", "Options"))]
if line.strip().startswith(("MinProtocol", "CipherString", "MaxProtocol", "Options"))
]
print(" /etc/ssl/openssl.cnf: " + ("; ".join(relevant) if relevant else "keine Protokoll-/Cipher-Vorgaben")) print(" /etc/ssl/openssl.cnf: " + ("; ".join(relevant) if relevant else "keine Protokoll-/Cipher-Vorgaben"))
except OSError: except OSError:
print(" /etc/ssl/openssl.cnf nicht lesbar") print(" /etc/ssl/openssl.cnf nicht lesbar")
print(f"Ziel: {args.host}:{args.port}\n") print(f"Ziel: {args.host}:{args.port}\n")
# 1) Welche Sicherheitsstufe waehlt der Server, wenn man ihm alles anbietet?
print("== Schritt 1: X.224-Aushandlung ==") print("== Schritt 1: X.224-Aushandlung ==")
requested = PROTOCOL_SSL | PROTOCOL_HYBRID requested = PROTOCOL_SSL | PROTOCOL_HYBRID
try: try:
@ -196,8 +255,8 @@ def main() -> int:
print(f" Angeboten: TLS + CredSSP/NLA -> Server waehlt: " print(f" Angeboten: TLS + CredSSP/NLA -> Server waehlt: "
f"{_PROTOCOL_NAMES.get(selected, hex(selected))}") f"{_PROTOCOL_NAMES.get(selected, hex(selected))}")
except Exception as exc: except Exception as exc:
print(f" FEHLGESCHLAGEN: {exc}") print(f" FEHLGESCHLAGEN: {exc}\n")
print("\n Damit erübrigt sich der TLS-Test -- die RDP-Vorstufe scheitert bereits.") print(" Damit eruebrigt sich der TLS-Test -- die RDP-Vorstufe scheitert bereits.")
return 1 return 1
if selected == PROTOCOL_RDP: if selected == PROTOCOL_RDP:
@ -206,43 +265,52 @@ def main() -> int:
print(" (GPO: 'Bestimmte Sicherheitsebene fuer RDP-Verbindungen anfordern').") print(" (GPO: 'Bestimmte Sicherheitsebene fuer RDP-Verbindungen anfordern').")
return 1 return 1
# 2) TLS-Handshake unter verschiedenen OpenSSL-Vorgaben print("\n== Schritt 2: TLS-Handshake, aufsteigend gelockert ==")
print("\n== Schritt 2: TLS-Handshake (Zertifikatspruefung ueberall AUS, wie ignore-cert) ==") print(" (Zertifikatspruefung ueberall AUS -- wie bei ignore-cert)\n")
varianten = [
("system", "System-Standard (das, was guacd/FreeRDP bekommt)"),
("relaxed", "Alles erlaubt (SECLEVEL=0, ab TLS 1.0)"),
("tls1.0", "nur TLS 1.0"),
("tls1.1", "nur TLS 1.1"),
("tls1.2", "nur TLS 1.2"),
("tls1.3", "nur TLS 1.3"),
]
ergebnisse = {} ergebnisse = {}
for variant, label in varianten: for v in VARIANTEN:
ok, text = try_handshake(args.host, args.port, requested, variant, args.timeout) ok, text = try_handshake(args.host, args.port, requested, v, args.timeout)
ergebnisse[variant] = ok ergebnisse[v.key] = ok
print(f" [{'OK ' if ok else 'FEHL'}] {label}\n {text}") print(f" [{'OK ' if ok else 'FEHL'}] {v.label}\n {text}")
print("\n== Schritt 3: welche TLS-Versionen spricht das Ziel? ==")
gesprochen = []
for v in VERSIONSTESTS:
ok, text = try_handshake(args.host, args.port, requested, v, args.timeout)
if ok:
gesprochen.append(v.label.replace("nur ", ""))
print(f" [{'OK ' if ok else 'FEHL'}] {v.label}: {text}")
# 3) Auswertung
print("\n== Befund ==") print("\n== Befund ==")
if ergebnisse["system"]: if ergebnisse["system"]:
print(" Der TLS-Handshake gelingt mit den System-Vorgaben. Die Ursache liegt") print(" Der TLS-Handshake gelingt bereits mit den System-Vorgaben.")
print(" dann NICHT in der TLS-Schicht, sondern eine Stufe weiter (CredSSP/NLA,") print(" Die Ursache liegt dann NICHT in der TLS-Schicht, sondern eine Stufe")
print(" Anmeldedaten) oder in guacd/FreeRDP selbst.") print(" weiter (CredSSP/NLA, Anmeldedaten) oder in guacd/FreeRDP selbst.")
print(" Naechster Schritt: guacd_log_level auf 'debug' und erneut verbinden.") print(" Naechster Schritt: guacd_log_level auf 'debug', erneut verbinden,")
print(" 'journalctl -u guacd -n 200' auswerten.")
return 0 return 0
if ergebnisse["relaxed"]:
moeglich = [v for v in ("tls1.0", "tls1.1", "tls1.2", "tls1.3") if ergebnisse[v]] empfehlung = next((v for v in VARIANTEN if v.kandidat and ergebnisse.get(v.key)), None)
print(" EINDEUTIG: Der Handshake scheitert NUR an den OpenSSL-Vorgaben DIESES") if empfehlung is None:
print(" Jumphosts, nicht am Ziel und nicht am Zertifikat.")
print(f" Das Ziel spricht: {', '.join(moeglich) if moeglich else '(nur mit SECLEVEL=0)'}")
print(" Debian 12 setzt systemweit MinProtocol=TLSv1.2 und DEFAULT@SECLEVEL=2;")
print(" guacd erbt das. Abhilfe: eine eigene OPENSSL_CONF NUR fuer die")
print(" guacd-Unit (nicht systemweit lockern).")
return 1
print(" Der Handshake scheitert in JEDER Variante -- die Ursache liegt am Ziel") print(" Der Handshake scheitert in JEDER Variante -- die Ursache liegt am Ziel")
print(" (10.0.0.12) oder dazwischen, nicht an den OpenSSL-Vorgaben des Jumphosts.") print(f" ({args.host}) oder dazwischen, nicht an den OpenSSL-Vorgaben des Jumphosts.")
print(" Am Windows-Ziel pruefen: SCHANNEL-Registry (aktivierte TLS-Versionen),") print(" Am Windows-Ziel pruefen: SCHANNEL-Registry (aktivierte TLS-Versionen und")
print(" Cipher-Suite-Richtlinie, und ob ein RDP-Zertifikat hinterlegt ist.") print(" Cipher-Suites), Gruppenrichtlinie 'SSL-Verschluesselungsverfahren', und ob")
print(" ueberhaupt ein RDP-Zertifikat hinterlegt ist.")
return 1
print(" Der Handshake scheitert an den OpenSSL-Vorgaben DIESES Jumphosts,")
print(" nicht am Ziel und nicht am Zertifikat.")
print(f" Das Ziel spricht: {', '.join(gesprochen) if gesprochen else '(unklar)'}")
print(f"\n MILDESTE ausreichende Lockerung: {empfehlung.label}")
print("\n In ansible/inventory/group_vars eintragen:")
print(" guacd_tls_legacy_compat: true")
for key, value in empfehlung.ansible.items():
gezeigt = '""' if value == "" else (f'"{value}"' if isinstance(value, str) else value)
print(f" {key}: {gezeigt}")
print("\n Das wirkt NUR auf den guacd-Prozess (Environment=OPENSSL_CONF in dessen")
print(" Unit), nicht systemweit. Die saubere Loesung bleibt, das Windows-Ziel")
print(" passend zu konfigurieren -- dies ist die Ueberbrueckung bis dahin.")
return 1 return 1