Merge remote-tracking branch 'origin/pr/535'

This commit is contained in:
fawney19
2026-05-23 13:00:18 +08:00
34 changed files with 1173 additions and 87 deletions
@@ -63,6 +63,10 @@ struct ProxyNodeRegisterRequest {
proxy_version: Option<String>,
#[serde(default)]
tunnel_mode: Option<bool>,
#[serde(default)]
tunnel_security: Option<String>,
#[serde(default)]
tunnel_encryption_key: Option<String>,
}
#[derive(Debug, Deserialize)]
@@ -349,9 +353,21 @@ pub(crate) async fn maybe_build_local_admin_proxy_nodes_response(
Ok(mutation) => mutation,
Err(response) => return Ok(Some(response)),
};
let tunnel_encryption_key = mutation
.proxy_metadata
.as_ref()
.and_then(|metadata| metadata.pointer("/tunnel_security/encryption_key"))
.and_then(|value| value.as_str())
.map(str::to_string);
let Some(node) = state.register_proxy_node(&mutation).await? else {
return Ok(Some(build_admin_proxy_nodes_data_unavailable_response()));
};
if let Some(key) = tunnel_encryption_key {
state
.app()
.tunnel
.register_secure_tunnel_key(node.id.clone(), key);
}
return Ok(Some(
Json(json!({
"node_id": node.id,
@@ -1420,12 +1436,43 @@ fn validate_register_request(
}
validate_optional_object(input.hardware_info.as_ref(), "hardware_info")?;
validate_optional_object(input.proxy_metadata.as_ref(), "proxy_metadata")?;
let tunnel_security =
normalize_optional_string(input.tunnel_security.as_deref(), "tunnel_security", 64)?;
let tunnel_encryption_key = normalize_optional_string(
input.tunnel_encryption_key.as_deref(),
"tunnel_encryption_key",
128,
)?;
let registered_by = request_context
.decision()
.and_then(|decision| decision.admin_principal.as_ref())
.map(|principal| principal.user_id.clone());
let mut proxy_metadata = input.proxy_metadata;
if tunnel_security.as_deref()
== Some(aether_contracts::tunnel_security::TUNNEL_SECURITY_NON_TLS_REQUIRED)
{
let key = tunnel_encryption_key.as_deref().ok_or_else(|| {
bad_request_response(
"tunnel_encryption_key is required when tunnel_security=non_tls_required",
)
})?;
aether_contracts::tunnel_security::decode_psk(key)
.map_err(|err| bad_request_response(err.to_string()))?;
let mut metadata = proxy_metadata
.and_then(|value| value.as_object().cloned())
.unwrap_or_default();
metadata.insert(
"tunnel_security".to_string(),
json!({
"mode": aether_contracts::tunnel_security::TUNNEL_SECURITY_NON_TLS_REQUIRED,
"encryption_key": key,
}),
);
proxy_metadata = Some(Value::Object(metadata));
}
Ok(
aether_data::repository::proxy_nodes::ProxyNodeRegistrationMutation {
name,
@@ -1438,7 +1485,7 @@ fn validate_register_request(
avg_latency_ms: input.avg_latency_ms,
hardware_info: input.hardware_info,
estimated_max_concurrency: input.estimated_max_concurrency,
proxy_metadata: input.proxy_metadata,
proxy_metadata,
proxy_version: normalize_optional_string(
input.proxy_version.as_deref(),
"proxy_version",
@@ -16,7 +16,7 @@ const INSTALL_SESSION_TTL_SECS: u64 = 15 * 60;
const INSTALL_SESSION_KEY_PREFIX: &str = "install:session:";
const TUNNEL_INSTALL_SESSION_KEY_PREFIX: &str = "tunnel-install:session:";
const TUNNEL_INSTALL_UNIX_SCRIPT_URL: &str =
"https://raw.githubusercontent.com/fawney19/Aether/main/apps/aether-tunnel/install.sh";
"https://raw.githubusercontent.com/fawney19/Aether/refs/heads/main/apps/aether-tunnel/install.sh";
const TUNNEL_INSTALL_POWERSHELL_SCRIPT_URL: &str =
"https://raw.githubusercontent.com/fawney19/Aether/main/apps/aether-tunnel/install.ps1";
@@ -59,6 +59,8 @@ struct StoredTunnelInstallSession {
aether_url: String,
management_token: String,
node_name: String,
tunnel_security: String,
tunnel_encryption_key: String,
expires_at_unix_secs: u64,
}
@@ -93,8 +95,7 @@ fn install_code_from_path(request_path: &str) -> Option<(String, bool)> {
fn tunnel_install_code_from_path(request_path: &str) -> Option<(String, bool)> {
let raw = request_path
.strip_prefix("/install-tunnel/")
.or_else(|| request_path.strip_prefix("/install-proxy/"))?
.strip_prefix("/install-tunnel/")?
.trim()
.trim_matches('/');
if raw.is_empty() || raw.contains('/') {
@@ -122,6 +123,17 @@ fn generate_install_code() -> String {
.collect()
}
fn generate_tunnel_encryption_key() -> String {
use base64::Engine;
let first = uuid::Uuid::new_v4();
let second = uuid::Uuid::new_v4();
let mut key = [0_u8; 32];
key[..16].copy_from_slice(first.as_bytes());
key[16..].copy_from_slice(second.as_bytes());
base64::engine::general_purpose::STANDARD.encode(key)
}
fn unix_secs_now() -> u64 {
chrono::Utc::now().timestamp().max(0) as u64
}
@@ -172,6 +184,8 @@ set -eu
export AETHER_TUNNEL_AETHER_URL={aether_url}
export AETHER_TUNNEL_MANAGEMENT_TOKEN={management_token}
export AETHER_TUNNEL_NODE_NAME={node_name}
export AETHER_TUNNEL_SECURITY={tunnel_security}
export AETHER_TUNNEL_ENCRYPTION_KEY={tunnel_encryption_key}
if command -v curl >/dev/null 2>&1; then
curl -fsSL {script_url} | sh
@@ -185,6 +199,8 @@ fi
aether_url = shell_single_quote(&session.aether_url),
management_token = shell_single_quote(&session.management_token),
node_name = shell_single_quote(&session.node_name),
tunnel_security = shell_single_quote(&session.tunnel_security),
tunnel_encryption_key = shell_single_quote(&session.tunnel_encryption_key),
script_url = shell_single_quote(TUNNEL_INSTALL_UNIX_SCRIPT_URL),
)
}
@@ -195,11 +211,15 @@ fn build_tunnel_powershell_script(session: &StoredTunnelInstallSession) -> Strin
$env:AETHER_TUNNEL_AETHER_URL = {aether_url}
$env:AETHER_TUNNEL_MANAGEMENT_TOKEN = {management_token}
$env:AETHER_TUNNEL_NODE_NAME = {node_name}
$env:AETHER_TUNNEL_SECURITY = {tunnel_security}
$env:AETHER_TUNNEL_ENCRYPTION_KEY = {tunnel_encryption_key}
irm {script_url} | iex
"###,
aether_url = powershell_single_quote(&session.aether_url),
management_token = powershell_single_quote(&session.management_token),
node_name = powershell_single_quote(&session.node_name),
tunnel_security = powershell_single_quote(&session.tunnel_security),
tunnel_encryption_key = powershell_single_quote(&session.tunnel_encryption_key),
script_url = powershell_single_quote(TUNNEL_INSTALL_POWERSHELL_SCRIPT_URL),
)
}
@@ -664,6 +684,8 @@ pub(crate) async fn build_proxy_node_install_session_response(
aether_url: base_url_from_request(headers, request_context),
management_token,
node_name,
tunnel_security: "non_tls_required".to_string(),
tunnel_encryption_key: generate_tunnel_encryption_key(),
expires_at_unix_secs,
};
let serialized = match serde_json::to_string(&session) {
@@ -712,9 +734,7 @@ pub(super) async fn maybe_build_local_install_response(
if decision.route_family.as_deref() != Some("install") {
return None;
}
if request_context.request_path.starts_with("/install-tunnel/")
|| request_context.request_path.starts_with("/install-proxy/")
{
if request_context.request_path.starts_with("/install-tunnel/") {
return Some(maybe_build_local_tunnel_install_response(state, request_context).await);
}
let Some((code, wants_powershell)) = install_code_from_path(&request_context.request_path)
@@ -893,6 +913,8 @@ mod tests {
aether_url: "https://aether.example".to_string(),
management_token: "ae-test-token".to_string(),
node_name: "jp-proxy-01".to_string(),
tunnel_security: "non_tls_required".to_string(),
tunnel_encryption_key: "base64-32-bytes".to_string(),
expires_at_unix_secs: u64::MAX,
}
}
@@ -907,10 +929,6 @@ mod tests {
tunnel_install_code_from_path("/install-tunnel/abc123.ps1"),
Some(("abc123".to_string(), true))
);
assert_eq!(
tunnel_install_code_from_path("/install-proxy/abc123"),
Some(("abc123".to_string(), false))
);
assert_eq!(tunnel_install_code_from_path("/install-tunnel/a/b"), None);
}
@@ -921,8 +939,10 @@ mod tests {
assert!(script.contains("export AETHER_TUNNEL_AETHER_URL='https://aether.example'"));
assert!(script.contains("export AETHER_TUNNEL_MANAGEMENT_TOKEN='ae-test-token'"));
assert!(script.contains("export AETHER_TUNNEL_NODE_NAME='jp-proxy-01'"));
assert!(script.contains("export AETHER_TUNNEL_SECURITY='non_tls_required'"));
assert!(script.contains("export AETHER_TUNNEL_ENCRYPTION_KEY='base64-32-bytes'"));
assert!(script.contains(
"https://raw.githubusercontent.com/fawney19/Aether/main/apps/aether-tunnel/install.sh"
"https://raw.githubusercontent.com/fawney19/Aether/refs/heads/main/apps/aether-tunnel/install.sh"
));
assert!(!script.contains("aether-rust-pioneer"));
assert!(!script.contains("[[servers]]"));
@@ -935,6 +955,8 @@ mod tests {
assert!(script.contains("$env:AETHER_TUNNEL_AETHER_URL = 'https://aether.example'"));
assert!(script.contains("$env:AETHER_TUNNEL_MANAGEMENT_TOKEN = 'ae-test-token'"));
assert!(script.contains("$env:AETHER_TUNNEL_NODE_NAME = 'jp-proxy-01'"));
assert!(script.contains("$env:AETHER_TUNNEL_SECURITY = 'non_tls_required'"));
assert!(script.contains("$env:AETHER_TUNNEL_ENCRYPTION_KEY = 'base64-32-bytes'"));
assert!(script.contains(
"https://raw.githubusercontent.com/fawney19/Aether/main/apps/aether-tunnel/install.ps1"
));