mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-09-20 07:35:58 +08:00
feat: 网站增加真实 IP 配置 (#6488)
Some checks failed
sync2gitee / repo-sync (push) Failing after -9m1s
Some checks failed
sync2gitee / repo-sync (push) Failing after -9m1s
Refs https://github.com/1Panel-dev/1Panel/issues/1028
This commit is contained in:
parent
68433c922b
commit
417ad81aa4
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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": {}}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ var repeatKeys = map[string]struct {
|
|||
"include": {},
|
||||
"sub_filter": {},
|
||||
"add_header": {},
|
||||
"set_real_ip_from": {},
|
||||
}
|
||||
|
||||
func IsRepeatKey(key string) bool {
|
||||
|
|
|
@ -598,4 +598,11 @@ export namespace Website {
|
|||
cacheExpire: number;
|
||||
cacheExpireUnit: string;
|
||||
}
|
||||
|
||||
export interface WebsiteRealIPConfig {
|
||||
open: boolean;
|
||||
ipFrom: string;
|
||||
ipHeader: string;
|
||||
ipOther: string;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -311,3 +311,11 @@ export const UpdateCacheConfig = (req: Website.WebsiteCacheConfig) => {
|
|||
export const GetCacheConfig = (id: number) => {
|
||||
return http.get<Website.WebsiteCacheConfig>(`/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<Website.WebsiteRealIPConfig>(`/websites/realip/config/${id}`);
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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: '短標簽支持',
|
||||
|
|
|
@ -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: '短标签支持',
|
||||
|
|
|
@ -24,17 +24,20 @@
|
|||
<el-tab-pane :label="'HTTPS'">
|
||||
<HTTPS :id="id" v-if="tabIndex == '7'"></HTTPS>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.realIP')">
|
||||
<RealIP :id="id" v-if="tabIndex == '8'"></RealIP>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.rewrite')">
|
||||
<Rewrite :id="id" v-if="tabIndex == '8'"></Rewrite>
|
||||
<Rewrite :id="id" v-if="tabIndex == '9'"></Rewrite>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.antiLeech')">
|
||||
<AntiLeech :id="id" v-if="tabIndex == '9'"></AntiLeech>
|
||||
<AntiLeech :id="id" v-if="tabIndex == '10'"></AntiLeech>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.redirect')">
|
||||
<Redirect :id="id" v-if="tabIndex == '10'"></Redirect>
|
||||
<Redirect :id="id" v-if="tabIndex == '11'"></Redirect>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('website.other')">
|
||||
<Other :id="id" v-if="tabIndex == '11'"></Other>
|
||||
<Other :id="id" v-if="tabIndex == '12'"></Other>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
:label="'PHP'"
|
||||
|
@ -61,6 +64,7 @@ import AntiLeech from './anti-Leech/index.vue';
|
|||
import Redirect from './redirect/index.vue';
|
||||
import LoadBalance from './load-balance/index.vue';
|
||||
import PHP from './php/index.vue';
|
||||
import RealIP from './real-ip/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
website: {
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<el-row :gutter="20" v-loading="loading">
|
||||
<el-col :xs="24" :sm="14" :md="14" :lg="10" :xl="8">
|
||||
<el-alert :closable="false">
|
||||
{{ $t('website.ipFromHelper') }}
|
||||
<div>
|
||||
{{ $t('website.ipFromExample1') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('website.ipFromExample2') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('website.ipFromExample3') }}
|
||||
</div>
|
||||
</el-alert>
|
||||
<el-form
|
||||
v-loading="loading"
|
||||
@submit.prevent
|
||||
ref="realIPForm"
|
||||
label-position="right"
|
||||
label-width="100px"
|
||||
:model="req"
|
||||
:rules="rules"
|
||||
:validate-on-rule-change="false"
|
||||
>
|
||||
<el-form-item :label="$t('commons.button.start')" prop="open">
|
||||
<el-switch v-model="req.open"></el-switch>
|
||||
</el-form-item>
|
||||
<div v-if="req.open">
|
||||
<el-form-item :label="$t('website.ipFrom')" prop="ipFrom">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
clearable
|
||||
v-model="req.ipFrom"
|
||||
:placeholder="$t('website.wafInputHelper')"
|
||||
></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('website.wafInputHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="IP Header" prop="ipHeader">
|
||||
<el-select v-model="req.ipHeader">
|
||||
<el-option :label="$t('website.other')" key="other" value="other"></el-option>
|
||||
<el-option
|
||||
v-for="item in ['X-Forwarded-For', 'X-Real-IP', 'CF-Connecting-IP']"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="ipOther" v-if="req.ipHeader === 'other'">
|
||||
<el-input type="text" v-model.trim="req.ipOther" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit(realIPForm)" :loading="loading">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GetRealIPConfig, UpdateRealIPConfig } from '@/api/modules/website';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
|
||||
const loading = ref(false);
|
||||
const realIPForm = ref<FormInstance>();
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const req = reactive({
|
||||
websiteID: 0,
|
||||
open: false,
|
||||
ipFrom: '127.0.0.1',
|
||||
ipHeader: 'X-Real-IP',
|
||||
ipOther: '',
|
||||
});
|
||||
const rules = {
|
||||
ipFrom: [Rules.requiredInput],
|
||||
ipHeader: [Rules.requiredSelect],
|
||||
ipOther: [Rules.requiredInput],
|
||||
};
|
||||
|
||||
const get = () => {
|
||||
GetRealIPConfig(props.id).then((res) => {
|
||||
req.open = res.data.open;
|
||||
if (res.data.open) {
|
||||
req.ipFrom = res.data.ipFrom;
|
||||
req.ipHeader = res.data.ipHeader;
|
||||
req.ipOther = res.data.ipOther;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
req.websiteID = props.id;
|
||||
try {
|
||||
await UpdateRealIPConfig(req);
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
} catch (error) {}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
get();
|
||||
});
|
||||
</script>
|
Loading…
Reference in a new issue