/* * guacd-tls-seclevel-shim -- setzt die OpenSSL-Sicherheitsstufe, die FreeRDP * fuer seine RDP-Verbindungen verwendet. * * WOZU (Live-Test 3.9.2026, jumphost-97 -> 10.0.0.12) * --------------------------------------------------- * RDP-Verbindungen scheiterten mit * ERRCONNECT_TLS_CONNECT_FAILED [0x00020008] * und guacds daraus abgeleiteter Meldung * "SSL/TLS connection failed (untrusted/self-signed certificate?)". * Der Klammerzusatz ist irrefuehrend -- guacd setzt diesen festen Text fuer * genau diesen FreeRDP-Fehlercode. Am Zertifikat lag es nicht: im Journal * fehlte jede "Certificate validation ..."-Zeile, der Zertifikats-Rueckruf * wurde also nie erreicht. * * Gemessen mit scripts/diagnose_rdp_tls.py: * OpenSSL-Sicherheitsstufe 2 -> Verbindung vom Ziel zurueckgesetzt (TCP-RST) * OpenSSL-Sicherheitsstufe 1 -> IDENTISCH zurueckgesetzt * OpenSSL-Sicherheitsstufe 0 -> erfolgreich, TLS 1.2, ECDHE-RSA-AES256-SHA384 * * Die Cipher-Listen sind auf allen drei Stufen identisch (gegengeprueft mit * `openssl ciphers -s 'DEFAULT@SECLEVEL=0|1|2'` -- jeweils dieselben 30). Der * Unterschied liegt also in der signature_algorithms-Erweiterung: OpenSSL 3.0 * bietet SHA-1 ab Stufe 1 nicht mehr als Signaturverfahren an. Das Windows-Ziel * kann den ServerKeyExchange offenbar nicht anders signieren, findet nichts * Verwendbares und legt auf -- SCHANNEL schickt dabei ein TCP-RST statt einer * sauberen handshake_failure-Warnung. * * WARUM NICHT EINFACH OPENSSL_CONF * -------------------------------- * Wurde versucht und reicht nicht. FreeRDP 2 setzt die Sicherheitsstufe SELBST * (Kommandozeilenoption /tls-seclevel, Vorgabe 1) und ruft dafuer nach dem * Anlegen des SSL_CTX SSL_CTX_set_security_level() auf. Das ueberschreibt jede * Vorgabe aus /etc/ssl/openssl.cnf bzw. aus einer eigenen OPENSSL_CONF. * guacamole-server 1.5.5 reicht diese FreeRDP-Option nicht als RDP-Parameter * durch -- es gibt also keinen Konfigurationsweg dorthin. * * WARUM DIESER WEG UND NICHT EIN QUELLCODE-PATCH * ---------------------------------------------- * Ein Patch an guacamole-server muesste das Argument-Array GUAC_RDP_CLIENT_ARGS * und das dazu positionsgleiche IDX-Enum in src/protocols/rdp/settings.c * gemeinsam erweitern. Das ist bei jedem Versionswechsel neu anzupassen und * bricht den Build, sobald sich dort etwas verschiebt -- auf einem Host, der * guacd bei jedem Ansible-Lauf aus dem Quellcode baut, ein schlechtes Geschaeft. * Diese Zwischenschicht ist dagegen versionsunabhaengig, kann den Build nicht * beschaedigen und laesst sich durch Entfernen einer Environment-Zeile in der * guacd-Unit vollstaendig zuruecknehmen. * * VERHALTEN * --------- * Ohne die Umgebungsvariable JUMPHOST_GUACD_TLS_SECLEVEL tut diese Bibliothek * NICHTS: sie reicht jeden Aufruf unveraendert weiter. Erst ein gesetzter, * nicht-negativer Wert erzwingt diese Stufe. Die erste tatsaechliche Aenderung * wird nach syslog gemeldet, damit im Journal nachvollziehbar steht, dass hier * etwas eingreift. * * REICHWEITE * ---------- * Wird ausschliesslich ueber Environment=LD_PRELOAD= in der guacd-Unit geladen * und wirkt damit nur im guacd-Prozess. Da guacd auf diesem Jumphost nur RDP * spricht und selbst kein TLS anbietet (bind_host=127.0.0.1, kein [ssl]- * Abschnitt in guacd.conf), betrifft die Absenkung genau die Strecke * guacd -> Windows-Ziel. Browser -> Jumphost, nginx, die Anwendung, SSH und * apt bleiben unberuehrt. * * EINSCHRAENKUNG * -------------- * Die Stufe gilt fuer ALLE RDP-Ziele dieses Jumphosts, nicht je Host. Eine * Einstellung je Host waere nur ueber den oben verworfenen Quellcode-Patch zu * haben. * * Bewusst ohne : der SSL_CTX-Zeiger wird nur durchgereicht, nie * dereferenziert. Damit braucht das Uebersetzen nur gcc, kein libssl-dev. */ #define _GNU_SOURCE #include #include #include typedef struct ssl_ctx_st SSL_CTX; static int gewuenschte_stufe = -1; /* -1 = nicht eingreifen */ static int stufe_gelesen = 0; static int bereits_gemeldet = 0; static void stufe_lesen(void) { const char* wert = getenv("JUMPHOST_GUACD_TLS_SECLEVEL"); if (wert != NULL && *wert != '\0') { char* ende = NULL; long gelesen = strtol(wert, &ende, 10); /* Nur vollstaendig geparste Werte im gueltigen OpenSSL-Bereich 0..5 * uebernehmen. Bei Unsinn in der Variablen lieber nicht eingreifen, * als eine unbeabsichtigte Stufe zu erzwingen. */ if (ende != NULL && *ende == '\0' && gelesen >= 0 && gelesen <= 5) gewuenschte_stufe = (int) gelesen; else syslog(LOG_WARNING, "guacd-tls-seclevel-shim: unbrauchbarer Wert " "JUMPHOST_GUACD_TLS_SECLEVEL=\"%s\", bleibe untaetig", wert); } stufe_gelesen = 1; } void SSL_CTX_set_security_level(SSL_CTX* ctx, int stufe) { static void (*original)(SSL_CTX*, int) = NULL; if (original == NULL) original = (void (*)(SSL_CTX*, int)) dlsym(RTLD_NEXT, "SSL_CTX_set_security_level"); if (!stufe_gelesen) stufe_lesen(); if (gewuenschte_stufe >= 0 && stufe != gewuenschte_stufe) { if (!bereits_gemeldet) { syslog(LOG_INFO, "guacd-tls-seclevel-shim: OpenSSL-Sicherheitsstufe %d -> %d " "(siehe ansible/roles/guacd, guacd_tls_seclevel)", stufe, gewuenschte_stufe); bereits_gemeldet = 1; } stufe = gewuenschte_stufe; } /* Fehlt das Original wider Erwarten, ist Nichtstun richtig: OpenSSL bleibt * dann bei seiner Vorgabe, statt dass der Aufruf ins Leere laeuft. */ if (original != NULL) original(ctx, stufe); }