fix: support trusted proxies for API allowlist (#13409)

This commit is contained in:
ssongliu
2026-07-29 16:48:59 +08:00
committed by GitHub
parent 13e6bc4fac
commit 563df3da71
24 changed files with 863 additions and 24 deletions
+8 -6
View File
@@ -816,12 +816,13 @@
},
"/core/auth/api/update": {
"bodyKeys": [
"ipWhiteList"
"ipWhiteList",
"apiTrustedProxies"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList]",
"formatEN": "update api config =\u003e IP White List: [ipWhiteList]"
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
"formatEN": "update api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
},
"/core/auth/expired/reset": {
"bodyKeys": [],
@@ -1577,7 +1578,8 @@
"/core/enterprise/users/api/update": {
"bodyKeys": [
"id",
"ipWhiteList"
"ipWhiteList",
"apiTrustedProxies"
],
"paramKeys": [],
"beforeFunctions": [
@@ -1590,8 +1592,8 @@
"output_value": "name"
}
],
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList]",
"formatEN": "update user [name] api config =\u003e IP White List: [ipWhiteList]"
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
"formatEN": "update user [name] api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
},
"/core/enterprise/users/del": {
"bodyKeys": [
+8 -1
View File
@@ -7,6 +7,7 @@ import (
"path"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
appauth "github.com/1Panel-dev/1Panel/core/app/auth"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/buserr"
@@ -435,7 +436,7 @@ func (b *BaseApi) GenerateApiKey(c *gin.Context) {
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /core/auth/api/update [post]
// @x-panel-log {"bodyKeys":["ipWhiteList"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 API 接口配置 => IP 白名单: [ipWhiteList]","formatEN":"update api config => IP White List: [ipWhiteList]"}
// @x-panel-log {"bodyKeys":["ipWhiteList","apiTrustedProxies"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 API 接口配置 => IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]","formatEN":"update api config => IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"}
func (b *BaseApi) UpdateApiConfig(c *gin.Context) {
panelToken := c.GetHeader("1Panel-Token")
if panelToken != "" {
@@ -446,6 +447,12 @@ func (b *BaseApi) UpdateApiConfig(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
trustedProxies, err := appauth.NormalizeAPITrustedProxies(req.ApiTrustedProxies)
if err != nil {
helper.BadRequest(c, err)
return
}
req.ApiTrustedProxies = trustedProxies
if err := xpack.AuthProvider.UpdateApiConfig(c, req); err != nil {
helper.InternalServer(c, err)
+123 -1
View File
@@ -6,6 +6,7 @@ import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"net"
"strconv"
"strings"
@@ -24,6 +25,7 @@ type APIAuthConfig struct {
ApiInterfaceStatus string
ApiKey string
IpWhiteList string
ApiTrustedProxies string
ApiKeyValidityTime int
}
@@ -66,7 +68,7 @@ func APIAuthMiddleware(loadConfig APIAuthConfigLoader, onSuccess APIAuthSuccessH
helper.BadAuth(c, "ErrApiConfigKeyInvalid", nil)
return
}
if !isIPInWhiteList(c.ClientIP(), config.IpWhiteList) {
if !isIPInWhiteList(GetAPIClientIP(c, config.ApiTrustedProxies), config.IpWhiteList) {
helper.BadAuth(c, "ErrApiConfigIPInvalid", nil)
return
}
@@ -92,6 +94,9 @@ func LoadAPIAuthConfig(_ *gin.Context) (APIAuthConfig, error) {
if config.IpWhiteList, err = settingRepo.GetValueByKey("IpWhiteList"); err != nil {
return config, err
}
if config.ApiTrustedProxies, err = settingRepo.GetValueByKey("ApiTrustedProxies"); err != nil {
return config, err
}
apiValidity, err := settingRepo.GetValueByKey("ApiKeyValidityTime")
if err != nil {
return config, err
@@ -102,6 +107,123 @@ func LoadAPIAuthConfig(_ *gin.Context) (APIAuthConfig, error) {
return config, nil
}
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()
}
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
}
func IsValid1PanelTimestamp(panelTimestamp string, apiKeyValidityTime int) bool {
apiTime := apiKeyValidityTime
if apiTime < 0 {
+7
View File
@@ -311,6 +311,10 @@ func GenerateApiKey() (string, error) {
}
func UpdateApiConfig(req dto.ApiInterfaceConfig) error {
settingRepo := repo.NewISettingRepo()
trustedProxies, err := NormalizeAPITrustedProxies(req.ApiTrustedProxies)
if err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("ApiInterfaceStatus", req.ApiInterfaceStatus); err != nil {
return err
}
@@ -320,6 +324,9 @@ func UpdateApiConfig(req dto.ApiInterfaceConfig) error {
if err := settingRepo.UpdateOrCreate("IpWhiteList", req.IpWhiteList); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("ApiTrustedProxies", trustedProxies); err != nil {
return err
}
if err := settingRepo.UpdateOrCreate("ApiKeyValidityTime", strconv.Itoa(req.ApiKeyValidityTime)); err != nil {
return err
}
+2
View File
@@ -57,6 +57,7 @@ type ApiInterfaceConfig struct {
ApiInterfaceStatus string `json:"apiInterfaceStatus"`
ApiKey string `json:"apiKey"`
IpWhiteList string `json:"ipWhiteList"`
ApiTrustedProxies string `json:"apiTrustedProxies"`
ApiKeyValidityTime int `json:"apiKeyValidityTime"`
}
@@ -71,6 +72,7 @@ type CurrentUserInfo struct {
ApiInterfaceStatus string `json:"apiInterfaceStatus"`
ApiKey string `json:"apiKey"`
IpWhiteList string `json:"ipWhiteList"`
ApiTrustedProxies string `json:"apiTrustedProxies"`
ApiKeyValidityTime int `json:"apiKeyValidityTime"`
Role string `json:"role"`
+316 -3
View File
@@ -2120,6 +2120,158 @@ const docTemplate = `{
]
}
},
"/ai/agents/plugins/install": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AgentPluginMarketInstallReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Install an OpenClaw marketplace plugin",
"tags": [
"AI"
]
}
},
"/ai/agents/plugins/list": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AgentPluginsReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"items": {
"$ref": "#/definitions/dto.AgentPluginItem"
},
"type": "array"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "List OpenClaw plugins",
"tags": [
"AI"
]
}
},
"/ai/agents/plugins/operate": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AgentPluginOperateReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Operate an OpenClaw plugin",
"tags": [
"AI"
]
}
},
"/ai/agents/plugins/search": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AgentPluginSearchReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"items": {
"$ref": "#/definitions/dto.AgentPluginSearchItem"
},
"type": "array"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Search OpenClaw plugins",
"tags": [
"AI"
]
}
},
"/ai/agents/remark": {
"post": {
"consumes": [
@@ -9257,10 +9409,11 @@ const docTemplate = `{
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"ipWhiteList"
"ipWhiteList",
"apiTrustedProxies"
],
"formatEN": "update api config =\u003e IP White List: [ipWhiteList]",
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList]",
"formatEN": "update api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]",
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
"paramKeys": []
}
}
@@ -30404,6 +30557,143 @@ const docTemplate = `{
],
"type": "object"
},
"dto.AgentPluginItem": {
"properties": {
"enabled": {
"type": "boolean"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"origin": {
"type": "string"
},
"version": {
"type": "string"
}
},
"type": "object"
},
"dto.AgentPluginMarketInstallReq": {
"properties": {
"agentId": {
"type": "integer"
},
"package": {
"maxLength": 200,
"type": "string"
},
"taskID": {
"type": "string"
},
"version": {
"maxLength": 100,
"type": "string"
}
},
"required": [
"agentId",
"package",
"taskID",
"version"
],
"type": "object"
},
"dto.AgentPluginOperateReq": {
"properties": {
"agentId": {
"type": "integer"
},
"operate": {
"enum": [
"enable",
"disable",
"update",
"uninstall"
],
"type": "string"
},
"pluginId": {
"maxLength": 200,
"type": "string"
},
"taskID": {
"type": "string"
}
},
"required": [
"agentId",
"operate",
"pluginId",
"taskID"
],
"type": "object"
},
"dto.AgentPluginSearchItem": {
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": "array"
},
"channel": {
"type": "string"
},
"description": {
"type": "string"
},
"downloads": {
"type": "integer"
},
"name": {
"type": "string"
},
"official": {
"type": "boolean"
},
"package": {
"type": "string"
},
"pluginId": {
"type": "string"
},
"score": {
"type": "number"
},
"verificationTier": {
"type": "string"
},
"version": {
"type": "string"
}
},
"type": "object"
},
"dto.AgentPluginSearchReq": {
"properties": {
"agentId": {
"type": "integer"
},
"keyword": {
"maxLength": 100,
"type": "string"
},
"limit": {
"maximum": 100,
"minimum": 1,
"type": "integer"
}
},
"required": [
"agentId",
"keyword"
],
"type": "object"
},
"dto.AgentPluginStatus": {
"properties": {
"currentVersion": {
@@ -30473,6 +30763,17 @@ const docTemplate = `{
],
"type": "object"
},
"dto.AgentPluginsReq": {
"properties": {
"agentId": {
"type": "integer"
}
},
"required": [
"agentId"
],
"type": "object"
},
"dto.AgentQQBotBot": {
"properties": {
"accountId": {
@@ -31518,6 +31819,9 @@ const docTemplate = `{
"apiKeyValidityTime": {
"type": "integer"
},
"apiTrustedProxies": {
"type": "string"
},
"ipWhiteList": {
"type": "string"
}
@@ -33560,6 +33864,15 @@ const docTemplate = `{
"apiKeyValidityTime": {
"type": "integer"
},
"apiTrustedProxies": {
"type": "string"
},
"authSource": {
"type": "string"
},
"authSourceStatus": {
"type": "string"
},
"complexitySetting": {
"type": "string"
},
+316 -3
View File
@@ -2116,6 +2116,158 @@
]
}
},
"/ai/agents/plugins/install": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AgentPluginMarketInstallReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Install an OpenClaw marketplace plugin",
"tags": [
"AI"
]
}
},
"/ai/agents/plugins/list": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AgentPluginsReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"items": {
"$ref": "#/definitions/dto.AgentPluginItem"
},
"type": "array"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "List OpenClaw plugins",
"tags": [
"AI"
]
}
},
"/ai/agents/plugins/operate": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AgentPluginOperateReq"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Operate an OpenClaw plugin",
"tags": [
"AI"
]
}
},
"/ai/agents/plugins/search": {
"post": {
"consumes": [
"application/json"
],
"parameters": [
{
"description": "request",
"in": "body",
"name": "request",
"required": true,
"schema": {
"$ref": "#/definitions/dto.AgentPluginSearchReq"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"items": {
"$ref": "#/definitions/dto.AgentPluginSearchItem"
},
"type": "array"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"Timestamp": []
}
],
"summary": "Search OpenClaw plugins",
"tags": [
"AI"
]
}
},
"/ai/agents/remark": {
"post": {
"consumes": [
@@ -9253,10 +9405,11 @@
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"ipWhiteList"
"ipWhiteList",
"apiTrustedProxies"
],
"formatEN": "update api config =\u003e IP White List: [ipWhiteList]",
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList]",
"formatEN": "update api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]",
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
"paramKeys": []
}
}
@@ -30400,6 +30553,143 @@
],
"type": "object"
},
"dto.AgentPluginItem": {
"properties": {
"enabled": {
"type": "boolean"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"origin": {
"type": "string"
},
"version": {
"type": "string"
}
},
"type": "object"
},
"dto.AgentPluginMarketInstallReq": {
"properties": {
"agentId": {
"type": "integer"
},
"package": {
"maxLength": 200,
"type": "string"
},
"taskID": {
"type": "string"
},
"version": {
"maxLength": 100,
"type": "string"
}
},
"required": [
"agentId",
"package",
"taskID",
"version"
],
"type": "object"
},
"dto.AgentPluginOperateReq": {
"properties": {
"agentId": {
"type": "integer"
},
"operate": {
"enum": [
"enable",
"disable",
"update",
"uninstall"
],
"type": "string"
},
"pluginId": {
"maxLength": 200,
"type": "string"
},
"taskID": {
"type": "string"
}
},
"required": [
"agentId",
"operate",
"pluginId",
"taskID"
],
"type": "object"
},
"dto.AgentPluginSearchItem": {
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": "array"
},
"channel": {
"type": "string"
},
"description": {
"type": "string"
},
"downloads": {
"type": "integer"
},
"name": {
"type": "string"
},
"official": {
"type": "boolean"
},
"package": {
"type": "string"
},
"pluginId": {
"type": "string"
},
"score": {
"type": "number"
},
"verificationTier": {
"type": "string"
},
"version": {
"type": "string"
}
},
"type": "object"
},
"dto.AgentPluginSearchReq": {
"properties": {
"agentId": {
"type": "integer"
},
"keyword": {
"maxLength": 100,
"type": "string"
},
"limit": {
"maximum": 100,
"minimum": 1,
"type": "integer"
}
},
"required": [
"agentId",
"keyword"
],
"type": "object"
},
"dto.AgentPluginStatus": {
"properties": {
"currentVersion": {
@@ -30469,6 +30759,17 @@
],
"type": "object"
},
"dto.AgentPluginsReq": {
"properties": {
"agentId": {
"type": "integer"
}
},
"required": [
"agentId"
],
"type": "object"
},
"dto.AgentQQBotBot": {
"properties": {
"accountId": {
@@ -31514,6 +31815,9 @@
"apiKeyValidityTime": {
"type": "integer"
},
"apiTrustedProxies": {
"type": "string"
},
"ipWhiteList": {
"type": "string"
}
@@ -33556,6 +33860,15 @@
"apiKeyValidityTime": {
"type": "integer"
},
"apiTrustedProxies": {
"type": "string"
},
"authSource": {
"type": "string"
},
"authSourceStatus": {
"type": "string"
},
"complexitySetting": {
"type": "string"
},
+8 -6
View File
@@ -816,12 +816,13 @@
},
"/core/auth/api/update": {
"bodyKeys": [
"ipWhiteList"
"ipWhiteList",
"apiTrustedProxies"
],
"paramKeys": [],
"beforeFunctions": [],
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList]",
"formatEN": "update api config =\u003e IP White List: [ipWhiteList]"
"formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
"formatEN": "update api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
},
"/core/auth/expired/reset": {
"bodyKeys": [],
@@ -1577,7 +1578,8 @@
"/core/enterprise/users/api/update": {
"bodyKeys": [
"id",
"ipWhiteList"
"ipWhiteList",
"apiTrustedProxies"
],
"paramKeys": [],
"beforeFunctions": [
@@ -1590,8 +1592,8 @@
"output_value": "name"
}
],
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList]",
"formatEN": "update user [name] api config =\u003e IP White List: [ipWhiteList]"
"formatZH": "更新用户 [name] API 接口配置 =\u003e IP 白名单: [ipWhiteList], API 可信代理: [apiTrustedProxies]",
"formatEN": "update user [name] api config =\u003e IP Allowlist: [ipWhiteList], API Trusted Proxies: [apiTrustedProxies]"
},
"/core/enterprise/users/del": {
"bodyKeys": [
+1
View File
@@ -51,6 +51,7 @@ func Init() {
migrations.AddLoginLogUser,
migrations.AddAlertAuditUser,
migrations.AddMenuAccordionSetting,
migrations.AddAPITrustedProxiesSetting,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+17
View File
@@ -183,6 +183,9 @@ var InitSetting = &gormigrate.Migration{
if err := tx.Create(&model.Setting{Key: "IpWhiteList", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ApiTrustedProxies", Value: ""}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "ApiKeyValidityTime", Value: "120"}).Error; err != nil {
return err
}
@@ -1282,3 +1285,17 @@ var AddMenuAccordionSetting = &gormigrate.Migration{
return nil
},
}
var AddAPITrustedProxiesSetting = &gormigrate.Migration{
ID: "20260729-add-api-trusted-proxies-setting",
Migrate: func(tx *gorm.DB) error {
var setting model.Setting
if err := tx.Where("key = ?", "ApiTrustedProxies").First(&setting).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return tx.Create(&model.Setting{Key: "ApiTrustedProxies", Value: ""}).Error
}
return err
}
return nil
},
}
+2
View File
@@ -61,6 +61,7 @@ export namespace Login {
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
apiTrustedProxies: string;
apiKeyValidityTime: number;
}
export interface AuthInfoUpdate {
@@ -86,6 +87,7 @@ export namespace Login {
apiInterfaceStatus: string;
apiKey: string;
ipWhiteList: string;
apiTrustedProxies: string;
apiKeyValidityTime: number;
}
export interface PasswordUpdate {
+3
View File
@@ -2758,6 +2758,9 @@ const message = {
ipWhiteList: 'IP allowlist',
ipWhiteListEgs: 'One per line. For example,\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper: 'IPs within the allowlist can access the API,0.0.0.0/0 (all IPv4), ::/0 (all IPv6)',
apiTrustedProxies: 'API trusted proxies',
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.',
apiKeyValidityTime: 'Validity period of interface key',
apiKeyValidityTimeEgs: 'Validity period of interface key (in minutes)',
apiKeyValidityTimeHelper:
+3
View File
@@ -2807,6 +2807,9 @@ const message = {
ipWhiteListEgs: 'Una por línea. Por ejemplo,\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper:
'Las IP en la lista blanca pueden acceder a la API, 0.0.0.0/0 (todas IPv4), ::/0 (todas IPv6)',
apiTrustedProxies: 'Proxies de confianza de la API',
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.',
apiKeyValidityTime: 'Periodo de validez de la clave de interfaz',
apiKeyValidityTimeEgs: 'Periodo de validez de la clave de interfaz (en minutos)',
apiKeyValidityTimeHelper:
+3
View File
@@ -2733,6 +2733,9 @@ const message = {
ipWhiteListEgs: 'هر خط یک IP. مثلاً،\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper:
'IP‌های موجود در لیست سفید می‌توانند به API دسترسی داشته باشند، 0.0.0.0/0 (همه IPv4)، ::/0 (همه IPv6)',
apiTrustedProxies: 'پروکسی‌های قابل اعتماد API',
apiTrustedProxiesHelper:
'هنگام استفاده از پروکسی معکوس، IP یا شبکه سرور پروکسی را وارد کنید تا IP کلاینت به‌درستی دریافت شود. در صورت عدم استفاده، این بخش را خالی بگذارید. 0.0.0.0/0 و ::/0 پشتیبانی نمی‌شوند.',
apiKeyValidityTime: 'مدت اعتبار کلید رابط',
apiKeyValidityTimeEgs: 'مدت اعتبار کلید رابط (به دقیقه)',
apiKeyValidityTimeHelper:
+3
View File
@@ -2755,6 +2755,9 @@ const message = {
ipWhiteList: 'IP AllowList',
ipWhiteListEgs: '1行に1つたとえば n172.161.10.111 n172.161.10.0/24',
ipWhiteListHelper: 'AllowList内のIPSはAPIにアクセスできます0.0.0.0/0すべての IPv4::/0すべての IPv6',
apiTrustedProxies: 'API信頼済みプロキシ',
apiTrustedProxiesHelper:
'リバースプロキシを使用する場合はクライアントIPを正しく取得するためにプロキシサーバーのIPまたはネットワークを入力してください使用しない場合は空欄にしてください0.0.0.0/0::/0は指定できません',
apiKeyReset: 'インターフェイスキーリセット',
apiKeyResetHelper: '関連するキーサービスは無効になりますサービスに新しいキーを追加してください',
confDockerProxy: 'Dockerプロキシを構成します',
+3
View File
@@ -2706,6 +2706,9 @@ const message = {
ipWhiteList: 'IP 허용 목록',
ipWhiteListEgs: ' 줄에 하나씩 입력하십시오. :\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper: '허용 목록에 있는 IP만 API 접근할 있습니다. 0.0.0.0/0(모든 IPv4), ::/0(모든 IPv6)',
apiTrustedProxies: 'API 신뢰 프록시',
apiTrustedProxiesHelper:
'리버스 프록시를 사용하는 경우 클라이언트 IP를 올바르게 가져오려면 프록시 서버의 IP 또는 네트워크를 입력하세요. 사용하지 않는 경우 비워 두세요. 0.0.0.0/0 ::/0 지원되지 않습니다.',
apiKeyValidityTime: '인터페이스 유효 기간',
apiKeyValidityTimeEgs: '인터페이스 유효 기간 ( 단위)',
apiKeyValidityTimeHelper:
+3
View File
@@ -2696,6 +2696,9 @@ const message = {
ipWhiteList: 'IP allowlist',
ipWhiteListEgs: 'ໜຶ່ງລາຍການຕໍ່ແຖວ. ຕົວຢ່າງ:\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper: 'IP ໃນ allowlist ສາມາດເຂົ້າເຖິງ API ໄດ້, 0.0.0.0/0 (ທຸກ IPv4), ::/0 (ທຸກ IPv6)',
apiTrustedProxies: 'ພຣັອກຊີ API ທີ່ເຊື່ອຖື',
apiTrustedProxiesHelper:
'ເມື່ອໃຊ້ reverse proxy, ໃຫ້ໃສ່ IP ຫຼືເຄືອຂ່າຍຂອງ proxy server ເພື່ອໃຫ້ໄດ້ client IP ຢ່າງຖືກຕ້ອງ. ຖ້າບໍ່ໃຊ້ໃຫ້ປ່ອຍຫວ່າງ. ບໍ່ຮອງຮັບ 0.0.0.0/0 ແລະ ::/0.',
apiKeyValidityTime: 'ໄລຍະເວລາທີ່ API key ໃຊ້ງານໄດ້',
apiKeyValidityTimeEgs: 'ໄລຍະເວລາທີ່ API key ໃຊ້ງານໄດ້ (ນາທີ)',
apiKeyValidityTimeHelper:
+3
View File
@@ -2803,6 +2803,9 @@ const message = {
ipWhiteList: 'Senarai putih IP',
ipWhiteListEgs: 'Satu per baris. Contoh,\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper: 'IP dalam senarai putih boleh mengakses API, 0.0.0.0/0 (semua IPv4), ::/0 (semua IPv6)',
apiTrustedProxies: 'Proksi dipercayai API',
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.',
apiKeyValidityTime: 'Tempoh sah kunci antara muka',
apiKeyValidityTimeEgs: 'Tempoh sah kunci antara muka (dalam minit)',
apiKeyValidityTimeHelper:
+3
View File
@@ -2919,6 +2919,9 @@ const message = {
ipWhiteListEgs: 'Um por linha. Exemplo: \n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper:
'IPs na lista de permitidos podem acessar a API, 0.0.0.0/0 (todos os IPv4), ::/0 (todos os IPv6)',
apiTrustedProxies: 'Proxies confiáveis da API',
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.',
apiKeyReset: 'Redefinir chave da interface',
apiKeyResetHelper:
'O serviço associado à chave se tornará inválido. Por favor, adicione uma nova chave ao serviço',
+3
View File
@@ -2778,6 +2778,9 @@ const message = {
ipWhiteListEgs: 'По одному в строке. Например,\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper:
'IP-адреса из белого списка могут получить доступ к API, 0.0.0.0/0 (все IPv4), ::/0 (все IPv6)',
apiTrustedProxies: 'Доверенные прокси API',
apiTrustedProxiesHelper:
'При использовании обратного прокси укажите IP-адрес или сеть прокси-сервера, чтобы корректно определить IP клиента. Если прокси не используется, оставьте поле пустым. 0.0.0.0/0 и ::/0 не поддерживаются.',
apiKeyValidityTime: 'Срок действия ключа интерфейса',
apiKeyValidityTimeEgs: 'Срок действия ключа интерфейса (в единицах)',
apiKeyValidityTimeHelper:
+3
View File
@@ -2786,6 +2786,9 @@ const message = {
ipWhiteList: 'IP izin listesi',
ipWhiteListEgs: 'Her satıra bir tane. Örneğin,\n172.161.10.111\n172.161.10.0/24',
ipWhiteListHelper: 'İzin listesindeki IPler APIye erişebilir, 0.0.0.0/0 (tüm IPv4), ::/0 (tüm IPv6)',
apiTrustedProxies: 'API güvenilir proxyleri',
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.',
apiKeyValidityTime: 'Arayüz anahtarının geçerlilik süresi',
apiKeyValidityTimeEgs: 'Arayüz anahtarının geçerlilik süresi (dakika cinsinden)',
apiKeyValidityTimeHelper:
+3
View File
@@ -2587,6 +2587,9 @@ const message = {
ipWhiteListEgs: '當存在多個 IP 需要換行顯示\n172.16.10.111 \n172.16.10.0/24',
ipWhiteListHelper:
'必需在 IP 白名單清單中的 IP 才能存取面板 API 介面0.0.0.0/0所有 IPv4::/0所有 IPv6',
apiTrustedProxies: 'API 可信代理',
apiTrustedProxiesHelper:
'使用反向代理時請填寫代理伺服器的 IP 或網段以便正確取得用戶端 IP不使用時請留空不支援 0.0.0.0/0 ::/0',
apiKeyValidityTime: '介面金鑰有效期',
apiKeyValidityTimeEgs: '介面金鑰有效期組織分',
apiKeyValidityTimeHelper: '介面時間戳記到請求時的目前時間戳之間有效組織分設定為0時不做時間戳記校驗',
+3
View File
@@ -2582,6 +2582,9 @@ const message = {
ipWhiteListEgs: '当存在多个 IP 需要换行显示 \n172.16.10.111 \n172.16.10.0/24',
ipWhiteListHelper:
'必需在 IP 白名单列表中的 IP 才能访问面板 API 接口0.0.0.0/0所有 IPv4::/0所有 IPv6',
apiTrustedProxies: 'API 可信代理',
apiTrustedProxiesHelper:
'使用反向代理时请填写代理服务器的 IP 或网段以便正确获取客户端 IP不使用时请留空不支持 0.0.0.0/0 ::/0',
apiKeyValidityTime: '接口密钥有效期',
apiKeyValidityTimeEgs: '接口密钥有效期单位分',
apiKeyValidityTimeHelper: '接口时间戳到请求时的当前时间戳之间有效单位分设置为 0 不做时间戳校验',
@@ -356,6 +356,15 @@
/>
<span class="input-help">{{ $t('setting.ipWhiteListHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.apiTrustedProxies')" prop="apiTrustedProxies">
<el-input
type="textarea"
:placeholder="$t('setting.allowIPEgs')"
:rows="3"
v-model="form.apiTrustedProxies"
/>
<span class="input-help">{{ $t('setting.apiTrustedProxiesHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.apiKeyValidityTime')" prop="apiKeyValidityTime">
<el-input :placeholder="$t('setting.apiKeyValidityTimeEgs')" v-model.number="form.apiKeyValidityTime">
<template #append>{{ $t('commons.units.minute') }}</template>
@@ -446,6 +455,7 @@ const form = reactive({
apiInterfaceStatus: 'Disable',
apiKey: '',
ipWhiteList: '',
apiTrustedProxies: '',
apiKeyValidityTime: 120,
});
const mfaForm = reactive({
@@ -512,6 +522,7 @@ const mfaRules = reactive({
});
const apiRules = reactive({
ipWhiteList: [Rules.requiredInput, { validator: checkIPs, trigger: 'blur' }],
apiTrustedProxies: [{ validator: checkIPs, trigger: 'blur' }],
apiKey: [Rules.requiredInput],
apiKeyValidityTime: [Rules.requiredInput, Rules.integerNumberWith0],
});
@@ -523,7 +534,7 @@ const getUserFormFields = () => {
}
return fields;
};
const apiFormFields = ['apiKey', 'ipWhiteList', 'apiKeyValidityTime'];
const apiFormFields = ['apiKey', 'ipWhiteList', 'apiTrustedProxies', 'apiKeyValidityTime'];
const openDrawer = async () => {
if (!props.currentUser) {
@@ -538,6 +549,7 @@ const syncApiConfig = (currentUser: Login.AuthInfo) => {
form.apiInterfaceStatus = currentUser.apiInterfaceStatus || 'Disable';
form.apiKey = currentUser.apiKey;
form.ipWhiteList = currentUser.ipWhiteList;
form.apiTrustedProxies = currentUser.apiTrustedProxies || '';
form.apiKeyValidityTime = currentUser.apiKeyValidityTime;
savedApiStatus.value = form.apiInterfaceStatus;
};
@@ -586,9 +598,10 @@ const resetApiKey = async () => {
};
function checkIPs(rule: any, value: any, callback: any) {
if (form.ipWhiteList !== '') {
let addr = form.ipWhiteList.split('\n');
for (const item of addr) {
if (value !== '') {
let addr = value.split('\n');
for (const rawItem of addr) {
const item = rawItem.trim();
if (item === '') {
continue;
}
@@ -898,6 +911,7 @@ const onSaveApi = async (formEl: FormInstance | undefined) => {
const param = {
apiKey: form.apiKey,
ipWhiteList: form.ipWhiteList,
apiTrustedProxies: form.apiTrustedProxies,
apiInterfaceStatus: form.apiInterfaceStatus,
apiKeyValidityTime: form.apiKeyValidityTime,
};
@@ -972,6 +986,7 @@ const handleApi = async () => {
let param = {
apiKey: form.apiKey,
ipWhiteList: form.ipWhiteList,
apiTrustedProxies: form.apiTrustedProxies,
apiInterfaceStatus: form.apiInterfaceStatus,
apiKeyValidityTime: form.apiKeyValidityTime,
};