mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
fix: support trusted proxies for allowed IPs (#13608)
This commit is contained in:
@@ -108,6 +108,14 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
}
|
||||
req.Value = value
|
||||
}
|
||||
if req.Key == "AllowIPTrustedProxies" {
|
||||
value, err := common.NormalizeTrustedProxies(req.Value)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
req.Value = value
|
||||
}
|
||||
|
||||
if err := settingService.Update(c, req.Key, req.Value); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -108,120 +107,11 @@ func LoadAPIAuthConfig(_ *gin.Context) (APIAuthConfig, error) {
|
||||
}
|
||||
|
||||
func GetAPIClientIP(c *gin.Context, trustedProxies string) string {
|
||||
remoteAddr := common.GetRealClientIP(c)
|
||||
remoteIP := net.ParseIP(remoteAddr)
|
||||
if remoteIP == nil {
|
||||
return remoteAddr
|
||||
}
|
||||
|
||||
proxies, err := parseAPITrustedProxies(trustedProxies)
|
||||
if err != nil {
|
||||
if global.LOG != nil {
|
||||
global.LOG.Errorf("Failed to parse API trusted proxies: %v", err)
|
||||
}
|
||||
return remoteAddr
|
||||
}
|
||||
if !isIPInNetworks(remoteIP, proxies) {
|
||||
return remoteAddr
|
||||
}
|
||||
|
||||
forwardedFor := strings.Join(c.Request.Header.Values("X-Forwarded-For"), ",")
|
||||
if strings.TrimSpace(forwardedFor) != "" {
|
||||
clientIP, ok := clientIPFromForwardedFor(forwardedFor, proxies)
|
||||
if !ok {
|
||||
return remoteAddr
|
||||
}
|
||||
return clientIP
|
||||
}
|
||||
|
||||
realIPValue := strings.Join(c.Request.Header.Values("X-Real-IP"), ",")
|
||||
realIP := net.ParseIP(strings.TrimSpace(realIPValue))
|
||||
if realIP == nil {
|
||||
return remoteAddr
|
||||
}
|
||||
return realIP.String()
|
||||
return common.ResolveClientIP(c, trustedProxies)
|
||||
}
|
||||
|
||||
func NormalizeAPITrustedProxies(value string) (string, error) {
|
||||
lines := strings.Split(value, "\n")
|
||||
normalized := make([]string, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
item := strings.TrimSpace(line)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(item); ip != nil {
|
||||
normalized = append(normalized, ip.String())
|
||||
continue
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(item)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid API trusted proxy entry %q: %w", item, err)
|
||||
}
|
||||
ones, _ := ipNet.Mask.Size()
|
||||
if ones == 0 {
|
||||
return "", fmt.Errorf("invalid API trusted proxy entry %q: unrestricted CIDR is not allowed", item)
|
||||
}
|
||||
normalized = append(normalized, ipNet.String())
|
||||
}
|
||||
return strings.Join(normalized, "\n"), nil
|
||||
}
|
||||
|
||||
func parseAPITrustedProxies(value string) ([]*net.IPNet, error) {
|
||||
normalized, err := NormalizeAPITrustedProxies(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if normalized == "" {
|
||||
return []*net.IPNet{}, nil
|
||||
}
|
||||
|
||||
lines := strings.Split(normalized, "\n")
|
||||
proxies := make([]*net.IPNet, 0, len(lines))
|
||||
for _, item := range lines {
|
||||
if ip := net.ParseIP(item); ip != nil {
|
||||
bits := 128
|
||||
if ip.To4() != nil {
|
||||
bits = 32
|
||||
ip = ip.To4()
|
||||
}
|
||||
proxies = append(proxies, &net.IPNet{IP: ip, Mask: net.CIDRMask(bits, bits)})
|
||||
continue
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxies = append(proxies, ipNet)
|
||||
}
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
func clientIPFromForwardedFor(value string, trustedProxies []*net.IPNet) (string, bool) {
|
||||
items := strings.Split(value, ",")
|
||||
ips := make([]net.IP, len(items))
|
||||
for i, item := range items {
|
||||
ip := net.ParseIP(strings.TrimSpace(item))
|
||||
if ip == nil {
|
||||
return "", false
|
||||
}
|
||||
ips[i] = ip
|
||||
}
|
||||
for i := len(ips) - 1; i >= 0; i-- {
|
||||
if i == 0 || !isIPInNetworks(ips[i], trustedProxies) {
|
||||
return ips[i].String(), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func isIPInNetworks(ip net.IP, networks []*net.IPNet) bool {
|
||||
for _, network := range networks {
|
||||
if network.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return common.NormalizeTrustedProxies(value)
|
||||
}
|
||||
|
||||
func IsValid1PanelTimestamp(panelTimestamp string, apiKeyValidityTime int) bool {
|
||||
|
||||
@@ -27,6 +27,7 @@ type SettingInfo struct {
|
||||
BindDomain string `json:"bindDomain"`
|
||||
PasskeyTrustedProxies string `json:"passkeyTrustedProxies"`
|
||||
AllowIPs string `json:"allowIPs"`
|
||||
AllowIPTrustedProxies string `json:"allowIPTrustedProxies"`
|
||||
SecurityEntrance string `json:"securityEntrance"`
|
||||
DashboardMemoVisible string `json:"dashboardMemoVisible"`
|
||||
DashboardSimpleNodeVisible string `json:"dashboardSimpleNodeVisible"`
|
||||
|
||||
@@ -52,6 +52,7 @@ func Init() {
|
||||
migrations.AddAlertAuditUser,
|
||||
migrations.AddMenuAccordionSetting,
|
||||
migrations.AddAPITrustedProxiesSetting,
|
||||
migrations.AddAllowIPTrustedProxiesSetting,
|
||||
migrations.AddWebsiteTemplateMenu,
|
||||
migrations.RepairXpackAppMenus,
|
||||
})
|
||||
|
||||
@@ -172,6 +172,9 @@ var InitSetting = &gormigrate.Migration{
|
||||
if err := tx.Create(&model.Setting{Key: "AllowIPs", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "AllowIPTrustedProxies", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "NoAuthSetting", Value: "200"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1301,6 +1304,20 @@ var AddAPITrustedProxiesSetting = &gormigrate.Migration{
|
||||
},
|
||||
}
|
||||
|
||||
var AddAllowIPTrustedProxiesSetting = &gormigrate.Migration{
|
||||
ID: "20260820-add-allow-ip-trusted-proxies-setting",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var setting model.Setting
|
||||
if err := tx.Where("key = ?", "AllowIPTrustedProxies").First(&setting).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return tx.Create(&model.Setting{Key: "AllowIPTrustedProxies", Value: ""}).Error
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddWebsiteTemplateMenu = &gormigrate.Migration{
|
||||
ID: "20260728-add-website-template-menu",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
||||
@@ -45,6 +45,7 @@ var baseSettingKeys = map[string]struct{}{
|
||||
"SecurityEntrance": {},
|
||||
"BindDomain": {},
|
||||
"AllowIPs": {},
|
||||
"AllowIPTrustedProxies": {},
|
||||
"PasskeyTrustedProxies": {},
|
||||
"ComplexityVerification": {},
|
||||
"NoAuthSetting": {},
|
||||
|
||||
@@ -14,18 +14,21 @@ import (
|
||||
func WhiteAllow() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
tokenString := c.GetHeader("X-Panel-Local-Token")
|
||||
clientIP := common.GetRealClientIP(c)
|
||||
if isLocalSyncRequest(c.Request.URL.Path, clientIP, tokenString) {
|
||||
remoteIP := common.GetRealClientIP(c)
|
||||
if isLocalSyncRequest(c.Request.URL.Path, remoteIP, tokenString) {
|
||||
c.Set("LOCAL_REQUEST", true)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if common.IsPrivateIP(clientIP) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
settingRepo := repo.NewISettingRepo()
|
||||
trustedProxies, err := settingRepo.GetValueByKey("AllowIPTrustedProxies")
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
clientIP := common.ResolveClientIP(c, trustedProxies)
|
||||
|
||||
allowIPs, err := settingRepo.GetValueByKey("AllowIPs")
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
|
||||
122
core/utils/common/trusted_proxy.go
Normal file
122
core/utils/common/trusted_proxy.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ResolveClientIP returns the TCP peer address unless that peer is trusted.
|
||||
// Forwarded client address headers are never used for untrusted peers.
|
||||
func ResolveClientIP(c *gin.Context, trustedProxies string) string {
|
||||
remoteAddr := GetRealClientIP(c)
|
||||
remoteIP := net.ParseIP(remoteAddr)
|
||||
if remoteIP == nil {
|
||||
return remoteAddr
|
||||
}
|
||||
|
||||
proxies, err := parseTrustedProxies(trustedProxies)
|
||||
if err != nil || !isIPInNetworks(remoteIP, proxies) {
|
||||
return remoteAddr
|
||||
}
|
||||
|
||||
forwardedFor := strings.Join(c.Request.Header.Values("X-Forwarded-For"), ",")
|
||||
if strings.TrimSpace(forwardedFor) != "" {
|
||||
clientIP, ok := clientIPFromForwardedFor(forwardedFor, proxies)
|
||||
if !ok {
|
||||
return remoteAddr
|
||||
}
|
||||
return clientIP
|
||||
}
|
||||
|
||||
realIPValue := strings.Join(c.Request.Header.Values("X-Real-IP"), ",")
|
||||
realIP := net.ParseIP(strings.TrimSpace(realIPValue))
|
||||
if realIP == nil {
|
||||
return remoteAddr
|
||||
}
|
||||
return realIP.String()
|
||||
}
|
||||
|
||||
func NormalizeTrustedProxies(value string) (string, error) {
|
||||
lines := strings.Split(value, "\n")
|
||||
normalized := make([]string, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
item := strings.TrimSpace(line)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(item); ip != nil {
|
||||
normalized = append(normalized, ip.String())
|
||||
continue
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(item)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid trusted proxy entry %q: %w", item, err)
|
||||
}
|
||||
ones, _ := ipNet.Mask.Size()
|
||||
if ones == 0 {
|
||||
return "", fmt.Errorf("invalid trusted proxy entry %q: unrestricted CIDR is not allowed", item)
|
||||
}
|
||||
normalized = append(normalized, ipNet.String())
|
||||
}
|
||||
return strings.Join(normalized, "\n"), nil
|
||||
}
|
||||
|
||||
func parseTrustedProxies(value string) ([]*net.IPNet, error) {
|
||||
normalized, err := NormalizeTrustedProxies(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if normalized == "" {
|
||||
return []*net.IPNet{}, nil
|
||||
}
|
||||
|
||||
lines := strings.Split(normalized, "\n")
|
||||
proxies := make([]*net.IPNet, 0, len(lines))
|
||||
for _, item := range lines {
|
||||
if ip := net.ParseIP(item); ip != nil {
|
||||
bits := 128
|
||||
if ip.To4() != nil {
|
||||
bits = 32
|
||||
ip = ip.To4()
|
||||
}
|
||||
proxies = append(proxies, &net.IPNet{IP: ip, Mask: net.CIDRMask(bits, bits)})
|
||||
continue
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxies = append(proxies, ipNet)
|
||||
}
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
func clientIPFromForwardedFor(value string, trustedProxies []*net.IPNet) (string, bool) {
|
||||
items := strings.Split(value, ",")
|
||||
ips := make([]net.IP, len(items))
|
||||
for i, item := range items {
|
||||
ip := net.ParseIP(strings.TrimSpace(item))
|
||||
if ip == nil {
|
||||
return "", false
|
||||
}
|
||||
ips[i] = ip
|
||||
}
|
||||
for i := len(ips) - 1; i >= 0; i-- {
|
||||
if i == 0 || !isIPInNetworks(ips[i], trustedProxies) {
|
||||
return ips[i].String(), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func isIPInNetworks(ip net.IP, networks []*net.IPNet) bool {
|
||||
for _, network := range networks {
|
||||
if network.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -195,10 +195,8 @@ func checkIPLimit(c *gin.Context) bool {
|
||||
if len(status.Value) == 0 {
|
||||
return true
|
||||
}
|
||||
clientIP := common.GetRealClientIP(c)
|
||||
if common.IsPrivateIP(clientIP) {
|
||||
return true
|
||||
}
|
||||
trustedProxies, _ := settingRepo.Get(repo.WithByKey("AllowIPTrustedProxies"))
|
||||
clientIP := common.ResolveClientIP(c, trustedProxies.Value)
|
||||
|
||||
for _, ip := range strings.Split(status.Value, ",") {
|
||||
if len(ip) == 0 {
|
||||
|
||||
@@ -50,6 +50,7 @@ export namespace Setting {
|
||||
ssl: string;
|
||||
sslType: string;
|
||||
allowIPs: string;
|
||||
allowIPTrustedProxies: string;
|
||||
bindDomain: string;
|
||||
passkeyTrustedProxies: string;
|
||||
securityEntrance: string;
|
||||
|
||||
@@ -2823,7 +2823,7 @@ const message = {
|
||||
apiTrustedProxies: 'API trusted proxies',
|
||||
apiTrustedProxiesEgs: 'One proxy server IP or CIDR per line. For example,\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'When using a reverse proxy, enter the IP address or network of the proxy server to correctly obtain the client IP. Otherwise, leave this blank. 0.0.0.0/0 and ::/0 are not supported.',
|
||||
'When using a reverse proxy, enter its IP address or network. When empty, no proxy is trusted and the direct source IP is used. 0.0.0.0/0 and ::/0 are not supported.',
|
||||
apiKeyValidityTime: 'Validity period of interface key',
|
||||
apiKeyValidityTimeEgs: 'Validity period of interface key (in minutes)',
|
||||
apiKeyValidityTimeHelper:
|
||||
@@ -3025,6 +3025,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'After you set the authorized IP address list, only the IP address in the list can access the panel service. Continue?',
|
||||
allowIPsHelper1: 'Leave it blank to disable the IP address restriction.',
|
||||
allowIPsPrivateHelper:
|
||||
'Private-network addresses are also restricted. For local access, add 127.0.0.1 or ::1; for reverse proxies, configure trusted proxies.',
|
||||
allowIPEgs: 'One per line. For example,\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: 'Two-Factor Auth',
|
||||
mfaClose: 'Disabling MFA will reduce the security of the service. Continue?',
|
||||
|
||||
@@ -2870,7 +2870,7 @@ const message = {
|
||||
apiTrustedProxies: 'Proxies de confianza de la API',
|
||||
apiTrustedProxiesEgs: 'Una IP o CIDR del servidor proxy por línea. Por ejemplo,\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'Al usar un proxy inverso, introduce la IP o la red del servidor proxy para obtener correctamente la IP del cliente. Si no lo usas, deja este campo vacío. No se admiten 0.0.0.0/0 ni ::/0.',
|
||||
'Al usar un proxy inverso, introduce su IP o red. Si se deja vacío, no se confía en ningún proxy y se usa la IP de origen directa. No se admiten 0.0.0.0/0 ni ::/0.',
|
||||
apiKeyValidityTime: 'Periodo de validez de la clave de interfaz',
|
||||
apiKeyValidityTimeEgs: 'Periodo de validez de la clave de interfaz (en minutos)',
|
||||
apiKeyValidityTimeHelper:
|
||||
@@ -3079,6 +3079,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'Tras configurar la lista de IP autorizadas, solo las IP en la lista podrán acceder al panel. ¿Deseas continuar?',
|
||||
allowIPsHelper1: 'Déjalo en blanco para desactivar la restricción por IP.',
|
||||
allowIPsPrivateHelper:
|
||||
'Las direcciones de red privada también están restringidas. Para acceso local, añade 127.0.0.1 o ::1; para proxies inversos, configura proxies de confianza.',
|
||||
allowIPEgs: 'Una por línea. Por ejemplo,\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: 'Autenticación en Dos Pasos',
|
||||
mfaClose: 'Desactivar MFA reducirá la seguridad del servicio. ¿Deseas continuar?',
|
||||
|
||||
@@ -2794,7 +2794,7 @@ const message = {
|
||||
apiTrustedProxies: 'پروکسیهای قابل اعتماد API',
|
||||
apiTrustedProxiesEgs: 'در هر خط یک IP یا CIDR سرور پروکسی. مثلاً،\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'هنگام استفاده از پروکسی معکوس، IP یا شبکه سرور پروکسی را وارد کنید تا IP کلاینت بهدرستی دریافت شود. در صورت عدم استفاده، این بخش را خالی بگذارید. 0.0.0.0/0 و ::/0 پشتیبانی نمیشوند.',
|
||||
'هنگام استفاده از پروکسی معکوس، IP یا شبکه آن را وارد کنید. اگر خالی باشد، هیچ پروکسی قابل اعتماد نیست و IP مبدأ مستقیم استفاده میشود. 0.0.0.0/0 و ::/0 پشتیبانی نمیشوند.',
|
||||
apiKeyValidityTime: 'مدت اعتبار کلید رابط',
|
||||
apiKeyValidityTimeEgs: 'مدت اعتبار کلید رابط (به دقیقه)',
|
||||
apiKeyValidityTimeHelper:
|
||||
@@ -2993,6 +2993,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'پس از تنظیم لیست آدرسهای IP مجاز، فقط آدرس IP موجود در لیست میتواند به سرویس پنل دسترسی داشته باشد. ادامه میدهید؟',
|
||||
allowIPsHelper1: 'خالی بگذارید تا محدودیت IP غیرفعال شود.',
|
||||
allowIPsPrivateHelper:
|
||||
'آدرسهای شبکه خصوصی نیز محدود میشوند. برای دسترسی محلی 127.0.0.1 یا ::1 را اضافه کنید؛ برای پروکسی معکوس، پروکسی قابل اعتماد را تنظیم کنید.',
|
||||
allowIPEgs: 'هر خط یک IP. مثلاً،\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: 'احراز هویت دو مرحلهای',
|
||||
mfaClose: 'غیرفعالسازی MFA امنیت سرویس را کاهش میدهد. ادامه میدهید؟',
|
||||
|
||||
@@ -2802,7 +2802,7 @@ const message = {
|
||||
apiTrustedProxies: 'API信頼済みプロキシ',
|
||||
apiTrustedProxiesEgs: 'プロキシサーバーのIPまたはCIDRを1行に1つ入力します。例:\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'リバースプロキシを使用する場合は、クライアントIPを正しく取得するために、プロキシサーバーのIPまたはネットワークを入力してください。使用しない場合は空欄にしてください。0.0.0.0/0と::/0は指定できません。',
|
||||
'リバースプロキシを使用する場合は、その IP またはネットワークを入力してください。空欄の場合はどのプロキシも信頼せず、直接の接続元 IP を使用します。0.0.0.0/0 と ::/0 は指定できません。',
|
||||
apiKeyReset: 'インターフェイスキーリセット',
|
||||
apiKeyResetHelper: '関連するキーサービスは無効になります。サービスに新しいキーを追加してください',
|
||||
confDockerProxy: 'Dockerプロキシを構成します',
|
||||
@@ -2986,6 +2986,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'承認されたIPアドレスリストを設定した後、リスト内のIPアドレスのみがパネルサービスにアクセスできます。続けたいですか?',
|
||||
allowIPsHelper1: 'IPアドレスの制限を無効にするために空白のままにします。',
|
||||
allowIPsPrivateHelper:
|
||||
'プライベートネットワークのアドレスも制限されます。ローカル接続には 127.0.0.1 または ::1 を追加し、リバースプロキシには信頼済みプロキシを設定してください。',
|
||||
allowIPEgs: '1行に1つ。たとえば、 \n172.16.10.111 \n172.16.10.0/24',
|
||||
mfa: '二段階認証',
|
||||
mfaClose: 'MFAを無効にすると、サービスのセキュリティが減少します。続けたいですか?',
|
||||
|
||||
@@ -2763,7 +2763,7 @@ const message = {
|
||||
apiTrustedProxiesEgs:
|
||||
'프록시 서버 IP 또는 CIDR을 한 줄에 하나씩 입력하십시오. 예:\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'리버스 프록시를 사용하는 경우 클라이언트 IP를 올바르게 가져오려면 프록시 서버의 IP 또는 네트워크를 입력하세요. 사용하지 않는 경우 비워 두세요. 0.0.0.0/0 및 ::/0은 지원되지 않습니다.',
|
||||
'리버스 프록시를 사용하는 경우 해당 IP 또는 네트워크를 입력하세요. 비워 두면 어떤 프록시도 신뢰하지 않고 직접 연결된 출발지 IP를 사용합니다. 0.0.0.0/0 및 ::/0은 지원되지 않습니다.',
|
||||
apiKeyValidityTime: '인터페이스 키 유효 기간',
|
||||
apiKeyValidityTimeEgs: '인터페이스 키 유효 기간 (분 단위)',
|
||||
apiKeyValidityTimeHelper:
|
||||
@@ -2938,6 +2938,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'허가된 IP 주소 목록을 설정하면 목록에 있는 IP 주소만 패널 서비스에 접근할 수 있습니다. 계속하시겠습니까?',
|
||||
allowIPsHelper1: '공백으로 남기면 IP 주소 제한이 비활성화됩니다.',
|
||||
allowIPsPrivateHelper:
|
||||
'사설 네트워크 주소도 제한됩니다. 로컬 접속에는 127.0.0.1 또는 ::1을 추가하고, 리버스 프록시에는 신뢰할 수 있는 프록시를 설정하세요.',
|
||||
allowIPEgs: '한 줄에 하나씩 입력하십시오. 예:\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: '2단계 인증',
|
||||
mfaClose: 'MFA를 비활성화하면 서비스 보안이 낮아집니다. 계속하시겠습니까?',
|
||||
|
||||
@@ -2746,7 +2746,7 @@ const message = {
|
||||
apiTrustedProxies: 'ພຣັອກຊີ API ທີ່ເຊື່ອຖື',
|
||||
apiTrustedProxiesEgs: 'ໜຶ່ງ IP ຫຼື CIDR ຂອງ proxy server ຕໍ່ແຖວ. ຕົວຢ່າງ:\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'ເມື່ອໃຊ້ reverse proxy, ໃຫ້ໃສ່ IP ຫຼືເຄືອຂ່າຍຂອງ proxy server ເພື່ອໃຫ້ໄດ້ client IP ຢ່າງຖືກຕ້ອງ. ຖ້າບໍ່ໃຊ້ໃຫ້ປ່ອຍຫວ່າງ. ບໍ່ຮອງຮັບ 0.0.0.0/0 ແລະ ::/0.',
|
||||
'ເມື່ອໃຊ້ reverse proxy ໃຫ້ໃສ່ IP ຫຼືເຄືອຂ່າຍຂອງມັນ. ຖ້າປ່ອຍຫວ່າງ ຈະບໍ່ເຊື່ອຖື proxy ໃດ ແລະໃຊ້ IP ຕົ້ນທາງໂດຍກົງ. ບໍ່ຮອງຮັບ 0.0.0.0/0 ແລະ ::/0.',
|
||||
apiKeyValidityTime: 'ໄລຍະເວລາທີ່ API key ໃຊ້ງານໄດ້',
|
||||
apiKeyValidityTimeEgs: 'ໄລຍະເວລາທີ່ API key ໃຊ້ງານໄດ້ (ນາທີ)',
|
||||
apiKeyValidityTimeHelper:
|
||||
@@ -2940,6 +2940,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'ຫຼັງຈາກຕັ້ງລາຍຊື່ IP ທີ່ໄດ້ຮັບອະນຸຍາດ, ຈະມີພຽງ IP ໃນລາຍຊື່ເທົ່ານັ້ນທີ່ສາມາດເຂົ້າເຖິງພາເນລໄດ້. ຕ້ອງການເຮັດຕໍ່ບໍ?',
|
||||
allowIPsHelper1: 'ປະຫວ່າງໄວ້ເພື່ອປິດການຈຳກັດ IP.',
|
||||
allowIPsPrivateHelper:
|
||||
'ທີ່ຢູ່ເຄືອຂ່າຍສ່ວນຕົວກໍຖືກຈຳກັດ. ສຳລັບການເຂົ້າເຖິງພາຍໃນ ໃຫ້ເພີ່ມ 127.0.0.1 ຫຼື ::1; ສຳລັບ reverse proxy ໃຫ້ຕັ້ງຄ່າ proxy ທີ່ເຊື່ອຖື.',
|
||||
allowIPEgs: 'ໜຶ່ງລາຍການຕໍ່ແຖວ. ຕົວຢ່າງ:\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: 'ການຢືນຢັນຕົວຕົນສອງຊັ້ນ (MFA)',
|
||||
mfaClose: 'ການປິດ MFA ຈະຫຼຸດຄວາມປອດໄພຂອງບໍລິການ. ຕ້ອງການເຮັດຕໍ່ບໍ?',
|
||||
|
||||
@@ -2867,7 +2867,7 @@ const message = {
|
||||
apiTrustedProxiesEgs:
|
||||
'Satu IP atau CIDR pelayan proksi bagi setiap baris. Contoh:\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'Apabila menggunakan proksi songsang, masukkan IP atau rangkaian pelayan proksi untuk mendapatkan IP klien dengan betul. Jika tidak digunakan, biarkan kosong. 0.0.0.0/0 dan ::/0 tidak disokong.',
|
||||
'Apabila menggunakan proksi songsang, masukkan IP atau rangkaiannya. Jika kosong, tiada proksi dipercayai dan IP sumber langsung digunakan. 0.0.0.0/0 dan ::/0 tidak disokong.',
|
||||
apiKeyValidityTime: 'Tempoh sah kunci antara muka',
|
||||
apiKeyValidityTimeEgs: 'Tempoh sah kunci antara muka (dalam minit)',
|
||||
apiKeyValidityTimeHelper:
|
||||
@@ -3060,6 +3060,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'Selepas anda menetapkan senarai alamat IP yang dibenarkan, hanya alamat IP dalam senarai yang boleh mengakses perkhidmatan panel. Adakah anda mahu meneruskan?',
|
||||
allowIPsHelper1: 'Biarkan kosong untuk melumpuhkan sekatan alamat IP.',
|
||||
allowIPsPrivateHelper:
|
||||
'Alamat rangkaian peribadi juga disekat. Untuk akses setempat, tambahkan 127.0.0.1 atau ::1; untuk proksi songsang, tetapkan proksi dipercayai.',
|
||||
allowIPEgs: 'Satu per baris. Contoh,\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: 'Pengesahan Dua Faktor',
|
||||
mfaClose: 'Melumpuhkan MFA akan mengurangkan keselamatan perkhidmatan. Adakah anda mahu meneruskan?',
|
||||
|
||||
@@ -2868,7 +2868,7 @@ const message = {
|
||||
apiTrustedProxies: 'Proxies confiáveis da API',
|
||||
apiTrustedProxiesEgs: 'Um IP ou CIDR do servidor proxy por linha. Exemplo:\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'Ao usar um proxy reverso, insira o IP ou a rede do servidor proxy para obter corretamente o IP do cliente. Caso contrário, deixe este campo em branco. 0.0.0.0/0 e ::/0 não são suportados.',
|
||||
'Ao usar um proxy reverso, informe seu IP ou rede. Quando vazio, nenhum proxy é confiável e o IP de origem direta é usado. 0.0.0.0/0 e ::/0 não são suportados.',
|
||||
apiKeyReset: 'Redefinir chave da interface',
|
||||
apiKeyResetHelper:
|
||||
'O serviço associado à chave se tornará inválido. Por favor, adicione uma nova chave ao serviço',
|
||||
@@ -3057,6 +3057,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'Após definir a lista de IPs autorizados, somente os IPs da lista poderão acessar o serviço do painel. Você deseja continuar?',
|
||||
allowIPsHelper1: 'Deixe em branco para desabilitar a restrição de IP.',
|
||||
allowIPsPrivateHelper:
|
||||
'Endereços de rede privada também são restritos. Para acesso local, adicione 127.0.0.1 ou ::1; para proxy reverso, configure proxies confiáveis.',
|
||||
allowIPEgs: 'Um por linha. Por exemplo,\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: 'Autenticação em Dois Fatores',
|
||||
mfaClose: 'Desabilitar MFA reduzirá a segurança do serviço. Você deseja continuar?',
|
||||
|
||||
@@ -2841,7 +2841,7 @@ const message = {
|
||||
apiTrustedProxiesEgs:
|
||||
'По одному IP-адресу или CIDR прокси-сервера в строке. Например,\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'При использовании обратного прокси укажите IP-адрес или сеть прокси-сервера, чтобы корректно определить IP клиента. Если прокси не используется, оставьте поле пустым. 0.0.0.0/0 и ::/0 не поддерживаются.',
|
||||
'При использовании обратного прокси укажите его IP-адрес или сеть. Если поле пустое, ни один прокси не считается доверенным и используется IP прямого источника. 0.0.0.0/0 и ::/0 не поддерживаются.',
|
||||
apiKeyValidityTime: 'Срок действия ключа интерфейса',
|
||||
apiKeyValidityTimeEgs: 'Срок действия ключа интерфейса (в единицах)',
|
||||
apiKeyValidityTimeHelper:
|
||||
@@ -3031,6 +3031,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'После установки списка авторизованных IP-адресов только IP-адреса из списка смогут получить доступ к службе панели. Хотите продолжить?',
|
||||
allowIPsHelper1: 'Оставьте пустым, чтобы отключить ограничение IP-адресов.',
|
||||
allowIPsPrivateHelper:
|
||||
'Адреса частной сети также ограничиваются. Для локального доступа добавьте 127.0.0.1 или ::1; для обратного прокси настройте доверенные прокси.',
|
||||
allowIPEgs: 'По одному в строке. Например,\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: '2FA авторизация',
|
||||
mfaClose: 'Отключение MFA снизит безопасность службы. Хотите продолжить?',
|
||||
|
||||
@@ -2855,7 +2855,7 @@ const message = {
|
||||
apiTrustedProxiesEgs:
|
||||
'Her satıra bir proxy sunucusu IP’si veya CIDR girin. Örneğin,\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'Ters proxy kullanırken istemci IP adresini doğru şekilde almak için proxy sunucusunun IP adresini veya ağını girin. Kullanmıyorsanız boş bırakın. 0.0.0.0/0 ve ::/0 desteklenmez.',
|
||||
'Ters proxy kullanırken IP adresini veya ağını girin. Alan boşsa hiçbir proxy güvenilir sayılmaz ve doğrudan kaynak IP kullanılır. 0.0.0.0/0 ve ::/0 desteklenmez.',
|
||||
apiKeyValidityTime: 'Arayüz anahtarının geçerlilik süresi',
|
||||
apiKeyValidityTimeEgs: 'Arayüz anahtarının geçerlilik süresi (dakika cinsinden)',
|
||||
apiKeyValidityTimeHelper:
|
||||
@@ -3064,6 +3064,8 @@ const message = {
|
||||
allowIPsWarning:
|
||||
'Yetkili IP adres listesini ayarladıktan sonra, yalnızca listedeki IP adresi panel servisine erişebilir. Devam etmek istiyor musunuz?',
|
||||
allowIPsHelper1: 'IP adresi kısıtlamasını devre dışı bırakmak için boş bırakın.',
|
||||
allowIPsPrivateHelper:
|
||||
'Özel ağ adresleri de kısıtlanır. Yerel erişim için 127.0.0.1 veya ::1 ekleyin; ters proxy için güvenilir proxyleri yapılandırın.',
|
||||
allowIPEgs: 'Her satıra bir tane. Örneğin,\n172.16.10.111\n172.16.10.0/24',
|
||||
mfa: 'İki Aşamalı Doğrulama',
|
||||
mfaClose: 'MFA’yı devre dışı bırakmak servisin güvenliğini azaltabilir. Devam etmek istiyor musunuz?',
|
||||
|
||||
@@ -2657,7 +2657,7 @@ const message = {
|
||||
apiTrustedProxies: 'API 可信代理',
|
||||
apiTrustedProxiesEgs: '每行填寫一個代理伺服器 IP 或 CIDR,例如:\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'使用反向代理時,請填寫代理伺服器的 IP 或網段,以便正確取得用戶端 IP;不使用時請留空。不支援 0.0.0.0/0 和 ::/0。',
|
||||
'使用反向代理時,請填寫代理伺服器的 IP 或網段;留空時不信任任何代理,並使用請求的直接來源 IP。不支援 0.0.0.0/0 和 ::/0。',
|
||||
apiKeyValidityTime: '介面金鑰有效期',
|
||||
apiKeyValidityTimeEgs: '介面金鑰有效期(組織分)',
|
||||
apiKeyValidityTimeHelper: '介面時間戳記到請求時的目前時間戳之間有效(組織分),設定為0時,不做時間戳記校驗',
|
||||
@@ -2918,6 +2918,7 @@ const message = {
|
||||
allowIPsHelper: '設定授權 IP 後,僅有設定中的 IP 可以存取 1Panel 服務',
|
||||
allowIPsWarning: '設定授權 IP 後,僅有設定中的 IP 可以存取 1Panel 服務,是否繼續?',
|
||||
allowIPsHelper1: '授權 IP 為空時,則取消授權 IP',
|
||||
allowIPsPrivateHelper: '內網地址也受限制;本機直連請加入 127.0.0.1 或 ::1,反向代理請設定可信代理。',
|
||||
allowIPEgs: '當存在多個授權 IP 時,需要換行顯示,例: \n172.16.10.111 \n172.16.10.0/24',
|
||||
mfa: '兩步驗證',
|
||||
mfaClose: '關閉兩步驗證將導致服務安全性降低,是否繼續?',
|
||||
|
||||
@@ -2688,7 +2688,7 @@ const message = {
|
||||
apiTrustedProxies: 'API 可信代理',
|
||||
apiTrustedProxiesEgs: '每行填写一个代理服务器 IP 或 CIDR,例如:\n172.16.10.111\n172.16.10.0/24',
|
||||
apiTrustedProxiesHelper:
|
||||
'使用反向代理时,请填写代理服务器的 IP 或网段,以便正确获取客户端 IP;不使用时请留空。不支持 0.0.0.0/0 和 ::/0。',
|
||||
'使用反向代理时,请填写代理服务器的 IP 或网段;留空时不信任任何代理,并使用请求的直接来源 IP。不支持 0.0.0.0/0 和 ::/0。',
|
||||
apiKeyValidityTime: '接口密钥有效期',
|
||||
apiKeyValidityTimeEgs: '接口密钥有效期(单位分)',
|
||||
apiKeyValidityTimeHelper: '接口时间戳到请求时的当前时间戳之间有效(单位分),设置为 0 时,不做时间戳校验',
|
||||
@@ -2924,6 +2924,7 @@ const message = {
|
||||
allowIPsHelper: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务',
|
||||
allowIPsWarning: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务,是否继续?',
|
||||
allowIPsHelper1: '授权 IP 为空时,则取消授权 IP',
|
||||
allowIPsPrivateHelper: '内网地址也受限制;本机直连请添加 127.0.0.1 或 ::1,反向代理请配置可信代理。',
|
||||
allowIPEgs: '当存在多个授权 IP 时,需要换行显示,例: \n172.16.10.111 \n172.16.10.0/24',
|
||||
mfa: '两步验证',
|
||||
mfaClose: '关闭两步验证将导致服务安全性降低,是否继续?',
|
||||
|
||||
@@ -4,6 +4,16 @@
|
||||
<el-form-item :label="$t('setting.allowIPs')" prop="allowIPs">
|
||||
<el-input type="textarea" :placeholder="$t('setting.allowIPEgs')" :rows="3" v-model="form.allowIPs" />
|
||||
<span class="input-help">{{ $t('setting.allowIPsHelper1') }}</span>
|
||||
<span class="input-help">{{ $t('setting.allowIPsPrivateHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.passkeyTrustedProxies')" prop="allowIPTrustedProxies">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('setting.apiTrustedProxiesEgs')"
|
||||
:rows="3"
|
||||
v-model="form.allowIPTrustedProxies"
|
||||
/>
|
||||
<span class="input-help">{{ $t('setting.apiTrustedProxiesHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -25,9 +35,11 @@ const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const form = reactive({
|
||||
allowIPs: '',
|
||||
allowIPTrustedProxies: '',
|
||||
});
|
||||
const rules = reactive({
|
||||
allowIPs: [{ validator: checkIPs, trigger: 'blur' }],
|
||||
allowIPTrustedProxies: [{ validator: checkIPs, trigger: 'blur' }],
|
||||
});
|
||||
function checkIPs(rule: any, value: any, callback: any) {
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
@@ -36,7 +48,7 @@ function checkIPs(rule: any, value: any, callback: any) {
|
||||
if (item === '') {
|
||||
continue;
|
||||
}
|
||||
if (item.includes('0.0.0.0') || item.includes('::')) {
|
||||
if (item === '0.0.0.0' || item === '0.0.0.0/0' || item === '::' || item === '::/0') {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
if (item.indexOf('/') !== -1) {
|
||||
@@ -58,6 +70,7 @@ const formRef = ref<FormInstance>();
|
||||
|
||||
interface DialogProps {
|
||||
allowIPs: string;
|
||||
allowIPTrustedProxies: string;
|
||||
}
|
||||
|
||||
const drawerVisible = ref();
|
||||
@@ -65,6 +78,7 @@ const loading = ref();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
form.allowIPs = params.allowIPs;
|
||||
form.allowIPTrustedProxies = params.allowIPTrustedProxies;
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
|
||||
@@ -74,7 +88,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
if (!valid) return;
|
||||
let title = form.allowIPs ? i18n.global.t('setting.allowIPs') : i18n.global.t('setting.unAllowIPs');
|
||||
let allow = form.allowIPs
|
||||
? i18n.global.t('setting.allowIPsWarning')
|
||||
? `${i18n.global.t('setting.allowIPsWarning')} ${i18n.global.t('setting.allowIPsPrivateHelper')}`
|
||||
: i18n.global.t('setting.unAllowIPsWarning');
|
||||
ElMessageBox.confirm(allow, title, {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
@@ -89,7 +103,10 @@ const onSave = async (formEl: FormInstance | undefined) => {
|
||||
ips.push(item);
|
||||
}
|
||||
}
|
||||
await updateSetting({ key: 'AllowIPs', value: ips.join(',') })
|
||||
await Promise.all([
|
||||
updateSetting({ key: 'AllowIPs', value: ips.join(',') }),
|
||||
updateSetting({ key: 'AllowIPTrustedProxies', value: form.allowIPTrustedProxies }),
|
||||
])
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
|
||||
@@ -210,6 +210,7 @@ const form = reactive({
|
||||
expirationDays: 0,
|
||||
complexityVerification: 'Disable',
|
||||
allowIPs: '',
|
||||
allowIPTrustedProxies: '',
|
||||
bindDomain: '',
|
||||
noAuthSetting: '200 - ' + i18n.global.t('setting.help200'),
|
||||
noAuthSettingValue: '200',
|
||||
@@ -233,6 +234,7 @@ const search = async () => {
|
||||
form.expirationDays = Number(res.data.expirationDays);
|
||||
form.complexityVerification = res.data.complexityVerification;
|
||||
form.allowIPs = res.data.allowIPs.replaceAll(',', '\n');
|
||||
form.allowIPTrustedProxies = res.data.allowIPTrustedProxies || '';
|
||||
form.bindDomain = res.data.bindDomain;
|
||||
form.noAuthSettingValue = res.data.noAuthSetting;
|
||||
if (res.data.noAuthSetting !== '200') {
|
||||
@@ -275,7 +277,10 @@ const onChangeBindDomain = () => {
|
||||
domainRef.value.acceptParams({ bindDomain: form.bindDomain });
|
||||
};
|
||||
const onChangeAllowIPs = () => {
|
||||
allowIPsRef.value.acceptParams({ allowIPs: form.allowIPs });
|
||||
allowIPsRef.value.acceptParams({
|
||||
allowIPs: form.allowIPs,
|
||||
allowIPTrustedProxies: form.allowIPTrustedProxies,
|
||||
});
|
||||
};
|
||||
const onChangeExpirationDays = async () => {
|
||||
expirationRef.value.acceptParams({ expirationDays: form.expirationDays });
|
||||
|
||||
Reference in New Issue
Block a user