127 lines
5.6 KiB
C
127 lines
5.6 KiB
C
/*
|
|
* 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);
|
|
}
|