mirror of https://github.com/1Panel-dev/1Panel
feat: 增加CC防护
parent
ba77f2853f
commit
047bd907f0
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -268,3 +269,30 @@ func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) {
|
|||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetWebsiteWafConfig(c *gin.Context) {
|
||||
var req request.WebsiteWafReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
data, err := websiteService.GetWafConfig(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UpdateWebsiteWafConfig(c *gin.Context) {
|
||||
var req request.WebsiteWafUpdate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := websiteService.UpdateWafConfig(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
|
|
@ -76,6 +76,5 @@ var StaticFileKeyMap = map[NginxKey]struct {
|
|||
type NginxParam struct {
|
||||
UpdateScope string `json:"scope"`
|
||||
Name string `json:"name"`
|
||||
SecondKey string `json:"secondKey"`
|
||||
Params []string `json:"params"`
|
||||
}
|
||||
|
|
|
@ -121,3 +121,9 @@ type WebsitePreInstallCheck struct {
|
|||
type WebsiteInstallCheckReq struct {
|
||||
InstallIds []uint `json:"InstallIds"`
|
||||
}
|
||||
|
||||
type WebsiteWafConfig struct {
|
||||
Enable bool `json:"enable"`
|
||||
FilePath string `json:"filePath"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package request
|
||||
|
||||
type WebsiteWafReq struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
Rule string `json:"rule" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteWafUpdate struct {
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
Key string `json:"key" validate:"required"`
|
||||
Enable bool `json:"enable" validate:"required"`
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -33,6 +34,18 @@ type IWebsiteService interface {
|
|||
RecoverByUpload(req dto.WebSiteRecoverByFile) error
|
||||
UpdateWebsite(req dto.WebSiteUpdate) error
|
||||
DeleteWebSite(req dto.WebSiteDel) error
|
||||
GetWebsite(id uint) (dto.WebsiteDTO, error)
|
||||
CreateWebsiteDomain(create dto.WebSiteDomainCreate) (model.WebSiteDomain, error)
|
||||
GetWebsiteDomain(websiteId uint) ([]model.WebSiteDomain, error)
|
||||
DeleteWebsiteDomain(domainId uint) error
|
||||
GetNginxConfigByScope(req dto.NginxConfigReq) (*dto.WebsiteNginxConfig, error)
|
||||
UpdateNginxConfigByScope(req dto.NginxConfigReq) error
|
||||
GetWebsiteNginxConfig(websiteId uint) (dto.FileInfo, error)
|
||||
GetWebsiteHTTPS(websiteId uint) (dto.WebsiteHTTPS, error)
|
||||
OpWebsiteHTTPS(req dto.WebsiteHTTPSOp) (dto.WebsiteHTTPS, error)
|
||||
PreInstallCheck(req dto.WebsiteInstallCheckReq) ([]dto.WebsitePreInstallCheck, error)
|
||||
GetWafConfig(req request.WebsiteWafReq) (dto.WebsiteWafConfig, error)
|
||||
UpdateWafConfig(req request.WebsiteWafUpdate) error
|
||||
}
|
||||
|
||||
func NewWebsiteService() IWebsiteService {
|
||||
|
@ -54,7 +67,6 @@ func (w WebsiteService) PageWebSite(req dto.WebSiteReq) (int64, []dto.WebSiteDTO
|
|||
}
|
||||
|
||||
func (w WebsiteService) CreateWebsite(create dto.WebSiteCreate) error {
|
||||
|
||||
if exist, _ := websiteRepo.GetBy(websiteRepo.WithDomain(create.PrimaryDomain)); len(exist) > 0 {
|
||||
return buserr.New(constant.ErrNameIsExist)
|
||||
}
|
||||
|
@ -229,7 +241,6 @@ func (w WebsiteService) GetWebsite(id uint) (dto.WebsiteDTO, error) {
|
|||
}
|
||||
|
||||
func (w WebsiteService) DeleteWebSite(req dto.WebSiteDel) error {
|
||||
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -296,7 +307,6 @@ func (w WebsiteService) GetWebsiteDomain(websiteId uint) ([]model.WebSiteDomain,
|
|||
}
|
||||
|
||||
func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
||||
|
||||
webSiteDomain, err := websiteDomainRepo.GetFirst(commonRepo.WithByID(domainId))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -328,7 +338,6 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
|||
}
|
||||
|
||||
func (w WebsiteService) GetNginxConfigByScope(req dto.NginxConfigReq) (*dto.WebsiteNginxConfig, error) {
|
||||
|
||||
keys, ok := dto.ScopeKeyMap[req.Scope]
|
||||
if !ok || len(keys) == 0 {
|
||||
return nil, nil
|
||||
|
@ -350,7 +359,6 @@ func (w WebsiteService) GetNginxConfigByScope(req dto.NginxConfigReq) (*dto.Webs
|
|||
}
|
||||
|
||||
func (w WebsiteService) UpdateNginxConfigByScope(req dto.NginxConfigReq) error {
|
||||
|
||||
keys, ok := dto.ScopeKeyMap[req.Scope]
|
||||
if !ok || len(keys) == 0 {
|
||||
return nil
|
||||
|
@ -554,3 +562,50 @@ func (w WebsiteService) PreInstallCheck(req dto.WebsiteInstallCheckReq) ([]dto.W
|
|||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (dto.WebsiteWafConfig, error) {
|
||||
var res dto.WebsiteWafConfig
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"set"}, &website)
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
for _, param := range params {
|
||||
if param.Params[0] == req.Key {
|
||||
res.Enable = len(param.Params) > 1 && param.Params[1] == "on"
|
||||
break
|
||||
}
|
||||
}
|
||||
nginxFull, err := getNginxFull(&website)
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
filePath := path.Join(nginxFull.SiteDir, "sites", website.Alias, "waf", "rules", req.Rule)
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
res.FilePath = filePath
|
||||
res.Content = string(content)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (w WebsiteService) UpdateWafConfig(req request.WebsiteWafUpdate) error {
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
updateValue := "on"
|
||||
if !req.Enable {
|
||||
updateValue = "off"
|
||||
}
|
||||
return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{
|
||||
{Name: "set", Params: []string{req.Key, updateValue}},
|
||||
}, &website)
|
||||
}
|
||||
|
|
|
@ -33,5 +33,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
|
|||
groupRouter.POST("/config/update", baseApi.UpdateNginxConfig)
|
||||
groupRouter.GET("/:id/https", baseApi.GetHTTPSConfig)
|
||||
groupRouter.POST("/:id/https", baseApi.UpdateHTTPSConfig)
|
||||
groupRouter.POST("/waf/config", baseApi.GetWebsiteWafConfig)
|
||||
groupRouter.POST("/waf/update", baseApi.UpdateWebsiteWafConfig)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -212,4 +212,22 @@ export namespace WebSite {
|
|||
version: string;
|
||||
appName: string;
|
||||
}
|
||||
|
||||
export interface WafReq {
|
||||
websiteId: number;
|
||||
key: string;
|
||||
rule: string;
|
||||
}
|
||||
|
||||
export interface WafRes {
|
||||
enable: boolean;
|
||||
filePath: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface WafUpdate {
|
||||
enable: boolean;
|
||||
websiteId: number;
|
||||
key: string;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,3 +148,11 @@ export const UpdateHTTPSConfig = (req: WebSite.HTTPSReq) => {
|
|||
export const PreCheck = (req: WebSite.CheckReq) => {
|
||||
return http.post<WebSite.CheckRes[]>(`/websites/check`, req);
|
||||
};
|
||||
|
||||
export const GetWafConfig = (req: WebSite.WafReq) => {
|
||||
return http.post<WebSite.WafRes>(`/websites/waf/config`, req);
|
||||
};
|
||||
|
||||
export const UpdateWafEnable = (req: WebSite.WafUpdate) => {
|
||||
return http.post<any>(`/websites/waf/update`, req);
|
||||
};
|
||||
|
|
|
@ -850,6 +850,12 @@ export default {
|
|||
default: '默认',
|
||||
deleteHelper: '相关应用状态不正常,请检查',
|
||||
toApp: '去已安装列表',
|
||||
enableCC: '开启CC攻击防护',
|
||||
cycle: '周期',
|
||||
frequency: '频率',
|
||||
ccHelper: '{0} 秒内累计请求同一URL超过 {1} 次,触发CC防御,封锁此IP',
|
||||
seconds: '秒',
|
||||
count: '次',
|
||||
},
|
||||
nginx: {
|
||||
serverNamesHashBucketSizeHelper: '服务器名字的hash表大小',
|
||||
|
|
|
@ -73,10 +73,10 @@ const search = (req: WebSite.NginxConfigReq) => {
|
|||
loading.value = true;
|
||||
GetNginxConfig(req)
|
||||
.then((res) => {
|
||||
if (res.data && res.data.length > 0) {
|
||||
const indexParam = res.data[0];
|
||||
if (res.data && res.data.params.length > 0) {
|
||||
const params = res.data.params[0].params;
|
||||
let values = '';
|
||||
for (const param of indexParam.params) {
|
||||
for (const param of params) {
|
||||
values = values + param + '\n';
|
||||
}
|
||||
defaultModel.value.index = values;
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10" :offset="2">
|
||||
<el-form
|
||||
ref="wafForm"
|
||||
label-position="left"
|
||||
label-width="auto"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-form-item prop="enable" :label="$t('website.enableCC')">
|
||||
<el-switch v-model="form.enable" @change="updateEnable"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item prop="cycle" :label="$t('website.cycle')">
|
||||
<el-input v-model.number="form.cycle" type="number">
|
||||
<template #append>{{ $t('website.seconds') }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="frequency" :label="$t('website.frequency')">
|
||||
<el-input v-model.number="form.frequency" type="number">
|
||||
<template #append>{{ $t('website.count') }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-alert
|
||||
:title="$t('website.ccHelper', [form.cycle, form.frequency])"
|
||||
type="info"
|
||||
:closable="false"
|
||||
></el-alert>
|
||||
<el-form-item></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submit(wafForm)" :loading="loading">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { WebSite } from '@/api/interface/website';
|
||||
import { SaveFileContent } from '@/api/modules/files';
|
||||
import { GetWafConfig, UpdateWafEnable } from '@/api/modules/website';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessage, FormInstance } from 'element-plus';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
const id = computed(() => {
|
||||
return props.id;
|
||||
});
|
||||
|
||||
let data = ref<WebSite.WafRes>();
|
||||
let loading = ref(false);
|
||||
let form = reactive({
|
||||
enable: false,
|
||||
cycle: 60,
|
||||
frequency: 120,
|
||||
});
|
||||
let req = ref<WebSite.WafReq>({
|
||||
websiteId: 0,
|
||||
key: '$CCDeny',
|
||||
rule: 'ccrate',
|
||||
});
|
||||
let enableUpdate = ref<WebSite.WafUpdate>({
|
||||
websiteId: 0,
|
||||
key: '$CCDeny',
|
||||
enable: false,
|
||||
});
|
||||
let fileUpdate = reactive({
|
||||
path: '',
|
||||
content: '',
|
||||
});
|
||||
let rules = ref({
|
||||
cycle: [Rules.requiredInput],
|
||||
frequency: [Rules.requiredInput],
|
||||
});
|
||||
const wafForm = ref<FormInstance>();
|
||||
|
||||
const get = async () => {
|
||||
loading.value = true;
|
||||
const res = await GetWafConfig(req.value);
|
||||
loading.value = false;
|
||||
data.value = res.data;
|
||||
form.enable = data.value.enable;
|
||||
if (data.value.content != '') {
|
||||
const params = data.value.content.split('/');
|
||||
form.frequency = Number(params[0]);
|
||||
form.cycle = Number(params[1]);
|
||||
}
|
||||
fileUpdate.path = data.value.filePath;
|
||||
};
|
||||
|
||||
const updateEnable = async (enable: boolean) => {
|
||||
enableUpdate.value.enable = enable;
|
||||
loading.value = true;
|
||||
await UpdateWafEnable(enableUpdate.value);
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
fileUpdate.content = String(form.frequency) + '/' + String(form.cycle);
|
||||
loading.value = true;
|
||||
SaveFileContent(fileUpdate)
|
||||
.then(() => {
|
||||
ElMessage.success(i18n.global.t('commons.msg.updateSuccess'));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
req.value.websiteId = id.value;
|
||||
enableUpdate.value.websiteId = id.value;
|
||||
get();
|
||||
});
|
||||
</script>
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<el-tabs tab-position="left" type="border-card" v-model="index">
|
||||
<el-tab-pane :label="'CC 防护'" :id="id"></el-tab-pane>
|
||||
<el-tab-pane :label="'CC 防护'" name="cc">
|
||||
<CCDeny :id="id" v-if="index == 'cc'"></CCDeny>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="'白名单'"></el-tab-pane>
|
||||
<el-tab-pane :label="'黑名单'"></el-tab-pane>
|
||||
<el-tab-pane :label="'并发限制'"></el-tab-pane>
|
||||
|
@ -10,6 +12,7 @@
|
|||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import CCDeny from './ccdeny/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
|
@ -22,5 +25,5 @@ const id = computed(() => {
|
|||
return props.id;
|
||||
});
|
||||
|
||||
let index = ref('0');
|
||||
let index = ref('cc');
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue