forked from wangbo/easyai
fix(security): 收紧默认部署暴露面
This commit is contained in:
@@ -31,6 +31,7 @@ DEPLOY_HTTPS_ENV="${DEPLOY_HTTPS_INPUT:-${DEPLOY_HTTPS:-}}"
|
||||
DEPLOY_HTTPS=false
|
||||
SECURITY_ENV_MODE="upgrade"
|
||||
PUBLIC_API_BASE_URL_CANDIDATE=""
|
||||
PROXY_CONF=""
|
||||
|
||||
is_non_interactive() {
|
||||
[ "${DEPLOY_NON_INTERACTIVE:-0}" = "1" ] || [ "${CI:-}" = "true" ] || [ ! -t 0 ]
|
||||
@@ -58,6 +59,17 @@ normalize_bool() {
|
||||
esac
|
||||
}
|
||||
|
||||
upsert_env_value() {
|
||||
local file=$1
|
||||
local key=$2
|
||||
local value=$3
|
||||
if grep -q "^${key}=" "$file"; then
|
||||
sed -i.bak "s|^${key}=.*|${key}=${value}|" "$file"
|
||||
else
|
||||
printf '\n%s=%s\n' "$key" "$value" >> "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
set_deploy_https_from_env_or_default() {
|
||||
local normalized
|
||||
if ! normalized="$(normalize_bool "${DEPLOY_HTTPS_ENV:-false}")"; then
|
||||
@@ -162,7 +174,7 @@ run_deploy_questions() {
|
||||
if [ -z "$DEPLOY_ACCESS" ]; then
|
||||
require_interactive "DEPLOY_ACCESS=ip DEPLOY_IP=<服务器IP> 或 DEPLOY_ACCESS=domain DEPLOY_DOMAIN=<域名>"
|
||||
echo "1. 通过 IP 地址还是域名访问?"
|
||||
echo " [1] IP 地址(需开放 3001、3002、3003 端口)"
|
||||
echo " [1] IP 地址(仅需开放 80 端口,由 Nginx 统一反向代理)"
|
||||
echo " [2] 域名"
|
||||
read -r -p "请选择 [1/2]: " choice
|
||||
case "$choice" in
|
||||
@@ -181,7 +193,7 @@ run_deploy_questions() {
|
||||
echo "❌ IP 不能为空"
|
||||
exit 1
|
||||
fi
|
||||
echo " 请确保防火墙已放行 3001、3002、3003 端口"
|
||||
echo " 请确保防火墙已放行 80 端口"
|
||||
else
|
||||
# 3. 输入域名
|
||||
prompt_or_env DEPLOY_DOMAIN "3. 请输入域名(不含 https:// 前缀,如 51easyai.com)" "DEPLOY_DOMAIN" ""
|
||||
@@ -238,34 +250,63 @@ setup_env_files() {
|
||||
SECURITY_ENV_MODE="new"
|
||||
fi
|
||||
|
||||
local web_port server_port public_scheme socket_scheme
|
||||
local web_port server_port ws_port asg_port public_scheme socket_scheme public_host security_origins
|
||||
web_port="$(awk -F= '$1 == "WEB_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
||||
web_port="${web_port:-3010}"
|
||||
server_port="$(awk -F= '$1 == "SERVER_HTTP_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
||||
server_port="${server_port:-3001}"
|
||||
ws_port="$(awk -F= '$1 == "CONFIG_WS_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
||||
ws_port="${ws_port:-3002}"
|
||||
asg_port="$(awk -F= '$1 == "ASG_PORT" { print $2; exit }' .env | tr -d '[:space:]')"
|
||||
asg_port="${asg_port:-3003}"
|
||||
|
||||
public_scheme="http"
|
||||
socket_scheme="ws"
|
||||
if [ "$DEPLOY_MODE" = "ip" ]; then
|
||||
# IP 模式
|
||||
sed -i.bak "s|^NUXT_PUBLIC_BASE_APIURL=.*|NUXT_PUBLIC_BASE_APIURL=http://${DEPLOY_IP}:${server_port}|" .env
|
||||
sed -i.bak "s|^NUXT_PUBLIC_BASE_SOCKETURL=.*|NUXT_PUBLIC_BASE_SOCKETURL=ws://${DEPLOY_IP}:3002|" .env
|
||||
sed -i.bak "s|^NUXT_PUBLIC_SG_APIURL=.*|NUXT_PUBLIC_SG_APIURL=http://${DEPLOY_IP}:3003|" .env
|
||||
sed -i.bak "s|^CONFIG_SECURITY_ORIGIN=.*|CONFIG_SECURITY_ORIGIN=http://${DEPLOY_IP}:${web_port}|" .env
|
||||
PUBLIC_API_BASE_URL_CANDIDATE="${DEPLOY_PUBLIC_API_BASE_URL:-http://${DEPLOY_IP}:${server_port}}"
|
||||
echo " ✓ .env 已配置为 IP 模式 (${DEPLOY_IP})"
|
||||
public_host="$DEPLOY_IP"
|
||||
security_origins="http://${DEPLOY_IP}"
|
||||
else
|
||||
# 域名模式
|
||||
public_scheme="http"
|
||||
socket_scheme="ws"
|
||||
public_host="$DEPLOY_DOMAIN"
|
||||
security_origins="https://${DEPLOY_DOMAIN},http://${DEPLOY_DOMAIN}"
|
||||
if [ "$DEPLOY_HTTPS" = true ]; then
|
||||
public_scheme="https"
|
||||
socket_scheme="wss"
|
||||
fi
|
||||
sed -i.bak "s|^NUXT_PUBLIC_BASE_APIURL=.*|NUXT_PUBLIC_BASE_APIURL=/api|" .env
|
||||
sed -i.bak "s|^NUXT_PUBLIC_BASE_SOCKETURL=.*|NUXT_PUBLIC_BASE_SOCKETURL=${socket_scheme}://${DEPLOY_DOMAIN}/socket.io|" .env
|
||||
sed -i.bak "s|^NUXT_PUBLIC_SG_APIURL=.*|NUXT_PUBLIC_SG_APIURL=/asg-api|" .env
|
||||
# 域名模式同时允许同域 HTTP/HTTPS,兼容首次签证书和复用既有证书两种流程。
|
||||
sed -i.bak "s|^CONFIG_SECURITY_ORIGIN=.*|CONFIG_SECURITY_ORIGIN=https://${DEPLOY_DOMAIN},http://${DEPLOY_DOMAIN}|" .env
|
||||
PUBLIC_API_BASE_URL_CANDIDATE="${DEPLOY_PUBLIC_API_BASE_URL:-${public_scheme}://${DEPLOY_DOMAIN}/api}"
|
||||
fi
|
||||
|
||||
# IP 与域名模式共用同一组 Nginx 路径,只由公开 origin 决定 Host 与协议。
|
||||
upsert_env_value .env NUXT_PUBLIC_BASE_APIURL "/api"
|
||||
upsert_env_value .env NUXT_PUBLIC_BASE_SOCKETURL "${socket_scheme}://${public_host}/socket.io"
|
||||
upsert_env_value .env NUXT_PUBLIC_SG_APIURL "/asg-api"
|
||||
upsert_env_value .env CONFIG_SECURITY_ORIGIN "$security_origins"
|
||||
upsert_env_value .env EASYAI_PROXY_BIND_IP "127.0.0.1"
|
||||
upsert_env_value .env EASYAI_INFRA_BIND_IP "127.0.0.1"
|
||||
upsert_env_value .env GATEWAY_INBOUND_TCP_LISTEN_HOST "172.21.0.6"
|
||||
PUBLIC_API_BASE_URL_CANDIDATE="${DEPLOY_PUBLIC_API_BASE_URL:-${public_scheme}://${public_host}/api}"
|
||||
|
||||
if [ "$DEPLOY_MODE" = "ip" ]; then
|
||||
echo " ✓ .env 已配置为 IP + Nginx 模式 (${DEPLOY_IP})"
|
||||
|
||||
# 生成包含前端、API、WebSocket 与 ASG 的完整 IP 代理配置。
|
||||
PROXY_CONF="${DEPLOY_IP}.conf"
|
||||
if [ ! -f "$PROXY_CONF" ]; then
|
||||
local proxy_template="easyai-proxy.conf.sample"
|
||||
if [ ! -f "$proxy_template" ]; then
|
||||
echo "❌ 未找到 Nginx 配置模板: easyai-proxy.conf.sample"
|
||||
echo " 请确认仓库文件完整后重试"
|
||||
exit 1
|
||||
fi
|
||||
sed \
|
||||
-e '/^# EASYAI_WWW_REDIRECT_BEGIN$/,/^# EASYAI_WWW_REDIRECT_END$/d' \
|
||||
-e "s/51easyai\.com/${DEPLOY_IP}/g" \
|
||||
-e "s|127\.0\.0\.1:3010|127.0.0.1:${web_port}|g" \
|
||||
-e "s|127\.0\.0\.1:3001|127.0.0.1:${server_port}|g" \
|
||||
-e "s|127\.0\.0\.1:3002|127.0.0.1:${ws_port}|g" \
|
||||
-e "s|127\.0\.0\.1:3003|127.0.0.1:${asg_port}|g" \
|
||||
"$proxy_template" > "$PROXY_CONF"
|
||||
echo " ✓ Nginx IP 配置已生成: $PROXY_CONF"
|
||||
fi
|
||||
else
|
||||
echo " ✓ .env 已配置为域名模式 (${DEPLOY_DOMAIN})"
|
||||
|
||||
# 7. Nginx 配置(域名模式)
|
||||
@@ -277,7 +318,13 @@ setup_env_files() {
|
||||
echo " 请确认仓库文件完整后重试"
|
||||
exit 1
|
||||
fi
|
||||
sed "s/51easyai.com/${DEPLOY_DOMAIN}/g" "$proxy_template" > "$PROXY_CONF"
|
||||
sed \
|
||||
-e "s/51easyai\.com/${DEPLOY_DOMAIN}/g" \
|
||||
-e "s|127\.0\.0\.1:3010|127.0.0.1:${web_port}|g" \
|
||||
-e "s|127\.0\.0\.1:3001|127.0.0.1:${server_port}|g" \
|
||||
-e "s|127\.0\.0\.1:3002|127.0.0.1:${ws_port}|g" \
|
||||
-e "s|127\.0\.0\.1:3003|127.0.0.1:${asg_port}|g" \
|
||||
"$proxy_template" > "$PROXY_CONF"
|
||||
echo " ✓ Nginx 配置已生成: $PROXY_CONF"
|
||||
fi
|
||||
fi
|
||||
@@ -430,6 +477,72 @@ install_docker() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ==================== 配置 HTTP Nginx 入口 ====================
|
||||
configure_http_nginx_proxy() {
|
||||
case "$DEPLOY_MODE" in
|
||||
ip|domain) ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
# HTTPS 域名由 https.sh 安装配置并签发证书;IP 与纯 HTTP 域名走这里。
|
||||
if [ "$DEPLOY_MODE" = "domain" ] && [ "$DEPLOY_HTTPS" = true ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -z "$PROXY_CONF" ] || [ ! -f "$PROXY_CONF" ]; then
|
||||
echo "❌ 缺少 Nginx 配置文件: ${PROXY_CONF:-未生成}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🌐 配置 Nginx 统一入口(HTTP 80)..."
|
||||
if ! command -v nginx &>/dev/null; then
|
||||
if command -v apt-get &>/dev/null; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y nginx
|
||||
elif command -v dnf &>/dev/null; then
|
||||
sudo dnf install -y nginx
|
||||
elif command -v yum &>/dev/null; then
|
||||
sudo yum install -y nginx
|
||||
else
|
||||
echo "❌ 未找到受支持的 Nginx 包管理器(apt-get/dnf/yum)"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
local nginx_conf_dir="/etc/nginx/conf.d"
|
||||
local nginx_target="${nginx_conf_dir}/easyai-proxy.conf"
|
||||
local nginx_backup=""
|
||||
sudo install -d -m 0755 "$nginx_conf_dir"
|
||||
if sudo test -f "$nginx_target"; then
|
||||
nginx_backup="${nginx_target}.pre-ip-proxy-$(date +%Y%m%dT%H%M%S)"
|
||||
sudo cp -a "$nginx_target" "$nginx_backup"
|
||||
echo " ✓ 已备份现有配置: $nginx_backup"
|
||||
fi
|
||||
sudo install -m 0644 "$PROXY_CONF" "$nginx_target"
|
||||
|
||||
if ! sudo nginx -t; then
|
||||
echo "❌ Nginx 配置校验失败,正在恢复原配置"
|
||||
if [ -n "$nginx_backup" ]; then
|
||||
sudo cp -a "$nginx_backup" "$nginx_target"
|
||||
else
|
||||
sudo rm -f "$nginx_target"
|
||||
fi
|
||||
sudo nginx -t || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v systemctl &>/dev/null; then
|
||||
sudo systemctl enable nginx >/dev/null
|
||||
if sudo systemctl is-active --quiet nginx; then
|
||||
sudo systemctl reload nginx
|
||||
else
|
||||
sudo systemctl start nginx
|
||||
fi
|
||||
else
|
||||
sudo nginx -s reload 2>/dev/null || sudo nginx
|
||||
fi
|
||||
echo " ✓ Nginx 已监听 80,统一代理前端、API、WebSocket 与 ASG"
|
||||
}
|
||||
|
||||
# ==================== 启动服务 ====================
|
||||
start_services() {
|
||||
echo ""
|
||||
@@ -521,17 +634,14 @@ main() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# 域名和本机部署默认仅绑定回环;只有明确的 IP/LAN 直连模式才开放后端宿主机端口。
|
||||
# Linux 的 IP/域名模式都经由同机 Nginx 代理,后端宿主机端口保持回环绑定。
|
||||
# shellcheck source=scripts/init-server-http-bind-ip.sh
|
||||
. ./scripts/init-server-http-bind-ip.sh
|
||||
local server_bind_override=""
|
||||
if [ "$DEPLOY_MODE" = "domain" ]; then
|
||||
server_bind_override="127.0.0.1"
|
||||
elif [ "$DEPLOY_MODE" = "ip" ]; then
|
||||
case "$DEPLOY_IP" in
|
||||
127.0.0.1|localhost) server_bind_override="127.0.0.1" ;;
|
||||
*) server_bind_override="0.0.0.0" ;;
|
||||
esac
|
||||
server_bind_override="127.0.0.1"
|
||||
fi
|
||||
init_server_http_bind_ip .env "$server_bind_override"
|
||||
|
||||
@@ -557,6 +667,7 @@ main() {
|
||||
else
|
||||
install_docker
|
||||
start_services
|
||||
configure_http_nginx_proxy
|
||||
export DEPLOY_DOMAIN
|
||||
run_https_setup
|
||||
fi
|
||||
@@ -566,12 +677,12 @@ main() {
|
||||
echo " 部署完成"
|
||||
echo "================================"
|
||||
if [ "$DEPLOY_MODE" = "ip" ] && [ -n "$DEPLOY_IP" ]; then
|
||||
echo "访问地址: http://${DEPLOY_IP}:3010"
|
||||
echo "访问地址: http://${DEPLOY_IP}"
|
||||
elif [ "$DEPLOY_MODE" = "domain" ] && [ -n "$DEPLOY_DOMAIN" ]; then
|
||||
if [ "$DEPLOY_HTTPS" = true ]; then
|
||||
echo "访问地址: https://${DEPLOY_DOMAIN}"
|
||||
else
|
||||
echo "访问地址: http://${DEPLOY_DOMAIN} (配置 Nginx 后)"
|
||||
echo "访问地址: http://${DEPLOY_DOMAIN}"
|
||||
fi
|
||||
if [ "$DEPLOY_HTTPS" = true ]; then
|
||||
echo "HTTPS 已启用"
|
||||
|
||||
Reference in New Issue
Block a user