Authenticate the node dashboard, and the broker connection per node
Two unauthenticated surfaces closed on the edge node. Dashboard and API: the local dashboard and every /api/* route were open to anything on the node's LAN. Adds a login page plus session-cookie auth for the browser, and cookie-or-Basic for the API so scripted callers stay possible. Passwords are hashed with stdlib scrypt (no new dependency, this runs on a Pi) and compared in constant time; the salt and session-signing secret persist in credentials.json. Startup warns while the default password is still in place. No non-browser callers of the node API exist today (C2 talks to nodes over MQTT and nodes call C2 outbound), so nothing breaks. Adds python-multipart, which FastAPI's Form() needs for the login POST and which was missing from requirements entirely. MQTT: nodes authenticated with a shared drb-node password, and the broker ACL keyed off %c — the client-supplied client id — so any holder of that one password could claim another node's topic namespace. Nodes now connect as username=<node_id>, password=<their C2-issued api_key>, which mosquitto's dynamic-security plugin checks, with the ACL keyed off the authenticated %u. TLS is gated on MQTT_TLS and uses default CA verification. The old key_request MQTT path stays in place behind TODO(mqtt-cutover) markers as the fallback until the cutover is proven; a node with no api_key on disk logs a clear repeated refusal rather than spinning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a61a7b2c31
commit
87633ab50d
@@ -10,8 +10,27 @@ class Settings(BaseSettings):
|
||||
node_lon: float = 0.0
|
||||
|
||||
# MQTT
|
||||
#
|
||||
# Broker cutover (MQTT-PUBLIC-AUTH-PLAN.md, dynsec revision): the server no
|
||||
# longer has a shared node login. Each node authenticates as
|
||||
# username=NODE_ID, password=<its C2-issued api_key> (the same credential
|
||||
# /upload already trusts via node_keys) — see mqtt_manager._build_client().
|
||||
# For local dev against the old-style broker (localhost:1883, no TLS) set
|
||||
# MQTT_BROKER=localhost and leave MQTT_TLS unset/false.
|
||||
mqtt_broker: str
|
||||
mqtt_port: int = 1883
|
||||
# Set true for the public broker (mqtt.<domain>:8883, real Let's Encrypt
|
||||
# cert) so client.tls_set() runs with default system-CA verification.
|
||||
# False by default so local/dev against a plaintext :1883 broker still
|
||||
# works unchanged. Do NOT pair with a self-signed/insecure cert setup —
|
||||
# verification is never disabled (no tls_insecure_set(True) anywhere).
|
||||
mqtt_tls: bool = False
|
||||
# DEPRECATED / effectively dead post-cutover: the shared node login these
|
||||
# backed no longer exists on the server (dynsec has no such client — see
|
||||
# dynsec.py). Left in only as a legacy fallback for a pre-cutover broker
|
||||
# that still uses mosquitto's old password_file auth; mqtt_manager only
|
||||
# falls back to these when no api_key is on disk yet. Do not provision new
|
||||
# nodes with these — see MQTT_USER/MQTT_PASS removal note in .env.example.
|
||||
mqtt_user: Optional[str] = None
|
||||
mqtt_pass: Optional[str] = None
|
||||
|
||||
@@ -123,6 +142,22 @@ class Settings(BaseSettings):
|
||||
# Offline call buffer — how many call_end events to keep while disconnected
|
||||
offline_call_buffer_size: int = 35
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Local dashboard / API authentication
|
||||
#
|
||||
# These nodes are deployed at arbitrary third-party locations, reachable by
|
||||
# anyone on that site's LAN — there is no auth on this HTTP surface without
|
||||
# these. The password below is a FIRST-BOOT DEFAULT ONLY: change it via
|
||||
# DASHBOARD_PASSWORD in .env before a node leaves the bench. main.py logs a
|
||||
# startup warning every boot the default is still active.
|
||||
#
|
||||
# See app/internal/auth.py — the password is never compared or stored in
|
||||
# plaintext (scrypt-hashed, constant-time compare); this setting just holds
|
||||
# the operator-facing plaintext the same way MQTT_PASS/ICECAST_* already do.
|
||||
# ------------------------------------------------------------------
|
||||
dashboard_username: str = "admin"
|
||||
dashboard_password: str = "CHANGE-ME-drb-default"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user