From 417ad81aa4400decbfebb71e97a73c7fa03e61f6 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Fri, 13 Sep 2024 18:42:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BD=91=E7=AB=99=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=20IP=20=E9=85=8D=E7=BD=AE=20(#6488)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs https://github.com/1Panel-dev/1Panel/issues/1028 --- agent/app/api/v2/website.go | 44 ++++++ agent/app/dto/nginx.go | 2 + agent/app/dto/request/website.go | 8 ++ agent/app/dto/response/website.go | 8 ++ agent/app/service/website.go | 101 +++++++++++--- agent/app/service/website_utils.go | 17 +++ agent/router/ro_website.go | 3 + agent/utils/nginx/components/config.go | 1 + frontend/src/api/interface/website.ts | 7 + frontend/src/api/modules/website.ts | 8 ++ frontend/src/lang/modules/en.ts | 8 ++ frontend/src/lang/modules/tw.ts | 7 + frontend/src/lang/modules/zh.ts | 7 + .../website/website/config/basic/index.vue | 12 +- .../website/config/basic/real-ip/index.vue | 125 ++++++++++++++++++ 15 files changed, 337 insertions(+), 21 deletions(-) create mode 100644 frontend/src/views/website/website/config/basic/real-ip/index.vue diff --git a/agent/app/api/v2/website.go b/agent/app/api/v2/website.go index 8aef34538..7da137e5a 100644 --- a/agent/app/api/v2/website.go +++ b/agent/app/api/v2/website.go @@ -946,3 +946,47 @@ func (b *BaseApi) GetProxyCache(c *gin.Context) { } helper.SuccessWithData(c, res) } + +// @Tags Website +// @Summary Set Real IP +// @Description 设置真实IP +// @Accept json +// @Param request body request.WebsiteRealIP true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /websites/realip [post] +// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改 [domain] 网站真实IP配置 ","formatEN":"Modify the real IP configuration of [domain] website"} +func (b *BaseApi) SetRealIPConfig(c *gin.Context) { + var req request.WebsiteRealIP + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.SetRealIPConfig(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} + +// 写一个调用 GetRealIPConfig 的接口 +// @Tags Website +// @Summary Get Real IP Config +// @Description 获取真实 IP 配置 +// @Accept json +// @Param id path int true "id" +// @Success 200 {object} response.WebsiteRealIP +// @Security ApiKeyAuth +// @Router /websites/realip/config/{id} [get] +func (b *BaseApi) GetRealIPConfig(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) + return + } + res, err := websiteService.GetRealIPConfig(id) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, res) +} diff --git a/agent/app/dto/nginx.go b/agent/app/dto/nginx.go index 6b3927878..54908c53a 100644 --- a/agent/app/dto/nginx.go +++ b/agent/app/dto/nginx.go @@ -82,3 +82,5 @@ type NginxUpstreamServer struct { } var LBAlgorithms = map[string]struct{}{"ip_hash": {}, "least_conn": {}} + +var RealIPKeys = map[string]struct{}{"X-Forwarded-For": {}, "X-Real-IP": {}, "CF-Connecting-IP": {}} diff --git a/agent/app/dto/request/website.go b/agent/app/dto/request/website.go index 2ca39e31b..209e71a6b 100644 --- a/agent/app/dto/request/website.go +++ b/agent/app/dto/request/website.go @@ -266,3 +266,11 @@ type WebsiteLBUpdateFile struct { Name string `json:"name" validate:"required"` Content string `json:"content" validate:"required"` } + +type WebsiteRealIP struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Open bool `json:"open"` + IPFrom string `json:"ipFrom"` + IPHeader string `json:"ipHeader"` + IPOther string `json:"ipOther"` +} diff --git a/agent/app/dto/response/website.go b/agent/app/dto/response/website.go index 89a6a33d5..67da78b50 100644 --- a/agent/app/dto/response/website.go +++ b/agent/app/dto/response/website.go @@ -93,3 +93,11 @@ type WebsiteDirConfig struct { type WebsiteHtmlRes struct { Content string `json:"content"` } + +type WebsiteRealIP struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Open bool `json:"open"` + IPFrom string `json:"ipFrom"` + IPHeader string `json:"ipHeader"` + IPOther string `json:"ipOther"` +} diff --git a/agent/app/service/website.go b/agent/app/service/website.go index 85a2bfcea..39fc4e9e7 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -10,6 +10,7 @@ import ( "encoding/pem" "errors" "fmt" + "net" "os" "path" "reflect" @@ -108,6 +109,9 @@ type IWebsiteService interface { UpdateLoadBalance(req request.WebsiteLBUpdate) error UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error + SetRealIPConfig(req request.WebsiteRealIP) error + GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error) + ChangeGroup(group, newGroup uint) error } @@ -1578,23 +1582,6 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website) } -func openProxyCache(website model.Website) error { - cacheDir := GetSitePath(website, SiteCacheDir) - fileOp := files.NewFileOp() - if !fileOp.Stat(cacheDir) { - _ = fileOp.CreateDir(cacheDir, 0755) - } - content, err := fileOp.GetContent(GetSitePath(website, SiteConf)) - if err != nil { - return err - } - if strings.Contains(string(content), "proxy_cache_path") { - return nil - } - proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:5m max_size=1g inactive=24h", website.Alias, website.Alias) - return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website) -} - func (w WebsiteService) UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { @@ -2983,3 +2970,83 @@ func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) e func (w WebsiteService) ChangeGroup(group, newGroup uint) error { return websiteRepo.UpdateGroup(group, newGroup) } + +func (w WebsiteService) SetRealIPConfig(req request.WebsiteRealIP) error { + website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + params := []dto.NginxParam{ + {Name: "real_ip_recursive", Params: []string{"on"}}, + {Name: "set_real_ip_from", Params: []string{}}, + {Name: "real_ip_header", Params: []string{}}, + } + if req.Open { + if err := deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil { + return err + } + params = []dto.NginxParam{ + {Name: "real_ip_recursive", Params: []string{"on"}}, + } + var ips []string + ipArray := strings.Split(req.IPFrom, "\n") + for _, ip := range ipArray { + if ip == "" { + continue + } + if parsedIP := net.ParseIP(ip); parsedIP == nil { + if _, _, err := net.ParseCIDR(ip); err != nil { + return buserr.New("ErrParseIP") + } + } + ips = append(ips, strings.TrimSpace(ip)) + } + for _, ip := range ips { + params = append(params, dto.NginxParam{Name: "set_real_ip_from", Params: []string{ip}}) + } + if req.IPHeader == "other" { + params = append(params, dto.NginxParam{Name: "real_ip_header", Params: []string{req.IPOther}}) + } else { + params = append(params, dto.NginxParam{Name: "real_ip_header", Params: []string{req.IPHeader}}) + } + return updateNginxConfig(constant.NginxScopeServer, params, &website) + } + return deleteNginxConfig(constant.NginxScopeServer, params, &website) +} + +func (w WebsiteService) GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error) { + website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID)) + if err != nil { + return nil, err + } + params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"real_ip_recursive"}, &website) + if err != nil { + return nil, err + } + if len(params) == 0 || len(params[0].Params) == 0 { + return &response.WebsiteRealIP{Open: false}, nil + } + params, err = getNginxParamsByKeys(constant.NginxScopeServer, []string{"set_real_ip_from", "real_ip_header"}, &website) + if err != nil { + return nil, err + } + res := &response.WebsiteRealIP{ + Open: true, + } + var ips []string + for _, param := range params { + if param.Name == "set_real_ip_from" { + ips = append(ips, param.Params...) + } + if param.Name == "real_ip_header" { + if _, ok := dto.RealIPKeys[param.Params[0]]; ok { + res.IPHeader = param.Params[0] + } else { + res.IPHeader = "other" + res.IPOther = param.Params[0] + } + } + } + res.IPFrom = strings.Join(ips, "\n") + return res, err +} diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go index e456f3546..063db15dd 100644 --- a/agent/app/service/website_utils.go +++ b/agent/app/service/website_utils.go @@ -1199,3 +1199,20 @@ func GetSitePath(website model.Website, confType string) string { } return "" } + +func openProxyCache(website model.Website) error { + cacheDir := GetSitePath(website, SiteCacheDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(cacheDir) { + _ = fileOp.CreateDir(cacheDir, 0755) + } + content, err := fileOp.GetContent(GetSitePath(website, SiteConf)) + if err != nil { + return err + } + if strings.Contains(string(content), "proxy_cache_path") { + return nil + } + proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:5m max_size=1g inactive=24h", website.Alias, website.Alias) + return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website) +} diff --git a/agent/router/ro_website.go b/agent/router/ro_website.go index 554a42d1b..c5d837ca7 100644 --- a/agent/router/ro_website.go +++ b/agent/router/ro_website.go @@ -74,5 +74,8 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) { websiteRouter.POST("/lbs/file", baseApi.UpdateLoadBalanceFile) websiteRouter.POST("/php/version", baseApi.ChangePHPVersion) + + websiteRouter.POST("/realip/config", baseApi.SetRealIPConfig) + websiteRouter.GET("/realip/config/:id", baseApi.GetRealIPConfig) } } diff --git a/agent/utils/nginx/components/config.go b/agent/utils/nginx/components/config.go index 48bd1372a..4c178f9fd 100644 --- a/agent/utils/nginx/components/config.go +++ b/agent/utils/nginx/components/config.go @@ -44,6 +44,7 @@ var repeatKeys = map[string]struct { "include": {}, "sub_filter": {}, "add_header": {}, + "set_real_ip_from": {}, } func IsRepeatKey(key string) bool { diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index a740f4346..def0fb33e 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -598,4 +598,11 @@ export namespace Website { cacheExpire: number; cacheExpireUnit: string; } + + export interface WebsiteRealIPConfig { + open: boolean; + ipFrom: string; + ipHeader: string; + ipOther: string; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 65aca6982..bc121c1a2 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -311,3 +311,11 @@ export const UpdateCacheConfig = (req: Website.WebsiteCacheConfig) => { export const GetCacheConfig = (id: number) => { return http.get(`/websites/proxy/config/${id}`); }; + +export const UpdateRealIPConfig = (req: Website.WebsiteRealIPConfig) => { + return http.post(`/websites/realip/config`, req); +}; + +export const GetRealIPConfig = (id: number) => { + return http.get(`/websites/realip/config/${id}`); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 47b4dbc86..2fa36659c 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2194,6 +2194,14 @@ const message = { shareCaheHelper: 'Approximately 8000 cache objects can be stored per 1M of memory', cacheLimitHelper: 'Old cache will be automatically deleted when the limit is exceeded', cacheExpireJHelper: 'Cache will be deleted if it misses after the expiration time', + realIP: 'Real IP', + ipFrom: 'IP Source', + ipFromHelper: + "By configuring trusted IP sources, OpenResty will analyze IP information in HTTP headers, accurately identify and record visitors' real IP addresses, including in access logs", + ipFromExample1: "If the frontend is a tool like Frp, you can enter Frp's IP address, such as 127.0.0.1", + ipFromExample2: "If the frontend is a CDN, you can enter the CDN's IP address range", + ipFromExample3: + 'If unsure, you can enter 0.0.0.0/0 (ipv4) ::/0 (ipv6) [Note: Allowing any source IP is not secure]', }, php: { short_open_tag: 'Short tag support', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index cdc535a6b..675edf099 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -2042,6 +2042,13 @@ const message = { shareCaheHelper: '每1M內存可以存儲約8000個快取對象', cacheLimitHelper: '超過限制會自動刪除舊的快取', cacheExpireJHelper: '超出時間快取未命中將會被刪除', + realIP: '真實 IP', + ipFrom: 'IP 來源', + ipFromHelper: + '通過配置可信 IP 來源,OpenResty 會分析 HTTP Header 中的 IP 資訊,準確識別並記錄訪客的真實 IP 地址,包括在存取日誌中', + ipFromExample1: '如果前端是 Frp 等工具,可以填寫 Frp 的 IP 地址,類似 127.0.0.1', + ipFromExample2: '如果前端是 CDN,可以填寫 CDN 的 IP 地址段', + ipFromExample3: '如果不確定,可以填 0.0.0.0/0(ipv4) ::/0(ipv6) [注意:允許任意來源 IP 不安全]', }, php: { short_open_tag: '短標簽支持', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index dfe6f5f5a..4e6ad4028 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2043,6 +2043,13 @@ const message = { shareCaheHelper: '每1M内存可以存储约8000个缓存对象', cacheLimitHelper: '超过限制会自动删除旧的缓存', cacheExpireJHelper: '超出时间缓存未命中将会被删除', + realIP: '真实 IP', + ipFrom: 'IP 来源', + ipFromHelper: + '通过配置可信 IP 来源,OpenResty 会分析 HTTP Header 中的 IP 信息,准确识别并记录访客的真实 IP 地址,包括在访问日志中', + ipFromExample1: '如果前端是 Frp 等工具,可以填写 Frp 的 IP 地址,类似 127.0.0.1', + ipFromExample2: '如果前端是 CDN,可以填写 CDN 的 IP 地址段', + ipFromExample3: '如果不确定,可以填 0.0.0.0/0(ipv4) ::/0(ipv6) [注意:允许任意来源 IP 不安全]', }, php: { short_open_tag: '短标签支持', diff --git a/frontend/src/views/website/website/config/basic/index.vue b/frontend/src/views/website/website/config/basic/index.vue index 9b25ae4d2..c57b808da 100644 --- a/frontend/src/views/website/website/config/basic/index.vue +++ b/frontend/src/views/website/website/config/basic/index.vue @@ -24,17 +24,20 @@ + + + - + - + - + - + + + + + {{ $t('website.ipFromHelper') }} +
+ {{ $t('website.ipFromExample1') }} +
+
+ {{ $t('website.ipFromExample2') }} +
+
+ {{ $t('website.ipFromExample3') }} +
+
+ + + + +
+ + + + {{ $t('website.wafInputHelper') }} + + + + + + + + + + + + +
+ + + {{ $t('commons.button.save') }} + + +
+
+
+ + +