umbau 1.3.2

This commit is contained in:
2026-09-03 10:34:01 +02:00
parent b2c72de069
commit 0728a9af27
7 changed files with 392 additions and 53 deletions

View File

@ -0,0 +1,126 @@
/*
* 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 <openssl/ssl.h>: der SSL_CTX-Zeiger wird nur durchgereicht, nie
* dereferenziert. Damit braucht das Uebersetzen nur gcc, kein libssl-dev.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <syslog.h>
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);
}