From cd29b417add77d8497584b7d2f95dbf4416ef484 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Wed, 18 Jun 2025 20:36:38 -0700 Subject: [PATCH 1/2] secure subdomains: increase security stop user from manually navigating to a subdomain that can then be used to access a protected api --- hyperdrive/src/http/server.rs | 60 ++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/hyperdrive/src/http/server.rs b/hyperdrive/src/http/server.rs index d6ec42693..558fd6b57 100644 --- a/hyperdrive/src/http/server.rs +++ b/hyperdrive/src/http/server.rs @@ -15,7 +15,7 @@ use lib::types::core::{ }; use route_recognizer::Router; use sha2::{Digest, Sha256}; -use std::{collections::HashMap, net::SocketAddr, sync::Arc}; +use std::{collections::{HashMap, HashSet}, net::SocketAddr, sync::Arc}; use tokio::sync::RwLock; use warp::{ http::{ @@ -49,6 +49,7 @@ type WebSocketSender = tokio::sync::mpsc::Sender; type PathBindings = Arc>>; type WsPathBindings = Arc>>; +type SecureSubdomains = Arc>>; struct BoundPath { pub app: Option, // if None, path has been unbound @@ -203,6 +204,7 @@ pub async fn http_server( let path_bindings: PathBindings = Arc::new(RwLock::new(bindings_map)); let ws_path_bindings: WsPathBindings = Arc::new(RwLock::new(Router::new())); + let secure_subdomains: SecureSubdomains = Arc::new(RwLock::new(HashSet::new())); tokio::spawn(serve( Arc::new(our_name), @@ -211,6 +213,7 @@ pub async fn http_server( path_bindings.clone(), ws_path_bindings.clone(), ws_senders.clone(), + secure_subdomains.clone(), Arc::new(encoded_keyfile), Arc::new(jwt_secret_bytes), send_to_loop.clone(), @@ -224,6 +227,7 @@ pub async fn http_server( path_bindings.clone(), ws_path_bindings.clone(), ws_senders.clone(), + secure_subdomains.clone(), send_to_loop.clone(), print_tx.clone(), ) @@ -241,6 +245,7 @@ async fn serve( path_bindings: PathBindings, ws_path_bindings: WsPathBindings, ws_senders: WebSocketSenders, + secure_subdomains: SecureSubdomains, encoded_keyfile: Arc>, jwt_secret_bytes: Arc>, send_to_loop: MessageSender, @@ -303,6 +308,7 @@ async fn serve( .and(warp::any().map(move || our.clone())) .and(warp::any().map(move || http_response_senders.clone())) .and(warp::any().map(move || path_bindings.clone())) + .and(warp::any().map(move || secure_subdomains.clone())) .and(warp::any().map(move || jwt_secret_bytes.clone())) .and(warp::any().map(move || send_to_loop.clone())) .and(warp::any().map(move || print_tx.clone())) @@ -500,6 +506,11 @@ async fn ws_handler( { return Err(warp::reject::not_found()); } + + let expected_subdomain = utils::generate_secure_subdomain(&app); + if subdomain != &expected_subdomain { + return Err(warp::reject::not_found()); + } } else { if !utils::auth_token_valid(&our, None, auth_token, &jwt_secret_bytes) { return Err(warp::reject::not_found()); @@ -555,6 +566,7 @@ async fn http_handler( our: Arc, http_response_senders: HttpResponseSenders, path_bindings: PathBindings, + secure_subdomains: SecureSubdomains, jwt_secret_bytes: Arc>, send_to_loop: MessageSender, print_tx: PrintSender, @@ -640,6 +652,16 @@ async fn http_handler( .body(vec![]) .into_response()); } + + let expected_subdomain = utils::generate_secure_subdomain(&app); + if subdomain != &expected_subdomain { + return Ok(warp::reply::with_status( + "secure subdomain can only serve its associated package", + StatusCode::FORBIDDEN, + ) + .into_response()); + } + if !utils::auth_token_valid( &our, Some(&app), @@ -653,6 +675,23 @@ async fn http_handler( .into_response()); } } else { + // Check if the request is coming through a secure subdomain + let request_subdomain = host.host().split('.').next().unwrap_or(""); + if !request_subdomain.is_empty() { + let secure_subdomains = secure_subdomains.read().await; + if secure_subdomains.contains(request_subdomain) { + // This is a request to a secure subdomain, so enforce package restriction + let expected_subdomain = utils::generate_secure_subdomain(&app); + if request_subdomain != expected_subdomain { + return Ok(warp::reply::with_status( + "secure subdomain can only serve its associated package", + StatusCode::FORBIDDEN, + ) + .into_response()); + } + } + } + if !utils::auth_token_valid( &our, None, @@ -1111,6 +1150,7 @@ async fn handle_app_message( path_bindings: PathBindings, ws_path_bindings: WsPathBindings, ws_senders: WebSocketSenders, + secure_subdomains: SecureSubdomains, send_to_loop: MessageSender, print_tx: PrintSender, ) { @@ -1265,6 +1305,15 @@ async fn handle_app_message( } let path = utils::format_path_with_process(&km.source.process, &path); let subdomain = utils::generate_secure_subdomain(&km.source.process); + + // Add subdomain to the set of secure subdomains + // + // In block to drop write lock ASAP + { + let mut secure_subs = secure_subdomains.write().await; + secure_subs.insert(subdomain.clone()); + } + let mut path_bindings = path_bindings.write().await; Printout::new( 3, @@ -1369,6 +1418,15 @@ async fn handle_app_message( } let path = utils::format_path_with_process(&km.source.process, &path); let subdomain = utils::generate_secure_subdomain(&km.source.process); + + // Add subdomain to the set of secure subdomains + // + // In block to drop write lock ASAP + { + let mut secure_subs = secure_subdomains.write().await; + secure_subs.insert(subdomain.clone()); + } + let mut ws_path_bindings = ws_path_bindings.write().await; ws_path_bindings.add( &path, From 20a394a42522ffbc8858d82c71887a1f5a15f0b7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 03:37:51 +0000 Subject: [PATCH 2/2] Format Rust code using rustfmt --- hyperdrive/src/http/server.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hyperdrive/src/http/server.rs b/hyperdrive/src/http/server.rs index 558fd6b57..3f7768b75 100644 --- a/hyperdrive/src/http/server.rs +++ b/hyperdrive/src/http/server.rs @@ -15,7 +15,11 @@ use lib::types::core::{ }; use route_recognizer::Router; use sha2::{Digest, Sha256}; -use std::{collections::{HashMap, HashSet}, net::SocketAddr, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + net::SocketAddr, + sync::Arc, +}; use tokio::sync::RwLock; use warp::{ http::{