From f73b4771b23afbb0d3d51a6697ba07f628391ad4 Mon Sep 17 00:00:00 2001 From: Diego <45224689+tangmengqiu@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:41:28 -0500 Subject: [PATCH] Fix(encryption)/aiconfig, exchange config and the encryption setup (#735) --- api/server.go | 102 ++++++++++++++++++--- scripts/setup_encryption.sh | 131 ++++++++------------------- start.sh | 56 +++++------- web/src/components/AITradersPage.tsx | 14 ++- web/src/lib/api.ts | 7 +- 5 files changed, 164 insertions(+), 146 deletions(-) diff --git a/api/server.go b/api/server.go index 2b0459ec..01c2c0ae 100644 --- a/api/server.go +++ b/api/server.go @@ -413,9 +413,9 @@ type SafeExchangeConfig struct { Type string `json:"type"` // "cex" or "dex" Enabled bool `json:"enabled"` Testnet bool `json:"testnet,omitempty"` - HyperliquidWalletAddr string `json:"hyperliquid_wallet_addr"` // Hyperliquid钱包地址(不敏感) - AsterUser string `json:"aster_user"` // Aster用户名(不敏感) - AsterSigner string `json:"aster_signer"` // Aster签名者(不敏感) + HyperliquidWalletAddr string `json:"hyperliquidWalletAddr"` // Hyperliquid钱包地址(不敏感) + AsterUser string `json:"asterUser"` // Aster用户名(不敏感) + AsterSigner string `json:"asterSigner"` // Aster签名者(不敏感) } type UpdateModelConfigRequest struct { @@ -1014,15 +1014,53 @@ func (s *Server) handleGetModelConfigs(c *gin.Context) { c.JSON(http.StatusOK, safeModels) } -// handleUpdateModelConfigs 更新AI模型配置 +// handleUpdateModelConfigs 更新AI模型配置(仅支持加密数据) func (s *Server) handleUpdateModelConfigs(c *gin.Context) { userID := c.GetString("user_id") - var req UpdateModelConfigRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + + // 读取原始请求体 + bodyBytes, err := c.GetRawData() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败"}) return } + // 解析加密的 payload + var encryptedPayload crypto.EncryptedPayload + if err := json.Unmarshal(bodyBytes, &encryptedPayload); err != nil { + log.Printf("❌ 解析加密载荷失败: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "请求格式错误,必须使用加密传输"}) + return + } + + // 验证是否为加密数据 + if encryptedPayload.WrappedKey == "" { + log.Printf("❌ 检测到非加密请求 (UserID: %s)", userID) + c.JSON(http.StatusBadRequest, gin.H{ + "error": "此接口仅支持加密传输,请使用加密客户端", + "code": "ENCRYPTION_REQUIRED", + "message": "Encrypted transmission is required for security reasons", + }) + return + } + + // 解密数据 + decrypted, err := s.cryptoHandler.cryptoService.DecryptSensitiveData(&encryptedPayload) + if err != nil { + log.Printf("❌ 解密模型配置失败 (UserID: %s): %v", userID, err) + c.JSON(http.StatusBadRequest, gin.H{"error": "解密数据失败"}) + return + } + + // 解析解密后的数据 + var req UpdateModelConfigRequest + if err := json.Unmarshal([]byte(decrypted), &req); err != nil { + log.Printf("❌ 解析解密数据失败: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "解析解密数据失败"}) + return + } + log.Printf("🔓 已解密模型配置数据 (UserID: %s)", userID) + // 更新每个模型的配置 for modelID, modelData := range req.Models { err := s.database.UpdateAIModel(userID, modelID, modelData.Enabled, modelData.APIKey, modelData.CustomAPIURL, modelData.CustomModelName) @@ -1033,7 +1071,7 @@ func (s *Server) handleUpdateModelConfigs(c *gin.Context) { } // 重新加载该用户的所有交易员,使新配置立即生效 - err := s.traderManager.LoadUserTraders(s.database, userID) + err = s.traderManager.LoadUserTraders(s.database, userID) if err != nil { log.Printf("⚠️ 重新加载用户交易员到内存失败: %v", err) // 这里不返回错误,因为模型配置已经成功更新到数据库 @@ -1073,15 +1111,53 @@ func (s *Server) handleGetExchangeConfigs(c *gin.Context) { c.JSON(http.StatusOK, safeExchanges) } -// handleUpdateExchangeConfigs 更新交易所配置 +// handleUpdateExchangeConfigs 更新交易所配置(仅支持加密数据) func (s *Server) handleUpdateExchangeConfigs(c *gin.Context) { userID := c.GetString("user_id") - var req UpdateExchangeConfigRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + + // 读取原始请求体 + bodyBytes, err := c.GetRawData() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败"}) return } + // 解析加密的 payload + var encryptedPayload crypto.EncryptedPayload + if err := json.Unmarshal(bodyBytes, &encryptedPayload); err != nil { + log.Printf("❌ 解析加密载荷失败: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "请求格式错误,必须使用加密传输"}) + return + } + + // 验证是否为加密数据 + if encryptedPayload.WrappedKey == "" { + log.Printf("❌ 检测到非加密请求 (UserID: %s)", userID) + c.JSON(http.StatusBadRequest, gin.H{ + "error": "此接口仅支持加密传输,请使用加密客户端", + "code": "ENCRYPTION_REQUIRED", + "message": "Encrypted transmission is required for security reasons", + }) + return + } + + // 解密数据 + decrypted, err := s.cryptoHandler.cryptoService.DecryptSensitiveData(&encryptedPayload) + if err != nil { + log.Printf("❌ 解密交易所配置失败 (UserID: %s): %v", userID, err) + c.JSON(http.StatusBadRequest, gin.H{"error": "解密数据失败"}) + return + } + + // 解析解密后的数据 + var req UpdateExchangeConfigRequest + if err := json.Unmarshal([]byte(decrypted), &req); err != nil { + log.Printf("❌ 解析解密数据失败: %v", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "解析解密数据失败"}) + return + } + log.Printf("🔓 已解密交易所配置数据 (UserID: %s)", userID) + // 更新每个交易所的配置 for exchangeID, exchangeData := range req.Exchanges { err := s.database.UpdateExchange(userID, exchangeID, exchangeData.Enabled, exchangeData.APIKey, exchangeData.SecretKey, exchangeData.Testnet, exchangeData.HyperliquidWalletAddr, exchangeData.AsterUser, exchangeData.AsterSigner, exchangeData.AsterPrivateKey) @@ -1092,7 +1168,7 @@ func (s *Server) handleUpdateExchangeConfigs(c *gin.Context) { } // 重新加载该用户的所有交易员,使新配置立即生效 - err := s.traderManager.LoadUserTraders(s.database, userID) + err = s.traderManager.LoadUserTraders(s.database, userID) if err != nil { log.Printf("⚠️ 重新加载用户交易员到内存失败: %v", err) // 这里不返回错误,因为交易所配置已经成功更新到数据库 diff --git a/scripts/setup_encryption.sh b/scripts/setup_encryption.sh index 506c7b95..35b22c87 100755 --- a/scripts/setup_encryption.sh +++ b/scripts/setup_encryption.sh @@ -58,16 +58,16 @@ echo -e " • 私钥文件: ${YELLOW}$PRIVATE_KEY_FILE${NC}" echo -e " • 公钥文件: ${YELLOW}$PUBLIC_KEY_FILE${NC}" echo -e " • AES密钥: ${YELLOW}256 bits (自动生成)${NC}" -# 询问用户确认 +# 显示必要性说明 echo -read -p "是否继续设置加密环境? [Y/n]: " -n 1 -r +echo -e "${YELLOW}⚠️ 加密环境是系统运行的必需条件(不可跳过)${NC}" +echo -e "${BLUE}ℹ️ 将自动检查并生成以下密钥:${NC}" +echo -e " • RSA-2048 密钥对 (用于传输加密)" +echo -e " • AES-256 数据加密密钥 (用于数据库加密)" +echo -e " • JWT 认证密钥 (用于用户认证)" +echo -e "${BLUE}ℹ️ 如果密钥已存在,将保持现有密钥;如果缺失,将自动生成${NC}" echo -if [[ $REPLY =~ ^[Nn]$ ]]; then - echo -e "${BLUE}ℹ️ 操作已取消${NC}" - exit 0 -fi -echo echo -e "${CYAN}🚀 开始设置加密环境...${NC}" # ============= 步骤1: 创建目录 ============= @@ -94,20 +94,20 @@ echo echo -e "${YELLOW}🔐 步骤 2/4: 生成 RSA-$RSA_KEY_SIZE 密钥对...${NC}" # 检查现有RSA密钥 -if [ -f "$PRIVATE_KEY_FILE" ] || [ -f "$PUBLIC_KEY_FILE" ]; then - echo -e "${YELLOW}⚠️ 检测到现有的RSA密钥文件${NC}" - read -p "是否重新生成RSA密钥? [y/N]: " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - rm -f "$PRIVATE_KEY_FILE" "$PUBLIC_KEY_FILE" - echo -e "${YELLOW}🗑️ 删除旧密钥${NC}" +if [ -f "$PRIVATE_KEY_FILE" ] && [ -f "$PUBLIC_KEY_FILE" ]; then + echo -e "${BLUE}ℹ️ 检测到现有的RSA密钥文件,保持现有密钥${NC}" + # 验证现有密钥 + echo -e " ${CYAN}验证现有密钥对...${NC}" + if openssl rsa -in "$PRIVATE_KEY_FILE" -check -noout 2>/dev/null; then + echo -e "${GREEN} ✓ 现有密钥验证通过${NC}" else - echo -e "${BLUE}ℹ️ 保持现有RSA密钥${NC}" - RSA_SKIPPED=true + echo -e "${RED} ❌ 现有密钥验证失败,将重新生成${NC}" + rm -f "$PRIVATE_KEY_FILE" "$PUBLIC_KEY_FILE" fi fi -if [ "$RSA_SKIPPED" != "true" ]; then +# 如果密钥不存在或验证失败,生成新密钥 +if [ ! -f "$PRIVATE_KEY_FILE" ] || [ ! -f "$PUBLIC_KEY_FILE" ]; then # 生成私钥 echo -e " ${CYAN}生成RSA私钥...${NC}" openssl genrsa -out "$PRIVATE_KEY_FILE" $RSA_KEY_SIZE 2>/dev/null @@ -143,88 +143,33 @@ if [ -f ".env" ]; then fi fi -if [ "$DATA_KEY_EXISTS" = "true" ] || [ "$JWT_KEY_EXISTS" = "true" ]; then - echo -e "${YELLOW}⚠️ 检测到现有的密钥配置${NC}" - if [ "$DATA_KEY_EXISTS" = "true" ]; then - echo -e " • 数据加密密钥已存在" - fi - if [ "$JWT_KEY_EXISTS" = "true" ]; then - echo -e " • JWT认证密钥已存在" - fi - read -p "是否重新生成所有密钥? [y/N]: " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo -e "${BLUE}ℹ️ 保持现有密钥${NC}" - KEY_SKIPPED=true - # 读取现有密钥 - if [ "$DATA_KEY_EXISTS" = "true" ]; then - DATA_KEY=$(grep "^DATA_ENCRYPTION_KEY=" .env | cut -d'=' -f2) - fi - if [ "$JWT_KEY_EXISTS" = "true" ]; then - JWT_KEY=$(grep "^JWT_SECRET=" .env | cut -d'=' -f2) - fi - fi +# 确保 .env 文件存在 +if [ ! -f ".env" ]; then + touch .env fi -if [ "$KEY_SKIPPED" != "true" ]; then - # 生成新的密钥 +# 生成缺失的密钥(必需,不允许跳过) +if [ "$DATA_KEY_EXISTS" != "true" ]; then echo -e " ${CYAN}生成AES-256数据加密密钥...${NC}" - DATA_KEY=$(openssl rand -base64 32) + DATA_KEY=$(openssl rand -base64 32 | tr -d '\n') + echo "DATA_ENCRYPTION_KEY=$DATA_KEY" >> .env echo -e "${GREEN} ✓ 数据加密密钥生成完成${NC}" - - echo -e " ${CYAN}生成JWT认证密钥...${NC}" - JWT_KEY=$(openssl rand -base64 64) - echo -e "${GREEN} ✓ JWT认证密钥生成完成${NC}" - - # 保存到.env文件 - if [ -f ".env" ]; then - # 更新现有文件 - if grep -q "^DATA_ENCRYPTION_KEY=" .env; then - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' "s/^DATA_ENCRYPTION_KEY=.*/DATA_ENCRYPTION_KEY=$DATA_KEY/" .env - else - sed -i "s/^DATA_ENCRYPTION_KEY=.*/DATA_ENCRYPTION_KEY=$DATA_KEY/" .env - fi - else - echo "DATA_ENCRYPTION_KEY=$DATA_KEY" >> .env - fi - - if grep -q "^JWT_SECRET=" .env; then - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' "s/^JWT_SECRET=.*/JWT_SECRET=$JWT_KEY/" .env - else - sed -i "s/^JWT_SECRET=.*/JWT_SECRET=$JWT_KEY/" .env - fi - else - echo "JWT_SECRET=$JWT_KEY" >> .env - fi - else - # 创建新文件 - echo "DATA_ENCRYPTION_KEY=$DATA_KEY" > .env - echo "JWT_SECRET=$JWT_KEY" >> .env - fi - chmod 600 .env - echo -e "${GREEN} ✓ 密钥已保存到 .env 文件${NC}" -elif [ "$DATA_KEY_EXISTS" != "true" ] || [ "$JWT_KEY_EXISTS" != "true" ]; then - # 生成缺失的密钥 - if [ "$DATA_KEY_EXISTS" != "true" ]; then - echo -e " ${CYAN}生成缺失的AES-256数据加密密钥...${NC}" - DATA_KEY=$(openssl rand -base64 32) - echo "DATA_ENCRYPTION_KEY=$DATA_KEY" >> .env - echo -e "${GREEN} ✓ 数据加密密钥生成完成${NC}" - fi - - if [ "$JWT_KEY_EXISTS" != "true" ]; then - echo -e " ${CYAN}生成缺失的JWT认证密钥...${NC}" - JWT_KEY=$(openssl rand -base64 64) - echo "JWT_SECRET=$JWT_KEY" >> .env - echo -e "${GREEN} ✓ JWT认证密钥生成完成${NC}" - fi - - chmod 600 .env - echo -e "${GREEN} ✓ 密钥已保存到 .env 文件${NC}" +else + echo -e "${BLUE} ℹ️ 数据加密密钥已存在,保持现有密钥${NC}" fi +if [ "$JWT_KEY_EXISTS" != "true" ]; then + echo -e " ${CYAN}生成JWT认证密钥...${NC}" + JWT_KEY=$(openssl rand -base64 64 | tr -d '\n') + echo "JWT_SECRET=$JWT_KEY" >> .env + echo -e "${GREEN} ✓ JWT认证密钥生成完成${NC}" +else + echo -e "${BLUE} ℹ️ JWT认证密钥已存在,保持现有密钥${NC}" +fi + +chmod 600 .env +echo -e "${GREEN} ✓ 密钥配置已保存到 .env 文件${NC}" + # ============= 步骤4: 验证和总结 ============= echo echo -e "${YELLOW}✅ 步骤 4/4: 环境验证和总结...${NC}" diff --git a/start.sh b/start.sh index 2f7eb452..ef59b772 100755 --- a/start.sh +++ b/start.sh @@ -104,47 +104,37 @@ check_encryption() { # 如果需要设置加密环境 if [ "$need_setup" = "true" ]; then - print_info "🔐 需要设置加密环境" + print_info "🔐 需要设置加密环境(必需)" print_info "加密环境用于保护敏感数据(API密钥、私钥等)" + print_info "系统将自动配置加密环境..." echo "" - - # 询问用户是否自动设置 - read -p "是否自动设置加密环境?[Y/n]: " auto_setup - auto_setup=${auto_setup:-Y} - - if [[ "$auto_setup" =~ ^[Yy]$ ]]; then - print_info "正在设置加密环境..." - - # 检查加密设置脚本是否存在 - if [ -f "scripts/setup_encryption.sh" ]; then - print_info "正在自动设置加密环境..." - print_info "加密系统将保护: API密钥、私钥、Hyperliquid代理钱包" + + # 检查加密设置脚本是否存在 + if [ -f "scripts/setup_encryption.sh" ]; then + print_info "正在自动设置加密环境..." + print_info "加密系统将保护: API密钥、私钥、Hyperliquid代理钱包" + echo "" + + # 自动运行加密设置脚本 + # n: 保持现有RSA密钥(如果存在)| n: 保持现有数据密钥(如果存在) + echo -e "n\nn" | bash scripts/setup_encryption.sh + if [ $? -eq 0 ]; then + echo "" + print_success "🔐 加密环境设置完成!" + print_info " • RSA-2048密钥对已生成" + print_info " • AES-256数据加密密钥已配置" + print_info " • JWT认证密钥已配置" + print_info " • 所有敏感数据现在都受加密保护" echo "" - - # 自动运行加密设置脚本 - # Y: 继续设置加密环境 | n: 保持现有RSA密钥 | n: 保持现有密钥配置 - echo -e "Y\nn\nn" | bash scripts/setup_encryption.sh - if [ $? -eq 0 ]; then - echo "" - print_success "🔐 加密环境设置完成!" - print_info " • RSA-2048密钥对已生成" - print_info " • AES-256数据加密密钥已配置" - print_info " • JWT认证密钥已配置" - print_info " • 所有敏感数据现在都受加密保护" - echo "" - else - print_error "加密环境设置失败" - exit 1 - fi else - print_error "加密设置脚本不存在: scripts/setup_encryption.sh" + print_error "加密环境设置失败" print_info "请手动运行: ./scripts/setup_encryption.sh" exit 1 fi else - print_warning "跳过加密环境设置" - print_info "手动设置命令: ./scripts/setup_encryption.sh" - print_info "系统将使用未加密模式运行(不推荐)" + print_error "加密设置脚本不存在: scripts/setup_encryption.sh" + print_info "请手动运行: ./scripts/setup_encryption.sh" + exit 1 fi else print_success "🔐 加密环境已配置" diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx index de0aa4ac..1e55bc9a 100644 --- a/web/src/components/AITradersPage.tsx +++ b/web/src/components/AITradersPage.tsx @@ -415,7 +415,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { ]) ), }), - updateApi: api.updateModelConfigs, + updateApi: api.updateModelConfigsEncrypted, refreshApi: api.getModelConfigs, setItems: (items) => { // 使用函数式更新确保状态正确更新 @@ -488,7 +488,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { ), } - await api.updateModelConfigs(request) + await api.updateModelConfigsEncrypted(request) // 重新获取用户配置以确保数据同步 const refreshedModels = await api.getModelConfigs() @@ -515,6 +515,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { ...e, apiKey: '', secretKey: '', + hyperliquidWalletAddr: '', + asterUser: '', + asterSigner: '', + asterPrivateKey: '', enabled: false, }), buildRequest: (exchanges) => ({ @@ -526,11 +530,15 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { api_key: exchange.apiKey || '', secret_key: exchange.secretKey || '', testnet: exchange.testnet || false, + hyperliquid_wallet_addr: exchange.hyperliquidWalletAddr || '', + aster_user: exchange.asterUser || '', + aster_signer: exchange.asterSigner || '', + aster_private_key: exchange.asterPrivateKey || '', }, ]) ), }), - updateApi: api.updateExchangeConfigs, + updateApi: api.updateExchangeConfigsEncrypted, refreshApi: api.getExchangeConfigs, setItems: (items) => { // 使用函数式更新确保状态正确更新 diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 45a670b3..0bd79d8f 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -160,8 +160,7 @@ export const api = { sessionId ) - // 发送加密数据 - const res = await fetch(`${API_BASE}/models/encrypted`, { + const res = await fetch(`${API_BASE}/models`, { method: 'PUT', headers: getAuthHeaders(), body: JSON.stringify(encryptedPayload), @@ -217,8 +216,8 @@ export const api = { sessionId ) - // 发送加密数据 - const res = await fetch(`${API_BASE}/exchanges/encrypted`, { + // 发送加密数据到普通端点 + const res = await fetch(`${API_BASE}/exchanges`, { method: 'PUT', headers: getAuthHeaders(), body: JSON.stringify(encryptedPayload),