Respect explicit tunnel security off

This commit is contained in:
RWDai
2026-05-22 15:20:36 +08:00
parent 33633637e5
commit 08b27806a7
6 changed files with 46 additions and 24 deletions

View File

@@ -7,7 +7,7 @@ AETHER_TUNNEL_MANAGEMENT_TOKEN=ae_xxxxx
# Node identification
AETHER_TUNNEL_NODE_NAME=jp-proxy-01
# Secure non-TLS tunnel. http:// plus a key auto-enables non_tls_required.
# Secure non-TLS tunnel. Set non_tls_required with a key to enable secure tunnel.
AETHER_TUNNEL_SECURITY=off
# AETHER_TUNNEL_ENCRYPTION_KEY=base64-32-bytes

View File

@@ -113,7 +113,7 @@ sudo aether-tunnel uninstall
| `--aether-url` | `AETHER_TUNNEL_AETHER_URL` | **必填** | Aether 服务器地址 |
| `--management-token` | `AETHER_TUNNEL_MANAGEMENT_TOKEN` | **必填** | 管理员 Token`ae_xxx` 格式) |
| `--node-name` | `AETHER_TUNNEL_NODE_NAME` | **必填** | 节点名称标识 |
| `--tunnel-security` | `AETHER_TUNNEL_SECURITY` | `off` | Aether ↔ tunnel 通道安全模式;支持 `off` / `non_tls_required``http://` 提供 key 时会自动按 `non_tls_required` 生效 |
| `--tunnel-security` | `AETHER_TUNNEL_SECURITY` | `off` | Aether ↔ tunnel 通道安全模式;支持 `off` / `non_tls_required``[[servers]]` 中省略该字段且 `http://` 提供 key 时会自动按 `non_tls_required` 生效 |
| `--tunnel-encryption-key` | `AETHER_TUNNEL_ENCRYPTION_KEY` | 空 | secure tunnel 使用的长期 PSKbase64 32-byte每个 `[[servers]]` 节点独立配置 |
| `--public-ip` | `AETHER_TUNNEL_PUBLIC_IP` | 自动检测 | 公网 IP |
| `--node-region` | `AETHER_TUNNEL_NODE_REGION` | 自动检测 | 地区标识 |
@@ -231,7 +231,7 @@ tunnel_encryption_key = "base64-32-bytes"
`tunnel_security = "non_tls_required"` 是非 TLS secure tunnel 的 MVP 配置面:它要求同时提供当前 `[[servers]]` 条目的 `tunnel_encryption_key`,后续握手使用 `node_name` / `X-Node-Id` 查找对应 PSK不引入 `tunnel_encryption_key_id``wss://` 仍是推荐方案;`ws:// + secure tunnel` 只保护 Aether ↔ tunnel 之间的 token 和 payload不等价于 HTTPS 伪装,也不覆盖 tunnel ↔ origin/provider 这段链路。
如果 `aether_url` 使用 `http://` 且当前 `[[servers]]` 条目提供了 `tunnel_encryption_key`即使省略或保留 `tunnel_security = "off"`运行时会自动按 `non_tls_required` 生效。secure tunnel 会在 WebSocket tunnel 上加密所有二进制 tunnel frame未配置 key 的旧节点仍按原明文协议工作。
如果 `aether_url` 使用 `http://` 且当前 `[[servers]]` 条目提供了 `tunnel_encryption_key`省略 `tunnel_security`运行时会自动按 `non_tls_required` 生效;显式配置 `tunnel_security = "off"` 会关闭该自动推断。secure tunnel 会在 WebSocket tunnel 上加密所有二进制 tunnel frame未配置 key 或显式关闭的旧节点仍按原明文协议工作。
## 发布新版本

View File

@@ -116,7 +116,6 @@ function Add-ServerConfig([string]$AetherUrl, [string]$ManagementToken, [string]
$QuotedUrl = ConvertTo-TomlQuotedString $AetherUrl
$QuotedToken = ConvertTo-TomlQuotedString $ManagementToken
$QuotedName = ConvertTo-TomlQuotedString $NodeName
$QuotedTunnelSecurity = ConvertTo-TomlQuotedString $TunnelSecurity
$QuotedTunnelEncryptionKey = ConvertTo-TomlQuotedString $TunnelEncryptionKey
if (Test-ServerExists $script:ConfigPath $QuotedUrl $QuotedName) {
@@ -134,9 +133,12 @@ function Add-ServerConfig([string]$AetherUrl, [string]$ManagementToken, [string]
'[[servers]]',
"aether_url = $QuotedUrl",
"management_token = $QuotedToken",
"node_name = $QuotedName",
"tunnel_security = $QuotedTunnelSecurity"
"node_name = $QuotedName"
) -join "`n"
if ($TunnelSecurity) {
$QuotedTunnelSecurity = ConvertTo-TomlQuotedString $TunnelSecurity
$Block += "`ntunnel_security = $QuotedTunnelSecurity"
}
if ($TunnelEncryptionKey) {
$Block += "`ntunnel_encryption_key = $QuotedTunnelEncryptionKey"
}
@@ -149,9 +151,9 @@ function Main {
$AetherUrl = Prompt-IfEmpty 'AETHER_TUNNEL_AETHER_URL' $env:AETHER_TUNNEL_AETHER_URL 'Aether URL'
$ManagementToken = Prompt-IfEmpty 'AETHER_TUNNEL_MANAGEMENT_TOKEN' $env:AETHER_TUNNEL_MANAGEMENT_TOKEN 'Management token (ae_xxx)'
$NodeName = Prompt-IfEmpty 'AETHER_TUNNEL_NODE_NAME' $env:AETHER_TUNNEL_NODE_NAME 'Node name'
$TunnelSecurity = if ($env:AETHER_TUNNEL_SECURITY) { $env:AETHER_TUNNEL_SECURITY } else { 'off' }
$TunnelSecurity = if ($env:AETHER_TUNNEL_SECURITY) { $env:AETHER_TUNNEL_SECURITY } else { '' }
$TunnelEncryptionKey = if ($env:AETHER_TUNNEL_ENCRYPTION_KEY) { $env:AETHER_TUNNEL_ENCRYPTION_KEY } else { '' }
if ($TunnelSecurity -notin @('off', 'non_tls_required')) {
if ($TunnelSecurity -and ($TunnelSecurity -notin @('off', 'non_tls_required'))) {
Fail 'AETHER_TUNNEL_SECURITY must be off or non_tls_required'
}
if (($TunnelSecurity -eq 'non_tls_required') -and -not $TunnelEncryptionKey) {

View File

@@ -193,8 +193,10 @@ append_server_config() {
quoted_url=$(toml_quote "$aether_url")
quoted_token=$(toml_quote "$management_token")
quoted_name=$(toml_quote "$node_name")
quoted_security=$(toml_quote "$tunnel_security")
quoted_encryption_key=$(toml_quote "$tunnel_encryption_key")
if [ -n "$tunnel_security" ]; then
quoted_security=$(toml_quote "$tunnel_security")
fi
if has_legacy_single_server_keys; then
fail "现有配置仍使用旧的顶层 aether_url/management_token请先运行 aether-tunnel setup 迁移为 [[servers]] 后重试:$CONFIG_PATH"
@@ -218,7 +220,9 @@ append_server_config() {
printf 'aether_url = %s\n' "$quoted_url"
printf 'management_token = %s\n' "$quoted_token"
printf 'node_name = %s\n' "$quoted_name"
printf 'tunnel_security = %s\n' "$quoted_security"
if [ -n "$tunnel_security" ]; then
printf 'tunnel_security = %s\n' "$quoted_security"
fi
if [ -n "$tunnel_encryption_key" ]; then
printf 'tunnel_encryption_key = %s\n' "$quoted_encryption_key"
fi
@@ -235,10 +239,10 @@ main() {
aether_url=$(prompt_if_empty AETHER_TUNNEL_AETHER_URL "${AETHER_TUNNEL_AETHER_URL:-}" "Aether URL: ")
management_token=$(prompt_if_empty AETHER_TUNNEL_MANAGEMENT_TOKEN "${AETHER_TUNNEL_MANAGEMENT_TOKEN:-}" "Management token (ae_xxx): ")
node_name=$(prompt_if_empty AETHER_TUNNEL_NODE_NAME "${AETHER_TUNNEL_NODE_NAME:-}" "Node name: ")
tunnel_security="${AETHER_TUNNEL_SECURITY:-off}"
tunnel_security="${AETHER_TUNNEL_SECURITY:-}"
tunnel_encryption_key="${AETHER_TUNNEL_ENCRYPTION_KEY:-}"
case "$tunnel_security" in
off|non_tls_required) ;;
""|off|non_tls_required) ;;
*) fail "AETHER_TUNNEL_SECURITY 必须是 off 或 non_tls_required" ;;
esac
if [ "$tunnel_security" = "non_tls_required" ] && [ -z "$tunnel_encryption_key" ]; then

View File

@@ -289,8 +289,10 @@ pub fn effective_tunnel_security(
configured: Option<TunnelSecurity>,
tunnel_encryption_key: Option<&str>,
) -> TunnelSecurity {
if configured == Some(TunnelSecurity::NonTlsRequired) {
return TunnelSecurity::NonTlsRequired;
match configured {
Some(TunnelSecurity::NonTlsRequired) => return TunnelSecurity::NonTlsRequired,
Some(TunnelSecurity::Off) => return TunnelSecurity::Off,
None => {}
}
if aether_url.trim_start().starts_with("http://")
&& tunnel_encryption_key
@@ -738,12 +740,7 @@ impl Config {
if self.node_name.trim().is_empty() {
anyhow::bail!("node_name must not be empty");
}
let effective_security = effective_tunnel_security(
&self.aether_url,
Some(self.tunnel_security),
self.tunnel_encryption_key.as_deref(),
);
if effective_security == TunnelSecurity::NonTlsRequired {
if self.tunnel_security == TunnelSecurity::NonTlsRequired {
let Some(key) = normalized_proxy_url(&self.tunnel_encryption_key) else {
anyhow::bail!(
"tunnel_encryption_key must be set when tunnel_security=non_tls_required"
@@ -1796,14 +1793,22 @@ node_name = "tunnel-test"
assert_eq!(
effective_tunnel_security(
&config.aether_url,
Some(config.tunnel_security),
None,
config.tunnel_encryption_key.as_deref(),
),
TunnelSecurity::NonTlsRequired
);
assert_eq!(
effective_tunnel_security(
&config.aether_url,
Some(TunnelSecurity::Off),
config.tunnel_encryption_key.as_deref(),
),
TunnelSecurity::Off
);
config
.validate()
.expect("http URL with PSK should infer secure tunnel mode");
.expect("http URL with PSK should validate when tunnel_security is off");
}
#[test]
@@ -1816,6 +1821,8 @@ node_name = "tunnel-test"
"ae_test",
"--node-name",
"tunnel-test",
"--tunnel-security",
"non_tls_required",
"--tunnel-encryption-key",
"not-a-valid-32-byte-key",
]);

View File

@@ -96,10 +96,10 @@ impl ServerTab {
Field {
label: "Tunnel Security",
key: "tunnel_security",
value: "off".into(),
value: String::new(),
kind: FieldKind::Text,
required: false,
help: "off or non_tls_required; http:// plus a key auto-enables secure tunnel",
help: "off or non_tls_required; omit to auto-enable for http:// plus a key",
},
Field {
label: "Tunnel Encryption Key",
@@ -1180,6 +1180,15 @@ mod tests {
);
}
#[test]
fn to_config_omits_blank_tunnel_security_for_auto_inference() {
let app = sample_app();
let cfg = app.to_config().expect("config should serialize");
assert_eq!(cfg.servers.len(), 1);
assert_eq!(cfg.servers[0].tunnel_security, None);
}
#[test]
fn to_config_rejects_non_tls_security_without_key() {
let mut app = sample_app();