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:
2026-10-18 22:00:51 +00:00
parent 7a0201c0de
commit 70763108f8
3 changed files with 176 additions and 7 deletions
+13
View File
@@ -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()) => {}