Version 2.8.14: Diagnose in der Bildschirmsitzung, Vorauswahl ohne Passwort

This commit is contained in:
alientim committed 2026-09-13 11:31:41 +02:00
1 parent 436b080deb
commit f3b312046f
10 files changed
+116 -6

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm"
version = "2.8.13"
version = "2.8.14"
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
requires-python = ">=3.11"
dependencies = [
+5 -1
View File
@@ -424,7 +424,11 @@ def vnc_connection(device_id: int):
f"Verbindungsdatei für {ziel}:{port}",
)
return Response(
vnc.verbindungsdatei(device, port=port),
vnc.verbindungsdatei(
device,
port=port,
warnung_aus=bool(extension.settings.get(conn, "vnc_warn_unencrypted_off")),
),
mimetype="application/octet-stream",
headers={
"Content-Disposition": f'attachment; filename="{vnc.dateiname(device)}"',
@@ -105,6 +105,7 @@ def system():
"dashboard_public",
"vnc_enabled",
"vnc_port",
"vnc_warn_unencrypted_off",
"terminal_switches_enabled",
"terminal_clients_enabled",
"terminal_idle_timeout",
@@ -147,6 +148,7 @@ def system_save():
"dashboard_public",
"vnc_enabled",
"vnc_port",
"vnc_warn_unencrypted_off",
"terminal_switches_enabled",
"terminal_clients_enabled",
"terminal_idle_timeout",
+14 -1
View File
@@ -235,13 +235,24 @@ def zugang(device: Mapping[str, Any], *, port: int = STANDARD_PORT, pruefen: boo
return Zugang(ziel=host, port=port, erreichbar=bool(fassung), verfahren=verfahren)
def verbindungsdatei(device: Mapping[str, Any], *, port: int = STANDARD_PORT) -> str:
def verbindungsdatei(
device: Mapping[str, Any], *, port: int = STANDARD_PORT, warnung_aus: bool = False
) -> str:
"""Der Inhalt der ``.vnc``-Datei -- drei Zeilen, keine vierte.
``ConnMethod=tcp`` ist die direkte Verbindung (nicht ueber den Cloud-Dienst
von RealVNC), ``FriendlyName`` der Name, den der Viewer anzeigt, ``Host``
das Ziel. Ein abweichender Port gehoert in dieser Datei an den Host, mit
Doppelpunkt -- einen eigenen Schluessel dafuer gibt es nicht.
``warnung_aus`` haengt eine vierte Zeile an: ``WarnUnencrypted=False``. Sie
betrifft **den Viewer**, nicht den Server -- er zeigt sonst bei jeder
Verbindung zu einem Geraet ohne Streckenverschluesselung einen roten
Hinweis, den man wegklicken muss. Wer ``Encryption=AlwaysOff`` auf dem
Geraet gesetzt hat (damit der Browser ueberhaupt verbinden kann), sieht
diesen Hinweis fortan bei jedem Start. Er bleibt in der Vorgabe stehen:
eine unverschluesselte Strecke soll man einmal bewusst hinnehmen, nicht
stillschweigend. Abschaltbar unter Verwaltung -> System.
"""
host = ziel(device)
if port != STANDARD_PORT:
@@ -251,6 +262,8 @@ def verbindungsdatei(device: Mapping[str, Any], *, port: int = STANDARD_PORT) ->
f"FriendlyName={str(device.get('name') or host).strip()}",
f"Host={host}",
]
if warnung_aus:
zeilen.append("WarnUnencrypted=False")
return ZEILE.join(zeilen) + ZEILE
+10
View File
@@ -66,6 +66,16 @@ TESM_SETTINGS: tuple[SettingSpec, ...] = (
minimum=1,
maximum=65535,
),
SettingSpec(
"vnc_warn_unencrypted_off",
False,
"bool",
"Warnung des Viewers vor unverschlüsselter Verbindung unterdrücken",
hint="Setzt WarnUnencrypted=False in die Verbindungsdatei. Betrifft nur den "
"RealVNC Viewer auf dem Arbeitsplatz, nicht die Verbindung selbst: die bleibt "
"unverschlüsselt, wenn das Gerät es so anbietet.",
section="monitoring",
),
# -- DHCP --------------------------------------------------------------- #
SettingSpec("dhcp_domain", "local", "str", "Domainname", section="dhcp"),
SettingSpec(
@@ -74,6 +74,7 @@
<div id="screen" class="screen"
data-socket="{{ websocket_path }}"
data-ziel="{{ zugang.ziel }}:{{ zugang.port }}"
data-verfahren="{{ zugang.verfahren | join(',') }}"
data-blockiert="{{ '1' if zugang.hinderungsgrund else '0' }}"
data-grund="{{ zugang.hinderungsgrund }}">
<div id="screen-canvas" class="screen__canvas" hidden></div>
@@ -53,6 +53,10 @@
{{ field('vnc_port', 'VNC-Port', value=values.vnc_port, type='number',
hint_text=specs['vnc_port'].hint) }}
</div>
{{ checkbox('vnc_warn_unencrypted_off',
'Warnung des Viewers vor unverschlüsselter Verbindung unterdrücken',
checked=values.vnc_warn_unencrypted_off,
hint_text=specs['vnc_warn_unencrypted_off'].hint) }}
</div>
<div class="card__footer"><button class="btn btn--primary" type="submit">Speichern</button></div>
</form>
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm-core"
version = "2.8.5"
version = "2.8.6"
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
requires-python = ">=3.11"
dependencies = [
@@ -11,6 +11,25 @@
import RFB from "../vendor/novnc/core/rfb.js";
// Sicherheitsverfahren, die ohne Zugangsdaten auskommen. Bietet der Server
// eines davon an und haben wir keine, nehmen wir es -- auch wenn der Server
// ein anderes zuerst nennt.
//
// Warum das noetig ist: noVNC waehlt "das erste, das ich kann, in der
// Reihenfolge des Servers". Ein RealVNC Server nennt RA2ne vor "ohne
// Authentifizierung"; der Browser liefe also in eine Passwortabfrage, obwohl
// daneben ein Weg offensteht, den auch der RealVNC Viewer nimmt. Wer ein
// Passwort hinterlegt hat, behaelt die Reihenfolge des Servers -- dann ist
// der angemeldete Weg der bessere.
const OHNE_ZUGANGSDATEN = new Set([1]);
// Der Eingriff sitzt in einer privaten Methode der mitgelieferten Bibliothek.
// Das ist bewusst hier und nicht in der Bibliothek selbst: eine neue Fassung
// von noVNC laesst sich dann austauschen, ohne eine Aenderung nachzupflegen --
// faellt die Methode weg, greift die Vorauswahl nicht mehr, und die Sitzung
// laeuft wieder wie ohne sie. Kein stiller Bruch.
const istUnterstuetzt = RFB.prototype._isSupportedSecurityType;
const wurzel = document.getElementById("screen");
if (wurzel) {
starten(wurzel);
@@ -35,14 +54,58 @@ function starten(wurzel) {
);
let rfb = null;
let letzterFehler = "";
function melden(text, art = "") {
zeile.textContent = text;
zeile.className = `screen__status${art ? " screen__status--" + art : ""}`;
}
// noVNC meldet den *Grund* eines Fehlschlags ausschliesslich ins Protokoll
// der Entwicklerkonsole -- das Ereignis "disconnect" trägt ihn nicht. Ohne
// diesen Griff bliebe der Seite nur "Verbindung abgebrochen", und damit
// steht der Anwender genau dort, wo er ohne die ganze Anzeige auch stuende.
// Darum wird die Fehlerausgabe waehrend der Sitzung mitgelesen -- und
// hinterher wieder losgelassen.
const echteAusgabe = console.error.bind(console);
console.error = (...teile) => {
letzterFehler = teile.map((teil) => String(teil)).join(" ");
echteAusgabe(...teile);
};
window.addEventListener("beforeunload", () => {
console.error = echteAusgabe;
});
function grund() {
const teile = [];
if (letzterFehler) {
teile.push(letzterFehler.replace(/^Failed (when|while) \w+: /, ""));
}
if (rfb && rfb._rfbAuthScheme > 0) {
teile.push(`gewähltes Verfahren: ${rfb._rfbAuthScheme}`);
}
return teile.join(" · ");
}
function verbinden(zugang) {
melden("Verbindung wird aufgebaut …");
letzterFehler = "";
// Vorauswahl nur, wenn wir nichts anzubieten haben: dann zaehlt der Weg,
// der ohne Zugangsdaten auskommt -- sofern der Server ihn anbietet.
const hatZugangsdaten = Boolean(zugang && (zugang.password || zugang.username));
const angeboten = (wurzel.dataset.verfahren || "")
.split(",")
.map((wert) => parseInt(wert, 10))
.filter((wert) => !Number.isNaN(wert));
const ohnePasswortMoeglich = angeboten.some((wert) => OHNE_ZUGANGSDATEN.has(wert));
RFB.prototype._isSupportedSecurityType = function (typ) {
if (!hatZugangsdaten && ohnePasswortMoeglich) {
return OHNE_ZUGANGSDATEN.has(typ);
}
return istUnterstuetzt.call(this, typ);
};
rfb = new RFB(leinwand, adresse, {
credentials: zugang,
// Eine Bildschirmsitzung soll nicht nebenbei die Zwischenablage des
@@ -69,9 +132,12 @@ function starten(wurzel) {
if (ereignis.detail && ereignis.detail.clean) {
melden("Sitzung beendet.");
} else {
const erklaerung = grund();
melden(
"Verbindung abgebrochen. Antwortet der VNC-Server noch, und lässt der " +
"Arbeitsplatz die Verbindung zu?",
erklaerung
? `Verbindung abgebrochen – ${erklaerung}`
: "Verbindung abgebrochen, ohne dass der Client einen Grund nennt. " +
"Antwortet der VNC-Server noch?",
"fehler",
);
}
@@ -20,6 +20,16 @@ der einen Bildschirm fernsteuert.
Datei -- mit Grund und Fundstelle, damit ein Wechsel auf eine neuere Fassung
nicht stillschweigend darueberlaeuft.
**Was TESM von aussen umbiegt** (also *nicht* in diesen Dateien steht, sondern
in `js/screen.js`): `RFB.prototype._isSupportedSecurityType` wird ueberschrieben,
solange keine Zugangsdaten vorliegen und der Server ein Verfahren ohne
Anmeldung anbietet. Grund: noVNC nimmt "das erste, das ich kann, in der
Reihenfolge des Servers"; ein RealVNC Server nennt RA2ne vor "ohne
Authentifizierung", und der Browser liefe in eine Passwortabfrage, obwohl
daneben der Weg offensteht, den auch der RealVNC Viewer nimmt. Faellt die
Methode in einer neueren Fassung weg, greift die Vorauswahl nicht mehr -- die
Sitzung laeuft dann wie ohne sie, es bricht nichts.
**Was noVNC an Sicherheitsverfahren spricht** (`core/rfb.js`,
`_isSupportedSecurityType`): `1` None, `2` VNC-Passwort, `6` RA2ne (RSA-AES
ohne Streckenverschluesselung), `16` Tight, `19` VeNCrypt, `22` xvp, `30` ARD,