Server 2.10.4: HTTPS for the web panel and hostile client hardening

- Panel over HTTPS on the game port (TLS handshake detected like HTTP); self-signed
  certificate in data/panel_cert.pfx (fingerprint in the log) or an own PFX
  (PanelCertificatePath); remote HTTP is redirected to HTTPS (not locally / behind
  a proxy); cookie Secure over HTTPS; PanelHttps=false turns it off
- Panel login: also locked per user name (16 failures / 15 min), failure lists
  bounded, user names in the log without control characters
- panel_users.json, panel_sessions.json, server.json, certificate: owner-only on Linux
- Players: names/chat without control or direction characters (no fake log lines),
  no '<' '>' in names; '* ' system-style chat, msco.* messages (time/weather) and
  MSCO_INSTALL only from admins; NaN/absurd money changes dropped (could break the
  shared money for good)
- Extra headers: Permissions-Policy, Cross-Origin-Opener-Policy
- 11 new tests (165 pass)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 18:38:54 +02:00
co-authored by Claude Opus 5.5
parent d946c95198
commit 4cc71aa079
10 changed files with 315 additions and 20 deletions
+18 -1
View File
@@ -54,6 +54,16 @@ public sealed class ServerConfig
public bool PanelTrustProxy { get; set; } = false;
/// <summary>Unterpfad, unter dem das Panel läuft, z.B. "/panel" (leer = direkt unter /).</summary>
public string PanelBasePath { get; set; } = "";
/// <summary>
/// HTTPS im Panel. Auf dem Spiel-Port immer verfügbar (https://&lt;server&gt;:&lt;port&gt;/); true = Browser, die von
/// außen per HTTP kommen, werden auf HTTPS umgeleitet (Passwörter nie unverschlüsselt). Bei eigenem PanelPort spricht
/// dieser nur mit eigenem Zertifikat (PanelCertificatePath) HTTPS. Hinter einem Reverse-Proxy (PanelTrustProxy) macht
/// der Proxy HTTPS.
/// </summary>
public bool PanelHttps { get; set; } = true;
/// <summary>Eigenes Zertifikat (PFX, Pfad absolut oder relativ zu data/) - leer = selbst signiertes in data/panel_cert.pfx.</summary>
public string PanelCertificatePath { get; set; } = "";
public string PanelCertificatePassword { get; set; } = "";
static readonly JsonSerializerOptions Options = new() { WriteIndented = true };
@@ -72,7 +82,11 @@ public sealed class ServerConfig
return config;
}
public void Save(string path) => File.WriteAllText(path, JsonSerializer.Serialize(this, Options));
public void Save(string path)
{
File.WriteAllText(path, JsonSerializer.Serialize(this, Options));
PanelTls.Restrict(path); // enthält den Lizenzschlüssel und Passwörter (Linux: nur der Server-Benutzer)
}
/// <summary>
/// Umgebungsvariablen überschreiben server.json (praktisch für Docker/systemd):
@@ -111,6 +125,9 @@ public sealed class ServerConfig
if (bool.TryParse(Env("MSCO_PANEL_TRUST_PROXY"), out bool trustProxy)) { PanelTrustProxy = trustProxy; used.Add("MSCO_PANEL_TRUST_PROXY"); }
PanelBindAddress = Take("MSCO_PANEL_BIND", PanelBindAddress);
PanelBasePath = Take("MSCO_PANEL_BASE_PATH", PanelBasePath);
if (bool.TryParse(Env("MSCO_PANEL_HTTPS"), out bool panelHttps)) { PanelHttps = panelHttps; used.Add("MSCO_PANEL_HTTPS"); }
PanelCertificatePath = Take("MSCO_PANEL_CERT", PanelCertificatePath);
PanelCertificatePassword = Take("MSCO_PANEL_CERT_PASSWORD", PanelCertificatePassword);
return used;
}