Browse Source

feat(auth): enhance IP restriction handling with CIDR support

CaIon 3 weeks ago
parent
commit
39593052b6

+ 29 - 0
common/ip.go

@@ -2,6 +2,15 @@ package common
 
 import "net"
 
+func IsIP(s string) bool {
+	ip := net.ParseIP(s)
+	return ip != nil
+}
+
+func ParseIP(s string) net.IP {
+	return net.ParseIP(s)
+}
+
 func IsPrivateIP(ip net.IP) bool {
 	if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
 		return true
@@ -20,3 +29,23 @@ func IsPrivateIP(ip net.IP) bool {
 	}
 	return false
 }
+
+func IsIpInCIDRList(ip net.IP, cidrList []string) bool {
+	for _, cidr := range cidrList {
+		_, network, err := net.ParseCIDR(cidr)
+		if err != nil {
+			// 尝试作为单个IP处理
+			if whitelistIP := net.ParseIP(cidr); whitelistIP != nil {
+				if ip.Equal(whitelistIP) {
+					return true
+				}
+			}
+			continue
+		}
+
+		if network.Contains(ip) {
+			return true
+		}
+	}
+	return false
+}

+ 1 - 17
common/ssrf_protection.go

@@ -186,23 +186,7 @@ func isIPListed(ip net.IP, list []string) bool {
 		return false
 	}
 
-	for _, whitelistCIDR := range list {
-		_, network, err := net.ParseCIDR(whitelistCIDR)
-		if err != nil {
-			// 尝试作为单个IP处理
-			if whitelistIP := net.ParseIP(whitelistCIDR); whitelistIP != nil {
-				if ip.Equal(whitelistIP) {
-					return true
-				}
-			}
-			continue
-		}
-
-		if network.Contains(ip) {
-			return true
-		}
-	}
-	return false
+	return IsIpInCIDRList(ip, list)
 }
 
 // IsIPAccessAllowed 检查IP是否允许访问

+ 0 - 5
common/utils.go

@@ -217,11 +217,6 @@ func IntMax(a int, b int) int {
 	}
 }
 
-func IsIP(s string) bool {
-	ip := net.ParseIP(s)
-	return ip != nil
-}
-
 func GetUUID() string {
 	code := uuid.New().String()
 	code = strings.Replace(code, "-", "", -1)

+ 12 - 3
middleware/auth.go

@@ -2,12 +2,14 @@ package middleware
 
 import (
 	"fmt"
+	"net"
 	"net/http"
 	"strconv"
 	"strings"
 
 	"github.com/QuantumNous/new-api/common"
 	"github.com/QuantumNous/new-api/constant"
+	"github.com/QuantumNous/new-api/logger"
 	"github.com/QuantumNous/new-api/model"
 	"github.com/QuantumNous/new-api/service"
 	"github.com/QuantumNous/new-api/setting/ratio_setting"
@@ -240,13 +242,20 @@ func TokenAuth() func(c *gin.Context) {
 			return
 		}
 
-		allowIpsMap := token.GetIpLimitsMap()
-		if len(allowIpsMap) != 0 {
+		allowIpsMap := token.GetIpLimits()
+		if len(allowIpsMap) > 0 {
 			clientIp := c.ClientIP()
-			if _, ok := allowIpsMap[clientIp]; !ok {
+			logger.LogDebug(c, "Token has IP restrictions, checking client IP %s", clientIp)
+			ip := net.ParseIP(clientIp)
+			if ip == nil {
+				abortWithOpenAiMessage(c, http.StatusForbidden, "无法解析客户端 IP 地址")
+				return
+			}
+			if common.IsIpInCIDRList(ip, allowIpsMap) == false {
 				abortWithOpenAiMessage(c, http.StatusForbidden, "您的 IP 不在令牌允许访问的列表中")
 				return
 			}
+			logger.LogDebug(c, "Client IP %s passed the token IP restrictions check", clientIp)
 		}
 
 		userCache, err := model.GetUserCache(token.UserId)

+ 7 - 8
model/token.go

@@ -6,7 +6,6 @@ import (
 	"strings"
 
 	"github.com/QuantumNous/new-api/common"
-
 	"github.com/bytedance/gopkg/util/gopool"
 	"gorm.io/gorm"
 )
@@ -35,26 +34,26 @@ func (token *Token) Clean() {
 	token.Key = ""
 }
 
-func (token *Token) GetIpLimitsMap() map[string]any {
+func (token *Token) GetIpLimits() []string {
 	// delete empty spaces
 	//split with \n
-	ipLimitsMap := make(map[string]any)
+	ipLimits := make([]string, 0)
 	if token.AllowIps == nil {
-		return ipLimitsMap
+		return ipLimits
 	}
 	cleanIps := strings.ReplaceAll(*token.AllowIps, " ", "")
 	if cleanIps == "" {
-		return ipLimitsMap
+		return ipLimits
 	}
 	ips := strings.Split(cleanIps, "\n")
 	for _, ip := range ips {
 		ip = strings.TrimSpace(ip)
 		ip = strings.ReplaceAll(ip, ",", "")
-		if common.IsIP(ip) {
-			ipLimitsMap[ip] = true
+		if ip != "" {
+			ipLimits = append(ipLimits, ip)
 		}
 	}
-	return ipLimitsMap
+	return ipLimits
 }
 
 func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {

+ 2 - 2
web/src/components/table/tokens/modals/EditTokenModal.jsx

@@ -557,11 +557,11 @@ const EditTokenModal = (props) => {
                   <Col span={24}>
                     <Form.TextArea
                       field='allow_ips'
-                      label={t('IP白名单')}
+                      label={t('IP白名单(支持CIDR表达式)')}
                       placeholder={t('允许的IP,一行一个,不填写则不限制')}
                       autosize
                       rows={1}
-                      extraText={t('请勿过度信任此功能,IP可能被伪造')}
+                      extraText={t('请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用')}
                       showClear
                       style={{ width: '100%' }}
                     />

+ 2 - 2
web/src/i18n/locales/en.json

@@ -97,7 +97,7 @@
     "Homepage URL 填": "Fill in the Homepage URL",
     "ID": "ID",
     "IP": "IP",
-    "IP白名单": "IP whitelist",
+    "IP白名单(支持CIDR表达式)": "IP whitelist (supports CIDR expressions)",
     "IP限制": "IP restrictions",
     "IP黑名单": "IP blacklist",
     "JSON": "JSON",
@@ -1752,7 +1752,7 @@
     "请先阅读并同意用户协议和隐私政策": "Please read and agree to the user agreement and privacy policy first",
     "请再次输入新密码": "Please enter the new password again",
     "请前往个人设置 → 安全设置进行配置。": "Please go to Personal Settings → Security Settings to configure.",
-    "请勿过度信任此功能,IP可能被伪造": "Do not over-trust this feature, IP can be spoofed",
+    "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "Do not over-trust this feature, IP can be spoofed, please use it in conjunction with gateways such as nginx and CDN",
     "请在系统设置页面编辑分组倍率以添加新的分组:": "Please edit Group ratios in system settings to add new groups:",
     "请填写完整的产品信息": "Please fill in complete product information",
     "请填写完整的管理员账号信息": "Please fill in the complete administrator account information",

+ 2 - 2
web/src/i18n/locales/fr.json

@@ -99,7 +99,7 @@
     "Homepage URL 填": "Remplir l'URL de la page d'accueil",
     "ID": "ID",
     "IP": "IP",
-    "IP白名单": "Liste blanche d'adresses IP",
+    "IP白名单(支持CIDR表达式)": "Liste blanche d'adresses IP (prise en charge des expressions CIDR)",
     "IP限制": "Restrictions d'IP",
     "IP黑名单": "Liste noire d'adresses IP",
     "JSON": "JSON",
@@ -1762,7 +1762,7 @@
     "请先阅读并同意用户协议和隐私政策": "Veuillez d'abord lire et accepter l'accord utilisateur et la politique de confidentialité",
     "请再次输入新密码": "Veuillez saisir à nouveau le nouveau mot de passe",
     "请前往个人设置 → 安全设置进行配置。": "Veuillez aller dans Paramètres personnels → Paramètres de sécurité pour configurer.",
-    "请勿过度信任此功能,IP可能被伪造": "Ne faites pas trop confiance à cette fonctionnalité, l'IP peut être usurpée",
+    "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "Ne faites pas trop confiance à cette fonctionnalité, l'IP peut être usurpée, veuillez l'utiliser en conjonction avec des passerelles telles que nginx et cdn",
     "请在系统设置页面编辑分组倍率以添加新的分组:": "Veuillez modifier les ratios de groupe dans les paramètres système pour ajouter de nouveaux groupes :",
     "请填写完整的产品信息": "Veuillez renseigner l'ensemble des informations produit",
     "请填写完整的管理员账号信息": "Veuillez remplir les informations complètes du compte administrateur",

+ 2 - 2
web/src/i18n/locales/ja.json

@@ -82,7 +82,7 @@
     "Homepage URL 填": "ホームページURLを入力してください",
     "ID": "ID",
     "IP": "IP",
-    "IP白名单": "IPホワイトリスト",
+    "IP白名单(支持CIDR表达式)": "IPホワイトリスト(CIDR表記に対応)",
     "IP限制": "IP制限",
     "IP黑名单": "IPブラックリスト",
     "JSON": "JSON",
@@ -1669,7 +1669,7 @@
     "请先阅读并同意用户协议和隐私政策": "まずユーザー利用規約とプライバシーポリシーをご確認の上、同意してください",
     "请再次输入新密码": "新しいパスワードを再入力してください",
     "请前往个人设置 → 安全设置进行配置。": "アカウント設定 → セキュリティ設定 にて設定してください。",
-    "请勿过度信任此功能,IP可能被伪造": "IPは偽装される可能性があるため、この機能を過信しないでください",
+    "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "IPは偽装される可能性があるため、この機能を過信しないでください。nginxやCDNなどのゲートウェイと組み合わせて使用してください。",
     "请在系统设置页面编辑分组倍率以添加新的分组:": "新規グループを追加するには、システム設定ページでグループ倍率を編集してください:",
     "请填写完整的管理员账号信息": "管理者アカウント情報をすべて入力してください",
     "请填写密钥": "APIキーを入力してください",

+ 2 - 2
web/src/i18n/locales/ru.json

@@ -101,7 +101,7 @@
     "Homepage URL 填": "URL домашней страницы:",
     "ID": "ID",
     "IP": "IP",
-    "IP白名单": "Белый список IP",
+    "IP白名单(支持CIDR表达式)": "Белый список IP (поддерживает выражения CIDR)",
     "IP限制": "Ограничения IP",
     "IP黑名单": "Черный список IP",
     "JSON": "JSON",
@@ -1773,7 +1773,7 @@
     "请先阅读并同意用户协议和隐私政策": "Пожалуйста, сначала прочтите и согласитесь с пользовательским соглашением и политикой конфиденциальности",
     "请再次输入新密码": "Пожалуйста, введите новый пароль ещё раз",
     "请前往个人设置 → 安全设置进行配置。": "Пожалуйста, перейдите в Личные настройки → Настройки безопасности для конфигурации.",
-    "请勿过度信任此功能,IP可能被伪造": "Не доверяйте этой функции чрезмерно, IP может быть подделан",
+    "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "Не доверяйте этой функции чрезмерно, IP может быть подделан, используйте её вместе с nginx и CDN и другими шлюзами",
     "请在系统设置页面编辑分组倍率以添加新的分组:": "Пожалуйста, отредактируйте коэффициенты групп на странице системных настроек для добавления новой группы:",
     "请填写完整的产品信息": "Пожалуйста, заполните всю информацию о продукте",
     "请填写完整的管理员账号信息": "Пожалуйста, заполните полную информацию об учётной записи администратора",

+ 2 - 2
web/src/i18n/locales/vi.json

@@ -82,7 +82,7 @@
     "Homepage URL 填": "Điền URL trang chủ",
     "ID": "ID",
     "IP": "IP",
-    "IP白名单": "Danh sách trắng IP",
+    "IP白名单(支持CIDR表达式)": "Danh sách trắng IP (hỗ trợ biểu thức CIDR)",
     "IP限制": "Hạn chế IP",
     "IP黑名单": "Danh sách đen IP",
     "JSON": "JSON",
@@ -1987,7 +1987,7 @@
     "请先阅读并同意用户协议和隐私政策": "Vui lòng đọc và đồng ý với thỏa thuận người dùng và chính sách bảo mật trước",
     "请再次输入新密码": "Vui lòng nhập lại mật khẩu mới",
     "请前往个人设置 → 安全设置进行配置。": "Vui lòng truy cập Cài đặt cá nhân → Cài đặt bảo mật để cấu hình.",
-    "请勿过度信任此功能,IP可能被伪造": "Đừng quá tin tưởng tính năng này, IP có thể bị giả mạo",
+    "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "Đừng quá tin tưởng tính năng này, IP có thể bị giả mạo, vui lòng sử dụng cùng với nginx và các cổng khác như cdn",
     "请在系统设置页面编辑分组倍率以添加新的分组:": "Vui lòng chỉnh sửa tỷ lệ nhóm trên trang cài đặt hệ thống để thêm nhóm mới:",
     "请填写完整的管理员账号信息": "Vui lòng điền đầy đủ thông tin tài khoản quản trị viên",
     "请填写密钥": "Vui lòng điền khóa",

+ 2 - 2
web/src/i18n/locales/zh.json

@@ -95,7 +95,7 @@
     "Homepage URL 填": "Homepage URL 填",
     "ID": "ID",
     "IP": "IP",
-    "IP白名单": "IP白名单",
+    "IP白名单(支持CIDR表达式)": "IP白名单(支持CIDR表达式)",
     "IP限制": "IP限制",
     "IP黑名单": "IP黑名单",
     "JSON": "JSON",
@@ -1740,7 +1740,7 @@
     "请先阅读并同意用户协议和隐私政策": "请先阅读并同意用户协议和隐私政策",
     "请再次输入新密码": "请再次输入新密码",
     "请前往个人设置 → 安全设置进行配置。": "请前往个人设置 → 安全设置进行配置。",
-    "请勿过度信任此功能,IP可能被伪造": "请勿过度信任此功能,IP可能被伪造",
+    "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用": "请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用",
     "请在系统设置页面编辑分组倍率以添加新的分组:": "请在系统设置页面编辑分组倍率以添加新的分组:",
     "请填写完整的产品信息": "请填写完整的产品信息",
     "请填写完整的管理员账号信息": "请填写完整的管理员账号信息",