Skip to content

Commit daa53f7

Browse files
authored
Minor MoQ gateway improvements (#271)
* feat: moq improvements * feat: allow public moq paths * fix: address review * fix: fail with error * address review
1 parent 2b50eba commit daa53f7

File tree

4 files changed

+78
-13
lines changed

4 files changed

+78
-13
lines changed

apps/skit/src/auth/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ mod tests {
552552
api_max_ttl_secs: 86400,
553553
moq_default_ttl_secs: 3600,
554554
moq_max_ttl_secs: 86400,
555+
moq_public_paths: Vec::new(),
555556
};
556557

557558
let state = AuthState::new(&config, true).await.unwrap();

apps/skit/src/config.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,16 @@ pub struct ServerConfig {
312312
pub cors: CorsConfig,
313313
#[cfg(feature = "moq")]
314314
pub moq_address: Option<String>,
315+
/// TLS certificate for the MoQ WebTransport listener.
316+
/// When set, the MoQ QUIC server uses these certs independently of `[server].tls`.
317+
/// When unset, falls back to `cert_path`/`key_path` (if `tls = true`) or self-signed.
318+
#[cfg(feature = "moq")]
319+
#[serde(default)]
320+
pub moq_cert_path: Option<String>,
321+
/// TLS private key for the MoQ WebTransport listener (see `moq_cert_path`).
322+
#[cfg(feature = "moq")]
323+
#[serde(default)]
324+
pub moq_key_path: Option<String>,
315325
/// MoQ Gateway URL to use in the frontend (can be overridden via SK_SERVER__MOQ_GATEWAY_URL)
316326
#[cfg(feature = "moq")]
317327
pub moq_gateway_url: Option<String>,
@@ -329,7 +339,11 @@ impl Default for ServerConfig {
329339
base_path: None,
330340
cors: CorsConfig::default(),
331341
#[cfg(feature = "moq")]
332-
moq_address: Some("127.0.0.1:4545".to_string()),
342+
moq_address: None,
343+
#[cfg(feature = "moq")]
344+
moq_cert_path: None,
345+
#[cfg(feature = "moq")]
346+
moq_key_path: None,
333347
#[cfg(feature = "moq")]
334348
moq_gateway_url: None,
335349
}
@@ -747,6 +761,13 @@ pub struct AuthConfig {
747761
/// Maximum TTL for MoQ tokens in seconds. Default: 86400 (1 day)
748762
#[serde(default = "default_moq_max_ttl")]
749763
pub moq_max_ttl_secs: u64,
764+
765+
/// Gateway paths that allow unauthenticated MoQ WebTransport connections.
766+
/// Connections to listed path prefixes skip JWT validation; the HTTP API remains protected.
767+
/// Example: `["/moq"]` makes all `/moq/**` paths public; `["/moq/abc123"]` for a single path.
768+
/// Empty list (default) = all MoQ connections require auth.
769+
#[serde(default)]
770+
pub moq_public_paths: Vec<String>,
750771
}
751772

752773
impl Default for AuthConfig {
@@ -759,6 +780,7 @@ impl Default for AuthConfig {
759780
api_max_ttl_secs: default_api_max_ttl(),
760781
moq_default_ttl_secs: default_moq_default_ttl(),
761782
moq_max_ttl_secs: default_moq_max_ttl(),
783+
moq_public_paths: Vec::new(),
762784
}
763785
}
764786
}

apps/skit/src/server/mod.rs

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3382,18 +3382,37 @@ fn start_moq_webtransport_acceptor(
33823382

33833383
let auth_state = Arc::clone(&app_state.auth);
33843384

3385-
// Parse address for WebTransport (UDP will use the same port as HTTP/HTTPS)
3386-
let addr: SocketAddr = config.server.address.parse()?;
3385+
// Parse address for WebTransport — use moq_address when set, otherwise fall back
3386+
// to the main server address (same port for HTTP and QUIC).
3387+
let addr: SocketAddr =
3388+
config.server.moq_address.as_deref().unwrap_or(&config.server.address).parse()?;
3389+
3390+
// Configure TLS for MoQ WebTransport.
3391+
// Priority: moq_cert_path/moq_key_path → server cert_path/key_path (when tls=true) → self-signed.
3392+
let moq_cert = config.server.moq_cert_path.as_deref().filter(|s| !s.is_empty());
3393+
let moq_key = config.server.moq_key_path.as_deref().filter(|s| !s.is_empty());
3394+
3395+
if moq_cert.is_some() != moq_key.is_some() {
3396+
return Err(format!(
3397+
"Invalid MoQ TLS config: both moq_cert_path and moq_key_path must be set (got cert={:?}, key={:?})",
3398+
config.server.moq_cert_path, config.server.moq_key_path
3399+
).into());
3400+
}
33873401

3388-
// Configure TLS - use provided certificates if available, otherwise auto-generate
3389-
let tls = if config.server.tls
3402+
let tls = if let (Some(cert), Some(key)) = (moq_cert, moq_key) {
3403+
info!(cert_path = %cert, key_path = %key, "Using MoQ-specific TLS certificates for WebTransport");
3404+
let mut tls = ServerTlsConfig::default();
3405+
tls.cert = vec![std::path::PathBuf::from(cert)];
3406+
tls.key = vec![std::path::PathBuf::from(key)];
3407+
tls
3408+
} else if config.server.tls
33903409
&& !config.server.cert_path.is_empty()
33913410
&& !config.server.key_path.is_empty()
33923411
{
33933412
info!(
33943413
cert_path = %config.server.cert_path,
33953414
key_path = %config.server.key_path,
3396-
"Using provided TLS certificates for MoQ WebTransport"
3415+
"Using server TLS certificates for MoQ WebTransport"
33973416
);
33983417
let mut tls = ServerTlsConfig::default();
33993418
tls.cert = vec![std::path::PathBuf::from(&config.server.cert_path)];
@@ -3410,9 +3429,26 @@ fn start_moq_webtransport_acceptor(
34103429
moq_config.bind = Some(addr);
34113430
moq_config.tls = tls;
34123431

3432+
let moq_public_paths: Arc<[String]> = config
3433+
.auth
3434+
.moq_public_paths
3435+
.iter()
3436+
.filter(|p| {
3437+
if p.is_empty() {
3438+
warn!("Ignoring empty string in moq_public_paths (would bypass all MoQ auth)");
3439+
false
3440+
} else {
3441+
true
3442+
}
3443+
})
3444+
.cloned()
3445+
.collect::<Vec<_>>()
3446+
.into();
3447+
34133448
info!(
34143449
address = %addr,
3415-
"Starting MoQ WebTransport acceptor on UDP (same port as HTTP server)"
3450+
moq_public_paths = ?moq_public_paths,
3451+
"Starting MoQ WebTransport acceptor on UDP"
34163452
);
34173453

34183454
tokio::spawn(async move {
@@ -3430,14 +3466,15 @@ fn start_moq_webtransport_acceptor(
34303466
for (i, fp) in fingerprints.iter().enumerate() {
34313467
info!("🔐 MoQ WebTransport certificate fingerprint #{}: {}", i + 1, fp);
34323468
}
3433-
info!("💡 Access fingerprints at: http://{}/api/v1/moq/fingerprints", addr);
3469+
info!("💡 Access fingerprints at: /api/v1/moq/fingerprints (served by the HTTP server)");
34343470

34353471
info!("MoQ WebTransport server listening for connections");
34363472

34373473
// Accept connections in a loop
34383474
while let Some(request) = server.accept().await {
34393475
let gateway = Arc::clone(&gateway);
34403476
let auth_state = Arc::clone(&auth_state);
3477+
let moq_public_paths = Arc::clone(&moq_public_paths);
34413478

34423479
tokio::spawn(async move {
34433480
// Extract URL data before consuming the request.
@@ -3458,8 +3495,12 @@ fn start_moq_webtransport_acceptor(
34583495
// SECURITY: Never log the full URL (may contain jwt)
34593496
debug!(path = %path, "Received MoQ connection request");
34603497

3461-
// Validate MoQ auth if enabled
3462-
let moq_auth = if auth_state.is_enabled() {
3498+
// Validate MoQ auth if enabled (skipped for paths matching moq_public_paths).
3499+
// Segment-based: "/moq" matches "/moq" and "/moq/foo" but NOT "/moq2".
3500+
let is_public = moq_public_paths.iter().any(|prefix| {
3501+
path == prefix.as_str() || path.starts_with(&format!("{prefix}/"))
3502+
});
3503+
let moq_auth = if auth_state.is_enabled() && !is_public {
34633504
match validate_moq_auth(&auth_state, &path, jwt_param).await {
34643505
Ok(ctx) => Some(ctx),
34653506
Err(status) => {

samples/skit.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ samples_dir = "./samples/pipelines"
3131
# Default: 100MB
3232
max_body_size = 104857600
3333

34-
# MoQ WebTransport address (optional, requires 'moq' feature)
35-
# Note: currently unused; the MoQ acceptor binds to `server.address`.
36-
# moq_address = "127.0.0.1:4545"
34+
# MoQ WebTransport bind address (optional, requires 'moq' feature).
35+
# When unset, MoQ QUIC binds to the same address as the HTTP server.
36+
# Set to a different address/port to separate HTTP and MoQ traffic.
37+
# moq_address = "0.0.0.0:4445"
3738

3839
# MoQ Gateway URL to expose to the UI (optional, requires 'moq' feature)
3940
# moq_gateway_url = "https://example.com/moq"

0 commit comments

Comments
 (0)