Require a password on go2rtc's API
The API listener is what serves WebRTC, and it defaults to :1984 on every interface, so anyone who could reach the gateway could watch any printer with a camera. The RTSP server was closed on 2026-10-18; this was the other half and the one that actually carries video. Measured against go2rtc 1.9.14, feeding it this module's own rendered config: from a non-loopback address /api/webrtc, /api/ws, /api/streams and the web UI all answer 401 without credentials and 200 with them, while :8554 stays closed. go2rtc exempts loopback, which is worth knowing twice - it is why a probe from the gateway still works without credentials, and why such a probe cannot be used to confirm auth is on. The password is generated once per run from /dev/urandom, or taken from CONTINUUM_GO2RTC_PASSWORD. Once per run matters: the config is re-rendered every 30s and compared byte-for-byte to decide whether to restart go2rtc, so a per-render credential would restart it and drop every viewer every half minute, with no symptom but dying video. A test pins that. It is never logged - it goes into the config file, which already holds every printer's access code and is already documented as a secret, so there is one place to protect rather than two. An operator-supplied password is escaped for YAML, because a config go2rtc cannot parse means a camera that silently never appears. No opt-out flag. The alternative is an unauthenticated video feed of someone's workshop, which is not a thing to leave behind a setting, and nothing in this crate calls go2rtc's API so nothing internal breaks. Note this does NOT let a browser play a stream directly - see the next commit's notes and PLAN.md. go2rtc sends no CORS headers and 401s the unauthenticated preflight, so the relay is required, not optional.
This commit is contained in:
+13
@@ -6,6 +6,7 @@
|
||||
// about the library's actual public API. One copy, no false warnings.
|
||||
mod go2rtc;
|
||||
|
||||
use tracing::info;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use continuum_proxy::config::Config;
|
||||
@@ -59,6 +60,17 @@ async fn main() -> anyhow::Result<()> {
|
||||
// so in practice this runs until the process is killed.
|
||||
let restart_go2rtc = std::sync::Arc::new(tokio::sync::Notify::new());
|
||||
|
||||
// Built once, here, rather than inside the rebuild loop: the config is
|
||||
// re-rendered every 30s and compared byte-for-byte to decide whether to
|
||||
// restart go2rtc, so a credential that changed per render would restart
|
||||
// it - and drop every viewer - on every tick.
|
||||
let camera_auth = camera::ApiAuth::new()?;
|
||||
info!(
|
||||
user = %camera_auth.username,
|
||||
config = %config.go2rtc_config,
|
||||
"go2rtc's API requires basic auth; the password is in its config file"
|
||||
);
|
||||
|
||||
tokio::select! {
|
||||
_ = uplink::run(config.clone(), fleet.clone()) => {}
|
||||
_ = go2rtc::watchdog(
|
||||
@@ -70,6 +82,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
fleet.clone(),
|
||||
config.go2rtc_config.clone(),
|
||||
config.go2rtc_api_listen.clone(),
|
||||
camera_auth,
|
||||
restart_go2rtc.clone(),
|
||||
) => {}
|
||||
_ = completion::run(fleet.clone()) => {}
|
||||
|
||||
Reference in New Issue
Block a user