fix(deploy): 默认关闭沙箱公网访问

This commit is contained in:
2026-08-17 10:19:10 +08:00
parent 919a797510
commit ee52bcf34f
15 changed files with 332 additions and 33 deletions
+47
View File
@@ -0,0 +1,47 @@
function Get-ServerHttpBindEnvValue {
param([string]$Content, [string]$Key)
$match = [regex]::Match($Content, "(?m)^$([regex]::Escape($Key))=([^\r\n]*)$")
if ($match.Success) { return $match.Groups[1].Value.Trim() }
return ""
}
function Set-ServerHttpBindEnvValue {
param([string]$Content, [string]$Key, [string]$Value)
$line = "$Key=$Value"
$pattern = "(?m)^$([regex]::Escape($Key))=.*$"
if ($Content -match $pattern) { return ($Content -replace $pattern, $line) }
if ($Content -and -not $Content.EndsWith("`n")) { $Content += "`n" }
return ($Content + $line + "`n")
}
function Initialize-ServerHttpBindIp {
param(
[Parameter(Mandatory = $true)][string]$Path,
[string]$Override = ""
)
if (-not (Test-Path $Path)) { throw "Environment file not found: $Path" }
$content = Get-Content $Path -Raw -Encoding UTF8
if (-not $content) { $content = "" }
$current = Get-ServerHttpBindEnvValue $content "SERVER_HTTP_BIND_IP"
$target = if (-not [string]::IsNullOrWhiteSpace($Override)) { $Override.Trim() } else { $current }
if ([string]::IsNullOrWhiteSpace($target)) {
$publicApiUrl = Get-ServerHttpBindEnvValue $content "NUXT_PUBLIC_BASE_APIURL"
$target = if (
$publicApiUrl -eq "/api" -or
$publicApiUrl.StartsWith("/api/") -or
$publicApiUrl -match '^https?://(127\.0\.0\.1|localhost)(:\d+)?(?:/|$)'
) { "127.0.0.1" } elseif ($publicApiUrl -match '^https?://') { "0.0.0.0" } else { "127.0.0.1" }
}
if ($target -notin @("127.0.0.1", "0.0.0.0")) {
throw "SERVER_HTTP_BIND_IP only supports 127.0.0.1 or 0.0.0.0, current value: $target"
}
if ($current -ne $target) {
$content = Set-ServerHttpBindEnvValue $content "SERVER_HTTP_BIND_IP" $target
[System.IO.File]::WriteAllText($Path, $content, [System.Text.UTF8Encoding]::new($false))
Write-Host " ✓ SERVER_HTTP_BIND_IP=$target"
}
}
+40
View File
@@ -0,0 +1,40 @@
$ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "Initialize-ServerHttpBindIp.ps1")
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("easyai-bind-test-" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $tempDir | Out-Null
$envPath = Join-Path $tempDir ".env"
function Assert-BindIp {
param([string]$PublicApiUrl, [string]$Expected)
[System.IO.File]::WriteAllText($envPath, "NUXT_PUBLIC_BASE_APIURL=$PublicApiUrl`n", [System.Text.UTF8Encoding]::new($false))
Initialize-ServerHttpBindIp -Path $envPath
$content = Get-Content $envPath -Raw -Encoding UTF8
if ($content -notmatch "(?m)^SERVER_HTTP_BIND_IP=$([regex]::Escape($Expected))$") {
throw "Expected SERVER_HTTP_BIND_IP=$Expected for $PublicApiUrl"
}
}
try {
Assert-BindIp "/api" "127.0.0.1"
Assert-BindIp "http://10.0.0.8:3001" "0.0.0.0"
Assert-BindIp "http://127.0.0.1:3001" "127.0.0.1"
[System.IO.File]::WriteAllText($envPath, "NUXT_PUBLIC_BASE_APIURL=/api`nSERVER_HTTP_BIND_IP=0.0.0.0`n", [System.Text.UTF8Encoding]::new($false))
Initialize-ServerHttpBindIp -Path $envPath
if ((Get-Content $envPath -Raw -Encoding UTF8) -notmatch '(?m)^SERVER_HTTP_BIND_IP=0\.0\.0\.0$') {
throw "Existing explicit bind value was not preserved"
}
$invalidAccepted = $false
try {
Initialize-ServerHttpBindIp -Path $envPath -Override "192.168.1.8"
$invalidAccepted = $true
} catch { }
if ($invalidAccepted) { throw "Invalid SERVER_HTTP_BIND_IP unexpectedly accepted" }
Write-Host "Server HTTP bind IP PowerShell tests passed"
} finally {
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
}
+83
View File
@@ -0,0 +1,83 @@
#!/bin/bash
server_bind_read_env_value() {
local file="$1"
local key="$2"
awk -F= -v key="$key" '$1 == key { sub(/^[^=]*=/, ""); print; exit }' "$file" | tr -d '\r'
}
server_bind_write_env_value() {
local file="$1"
local key="$2"
local value="$3"
local tmp_file
tmp_file="$(mktemp "${file}.XXXXXX")"
awk -v key="$key" -v value="$value" '
BEGIN { replaced = 0 }
$0 ~ "^" key "=" {
if (!replaced) {
print key "=" value
replaced = 1
}
next
}
{ print }
END {
if (!replaced) print key "=" value
}
' "$file" > "$tmp_file"
chmod --reference="$file" "$tmp_file" 2>/dev/null || true
mv "$tmp_file" "$file"
}
infer_server_http_bind_ip() {
local file="$1"
local public_api_url
public_api_url="$(server_bind_read_env_value "$file" "NUXT_PUBLIC_BASE_APIURL")"
case "$public_api_url" in
/api|/api/*|http://127.0.0.1:*|https://127.0.0.1:*|http://localhost:*|https://localhost:*)
printf '%s\n' "127.0.0.1"
;;
http://*|https://*)
printf '%s\n' "0.0.0.0"
;;
*)
printf '%s\n' "127.0.0.1"
;;
esac
}
init_server_http_bind_ip() {
local file="${1:-.env}"
local override="${2:-}"
local current target
if [ ! -f "$file" ]; then
echo "❌ 未找到环境文件: $file" >&2
return 1
fi
current="$(server_bind_read_env_value "$file" "SERVER_HTTP_BIND_IP")"
target="${override:-$current}"
if [ -z "$target" ]; then
target="$(infer_server_http_bind_ip "$file")"
fi
case "$target" in
127.0.0.1|0.0.0.0) ;;
*)
echo "❌ SERVER_HTTP_BIND_IP 仅支持 127.0.0.1 或 0.0.0.0,当前为: $target" >&2
return 1
;;
esac
if [ "$current" != "$target" ]; then
server_bind_write_env_value "$file" "SERVER_HTTP_BIND_IP" "$target"
echo " ✓ SERVER_HTTP_BIND_IP=$target"
fi
}
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
init_server_http_bind_ip "${1:-.env}" "${2:-}"
fi
+37 -1
View File
@@ -20,6 +20,7 @@ mkdir -p "$TMP_DIR/scripts"
cp \
"$REPO_ROOT/scripts/init-security-env.sh" \
"$REPO_ROOT/scripts/init-public-api-base-url.sh" \
"$REPO_ROOT/scripts/init-server-http-bind-ip.sh" \
"$TMP_DIR/scripts/"
cd "$TMP_DIR"
@@ -48,6 +49,34 @@ assert_compression_config() {
fi
}
assert_sandbox_public_access_disabled() {
local config_file="$1"
grep -q '^ location = /api/sandbox {' "$config_file"
grep -q '^ location \^~ /api/sandbox/ {' "$config_file"
grep -q '^ location = /jupyterlab {' "$config_file"
grep -q '^ location \^~ /jupyterlab/ {' "$config_file"
grep -q '^ location = /sandbox {' "$config_file"
grep -q '^ location \^~ /sandbox/ {' "$config_file"
if grep -Eq 'proxy_pass http://127\.0\.0\.1:(8081|8888)' "$config_file"; then
echo "Unexpected public Sandbox/Jupyter proxy in $config_file" >&2
return 1
fi
}
assert_compose_exposure() {
local expected_server_host_ip="$1"
docker compose config --format json | python3 -c '
import json, sys
expected = sys.argv[1]
config = json.load(sys.stdin)
sandbox_ports = config["services"]["sandbox"].get("ports") or []
assert sandbox_ports == [], f"sandbox ports published: {sandbox_ports}"
server_ports = config["services"]["easyai-server"].get("ports") or []
assert len(server_ports) == 1, server_ports
assert server_ports[0].get("host_ip") == expected, server_ports
' "$expected_server_host_ip"
}
sed -i.bak 's/^SERVER_HTTP_PORT=.*/SERVER_HTTP_PORT=4100/' .env.sample
rm -f .env.sample.bak
DEPLOY_NON_INTERACTIVE=1 \
@@ -57,8 +86,9 @@ DEPLOY_NON_INTERACTIVE=1 \
bash start.sh >/dev/null
grep -qx 'NUXT_PUBLIC_BASE_APIURL=http://10.0.0.8:4100' .env
grep -qx 'CONFIG_PUBLIC_API_BASE_URL=http://10.0.0.8:4100' .env
grep -qx 'SERVER_HTTP_BIND_IP=0.0.0.0' .env
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
docker compose config --quiet
assert_compose_exposure 0.0.0.0
fi
reset_case
@@ -75,6 +105,11 @@ grep -q 'proxy_set_header X-Forwarded-Port $server_port;' demo.example.com.conf
grep -q "proxy_set_header X-Original-Prefix '/api';" demo.example.com.conf
grep -q 'location = /api {' demo.example.com.conf
assert_compression_config demo.example.com.conf
assert_sandbox_public_access_disabled demo.example.com.conf
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' .env
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
assert_compose_exposure 127.0.0.1
fi
# 直接验证 https.sh 的缺省配置生成函数会采用完整模板,而不是只代理 3010。
eval "$(awk '
@@ -90,6 +125,7 @@ grep -q 'proxy_pass http://127.0.0.1:3001/;' easyai-proxy.conf
grep -q 'location /socket.io {' easyai-proxy.conf
grep -q 'proxy_set_header X-Forwarded-Host $easyai_forwarded_host;' easyai-proxy.conf
assert_compression_config easyai-proxy.conf
assert_sandbox_public_access_disabled easyai-proxy.conf
reset_case
DEPLOY_NON_INTERACTIVE=1 \
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
# shellcheck source=init-server-http-bind-ip.sh
. "$REPO_ROOT/scripts/init-server-http-bind-ip.sh"
write_env() {
printf '%s\n' "$1" > "$TMP_DIR/.env"
}
write_env 'NUXT_PUBLIC_BASE_APIURL=/api'
init_server_http_bind_ip "$TMP_DIR/.env" >/dev/null
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' "$TMP_DIR/.env"
write_env 'NUXT_PUBLIC_BASE_APIURL=http://10.0.0.8:3001'
init_server_http_bind_ip "$TMP_DIR/.env" >/dev/null
grep -qx 'SERVER_HTTP_BIND_IP=0.0.0.0' "$TMP_DIR/.env"
write_env 'NUXT_PUBLIC_BASE_APIURL=http://127.0.0.1:3001'
init_server_http_bind_ip "$TMP_DIR/.env" >/dev/null
grep -qx 'SERVER_HTTP_BIND_IP=127.0.0.1' "$TMP_DIR/.env"
write_env $'NUXT_PUBLIC_BASE_APIURL=/api\nSERVER_HTTP_BIND_IP=0.0.0.0'
init_server_http_bind_ip "$TMP_DIR/.env" >/dev/null
grep -qx 'SERVER_HTTP_BIND_IP=0.0.0.0' "$TMP_DIR/.env"
if init_server_http_bind_ip "$TMP_DIR/.env" '192.168.1.8' >/dev/null 2>&1; then
echo 'Invalid SERVER_HTTP_BIND_IP unexpectedly accepted' >&2
exit 1
fi
echo 'Server HTTP bind IP shell tests passed'