feat(discord-bridge): dashboard fara token (DASHBOARD_AUTH=off)
Cerut explicit: tokenul nu se retine. Cu DASHBOARD_AUTH=off nu mai exista login, /login.html duce inapoi la panou, iar butonul "Iesi" dispare. Se sprijina pe doua lucruri si nu are sens fara ele: serviciul e legat de 127.0.0.1, deci din retea ajunge la el doar tailscaled; iar tailscale serve il publica tainet only, unde accesul e deja autentificat de Tailscale. Compensatie partiala pentru ce se pierde: fiecare start/stop/restart se scrie in logs/dashboard.log cu identitatea din antetul Tailscale-User-Login pus de tailscale serve (verificat: ajunge pana la noi). Antetul e DOAR pentru jurnal — nu decide accesul, fiindca un proces local l-ar putea fabrica. Implicitul ramane cu token: doar off/none/0/false scot login-ul, orice alta valoare il pastreaza (are test). Sase teste noi, 41 pe dashboard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
This commit is contained in:
@@ -90,11 +90,24 @@ def reset_token_cache() -> None:
|
||||
_TOKEN = None
|
||||
|
||||
|
||||
def auth_disabled() -> bool:
|
||||
"""`DASHBOARD_AUTH=off` in `~/.claude-discord/env` scoate complet login-ul.
|
||||
|
||||
Alegere constienta a operatorului, nu implicit: panoul poate opri un agent cu
|
||||
`bypassPermissions` si chei SSH catre tot clusterul, deci fara token oricine
|
||||
ajunge la port il poate folosi. Are sens doar pentru ca serviciul e legat de
|
||||
`127.0.0.1` si publicat exclusiv in tailnet (`tailscale serve`, tainet only),
|
||||
unde identitatea o face deja Tailscale.
|
||||
"""
|
||||
return (config.get("DASHBOARD_AUTH") or "").strip().lower() in ("off", "none", "0", "false")
|
||||
|
||||
|
||||
def dashboard_token() -> str:
|
||||
"""Tokenul de acces, din `~/.claude-discord/env` (`DASHBOARD_TOKEN`).
|
||||
|
||||
Lipsa lui NU deschide dashboard-ul: se genereaza unul aleator per proces si se
|
||||
tipareste in log, deci ramane accesibil doar cui poate citi logul.
|
||||
tipareste in log, deci ramane accesibil doar cui poate citi logul. Ca sa fie
|
||||
deschis intentionat, se pune `DASHBOARD_AUTH=off`.
|
||||
"""
|
||||
global _TOKEN
|
||||
if _TOKEN is None:
|
||||
@@ -110,6 +123,13 @@ def dashboard_token() -> str:
|
||||
return _TOKEN
|
||||
|
||||
|
||||
def tailnet_user(headers) -> str:
|
||||
"""Cine e, dupa Tailscale. `tailscale serve` pune antetul pe cererile din
|
||||
tailnet; pe localhost lipseste. Doar pentru jurnal — nu e folosit ca decizie
|
||||
de acces, fiindca un proces local ar putea sa-l fabrice."""
|
||||
return (headers.get("Tailscale-User-Login") or "").strip()
|
||||
|
||||
|
||||
# ── systemd ─────────────────────────────────────────────────────────────
|
||||
def _sysctl(*args: str, timeout: float = 30.0) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
@@ -391,6 +411,8 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
return {}
|
||||
|
||||
def authed(self) -> bool:
|
||||
if auth_disabled():
|
||||
return True
|
||||
got = _parse_cookies(self.headers.get("Cookie", "")).get(COOKIE_NAME, "")
|
||||
return bool(got) and secrets.compare_digest(got, dashboard_token())
|
||||
|
||||
@@ -429,6 +451,12 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
if path in ("/", "/index.html"):
|
||||
return self.send_html("index.html")
|
||||
if path == "/login.html":
|
||||
if auth_disabled():
|
||||
self.send_response(302)
|
||||
self.send_header("Location", mount_prefix() + "/")
|
||||
self.send_header("Content-Length", "0")
|
||||
self.end_headers()
|
||||
return None
|
||||
return self.send_html("login.html")
|
||||
self.path = path
|
||||
return super().do_GET()
|
||||
@@ -474,6 +502,8 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
"cost": state.get("cost") or {},
|
||||
"cost_cap": float(config.get("COST_CAP_USD_DAY") or 0),
|
||||
"pending_approvals": len(pending_approvals()),
|
||||
"auth": not auth_disabled(),
|
||||
"user": tailnet_user(self.headers),
|
||||
"now": time.time(),
|
||||
})
|
||||
if path == "/api/logs":
|
||||
@@ -548,6 +578,9 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
"hint": "retrimite cu force=true ca sa le intrerupi",
|
||||
}, 409)
|
||||
|
||||
who = tailnet_user(self.headers) or "local"
|
||||
log_line = f"[actiune] {action} pe {SERVICE}, cerut de {who}"
|
||||
print(log_line, file=sys.stderr, flush=True)
|
||||
try:
|
||||
r = _sysctl(action, SERVICE)
|
||||
except subprocess.TimeoutExpired:
|
||||
|
||||
Reference in New Issue
Block a user