1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-13 17:24:44 +08:00

feat: 防火墙支持设置备注信息 (#1962)

Refs #1930 #1121 #1642
This commit is contained in:
ssongliu 2023-08-15 22:48:11 +08:00 committed by GitHub
parent a0456d0f7a
commit 89faf01391
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 458 additions and 43 deletions

View File

@ -156,6 +156,31 @@ func (b *BaseApi) BatchOperateRule(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Update rule description
// @Description 更新防火墙描述
// @Accept json
// @Param request body dto.UpdateFirewallDescription true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/update/description [post]
func (b *BaseApi) UpdateFirewallDescription(c *gin.Context) {
var req dto.UpdateFirewallDescription
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := firewallService.UpdateDescription(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 更新端口防火墙规则

View File

@ -23,12 +23,25 @@ type PortRuleOperate struct {
Port string `json:"port" validate:"required"`
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
Description string `json:"description"`
}
type UpdateFirewallDescription struct {
Address string `json:"address"`
Port string `json:"port"`
Protocol string `json:"protocol"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
Description string `json:"description"`
}
type AddrRuleOperate struct {
Operation string `json:"operation" validate:"required,oneof=add remove"`
Address string `json:"address" validate:"required"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
Description string `json:"description"`
}
type PortRuleUpdate struct {

View File

@ -0,0 +1,11 @@
package model
type Firewall struct {
BaseModel
Port string `gorm:"type:varchar(64);not null" json:"port"`
Protocol string `gorm:"type:varchar(64);not null" json:"protocol"`
Address string `gorm:"type:varchar(64);not null" json:"address"`
Strategy string `gorm:"type:varchar(64);not null" json:"strategy"`
Description string `gorm:"type:varchar(64);not null" json:"description"`
}

View File

@ -19,6 +19,12 @@ type IHostRepo interface {
Create(host *model.Host) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
ListFirewallRecord() ([]model.Firewall, error)
SaveFirewallRecord(firewall *model.Firewall) error
DeleteFirewallRecordByID(id uint) error
DeleteFirewallRecord(port, protocol, address, strategy string) error
}
func NewIHostRepo() IHostRepo {
@ -106,3 +112,41 @@ func (h *HostRepo) Delete(opts ...DBOption) error {
}
return db.Delete(&model.Host{}).Error
}
func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
var firewall model.Firewall
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&firewall).Error
return firewall, err
}
func (h *HostRepo) ListFirewallRecord() ([]model.Firewall, error) {
var datas []model.Firewall
if err := global.DB.Find(&datas).Error; err != nil {
return datas, nil
}
return datas, nil
}
func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error {
if firewall.ID != 0 {
return global.DB.Save(firewall).Error
}
var data model.Firewall
_ = global.DB.Where("port = ? AND protocol = ? AND address = ? AND strategy = ?", firewall.Port, firewall.Protocol, firewall.Address, firewall.Strategy).First(&data)
if data.ID != 0 {
firewall.ID = data.ID
}
return global.DB.Save(firewall).Error
}
func (h *HostRepo) DeleteFirewallRecordByID(id uint) error {
return global.DB.Where("id = ?", id).Delete(&model.Firewall{}).Error
}
func (h *HostRepo) DeleteFirewallRecord(port, protocol, address, strategy string) error {
return global.DB.Where("port = ? AND protocol = ? AND address = ? AND strategy = ?", port, protocol, address, strategy).Delete(&model.Firewall{}).Error
}

View File

@ -7,12 +7,14 @@ import (
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/firewall"
fireClient "github.com/1Panel-dev/1Panel/backend/utils/firewall/client"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
const confPath = "/etc/sysctl.conf"
@ -27,6 +29,7 @@ type IFirewallService interface {
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
UpdatePortRule(req dto.PortRuleUpdate) error
UpdateAddrRule(req dto.AddrRuleUpdate) error
UpdateDescription(req dto.UpdateFirewallDescription) error
BatchOperateRule(req dto.BatchRuleOperate) error
}
@ -67,6 +70,7 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
datas []fireClient.FireInfo
backDatas []fireClient.FireInfo
)
client, err := firewall.NewFirewallClient()
if err != nil {
return 0, nil, err
@ -110,6 +114,16 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
backDatas = datas[start:end]
}
datasFromDB, _ := hostRepo.ListFirewallRecord()
for i := 0; i < len(backDatas); i++ {
for _, des := range datasFromDB {
if backDatas[i].Port == des.Port && backDatas[i].Protocol == des.Protocol && backDatas[i].Strategy == des.Strategy && backDatas[i].Address == des.Address {
backDatas[i].Description = des.Description
break
}
}
}
if req.Type == "port" {
apps := u.loadPortByApp()
for i := 0; i < len(backDatas); i++ {
@ -127,6 +141,7 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
}
}
}
go u.cleanUnUsedData(client)
return int64(total), backDatas, nil
}
@ -166,59 +181,55 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
if err != nil {
return err
}
protos := strings.Split(req.Protocol, "/")
if client.Name() == "ufw" {
req.Port = strings.ReplaceAll(req.Port, "-", ":")
if req.Operation == "remove" && req.Protocol == "tcp/udp" {
req.Protocol = ""
return u.operatePort(client, req)
if len(req.Address) == 0 {
req.Address = "Anywhere"
}
if strings.Contains(req.Port, ",") || strings.Contains(req.Port, "-") {
for _, proto := range protos {
req.Port = strings.ReplaceAll(req.Port, "-", ":")
req.Protocol = proto
if err := u.operatePort(client, req); err != nil {
return err
}
_ = u.addPortRecord(req)
}
return nil
}
if req.Protocol == "tcp/udp" {
req.Protocol = ""
}
if err := u.operatePort(client, req); err != nil {
return err
}
_ = u.addPortRecord(req)
return nil
}
if req.Protocol == "tcp/udp" {
if client.Name() == "firewalld" && strings.Contains(req.Port, ",") {
for _, proto := range protos {
if strings.Contains(req.Port, "-") {
req.Protocol = proto
if err := u.operatePort(client, req); err != nil {
return err
}
_ = u.addPortRecord(req)
} else {
ports := strings.Split(req.Port, ",")
for _, port := range ports {
if len(port) == 0 {
continue
}
req.Port = port
req.Protocol = "tcp"
req.Protocol = proto
if err := u.operatePort(client, req); err != nil {
return err
}
req.Protocol = "udp"
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
req.Protocol = "tcp"
if err := u.operatePort(client, req); err != nil {
return err
}
req.Protocol = "udp"
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
if strings.Contains(req.Port, ",") {
ports := strings.Split(req.Port, ",")
for _, port := range ports {
req.Port = port
if err := u.operatePort(client, req); err != nil {
return err
}
}
} else {
if err := u.operatePort(client, req); err != nil {
return err
_ = u.addPortRecord(req)
}
}
}
if reload {
return client.Reload()
}
return nil
return client.Reload()
}
func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload bool) error {
@ -241,6 +252,7 @@ func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload boo
if err := client.RichRules(fireInfo, req.Operation); err != nil {
return err
}
_ = u.addAddressRecord(req)
}
if reload {
return client.Reload()
@ -276,6 +288,14 @@ func (u *FirewallService) UpdateAddrRule(req dto.AddrRuleUpdate) error {
return client.Reload()
}
func (u *FirewallService) UpdateDescription(req dto.UpdateFirewallDescription) error {
var firewall model.Firewall
if err := copier.Copy(&firewall, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
return hostRepo.SaveFirewallRecord(&firewall)
}
func (u *FirewallService) BatchOperateRule(req dto.BatchRuleOperate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
@ -323,7 +343,7 @@ func (u *FirewallService) operatePort(client firewall.FirewallClient, req dto.Po
}
if client.Name() == "ufw" {
if len(fireInfo.Address) != 0 && fireInfo.Address != "Anywhere" {
if len(fireInfo.Address) != 0 && !strings.EqualFold(fireInfo.Address, "Anywhere") {
return client.RichRules(fireInfo, req.Operation)
}
return client.Port(fireInfo, req.Operation)
@ -363,6 +383,29 @@ func (u *FirewallService) loadPortByApp() []portOfApp {
return datas
}
func (u *FirewallService) cleanUnUsedData(client firewall.FirewallClient) {
list, _ := client.ListPort()
addressList, _ := client.ListAddress()
list = append(list, addressList...)
if len(list) == 0 {
return
}
records, _ := hostRepo.ListFirewallRecord()
if len(records) == 0 {
return
}
for _, item := range list {
for i := 0; i < len(records); i++ {
if records[i].Port == item.Port && records[i].Protocol == item.Protocol && records[i].Strategy == item.Strategy && records[i].Address == item.Address {
records = append(records[:i], records[i+1:]...)
}
}
}
for _, record := range records {
_ = hostRepo.DeleteFirewallRecordByID(record.ID)
}
}
func (u *FirewallService) pingStatus() string {
if _, err := os.Stat("/etc/sysctl.conf"); err != nil {
return constant.StatusNone
@ -441,3 +484,28 @@ func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) er
return client.Reload()
}
func (u *FirewallService) addPortRecord(req dto.PortRuleOperate) error {
if req.Operation == "remove" {
return hostRepo.DeleteFirewallRecord(req.Port, req.Protocol, req.Address, req.Strategy)
}
return hostRepo.SaveFirewallRecord(&model.Firewall{
Port: req.Port,
Protocol: req.Protocol,
Address: req.Address,
Strategy: req.Strategy,
Description: req.Description,
})
}
func (u *FirewallService) addAddressRecord(req dto.AddrRuleOperate) error {
if req.Operation == "remove" {
return hostRepo.DeleteFirewallRecord("", "", req.Address, req.Strategy)
}
return hostRepo.SaveFirewallRecord(&model.Firewall{
Address: req.Address,
Strategy: req.Strategy,
Description: req.Description,
})
}

View File

@ -38,6 +38,7 @@ func Init() {
migrations.AddRemoteDB,
migrations.UpdateRedisParam,
migrations.UpdateCronjobWithDb,
migrations.AddTableFirewall,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View File

@ -570,3 +570,13 @@ var UpdateCronjobWithDb = &gormigrate.Migration{
return nil
},
}
var AddTableFirewall = &gormigrate.Migration{
ID: "20230814-add-table-firewall",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Firewall{}); err != nil {
return err
}
return nil
},
}

View File

@ -33,6 +33,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
hostRouter.POST("/firewall/batch", baseApi.BatchOperateRule)
hostRouter.POST("/firewall/update/port", baseApi.UpdatePortRule)
hostRouter.POST("/firewall/update/addr", baseApi.UpdateAddrRule)
hostRouter.POST("/firewall/update/description", baseApi.UpdateFirewallDescription)
hostRouter.GET("/ssh/conf", baseApi.LoadSSHConf)
hostRouter.POST("/ssh/search", baseApi.GetSSHInfo)

View File

@ -7,8 +7,9 @@ type FireInfo struct {
Protocol string `json:"protocol"` // tcp udp tcp/udp
Strategy string `json:"strategy"` // accept drop
APPName string `json:"appName"`
IsUsed bool `json:"isUsed"`
APPName string `json:"appName"`
IsUsed bool `json:"isUsed"`
Description string `json:"description"`
}
type Forward struct {

View File

@ -6906,6 +6906,39 @@ const docTemplate = `{
}
}
},
"/hosts/firewall/update/description": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新防火墙描述",
"consumes": [
"application/json"
],
"tags": [
"Firewall"
],
"summary": "Update rule description",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateFirewallDescription"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/hosts/firewall/update/port": {
"post": {
"security": [
@ -11517,6 +11550,9 @@ const docTemplate = `{
"address": {
"type": "string"
},
"description": {
"type": "string"
},
"operation": {
"type": "string",
"enum": [
@ -13610,6 +13646,9 @@ const docTemplate = `{
"address": {
"type": "string"
},
"description": {
"type": "string"
},
"operation": {
"type": "string",
"enum": [
@ -14469,6 +14508,33 @@ const docTemplate = `{
}
}
},
"dto.UpdateFirewallDescription": {
"type": "object",
"required": [
"strategy"
],
"properties": {
"address": {
"type": "string"
},
"description": {
"type": "string"
},
"port": {
"type": "string"
},
"protocol": {
"type": "string"
},
"strategy": {
"type": "string",
"enum": [
"accept",
"drop"
]
}
}
},
"dto.Upgrade": {
"type": "object",
"properties": {

View File

@ -6899,6 +6899,39 @@
}
}
},
"/hosts/firewall/update/description": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新防火墙描述",
"consumes": [
"application/json"
],
"tags": [
"Firewall"
],
"summary": "Update rule description",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateFirewallDescription"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/hosts/firewall/update/port": {
"post": {
"security": [
@ -11510,6 +11543,9 @@
"address": {
"type": "string"
},
"description": {
"type": "string"
},
"operation": {
"type": "string",
"enum": [
@ -13603,6 +13639,9 @@
"address": {
"type": "string"
},
"description": {
"type": "string"
},
"operation": {
"type": "string",
"enum": [
@ -14462,6 +14501,33 @@
}
}
},
"dto.UpdateFirewallDescription": {
"type": "object",
"required": [
"strategy"
],
"properties": {
"address": {
"type": "string"
},
"description": {
"type": "string"
},
"port": {
"type": "string"
},
"protocol": {
"type": "string"
},
"strategy": {
"type": "string",
"enum": [
"accept",
"drop"
]
}
}
},
"dto.Upgrade": {
"type": "object",
"properties": {

View File

@ -4,6 +4,8 @@ definitions:
properties:
address:
type: string
description:
type: string
operation:
enum:
- add
@ -1409,6 +1411,8 @@ definitions:
properties:
address:
type: string
description:
type: string
operation:
enum:
- add
@ -1988,6 +1992,24 @@ definitions:
required:
- id
type: object
dto.UpdateFirewallDescription:
properties:
address:
type: string
description:
type: string
port:
type: string
protocol:
type: string
strategy:
enum:
- accept
- drop
type: string
required:
- strategy
type: object
dto.Upgrade:
properties:
version:
@ -8251,6 +8273,26 @@ paths:
summary: Create group
tags:
- Firewall
/hosts/firewall/update/description:
post:
consumes:
- application/json
description: 更新防火墙描述
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.UpdateFirewallDescription'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Update rule description
tags:
- Firewall
/hosts/firewall/update/port:
post:
consumes:

View File

@ -77,6 +77,14 @@ export namespace Host {
strategy: string;
appName: string;
isUsed: boolean;
description: string;
}
export interface UpdateDescription {
address: string;
port: string;
protocol: string;
strategy: string;
description: string;
}
export interface RulePort {
operation: string;
@ -85,11 +93,13 @@ export namespace Host {
source: string;
protocol: string;
strategy: string;
description: string;
}
export interface RuleIP {
operation: string;
address: string;
strategy: string;
description: string;
}
export interface UpdatePortRule {
oldRule: RulePort;

View File

@ -90,6 +90,9 @@ export const updatePortRule = (params: Host.UpdatePortRule) => {
export const updateAddrRule = (params: Host.UpdateAddrRule) => {
return http.post(`/hosts/firewall/update/addr`, params);
};
export const updateFirewallDescription = (params: Host.UpdateDescription) => {
return http.post(`/hosts/firewall/update/description`, params);
};
export const batchOperateRule = (params: Host.BatchRule) => {
return http.post(`/hosts/firewall/batch`, params);
};

View File

@ -74,6 +74,21 @@
</el-button>
</template>
</el-table-column>
<el-table-column
:min-width="150"
:label="$t('commons.table.description')"
prop="description"
>
<template #default="{ row }">
<fu-read-write-switch
:data="row.description"
v-model="row.edit"
@change="onChange(row)"
>
<el-input v-model="row.description" @blur="row.edit = false" />
</fu-read-write-switch>
</template>
</el-table-column>
<fu-table-operations
width="200px"
:buttons="buttons"
@ -118,7 +133,7 @@ import FireRouter from '@/views/host/firewall/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import FireStatus from '@/views/host/firewall/status/index.vue';
import { onMounted, reactive, ref } from 'vue';
import { batchOperateRule, searchFireRule, updateAddrRule } from '@/api/modules/host';
import { batchOperateRule, searchFireRule, updateAddrRule, updateFirewallDescription } from '@/api/modules/host';
import { Host } from '@/api/interface/host';
import { ElMessageBox } from 'element-plus';
import i18n from '@/lang';
@ -184,6 +199,13 @@ const toDoc = () => {
window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank');
};
const onChange = async (info: any) => {
if (!info.edit) {
await updateFirewallDescription(info);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
}
};
const onChangeStatus = async (row: Host.RuleInfo, status: string) => {
let operation =
status === 'accept'
@ -198,11 +220,13 @@ const onChangeStatus = async (row: Host.RuleInfo, status: string) => {
operation: 'remove',
address: row.address,
strategy: row.strategy,
description: row.description,
},
newRule: {
operation: 'add',
address: row.address,
strategy: status,
description: row.description,
},
};
loading.value = true;

View File

@ -24,6 +24,9 @@
<el-radio label="drop">{{ $t('firewall.deny') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('commons.table.description')" prop="description">
<el-input clearable v-model.trim="dialogData.rowData!.description" />
</el-form-item>
</el-col>
</el-row>
</el-form>

View File

@ -105,6 +105,21 @@
<span v-else>{{ $t('firewall.allIP') }}</span>
</template>
</el-table-column>
<el-table-column
:min-width="150"
:label="$t('commons.table.description')"
prop="description"
>
<template #default="{ row }">
<fu-read-write-switch
:data="row.description"
v-model="row.edit"
@change="onChange(row)"
>
<el-input v-model="row.description" @blur="row.edit = false" />
</fu-read-write-switch>
</template>
</el-table-column>
<fu-table-operations
width="200px"
:buttons="buttons"
@ -150,7 +165,7 @@ import TableSetting from '@/components/table-setting/index.vue';
import OperatrDialog from '@/views/host/firewall/port/operate/index.vue';
import FireStatus from '@/views/host/firewall/status/index.vue';
import { onMounted, reactive, ref } from 'vue';
import { batchOperateRule, searchFireRule, updatePortRule } from '@/api/modules/host';
import { batchOperateRule, searchFireRule, updateFirewallDescription, updatePortRule } from '@/api/modules/host';
import { Host } from '@/api/interface/host';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
@ -239,6 +254,7 @@ const onChangeStatus = async (row: Host.RuleInfo, status: string) => {
source: '',
protocol: row.protocol,
strategy: row.strategy,
description: row.description,
},
newRule: {
operation: 'add',
@ -247,6 +263,7 @@ const onChangeStatus = async (row: Host.RuleInfo, status: string) => {
source: '',
protocol: row.protocol,
strategy: status,
description: row.description,
},
};
loading.value = true;
@ -262,6 +279,13 @@ const onChangeStatus = async (row: Host.RuleInfo, status: string) => {
});
};
const onChange = async (info: any) => {
if (!info.edit) {
await updateFirewallDescription(info);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
}
};
const onDelete = async (row: Host.RuleInfo | null) => {
ElMessageBox.confirm(i18n.global.t('commons.msg.delete'), i18n.global.t('commons.msg.deleteTitle'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),

View File

@ -49,6 +49,9 @@
<el-radio label="drop">{{ $t('firewall.drop') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('commons.table.description')" prop="description">
<el-input clearable v-model.trim="dialogData.rowData!.description" />
</el-form-item>
</el-col>
</el-row>
</el-form>