feat(satellite): Sicherungs-Kennwort je FritzBox — hinterlegt, stark erzwungen, ARIA kennt es nicht
- Zugangsdaten FritzBox: neues Feld "Kennwort fuer Sicherungen" (backupPass, verschluesselt auf dem Satelliten, mit 👁 wie die anderen). - fritzbox.backup nimmt: ausdruecklich angegebenes > hinterlegtes Sicherungs-Kennwort > FritzBox-Login. Das Ergebnis nennt nur die Quelle, nie das Kennwort. ARIAs Beschreibung: kein 'password' uebergeben. - Kennwort-Regel (Formular UND Satellit): mind. 12 Zeichen, Gross- und Kleinbuchstabe, Ziffer, Sonderzeichen. Schwaches Kennwort wird beim Speichern abgelehnt (Meldung im Formular), und eine Sicherung mit schwachem Kennwort (auch ueber das Login) bricht mit Hinweis ab. - Formular: Eingabefelder mit Auge gleich hoch wie die anderen. Getestet im Satelliten: Regel, Speichern schwach/stark (Login bleibt), Sicherung ohne/mit hinterlegtem Kennwort; das Kennwort taucht im Ergebnis nicht auf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+4
-2
@@ -1313,8 +1313,10 @@ META_TOOLS = [
|
||||
"Stefan, ob er an der Box steht; nach seinem Ja erneut mit button_wait=true "
|
||||
"aufrufen, dann hat er 2 Minuten fuer einen Tastendruck (sonst Timeout, "
|
||||
"nichts passiert — einfach nachfragen und neu versuchen). 'fritzbox.backup' "
|
||||
"(params={'ip':..,'password':'<Kennwort der Sicherung>','button_wait':true}) "
|
||||
"sichert die Einstellungen; die Datei liegt danach unter /shared/uploads/ "
|
||||
"(params={'ip':..,'button_wait':true}) sichert die Einstellungen — OHNE "
|
||||
"'password': der Satellit nimmt das hinterlegte Sicherungs-Kennwort (das "
|
||||
"Du nicht kennst und nicht kennen sollst); 'password' nur, wenn Stefan "
|
||||
"ausdruecklich ein anderes will. die Datei liegt danach unter /shared/uploads/ "
|
||||
"und kommt mit [FILE: <pfad>] in den Chat. 'ssh.exec' (params={'ip':'...','cmd':'...'}) "
|
||||
"— fuehrt EIN Shell-Kommando per SSH auf einem Geraet aus (Server, Pi, NAS) "
|
||||
"und gibt exit_code + stdout (gefenstert: contains/offset/max_chars) + "
|
||||
|
||||
+25
-9
@@ -299,7 +299,7 @@
|
||||
</select>
|
||||
<div id="dc-snmp-community-row">
|
||||
<label style="font-size:11px;color:#8888AA;">Community</label>
|
||||
<div style="display:flex;gap:4px;margin-bottom:4px;"><input id="dc-snmp-community" type="password" placeholder="public" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-snmp-community')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
<div style="display:flex;gap:4px;align-items:center;margin-bottom:4px;"><input id="dc-snmp-community" type="password" placeholder="public" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-snmp-community')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
</div>
|
||||
<div id="dc-snmp-v3" style="display:none;">
|
||||
<input id="dc-v3-user" placeholder="v3 User" style="width:100%;margin-bottom:4px;">
|
||||
@@ -322,14 +322,15 @@
|
||||
<div style="border:1px solid #1E1E2E;border-radius:6px;padding:10px;">
|
||||
<div style="color:#E0E0F0;font-weight:600;margin-bottom:6px;">HTTP-Basic</div>
|
||||
<input id="dc-http-user" placeholder="Benutzer (optional)" style="width:100%;margin-bottom:4px;">
|
||||
<div style="display:flex;gap:4px;"><input id="dc-http-pass" type="password" placeholder="Passwort" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-http-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
<div style="display:flex;gap:4px;align-items:center;"><input id="dc-http-pass" type="password" placeholder="Passwort" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-http-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid #1E1E2E;border-radius:6px;padding:10px;">
|
||||
<div style="color:#E0E0F0;font-weight:600;margin-bottom:6px;">FritzBox (TR-064)</div>
|
||||
<div style="display:flex;gap:4px;margin-bottom:4px;"><input id="dc-fb-user" placeholder="FritzBox-Benutzer (z.B. fritz1234)" style="flex:1;">
|
||||
<div style="display:flex;gap:4px;align-items:center;margin-bottom:4px;"><input id="dc-fb-user" placeholder="FritzBox-Benutzer (z.B. fritz1234)" style="flex:1;">
|
||||
<button type="button" id="dc-fb-detect" class="btn secondary" onclick="dcDetectFritzUser()" title="Fragt die FritzBox, welche Benutzer es gibt (geht ohne Anmeldung)" style="padding:2px 8px;font-size:11px;white-space:nowrap;">Benutzer ermitteln</button></div>
|
||||
<div style="display:flex;gap:4px;"><input id="dc-fb-pass" type="password" placeholder="FritzBox-Passwort" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-fb-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
<div style="display:flex;gap:4px;align-items:center;margin-bottom:4px;"><input id="dc-fb-pass" type="password" placeholder="FritzBox-Passwort" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-fb-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
<div style="display:flex;gap:4px;align-items:center;"><input id="dc-fb-backuppass" type="password" placeholder="Kennwort für Sicherungen (mind. 12 Zeichen, Aa, 1, !)" title="Damit verschlüsselt ARIA Sicherungen der Box. ARIA nutzt es, kennt es aber nicht — zum Wiederherstellen brauchst du es." style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-fb-backuppass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
</div>
|
||||
|
||||
<div style="border:1px solid #1E1E2E;border-radius:6px;padding:10px;">
|
||||
@@ -338,7 +339,7 @@
|
||||
<input id="dc-ssh-user" placeholder="Benutzer" style="flex:2;">
|
||||
<input id="dc-ssh-port" placeholder="Port 22" style="flex:1;">
|
||||
</div>
|
||||
<div style="display:flex;gap:4px;margin-bottom:4px;"><input id="dc-ssh-pass" type="password" placeholder="Passwort (oder Key unten)" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-ssh-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
<div style="display:flex;gap:4px;align-items:center;margin-bottom:4px;"><input id="dc-ssh-pass" type="password" placeholder="Passwort (oder Key unten)" style="flex:1;"><button type="button" class="btn secondary" onclick="dcEye('dc-ssh-pass')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
<div style="display:flex;gap:4px;"><textarea id="dc-ssh-key" placeholder="Privater SSH-Key (optional, PEM/OpenSSH)" style="flex:1;height:60px;font-family:monospace;font-size:10px;"></textarea><button type="button" class="btn secondary" onclick="dcEye('dc-ssh-key')" title="Anzeigen / verbergen" style="padding:2px 8px;font-size:12px;">👁</button></div>
|
||||
</div>
|
||||
|
||||
@@ -2716,7 +2717,7 @@
|
||||
if (msg.type === 'sat_creds_result') {
|
||||
const p = msg.payload || msg;
|
||||
const el = document.getElementById('dc-status');
|
||||
if (el) el.textContent = p.ok ? '✓ Gespeichert.' : '⚠ Fehlgeschlagen.';
|
||||
if (el) el.textContent = p.ok ? '✓ Gespeichert.' : ('⚠ ' + (p.error || 'Fehlgeschlagen.'));
|
||||
if (p.ok && p.satellite) requestSatCreds(p.satellite);
|
||||
if (p.ok) setTimeout(closeDeviceCreds, 700);
|
||||
return;
|
||||
@@ -6326,7 +6327,8 @@
|
||||
const DC_SECRET_FIELDS = {
|
||||
'dc-snmp-community': ['snmp', 'community'], 'dc-v3-authkey': ['snmp', 'v3', 'authKey'],
|
||||
'dc-v3-privkey': ['snmp', 'v3', 'privKey'], 'dc-http-pass': ['http', 'pass'],
|
||||
'dc-fb-pass': ['fritzbox', 'pass'], 'dc-ssh-pass': ['ssh', 'pass'], 'dc-ssh-key': ['ssh', 'key'],
|
||||
'dc-fb-pass': ['fritzbox', 'pass'], 'dc-fb-backuppass': ['fritzbox', 'backupPass'],
|
||||
'dc-ssh-pass': ['ssh', 'pass'], 'dc-ssh-key': ['ssh', 'key'],
|
||||
};
|
||||
let dcStored = {}, dcRevealed = false, dcFilled = false, dcFbReq = '';
|
||||
const _dcEl = id => document.getElementById(id);
|
||||
@@ -6334,7 +6336,7 @@
|
||||
credsModalSat = satId; credsModalIp = ip;
|
||||
dcStored = {}; dcRevealed = false; dcFilled = false; dcFbReq = '';
|
||||
_dcEl('dev-creds-title').textContent = (name || ip) + ' (' + ip + ')';
|
||||
['dc-snmp-community','dc-v3-user','dc-v3-authkey','dc-v3-privkey','dc-http-user','dc-http-pass','dc-fb-user','dc-fb-pass','dc-ssh-user','dc-ssh-port','dc-ssh-pass','dc-ssh-key'].forEach(id => {
|
||||
['dc-snmp-community','dc-v3-user','dc-v3-authkey','dc-v3-privkey','dc-http-user','dc-http-pass','dc-fb-user','dc-fb-pass','dc-fb-backuppass','dc-ssh-user','dc-ssh-port','dc-ssh-pass','dc-ssh-key'].forEach(id => {
|
||||
const el = _dcEl(id);
|
||||
el.value = '';
|
||||
if (el.dataset.ph == null) el.dataset.ph = el.placeholder;
|
||||
@@ -6434,7 +6436,21 @@
|
||||
// FritzBox: Benutzer optional — es reicht ein Passwort.
|
||||
const fu = document.getElementById('dc-fb-user').value.trim();
|
||||
const fp = document.getElementById('dc-fb-pass').value;
|
||||
if (fu || fp || dcStored['dc-fb-pass']) { creds.fritzbox = { user: fu }; if (fp) creds.fritzbox.pass = fp; }
|
||||
const fbp = document.getElementById('dc-fb-backuppass').value;
|
||||
if (fbp) {
|
||||
const miss = [];
|
||||
if (fbp.length < 12) miss.push('mindestens 12 Zeichen');
|
||||
if (!/[A-Z]/.test(fbp)) miss.push('ein Großbuchstabe');
|
||||
if (!/[a-z]/.test(fbp)) miss.push('ein Kleinbuchstabe');
|
||||
if (!/\d/.test(fbp)) miss.push('eine Ziffer');
|
||||
if (!/[^A-Za-z0-9]/.test(fbp)) miss.push('ein Sonderzeichen');
|
||||
if (miss.length) { document.getElementById('dc-status').textContent = '⚠ Sicherungs-Kennwort zu schwach — es fehlt: ' + miss.join(', ') + '.'; return; }
|
||||
}
|
||||
if (fu || fp || fbp || dcStored['dc-fb-pass'] || dcStored['dc-fb-backuppass']) {
|
||||
creds.fritzbox = { user: fu };
|
||||
if (fp) creds.fritzbox.pass = fp;
|
||||
if (fbp) creds.fritzbox.backupPass = fbp;
|
||||
}
|
||||
// SSH: Benutzer + (Passwort ODER Key). Port optional.
|
||||
const su = document.getElementById('dc-ssh-user').value.trim();
|
||||
const sp = document.getElementById('dc-ssh-pass').value;
|
||||
|
||||
+44
-3
@@ -290,7 +290,25 @@ def _creds_for(ip: str) -> dict:
|
||||
return _CREDS.get((ip or "").strip(), {}) if ip else {}
|
||||
|
||||
|
||||
_CRED_SECRETS = ("pass", "key", "community", "authKey", "privKey")
|
||||
_CRED_SECRETS = ("pass", "key", "community", "authKey", "privKey", "backupPass")
|
||||
|
||||
|
||||
def _strong_password_problem(pw: str) -> str:
|
||||
""""" wenn stark genug, sonst was fehlt. Regel fuer Sicherungs-Kennwoerter:
|
||||
mind. 12 Zeichen, Gross- und Kleinbuchstabe, Ziffer, Sonderzeichen."""
|
||||
pw = str(pw or "")
|
||||
missing = []
|
||||
if len(pw) < 12:
|
||||
missing.append("mindestens 12 Zeichen")
|
||||
if not re.search(r"[A-Z]", pw):
|
||||
missing.append("ein Grossbuchstabe")
|
||||
if not re.search(r"[a-z]", pw):
|
||||
missing.append("ein Kleinbuchstabe")
|
||||
if not re.search(r"\d", pw):
|
||||
missing.append("eine Ziffer")
|
||||
if not re.search(r"[^A-Za-z0-9]", pw):
|
||||
missing.append("ein Sonderzeichen")
|
||||
return ", ".join(missing)
|
||||
|
||||
|
||||
def _creds_detail(d: dict, reveal: bool) -> dict:
|
||||
@@ -1415,7 +1433,19 @@ def _fb_backup(ip: str, user: str, pw: str, params: dict) -> dict:
|
||||
noetig). Braucht meist die Taste an der Box (button_wait)."""
|
||||
import requests
|
||||
from requests.auth import HTTPDigestAuth
|
||||
export_pw = str(params.get("password") or pw)
|
||||
# Kennwort der Sicherung: ausdruecklich angegeben > im Satelliten hinterlegt
|
||||
# (Feld "Sicherungs-Kennwort" — ARIA kennt es nicht) > FritzBox-Login.
|
||||
stored = _creds_for(ip).get("fritzbox", {}).get("backupPass")
|
||||
export_pw = str(params.get("password") or stored or pw)
|
||||
weak = _strong_password_problem(export_pw)
|
||||
if weak:
|
||||
src = ("Das angegebene Kennwort" if params.get("password")
|
||||
else "Das hinterlegte Sicherungs-Kennwort" if stored
|
||||
else "Es ist kein Sicherungs-Kennwort hinterlegt, und das FritzBox-Login")
|
||||
return {"ok": False, "error": (
|
||||
f"{src} ist fuer eine Sicherung zu schwach (es fehlt: {weak}). Stefan soll im "
|
||||
"Diagnostic beim Geraet (🔑) ein Sicherungs-Kennwort hinterlegen: mind. 12 Zeichen, "
|
||||
"Gross-/Kleinbuchstabe, Ziffer, Sonderzeichen.")}
|
||||
r = _fb_call(ip, user, pw, {"service": "DeviceConfig", "action": "X_AVM-DE_GetConfigFile",
|
||||
"args": {"X_AVM-DE_Password": export_pw},
|
||||
"button_wait": params.get("button_wait")})
|
||||
@@ -1453,7 +1483,9 @@ def _fb_backup(ip: str, user: str, pw: str, params: dict) -> dict:
|
||||
return {"ok": True, "result": {
|
||||
"ip": ip, "datei": path, "bytes": len(d.content), "auf": "Satellit",
|
||||
**({"file_b64": file_b64, "filename": fname} if file_b64 else {}),
|
||||
"kennwort": "das angegebene Kennwort" if params.get("password") else "das FritzBox-Login-Kennwort",
|
||||
"kennwort": ("das angegebene Kennwort" if params.get("password")
|
||||
else "das im Satelliten hinterlegte Sicherungs-Kennwort" if stored
|
||||
else "das FritzBox-Login-Kennwort"),
|
||||
"hinweis": f"Liegt auf dem Satelliten, die letzten {FB_BACKUP_KEEP} je Box bleiben. "
|
||||
"Zum Wiederherstellen in der FritzBox: System → Sicherung → Wiederherstellen, mit dem Kennwort."}}
|
||||
|
||||
@@ -2504,6 +2536,15 @@ class Satellite:
|
||||
ip = (payload.get("ip") or "").strip()
|
||||
creds = payload.get("creds") or {}
|
||||
ok = False
|
||||
weak = _strong_password_problem((creds.get("fritzbox") or {}).get("backupPass")) \
|
||||
if isinstance(creds, dict) and (creds.get("fritzbox") or {}).get("backupPass") else ""
|
||||
if weak:
|
||||
await self._send({"type": "sat_creds_result",
|
||||
"payload": {"requestId": payload.get("requestId", ""),
|
||||
"satellite": SATELLITE_ID, "ok": False, "ip": ip,
|
||||
"error": f"Sicherungs-Kennwort zu schwach — es fehlt: {weak}."},
|
||||
"timestamp": int(time.time() * 1000)})
|
||||
return
|
||||
if ip and isinstance(creds, dict):
|
||||
entry = _CREDS.setdefault(ip, {})
|
||||
for t in ("snmp", "http", "fritzbox", "ssh"):
|
||||
|
||||
Reference in New Issue
Block a user