mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 端口转发功能 (#5439)
This commit is contained in:
parent
b36e2c5676
commit
af0ecce25b
@ -94,6 +94,29 @@ func (b *BaseApi) OperatePortRule(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OperateForwardRule
|
||||||
|
// @Tags Firewall
|
||||||
|
// @Summary Create group
|
||||||
|
// @Description 更新防火墙端口转发规则
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.ForwardRuleOperate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /hosts/firewall/forward [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["source_port"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则 [source_port]","formatEN":"update port forward rules [source_port]"}
|
||||||
|
func (b *BaseApi) OperateForwardRule(c *gin.Context) {
|
||||||
|
var req dto.ForwardRuleOperate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := firewallService.OperateForwardRule(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Firewall
|
// @Tags Firewall
|
||||||
// @Summary Create group
|
// @Summary Create group
|
||||||
// @Description 创建防火墙 IP 规则
|
// @Description 创建防火墙 IP 规则
|
||||||
|
@ -29,6 +29,17 @@ type PortRuleOperate struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ForwardRuleOperate struct {
|
||||||
|
Rules []struct {
|
||||||
|
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||||
|
Num string `json:"num"`
|
||||||
|
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"`
|
||||||
|
Port string `json:"port" validate:"required"`
|
||||||
|
TargetIP string `json:"targetIP"`
|
||||||
|
TargetPort string `json:"targetPort" validate:"required"`
|
||||||
|
} `json:"rules"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateFirewallDescription struct {
|
type UpdateFirewallDescription struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
|
@ -10,3 +10,12 @@ type Firewall struct {
|
|||||||
Strategy string `gorm:"type:varchar(64);not null" json:"strategy"`
|
Strategy string `gorm:"type:varchar(64);not null" json:"strategy"`
|
||||||
Description string `gorm:"type:varchar(64);not null" json:"description"`
|
Description string `gorm:"type:varchar(64);not null" json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Forward struct {
|
||||||
|
BaseModel
|
||||||
|
|
||||||
|
Protocol string `gorm:"type:varchar(64);not null" json:"protocol"`
|
||||||
|
Port string `gorm:"type:varchar(64);not null" json:"port"`
|
||||||
|
TargetIP string `gorm:"type:varchar(64);not null" json:"targetIP"`
|
||||||
|
TargetPort string `gorm:"type:varchar(64);not null" json:"targetPort"`
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -28,6 +29,7 @@ type IFirewallService interface {
|
|||||||
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
|
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
|
||||||
OperateFirewall(operation string) error
|
OperateFirewall(operation string) error
|
||||||
OperatePortRule(req dto.PortRuleOperate, reload bool) error
|
OperatePortRule(req dto.PortRuleOperate, reload bool) error
|
||||||
|
OperateForwardRule(req dto.ForwardRuleOperate) error
|
||||||
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
|
OperateAddressRule(req dto.AddrRuleOperate, reload bool) error
|
||||||
UpdatePortRule(req dto.PortRuleUpdate) error
|
UpdatePortRule(req dto.PortRuleUpdate) error
|
||||||
UpdateAddrRule(req dto.AddrRuleUpdate) error
|
UpdateAddrRule(req dto.AddrRuleUpdate) error
|
||||||
@ -78,42 +80,36 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
if req.Type == "port" {
|
|
||||||
ports, err := client.ListPort()
|
var rules []fireClient.FireInfo
|
||||||
if err != nil {
|
switch req.Type {
|
||||||
return 0, nil, err
|
case "port":
|
||||||
}
|
rules, err = client.ListPort()
|
||||||
if len(req.Info) != 0 {
|
case "forward":
|
||||||
for _, port := range ports {
|
rules, err = client.ListForward()
|
||||||
if strings.Contains(port.Port, req.Info) {
|
case "address":
|
||||||
datas = append(datas, port)
|
rules, err = client.ListAddress()
|
||||||
}
|
}
|
||||||
}
|
if err != nil {
|
||||||
} else {
|
return 0, nil, err
|
||||||
datas = ports
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addrs, err := client.ListAddress()
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
if len(req.Info) != 0 {
|
|
||||||
for _, addr := range addrs {
|
|
||||||
if strings.Contains(addr.Address, req.Info) {
|
|
||||||
datas = append(datas, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
datas = addrs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(req.Info) != 0 {
|
||||||
|
for _, addr := range rules {
|
||||||
|
if strings.Contains(addr.Address, req.Info) {
|
||||||
|
datas = append(datas, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
datas = rules
|
||||||
|
}
|
||||||
if req.Type == "port" {
|
if req.Type == "port" {
|
||||||
apps := u.loadPortByApp()
|
apps := u.loadPortByApp()
|
||||||
for i := 0; i < len(datas); i++ {
|
for i := 0; i < len(datas); i++ {
|
||||||
datas[i].UsedStatus = checkPortUsed(datas[i].Port, datas[i].Protocol, apps)
|
datas[i].UsedStatus = checkPortUsed(datas[i].Port, datas[i].Protocol, apps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var datasFilterStatus []fireClient.FireInfo
|
var datasFilterStatus []fireClient.FireInfo
|
||||||
if len(req.Status) != 0 {
|
if len(req.Status) != 0 {
|
||||||
for _, data := range datas {
|
for _, data := range datas {
|
||||||
@ -127,6 +123,7 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
|
|||||||
} else {
|
} else {
|
||||||
datasFilterStatus = datas
|
datasFilterStatus = datas
|
||||||
}
|
}
|
||||||
|
|
||||||
var datasFilterStrategy []fireClient.FireInfo
|
var datasFilterStrategy []fireClient.FireInfo
|
||||||
if len(req.Strategy) != 0 {
|
if len(req.Strategy) != 0 {
|
||||||
for _, data := range datasFilterStatus {
|
for _, data := range datasFilterStatus {
|
||||||
@ -300,6 +297,37 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *FirewallService) OperateForwardRule(req dto.ForwardRuleOperate) error {
|
||||||
|
client, err := firewall.NewFirewallClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(req.Rules, func(i, j int) bool {
|
||||||
|
n1, _ := strconv.Atoi(req.Rules[i].Num)
|
||||||
|
n2, _ := strconv.Atoi(req.Rules[j].Num)
|
||||||
|
return n1 > n2
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, r := range req.Rules {
|
||||||
|
for _, p := range strings.Split(r.Protocol, "/") {
|
||||||
|
if r.TargetIP == "" {
|
||||||
|
r.TargetIP = "127.0.0.1"
|
||||||
|
}
|
||||||
|
if err = client.PortForward(fireClient.Forward{
|
||||||
|
Num: r.Num,
|
||||||
|
Protocol: p,
|
||||||
|
Port: r.Port,
|
||||||
|
TargetIP: r.TargetIP,
|
||||||
|
TargetPort: r.TargetPort,
|
||||||
|
}, r.Operation); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload bool) error {
|
func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload bool) error {
|
||||||
client, err := firewall.NewFirewallClient()
|
client, err := firewall.NewFirewallClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,6 +2,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/firewall"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
@ -31,6 +32,10 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = docker.CreateDefaultDockerNetwork()
|
_ = docker.CreateDefaultDockerNetwork()
|
||||||
|
|
||||||
|
if f, err := firewall.NewFirewallClient(); err == nil {
|
||||||
|
_ = f.EnableForward()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDir(fileOp files.FileOp, dirPath string) {
|
func createDir(fileOp files.FileOp, dirPath string) {
|
||||||
|
@ -89,6 +89,7 @@ func Init() {
|
|||||||
migrations.AddFtp,
|
migrations.AddFtp,
|
||||||
migrations.AddProxy,
|
migrations.AddProxy,
|
||||||
migrations.AddCronJobColumn,
|
migrations.AddCronJobColumn,
|
||||||
|
migrations.AddForward,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
@ -239,6 +239,16 @@ var AddProxy = &gormigrate.Migration{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AddForward = &gormigrate.Migration{
|
||||||
|
ID: "202400611-add-forward",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.AutoMigrate(&model.Forward{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var AddCronJobColumn = &gormigrate.Migration{
|
var AddCronJobColumn = &gormigrate.Migration{
|
||||||
ID: "20240524-add-cronjob-command",
|
ID: "20240524-add-cronjob-command",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
@ -29,6 +29,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
|
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
|
||||||
hostRouter.POST("/firewall/operate", baseApi.OperateFirewall)
|
hostRouter.POST("/firewall/operate", baseApi.OperateFirewall)
|
||||||
hostRouter.POST("/firewall/port", baseApi.OperatePortRule)
|
hostRouter.POST("/firewall/port", baseApi.OperatePortRule)
|
||||||
|
hostRouter.POST("/firewall/forward", baseApi.OperateForwardRule)
|
||||||
hostRouter.POST("/firewall/ip", baseApi.OperateIPRule)
|
hostRouter.POST("/firewall/ip", baseApi.OperateIPRule)
|
||||||
hostRouter.POST("/firewall/batch", baseApi.BatchOperateRule)
|
hostRouter.POST("/firewall/batch", baseApi.BatchOperateRule)
|
||||||
hostRouter.POST("/firewall/update/port", baseApi.UpdatePortRule)
|
hostRouter.POST("/firewall/update/port", baseApi.UpdatePortRule)
|
||||||
|
@ -18,11 +18,14 @@ type FirewallClient interface {
|
|||||||
Version() (string, error)
|
Version() (string, error)
|
||||||
|
|
||||||
ListPort() ([]client.FireInfo, error)
|
ListPort() ([]client.FireInfo, error)
|
||||||
|
ListForward() ([]client.FireInfo, error)
|
||||||
ListAddress() ([]client.FireInfo, error)
|
ListAddress() ([]client.FireInfo, error)
|
||||||
|
|
||||||
Port(port client.FireInfo, operation string) error
|
Port(port client.FireInfo, operation string) error
|
||||||
RichRules(rule client.FireInfo, operation string) error
|
RichRules(rule client.FireInfo, operation string) error
|
||||||
PortForward(info client.Forward, operation string) error
|
PortForward(info client.Forward, operation string) error
|
||||||
|
|
||||||
|
EnableForward() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFirewallClient() (FirewallClient, error) {
|
func NewFirewallClient() (FirewallClient, error) {
|
||||||
|
@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -10,6 +11,8 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ForwardListRegex = regexp.MustCompile(`^port=(\d{1,5}):proto=(.+?):toport=(\d{1,5}):toaddr=(.*)$`)
|
||||||
|
|
||||||
type Firewall struct{}
|
type Firewall struct{}
|
||||||
|
|
||||||
func NewFirewalld() (*Firewall, error) {
|
func NewFirewalld() (*Firewall, error) {
|
||||||
@ -115,6 +118,29 @@ func (f *Firewall) ListPort() ([]FireInfo, error) {
|
|||||||
return datas, nil
|
return datas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Firewall) ListForward() ([]FireInfo, error) {
|
||||||
|
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-forward-ports")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var datas []FireInfo
|
||||||
|
for _, line := range strings.Split(stdout, "\n") {
|
||||||
|
line = strings.TrimFunc(line, func(r rune) bool {
|
||||||
|
return r <= 32
|
||||||
|
})
|
||||||
|
if ForwardListRegex.MatchString(line) {
|
||||||
|
match := ForwardListRegex.FindStringSubmatch(line)
|
||||||
|
datas = append(datas, FireInfo{
|
||||||
|
Port: match[1],
|
||||||
|
Protocol: match[2],
|
||||||
|
TargetIP: match[4],
|
||||||
|
TargetPort: match[3],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return datas, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Firewall) ListAddress() ([]FireInfo, error) {
|
func (f *Firewall) ListAddress() ([]FireInfo, error) {
|
||||||
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules")
|
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -175,15 +201,18 @@ func (f *Firewall) RichRules(rule FireInfo, operation string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firewall) PortForward(info Forward, operation string) error {
|
func (f *Firewall) PortForward(info Forward, operation string) error {
|
||||||
ruleStr := fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Target)
|
ruleStr := fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetPort)
|
||||||
if len(info.Address) != 0 {
|
if info.TargetIP != "" && info.TargetIP != "127.0.0.1" && info.TargetIP != "localhost" {
|
||||||
ruleStr = fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Address, info.Target)
|
ruleStr = fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetIP, info.TargetPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout, err := cmd.Exec(ruleStr)
|
stdout, err := cmd.Exec(ruleStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s port forward failed, err: %s", operation, stdout)
|
return fmt.Errorf("%s port forward failed, err: %s", operation, stdout)
|
||||||
}
|
}
|
||||||
|
if err = f.Reload(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,3 +237,19 @@ func (f *Firewall) loadInfo(line string) FireInfo {
|
|||||||
}
|
}
|
||||||
return itemRule
|
return itemRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Firewall) EnableForward() error {
|
||||||
|
stdout, err := cmd.Exec("firewall-cmd --zone=public --query-masquerade")
|
||||||
|
if err != nil {
|
||||||
|
if strings.HasSuffix(strings.TrimSpace(stdout), "no") {
|
||||||
|
stdout, err = cmd.Exec("firewall-cmd --zone=public --add-masquerade --permanent")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", err, stdout)
|
||||||
|
}
|
||||||
|
return f.Reload()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: %s", err, stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -7,13 +7,29 @@ type FireInfo struct {
|
|||||||
Protocol string `json:"protocol"` // tcp udp tcp/udp
|
Protocol string `json:"protocol"` // tcp udp tcp/udp
|
||||||
Strategy string `json:"strategy"` // accept drop
|
Strategy string `json:"strategy"` // accept drop
|
||||||
|
|
||||||
|
Num string `json:"num"`
|
||||||
|
TargetIP string `json:"targetIP"`
|
||||||
|
TargetPort string `json:"targetPort"`
|
||||||
|
|
||||||
UsedStatus string `json:"usedStatus"`
|
UsedStatus string `json:"usedStatus"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Forward struct {
|
type Forward struct {
|
||||||
Protocol string `json:"protocol"`
|
Num string `json:"num"`
|
||||||
Address string `json:"address"`
|
Protocol string `json:"protocol"`
|
||||||
Port string `json:"port"`
|
Port string `json:"port"`
|
||||||
Target string `json:"target"`
|
TargetIP string `json:"targetIP"`
|
||||||
|
TargetPort string `json:"targetPort"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IptablesNatInfo struct {
|
||||||
|
Num string `json:"num"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Opt string `json:"opt"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Destination string `json:"destination"`
|
||||||
|
SrcPort string `json:"srcPort"`
|
||||||
|
DestPort string `json:"destPort"`
|
||||||
}
|
}
|
||||||
|
132
backend/utils/firewall/client/iptables.go
Normal file
132
backend/utils/firewall/client/iptables.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NatListRegex = regexp.MustCompile(`^(\d+)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?) .+?:(\d{1,5}(?::\d+)?).+?[ :](.+-.+|(?:.+:)?\d{1,5}(?:-\d{1,5})?)$`)
|
||||||
|
|
||||||
|
type Iptables struct {
|
||||||
|
CmdStr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIptables() (*Iptables, error) {
|
||||||
|
iptables := new(Iptables)
|
||||||
|
if cmd.HasNoPasswordSudo() {
|
||||||
|
iptables.CmdStr = "sudo"
|
||||||
|
}
|
||||||
|
|
||||||
|
return iptables, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iptables *Iptables) Check() error {
|
||||||
|
stdout, err := cmd.Exec("cat /proc/sys/net/ipv4/ip_forward")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stdout == "0" {
|
||||||
|
return fmt.Errorf("disable")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iptables *Iptables) NatList() ([]IptablesNatInfo, error) {
|
||||||
|
stdout, err := cmd.Execf("%s iptables -t nat -nL PREROUTING --line", iptables.CmdStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var forwardList []IptablesNatInfo
|
||||||
|
for _, line := range strings.Split(stdout, "\n") {
|
||||||
|
line = strings.TrimFunc(line, func(r rune) bool {
|
||||||
|
return r <= 32
|
||||||
|
})
|
||||||
|
if NatListRegex.MatchString(line) {
|
||||||
|
match := NatListRegex.FindStringSubmatch(line)
|
||||||
|
if !strings.Contains(match[9], ":") {
|
||||||
|
match[9] = fmt.Sprintf(":%s", match[9])
|
||||||
|
}
|
||||||
|
forwardList = append(forwardList, IptablesNatInfo{
|
||||||
|
Num: match[1],
|
||||||
|
Target: match[2],
|
||||||
|
Protocol: match[7],
|
||||||
|
Opt: match[4],
|
||||||
|
Source: match[5],
|
||||||
|
Destination: match[6],
|
||||||
|
SrcPort: match[8],
|
||||||
|
DestPort: match[9],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return forwardList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iptables *Iptables) NatAdd(protocol, src, destIp, destPort string, save bool) error {
|
||||||
|
rule := fmt.Sprintf("%s iptables -t nat -A PREROUTING -p %s --dport %s -j REDIRECT --to-port %s", iptables.CmdStr, protocol, src, destPort)
|
||||||
|
if destIp != "" && destIp != "127.0.0.1" && destIp != "localhost" {
|
||||||
|
rule = fmt.Sprintf("%s iptables -t nat -A PREROUTING -p %s --dport %s -j DNAT --to-destination %s:%s", iptables.CmdStr, protocol, src, destIp, destPort)
|
||||||
|
}
|
||||||
|
stdout, err := cmd.Exec(rule)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stdout != "" {
|
||||||
|
return errors.New(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if save {
|
||||||
|
return global.DB.Save(&model.Forward{
|
||||||
|
Protocol: protocol,
|
||||||
|
Port: src,
|
||||||
|
TargetIP: destIp,
|
||||||
|
TargetPort: destPort,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iptables *Iptables) NatRemove(num string, protocol, src, destIp, destPort string) error {
|
||||||
|
stdout, err := cmd.Execf("%s iptables -t nat -D PREROUTING %s", iptables.CmdStr, num)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stdout != "" {
|
||||||
|
return fmt.Errorf(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
global.DB.Where(
|
||||||
|
"protocol = ? AND port = ? AND target_ip = ? AND target_port = ?",
|
||||||
|
protocol,
|
||||||
|
src,
|
||||||
|
destIp,
|
||||||
|
destPort,
|
||||||
|
).Delete(&model.Forward{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iptables *Iptables) Reload() error {
|
||||||
|
stdout, err := cmd.Execf("%s iptables -t nat -F && %s iptables -t nat -X", iptables.CmdStr, iptables.CmdStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stdout != "" {
|
||||||
|
return fmt.Errorf(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rules []model.Forward
|
||||||
|
global.DB.Find(&rules)
|
||||||
|
for _, forward := range rules {
|
||||||
|
if err := iptables.NatAdd(forward.Protocol, forward.Port, forward.TargetIP, forward.TargetPort, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -103,6 +103,30 @@ func (f *Ufw) ListPort() ([]FireInfo, error) {
|
|||||||
return datas, nil
|
return datas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Ufw) ListForward() ([]FireInfo, error) {
|
||||||
|
iptables, err := NewIptables()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rules, err := iptables.NatList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var list []FireInfo
|
||||||
|
for _, rule := range rules {
|
||||||
|
dest := strings.SplitN(rule.DestPort, ":", 2)
|
||||||
|
list = append(list, FireInfo{
|
||||||
|
Num: rule.Num,
|
||||||
|
Protocol: rule.Protocol,
|
||||||
|
Port: rule.SrcPort,
|
||||||
|
TargetIP: dest[0],
|
||||||
|
TargetPort: dest[1],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Ufw) ListAddress() ([]FireInfo, error) {
|
func (f *Ufw) ListAddress() ([]FireInfo, error) {
|
||||||
stdout, err := cmd.Execf("%s status verbose", f.CmdStr)
|
stdout, err := cmd.Execf("%s status verbose", f.CmdStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -206,17 +230,18 @@ func (f *Ufw) RichRules(rule FireInfo, operation string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Ufw) PortForward(info Forward, operation string) error {
|
func (f *Ufw) PortForward(info Forward, operation string) error {
|
||||||
ruleStr := fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Target)
|
iptables, err := NewIptables()
|
||||||
if len(info.Address) != 0 {
|
if err != nil {
|
||||||
ruleStr = fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Address, info.Target)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout, err := cmd.Exec(ruleStr)
|
if operation == "add" {
|
||||||
if err != nil {
|
err = iptables.NatAdd(info.Protocol, info.Port, info.TargetIP, info.TargetPort, true)
|
||||||
return fmt.Errorf("%s port forward failed, err: %s", operation, stdout)
|
} else {
|
||||||
|
err = iptables.NatRemove(info.Num, info.Protocol, info.Port, info.TargetIP, info.TargetPort)
|
||||||
}
|
}
|
||||||
if err := f.Reload(); err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("%s port forward failed, err: %s", operation, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -258,3 +283,12 @@ func (f *Ufw) loadInfo(line string, fireType string) FireInfo {
|
|||||||
|
|
||||||
return itemInfo
|
return itemInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Ufw) EnableForward() error {
|
||||||
|
iptables, err := NewIptables()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return iptables.Reload()
|
||||||
|
}
|
||||||
|
@ -74,12 +74,17 @@ export namespace Host {
|
|||||||
export interface RuleInfo extends ReqPage {
|
export interface RuleInfo extends ReqPage {
|
||||||
family: string;
|
family: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
destination: string;
|
||||||
port: string;
|
port: string;
|
||||||
|
srcPort: string;
|
||||||
|
destPort: string;
|
||||||
protocol: string;
|
protocol: string;
|
||||||
strategy: string;
|
strategy: string;
|
||||||
|
|
||||||
usedStatus: string;
|
usedStatus: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export interface UpdateDescription {
|
export interface UpdateDescription {
|
||||||
address: string;
|
address: string;
|
||||||
@ -97,6 +102,13 @@ export namespace Host {
|
|||||||
strategy: string;
|
strategy: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
export interface RuleForward {
|
||||||
|
operation: string;
|
||||||
|
protocol: string;
|
||||||
|
port: string;
|
||||||
|
targetIP: string;
|
||||||
|
targetPort: string;
|
||||||
|
}
|
||||||
export interface RuleIP {
|
export interface RuleIP {
|
||||||
operation: string;
|
operation: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
@ -98,6 +98,9 @@ export const operateFire = (operation: string) => {
|
|||||||
export const operatePortRule = (params: Host.RulePort) => {
|
export const operatePortRule = (params: Host.RulePort) => {
|
||||||
return http.post<Host.RulePort>(`/hosts/firewall/port`, params, TimeoutEnum.T_40S);
|
return http.post<Host.RulePort>(`/hosts/firewall/port`, params, TimeoutEnum.T_40S);
|
||||||
};
|
};
|
||||||
|
export const operateForwardRule = (params: { rules: Host.RuleForward[] }) => {
|
||||||
|
return http.post<Host.RulePort>(`/hosts/firewall/forward`, params, TimeoutEnum.T_40S);
|
||||||
|
};
|
||||||
export const operateIPRule = (params: Host.RuleIP) => {
|
export const operateIPRule = (params: Host.RuleIP) => {
|
||||||
return http.post<Host.RuleIP>(`/hosts/firewall/ip`, params, TimeoutEnum.T_40S);
|
return http.post<Host.RuleIP>(`/hosts/firewall/ip`, params, TimeoutEnum.T_40S);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
|
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
|
||||||
|
|
||||||
let xpackEnLocale = {};
|
let xpackEnLocale = {};
|
||||||
const xpackModules = import.meta.glob('../../xpack/lang/en.ts', { eager: true });
|
const xpackModules = import.meta.glob('../../xpack/lang/en.ts', { eager: true });
|
||||||
if (xpackModules['../../xpack/lang/en.ts']) {
|
if (xpackModules['../../xpack/lang/en.ts']) {
|
||||||
@ -2204,8 +2205,14 @@ const message = {
|
|||||||
addressHelper2: 'For multiple IPs or IP ranges, separate with commas: 172.16.10.11, 172.16.0.0/24',
|
addressHelper2: 'For multiple IPs or IP ranges, separate with commas: 172.16.10.11, 172.16.0.0/24',
|
||||||
allIP: 'All IP',
|
allIP: 'All IP',
|
||||||
portRule: 'Port rule',
|
portRule: 'Port rule',
|
||||||
|
forwardRule: 'Forwarding',
|
||||||
ipRule: 'IP rule',
|
ipRule: 'IP rule',
|
||||||
userAgent: 'User-Agent filter',
|
userAgent: 'User-Agent filter',
|
||||||
|
sourcePort: 'Source Port',
|
||||||
|
targetIP: 'Destination IP',
|
||||||
|
targetPort: 'Destination Port',
|
||||||
|
forwardHelper1: 'In the case of local port forwarding, the destination IP is: 127.0.0.1',
|
||||||
|
forwardHelper2: 'If the destination IP is not filled in, it will be forwarded to the local port by default',
|
||||||
},
|
},
|
||||||
runtime: {
|
runtime: {
|
||||||
runtime: 'Runtime',
|
runtime: 'Runtime',
|
||||||
|
@ -2048,8 +2048,14 @@ const message = {
|
|||||||
addressHelper2: '多個 IP 或 IP 段 請用 "," 隔開:172.16.10.11,172.16.0.0/24',
|
addressHelper2: '多個 IP 或 IP 段 請用 "," 隔開:172.16.10.11,172.16.0.0/24',
|
||||||
allIP: '所有 IP',
|
allIP: '所有 IP',
|
||||||
portRule: '端口規則',
|
portRule: '端口規則',
|
||||||
|
forwardRule: '端口轉發',
|
||||||
ipRule: 'IP 規則',
|
ipRule: 'IP 規則',
|
||||||
userAgent: 'User-Agent 過濾',
|
userAgent: 'User-Agent 過濾',
|
||||||
|
sourcePort: '來源端口',
|
||||||
|
targetIP: '目標 IP',
|
||||||
|
targetPort: '目標端口',
|
||||||
|
forwardHelper1: '如果是本機端口轉發,目標 IP 為:127.0.0.1',
|
||||||
|
forwardHelper2: '如果目標 IP 不填寫,默認為本機端口轉發',
|
||||||
},
|
},
|
||||||
runtime: {
|
runtime: {
|
||||||
runtime: '運行環境',
|
runtime: '運行環境',
|
||||||
|
@ -92,6 +92,7 @@ const message = {
|
|||||||
user: '用户',
|
user: '用户',
|
||||||
title: '标题',
|
title: '标题',
|
||||||
port: '端口',
|
port: '端口',
|
||||||
|
forward: '转发',
|
||||||
protocol: '协议',
|
protocol: '协议',
|
||||||
tableSetting: '列表设置',
|
tableSetting: '列表设置',
|
||||||
refreshRate: '刷新频率',
|
refreshRate: '刷新频率',
|
||||||
@ -2049,8 +2050,15 @@ const message = {
|
|||||||
addressHelper2: '多个 IP 或 IP 段 请用 "," 隔开:172.16.10.11,172.16.0.0/24',
|
addressHelper2: '多个 IP 或 IP 段 请用 "," 隔开:172.16.10.11,172.16.0.0/24',
|
||||||
allIP: '所有 IP',
|
allIP: '所有 IP',
|
||||||
portRule: '端口规则',
|
portRule: '端口规则',
|
||||||
|
forwardRule: '端口转发',
|
||||||
ipRule: 'IP 规则',
|
ipRule: 'IP 规则',
|
||||||
userAgent: 'User-Agent 过滤',
|
userAgent: 'User-Agent 过滤',
|
||||||
|
destination: '目的地',
|
||||||
|
sourcePort: '源端口',
|
||||||
|
targetIP: '目标 IP',
|
||||||
|
targetPort: '目标端口',
|
||||||
|
forwardHelper1: '如果是本机端口转发,目标IP为:127.0.0.1',
|
||||||
|
forwardHelper2: '如果目标IP不填写,则默认为本机端口转发',
|
||||||
},
|
},
|
||||||
runtime: {
|
runtime: {
|
||||||
runtime: '运行环境',
|
runtime: '运行环境',
|
||||||
|
@ -60,6 +60,16 @@ const hostRouter = {
|
|||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/hosts/firewall/forward',
|
||||||
|
name: 'FirewallForward',
|
||||||
|
component: () => import('@/views/host/firewall/forward/index.vue'),
|
||||||
|
hidden: true,
|
||||||
|
meta: {
|
||||||
|
activeMenu: '/hosts/firewall/port',
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/hosts/firewall/ip',
|
path: '/hosts/firewall/ip',
|
||||||
name: 'FirewallIP',
|
name: 'FirewallIP',
|
||||||
|
@ -327,11 +327,7 @@ export function checkPort(value: string): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const reg = /^([1-9](\d{0,3}))$|^([1-5]\d{4})$|^(6[0-4]\d{3})$|^(65[0-4]\d{2})$|^(655[0-2]\d)$|^(6553[0-5])$/;
|
const reg = /^([1-9](\d{0,3}))$|^([1-5]\d{4})$|^(6[0-4]\d{3})$|^(65[0-4]\d{2})$|^(655[0-2]\d)$|^(6553[0-5])$/;
|
||||||
if (!reg.test(value) && value !== '') {
|
return !reg.test(value) && value !== '';
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProvider(provider: string): string {
|
export function getProvider(provider: string): string {
|
||||||
|
226
frontend/src/views/host/firewall/forward/index.vue
Normal file
226
frontend/src/views/host/firewall/forward/index.vue
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<FireRouter />
|
||||||
|
|
||||||
|
<div v-loading="loading">
|
||||||
|
<FireStatus
|
||||||
|
v-show="fireName !== '-'"
|
||||||
|
ref="fireStatusRef"
|
||||||
|
@search="search"
|
||||||
|
v-model:loading="loading"
|
||||||
|
v-model:mask-show="maskShow"
|
||||||
|
v-model:status="fireStatus"
|
||||||
|
v-model:name="fireName"
|
||||||
|
/>
|
||||||
|
<div v-if="fireName !== '-'">
|
||||||
|
<el-card v-if="fireStatus != 'running' && maskShow" class="mask-prompt">
|
||||||
|
<span>{{ $t('firewall.firewallNotStart') }}</span>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<LayoutContent :title="$t('firewall.forwardRule')" :class="{ mask: fireStatus != 'running' }">
|
||||||
|
<template #toolbar>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-button type="primary" @click="onOpenDialog('create')">
|
||||||
|
{{ $t('commons.button.create') }}{{ $t('firewall.forwardRule') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="onDelete(null)" plain :disabled="selects.length === 0">
|
||||||
|
{{ $t('commons.button.delete') }}
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<TableSetting @search="search()" />
|
||||||
|
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
<template #main>
|
||||||
|
<ComplexTable
|
||||||
|
:pagination-config="paginationConfig"
|
||||||
|
v-model:selects="selects"
|
||||||
|
@search="search"
|
||||||
|
:data="data"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" fix />
|
||||||
|
<el-table-column :label="$t('commons.table.protocol')" :min-width="70" prop="protocol" />
|
||||||
|
<el-table-column :label="$t('firewall.sourcePort')" :min-width="70" prop="port" />
|
||||||
|
<el-table-column :min-width="80" :label="$t('firewall.targetIP')" prop="targetIP">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.targetIP">{{ row.targetIP }}</span>
|
||||||
|
<span v-else>127.0.0.1</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('firewall.targetPort')" :min-width="70" prop="targetPort" />
|
||||||
|
<fu-table-operations
|
||||||
|
width="200px"
|
||||||
|
:buttons="buttons"
|
||||||
|
:ellipsis="10"
|
||||||
|
:label="$t('commons.table.operate')"
|
||||||
|
fix
|
||||||
|
/>
|
||||||
|
</ComplexTable>
|
||||||
|
</template>
|
||||||
|
</LayoutContent>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<LayoutContent :title="$t('firewall.firewall')" :divider="true">
|
||||||
|
<template #main>
|
||||||
|
<div class="app-warn">
|
||||||
|
<div>
|
||||||
|
<span>{{ $t('firewall.notSupport') }}</span>
|
||||||
|
<span @click="toDoc">
|
||||||
|
<el-icon class="ml-2"><Position /></el-icon>
|
||||||
|
{{ $t('firewall.quickJump') }}
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<img src="@/assets/images/no_app.svg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</LayoutContent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<OpDialog ref="opRef" @search="search" />
|
||||||
|
<OperateDialog @search="search" ref="dialogRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import FireRouter from '@/views/host/firewall/index.vue';
|
||||||
|
import OperateDialog from './operate/index.vue';
|
||||||
|
import FireStatus from '@/views/host/firewall/status/index.vue';
|
||||||
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
import { operateForwardRule, searchFireRule } from '@/api/modules/host';
|
||||||
|
import { Host } from '@/api/interface/host';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
|
||||||
|
const loading = ref();
|
||||||
|
const activeTag = ref('forward');
|
||||||
|
const selects = ref<any>([]);
|
||||||
|
const searchName = ref();
|
||||||
|
const searchStatus = ref('');
|
||||||
|
const searchStrategy = ref('');
|
||||||
|
|
||||||
|
const maskShow = ref(true);
|
||||||
|
const fireStatus = ref('running');
|
||||||
|
const fireName = ref();
|
||||||
|
const fireStatusRef = ref();
|
||||||
|
|
||||||
|
const opRef = ref();
|
||||||
|
|
||||||
|
const data = ref();
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
cacheSizeKey: 'firewall-forward-page-size',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
if (fireStatus.value !== 'running') {
|
||||||
|
loading.value = false;
|
||||||
|
data.value = [];
|
||||||
|
paginationConfig.total = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let params = {
|
||||||
|
type: activeTag.value,
|
||||||
|
status: searchStatus.value,
|
||||||
|
strategy: searchStrategy.value,
|
||||||
|
info: searchName.value,
|
||||||
|
page: paginationConfig.currentPage,
|
||||||
|
pageSize: paginationConfig.pageSize,
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
await searchFireRule(params)
|
||||||
|
.then((res) => {
|
||||||
|
loading.value = false;
|
||||||
|
data.value = res.data.items || [];
|
||||||
|
paginationConfig.total = res.data.total;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const dialogRef = ref();
|
||||||
|
const onOpenDialog = async (
|
||||||
|
title: string,
|
||||||
|
rowData: Partial<Host.RuleForward> = {
|
||||||
|
protocol: 'tcp',
|
||||||
|
port: '8080',
|
||||||
|
targetIP: '',
|
||||||
|
targetPort: '',
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let params = {
|
||||||
|
title,
|
||||||
|
rowData: { ...rowData },
|
||||||
|
};
|
||||||
|
dialogRef.value!.acceptParams(params);
|
||||||
|
};
|
||||||
|
const toDoc = () => {
|
||||||
|
window.open('https://1panel.cn/docs/user_manual/hosts/firewall/', '_blank', 'noopener,noreferrer');
|
||||||
|
};
|
||||||
|
const onDelete = async (row: Host.RuleForward | null) => {
|
||||||
|
let names = [];
|
||||||
|
let rules = [];
|
||||||
|
if (row) {
|
||||||
|
rules.push({
|
||||||
|
...row,
|
||||||
|
operation: 'remove',
|
||||||
|
});
|
||||||
|
names = [row.port + ' (' + row.protocol + ')'];
|
||||||
|
} else {
|
||||||
|
for (const item of selects.value) {
|
||||||
|
names.push(item.port + ' (' + item.protocol + ')');
|
||||||
|
rules.push({
|
||||||
|
...item,
|
||||||
|
operation: 'remove',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opRef.value.acceptParams({
|
||||||
|
title: i18n.global.t('commons.button.delete'),
|
||||||
|
names: names,
|
||||||
|
msg: i18n.global.t('commons.msg.operatorHelper', [
|
||||||
|
i18n.global.t('firewall.forwardRule'),
|
||||||
|
i18n.global.t('commons.button.delete'),
|
||||||
|
]),
|
||||||
|
api: operateForwardRule,
|
||||||
|
params: { rules: rules },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.edit'),
|
||||||
|
click: (row: Host.RuleForward) => {
|
||||||
|
onOpenDialog('edit', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.delete'),
|
||||||
|
click: (row: Host.RuleForward) => {
|
||||||
|
onDelete(row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (fireName.value !== '-') {
|
||||||
|
loading.value = true;
|
||||||
|
fireStatusRef.value.acceptParams();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.svg-icon {
|
||||||
|
font-size: 8px;
|
||||||
|
margin-bottom: -4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
176
frontend/src/views/host/firewall/forward/operate/index.vue
Normal file
176
frontend/src/views/host/firewall/forward/operate/index.vue
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
v-model="drawerVisible"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
size="50%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="title" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<div v-loading="loading">
|
||||||
|
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('commons.table.protocol')" prop="protocol">
|
||||||
|
<el-select style="width: 100%" v-model="dialogData.rowData!.protocol">
|
||||||
|
<el-option value="tcp" label="tcp" />
|
||||||
|
<el-option value="udp" label="udp" />
|
||||||
|
<el-option value="tcp/udp" label="tcp/udp" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('firewall.sourcePort')" prop="port">
|
||||||
|
<el-input clearable v-model.trim="dialogData.rowData!.port" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('firewall.targetIP')" prop="targetIP">
|
||||||
|
<el-input v-model.trim="dialogData.rowData!.targetIP" />
|
||||||
|
<span class="input-help">{{ $t('firewall.forwardHelper1') }}</span>
|
||||||
|
<span class="input-help">{{ $t('firewall.forwardHelper2') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="$t('firewall.targetPort')" prop="targetPort">
|
||||||
|
<el-input clearable v-model.trim="dialogData.rowData!.targetPort" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { ElForm, FormItemRule } from 'element-plus';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { Host } from '@/api/interface/host';
|
||||||
|
import { operateForwardRule } from '@/api/modules/host';
|
||||||
|
import { checkCidr, checkIpV4V6, deepCopy } from '@/utils/util';
|
||||||
|
|
||||||
|
const loading = ref();
|
||||||
|
const oldRule = ref<Host.RuleForward>();
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
title: string;
|
||||||
|
rowData?: Host.RuleForward;
|
||||||
|
getTableList?: () => Promise<any>;
|
||||||
|
}
|
||||||
|
const title = ref<string>('');
|
||||||
|
const drawerVisible = ref(false);
|
||||||
|
const dialogData = ref<DialogProps>({
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
const acceptParams = (params: DialogProps): void => {
|
||||||
|
dialogData.value = params;
|
||||||
|
if (dialogData.value.title === 'edit') {
|
||||||
|
oldRule.value = deepCopy(params.rowData);
|
||||||
|
}
|
||||||
|
title.value = i18n.global.t('firewall.' + dialogData.value.title);
|
||||||
|
drawerVisible.value = true;
|
||||||
|
};
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
drawerVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const strPortValidator: FormItemRule = {
|
||||||
|
required: true,
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: (_, value: string) => {
|
||||||
|
const port = parseInt(value);
|
||||||
|
return port >= 1 && port <= 65535;
|
||||||
|
},
|
||||||
|
message: i18n.global.t('commons.rule.port'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
protocol: [Rules.requiredSelect],
|
||||||
|
port: [Rules.requiredInput, strPortValidator],
|
||||||
|
targetPort: [Rules.requiredInput, strPortValidator],
|
||||||
|
targetIP: [{ validator: checkAddress, trigger: 'blur' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkAddress(rule: any, value: string, callback: any) {
|
||||||
|
if (!value) {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
let addrs = value.split(',');
|
||||||
|
for (const item of addrs) {
|
||||||
|
if (item.indexOf('/') !== -1) {
|
||||||
|
if (checkCidr(item)) {
|
||||||
|
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (checkIpV4V6(item)) {
|
||||||
|
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
const { rowData } = dialogData.value;
|
||||||
|
let rules = [];
|
||||||
|
if (!rowData) return;
|
||||||
|
rowData.operation = 'add';
|
||||||
|
if (rowData.targetIP === '') {
|
||||||
|
rowData.targetIP = '127.0.0.1';
|
||||||
|
}
|
||||||
|
rules.push(rowData);
|
||||||
|
loading.value = true;
|
||||||
|
if (dialogData.value.title === 'create') {
|
||||||
|
await operateForwardRule({ rules: rules })
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rules = [];
|
||||||
|
oldRule.value.operation = 'remove';
|
||||||
|
dialogData.value.rowData.operation = 'add';
|
||||||
|
rules.push(oldRule.value);
|
||||||
|
rules.push(dialogData.value.rowData);
|
||||||
|
await operateForwardRule({ rules: rules })
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
emit('search');
|
||||||
|
drawerVisible.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
@ -15,6 +15,10 @@ const buttons = [
|
|||||||
label: i18n.global.t('firewall.portRule'),
|
label: i18n.global.t('firewall.portRule'),
|
||||||
path: '/hosts/firewall/port',
|
path: '/hosts/firewall/port',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('firewall.forwardRule'),
|
||||||
|
path: '/hosts/firewall/forward',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('firewall.ipRule'),
|
label: i18n.global.t('firewall.ipRule'),
|
||||||
path: '/hosts/firewall/ip',
|
path: '/hosts/firewall/ip',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user