feat: 增加CC防护

pull/67/head
zhengkunwang223 2022-12-06 11:42:11 +08:00 committed by zhengkunwang223
parent ba77f2853f
commit 047bd907f0
12 changed files with 280 additions and 11 deletions

View File

@ -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)
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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;
}
}

View File

@ -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);
};

View File

@ -850,6 +850,12 @@ export default {
default: '',
deleteHelper: '',
toApp: '',
enableCC: 'CC',
cycle: '',
frequency: '',
ccHelper: '{0} URL {1} ,CC,IP',
seconds: '秒',
count: '次',
},
nginx: {
serverNamesHashBucketSizeHelper: 'hash',

View File

@ -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;

View File

@ -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>

View File

@ -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>