feat: 完成前端端口规则设置功能

This commit is contained in:
ssongliu 2023-03-27 19:02:36 +08:00 committed by ssongliu
parent a2fcdabb7b
commit d5f446d7cf
20 changed files with 847 additions and 132 deletions

View File

@ -26,9 +26,10 @@ var (
cronjobService = service.ServiceGroupApp.CronjobService
hostService = service.ServiceGroupApp.HostService
groupService = service.ServiceGroupApp.GroupService
fileService = service.ServiceGroupApp.FileService
hostService = service.ServiceGroupApp.HostService
groupService = service.ServiceGroupApp.GroupService
fileService = service.ServiceGroupApp.FileService
firewallService = service.NewIFirewallService()
settingService = service.ServiceGroupApp.SettingService
backupService = service.ServiceGroupApp.BackupService

View File

@ -0,0 +1,101 @@
package v1
import (
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/gin-gonic/gin"
)
// @Tags Firewall
// @Summary Page firewall rules
// @Description 获取防火墙规则列表分页
// @Accept json
// @Param request body dto.SearchWithPage true "request"
// @Success 200 {object} dto.PageResult
// @Security ApiKeyAuth
// @Router /hosts/firewall/search [post]
func (b *BaseApi) SearchFirewallRule(c *gin.Context) {
var req dto.RuleSearch
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
total, list, err := firewallService.SearchWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}
// @Tags Firewall
// @Summary Create group
// @Description 创建防火墙端口规则
// @Accept json
// @Param request body dto.PortRuleOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/port [post]
// @x-panel-log {"bodyKeys":["port","strategy"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"添加端口规则 {[strategy] [port]}","formatEN":"create port rules {[strategy][port]}"}
func (b *BaseApi) OperatePortRule(c *gin.Context) {
var req dto.PortRuleOperate
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 req.Protocol == "tcp/udp" {
req.Protocol = "tcp"
if err := firewallService.OperatePortRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
req.Protocol = "udp"
if err := firewallService.OperatePortRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
if err := firewallService.OperatePortRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Firewall
// @Summary Create group
// @Description 创建防火墙 IP 规则
// @Accept json
// @Param request body dto.AddressCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /hosts/firewall/ip [post]
// @x-panel-log {"bodyKeys":["strategy","address"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"添加 ip 规则 {[strategy] [address]}","formatEN":"create address rules {[strategy][address]}"}
func (b *BaseApi) OperateIPRule(c *gin.Context) {
var req dto.AddrRuleOperate
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.OperateAddressRule(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

View File

@ -0,0 +1,20 @@
package dto
type RuleSearch struct {
PageInfo
Type string `json:"type" validate:"required"`
}
type PortRuleOperate struct {
Operation string `json:"operation" validate:"required,oneof=add remove"`
Address string `json:"address"`
Port string `json:"port" validate:"required"`
Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/upd"`
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
}
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"`
}

View File

@ -24,6 +24,7 @@ type ServiceGroup struct {
GroupService
CommandService
FileService
FirewallService
SettingService
BackupService

View File

@ -0,0 +1,85 @@
package service
import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/utils/firewall"
fireClient "github.com/1Panel-dev/1Panel/backend/utils/firewall/client"
"github.com/jinzhu/copier"
)
type FirewallService struct{}
type IFirewallService interface {
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
OperatePortRule(req dto.PortRuleOperate) error
OperateAddressRule(req dto.AddrRuleOperate) error
}
func NewIFirewallService() IFirewallService {
return &FirewallService{}
}
func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}, error) {
var (
datas []fireClient.FireInfo
backDatas []fireClient.FireInfo
)
client, err := firewall.NewFirewallClient()
if err != nil {
return 0, nil, err
}
if req.Type == "port" {
ports, err := client.ListPort()
if err != nil {
return 0, nil, err
}
datas = ports
} else {
address, err := client.ListAddress()
if err != nil {
return 0, nil, err
}
datas = address
}
total, start, end := len(datas), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
backDatas = make([]fireClient.FireInfo, 0)
} else {
if end >= total {
end = total
}
backDatas = datas[start:end]
}
return int64(total), backDatas, nil
}
func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
var fireInfo fireClient.FireInfo
if err := copier.Copy(&fireInfo, &req); err != nil {
return err
}
if len(fireInfo.Address) != 0 || fireInfo.Strategy == "drop" {
return client.RichRules(fireInfo, req.Operation)
}
return client.Port(fireInfo, req.Operation)
}
func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate) error {
client, err := firewall.NewFirewallClient()
if err != nil {
return err
}
var fireInfo fireClient.FireInfo
if err := copier.Copy(&fireInfo, &req); err != nil {
return err
}
return client.RichRules(fireInfo, req.Operation)
}

View File

@ -26,6 +26,10 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) {
hostRouter.POST("/test/byid/:id", baseApi.TestByID)
hostRouter.GET(":id", baseApi.GetHostInfo)
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
hostRouter.POST("/firewall/port", baseApi.OperatePortRule)
hostRouter.POST("/firewall/ip", baseApi.OperateIPRule)
hostRouter.GET("/command", baseApi.ListCommand)
hostRouter.POST("/command", baseApi.CreateCommand)
hostRouter.POST("/command/del", baseApi.DeleteCommand)

View File

@ -1,9 +1,6 @@
package firewall
import (
"errors"
"os"
"github.com/1Panel-dev/1Panel/backend/utils/firewall/client"
)
@ -13,7 +10,7 @@ type FirewallClient interface {
Reload() error
Status() (string, error)
ListPort() ([]client.FireInfo, error)
ListRichRules() ([]client.FireInfo, error)
ListAddress() ([]client.FireInfo, error)
Port(port client.FireInfo, operation string) error
RichRules(rule client.FireInfo, operation string) error
@ -21,11 +18,11 @@ type FirewallClient interface {
}
func NewFirewallClient() (FirewallClient, error) {
if _, err := os.Stat("/usr/sbin/firewalld"); err == nil {
return client.NewFirewalld()
}
// if _, err := os.Stat("/usr/sbin/firewalld"); err == nil {
return client.NewFirewalld()
// }
// if _, err := os.Stat("/usr/sbin/ufw"); err == nil {
// return client.NewUfw()
// }
return nil, errors.New("no such type")
// return nil, errors.New("no such type")
}

View File

@ -4,17 +4,25 @@ import (
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/ssh"
)
type Firewall struct{}
type Firewall struct {
Client ssh.ConnInfo
}
func NewFirewalld() (*Firewall, error) {
return &Firewall{}, nil
ConnInfo := ssh.ConnInfo{
Addr: "172.16.10.143",
User: "root",
AuthMode: "password",
Port: 22,
}
return &Firewall{Client: ConnInfo}, nil
}
func (f *Firewall) Status() (string, error) {
stdout, err := cmd.Exec("firewall-cmd --state")
stdout, err := f.Client.Run("firewall-cmd --state")
if err != nil {
return "", fmt.Errorf("load the firewall status failed, err: %s", stdout)
}
@ -22,7 +30,7 @@ func (f *Firewall) Status() (string, error) {
}
func (f *Firewall) Start() error {
stdout, err := cmd.Exec("systemctl start firewalld")
stdout, err := f.Client.Run("systemctl start firewalld")
if err != nil {
return fmt.Errorf("enable the firewall failed, err: %s", stdout)
}
@ -30,7 +38,7 @@ func (f *Firewall) Start() error {
}
func (f *Firewall) Stop() error {
stdout, err := cmd.Exec("systemctl stop firewalld")
stdout, err := f.Client.Run("systemctl stop firewalld")
if err != nil {
return fmt.Errorf("stop the firewall failed, err: %s", stdout)
}
@ -38,7 +46,7 @@ func (f *Firewall) Stop() error {
}
func (f *Firewall) Reload() error {
stdout, err := cmd.Exec("firewall-cmd --reload")
stdout, err := f.Client.Run("firewall-cmd --reload")
if err != nil {
return fmt.Errorf("reload firewall failed, err: %s", stdout)
}
@ -46,7 +54,7 @@ func (f *Firewall) Reload() error {
}
func (f *Firewall) ListPort() ([]FireInfo, error) {
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-ports")
stdout, err := f.Client.Run("firewall-cmd --zone=public --list-ports")
if err != nil {
return nil, err
}
@ -58,13 +66,29 @@ func (f *Firewall) ListPort() ([]FireInfo, error) {
itemPort.Port = strings.Split(port, "/")[0]
itemPort.Protocol = strings.Split(port, "/")[1]
}
itemPort.Strategy = "accept"
datas = append(datas, itemPort)
}
stdout1, err := f.Client.Run("firewall-cmd --zone=public --list-rich-rules")
if err != nil {
return nil, err
}
rules := strings.Split(stdout1, "\n")
for _, rule := range rules {
if len(rule) == 0 {
continue
}
itemRule := f.loadInfo(rule)
if len(itemRule.Port) != 0 {
datas = append(datas, itemRule)
}
}
return datas, nil
}
func (f *Firewall) ListRichRules() ([]FireInfo, error) {
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules")
func (f *Firewall) ListAddress() ([]FireInfo, error) {
stdout, err := f.Client.Run("firewall-cmd --zone=public --list-rich-rules")
if err != nil {
return nil, err
}
@ -74,29 +98,16 @@ func (f *Firewall) ListRichRules() ([]FireInfo, error) {
if len(rule) == 0 {
continue
}
var itemRule FireInfo
ruleInfo := strings.Split(strings.ReplaceAll(rule, "\"", ""), " ")
for _, item := range ruleInfo {
switch {
case strings.Contains(item, "family="):
itemRule.Family = strings.ReplaceAll(item, "family=", "")
case strings.Contains(item, "address="):
itemRule.Address = strings.ReplaceAll(item, "address=", "")
case strings.Contains(item, "port="):
itemRule.Port = strings.ReplaceAll(item, "port=", "")
case strings.Contains(item, "protocol="):
itemRule.Protocol = strings.ReplaceAll(item, "protocol=", "")
case item == "accept" || item == "drop":
itemRule.Strategy = item
}
itemRule := f.loadInfo(rule)
if len(itemRule.Port) == 0 {
datas = append(datas, itemRule)
}
datas = append(datas, itemRule)
}
return datas, nil
}
func (f *Firewall) Port(port FireInfo, operation string) error {
stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-port=%s/%s --permanent", operation, port.Port, port.Protocol)
stdout, err := f.Client.Run(fmt.Sprintf("firewall-cmd --zone=public --%s-port=%s/%s --permanent", operation, port.Port, port.Protocol))
if err != nil {
return fmt.Errorf("%s port failed, err: %s", operation, stdout)
}
@ -119,7 +130,7 @@ func (f *Firewall) RichRules(rule FireInfo, operation string) error {
}
ruleStr += rule.Strategy
stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, ruleStr)
stdout, err := f.Client.Run(fmt.Sprintf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, ruleStr))
if err != nil {
return fmt.Errorf("%s rich rules failed, err: %s", operation, stdout)
}
@ -135,7 +146,7 @@ func (f *Firewall) PortForward(info Forward, operation string) error {
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)
}
stdout, err := cmd.Exec(ruleStr)
stdout, err := f.Client.Run(ruleStr)
if err != nil {
return fmt.Errorf("%s port forward failed, err: %s", operation, stdout)
}
@ -144,3 +155,23 @@ func (f *Firewall) PortForward(info Forward, operation string) error {
}
return nil
}
func (f *Firewall) loadInfo(line string) FireInfo {
var itemRule FireInfo
ruleInfo := strings.Split(strings.ReplaceAll(line, "\"", ""), " ")
for _, item := range ruleInfo {
switch {
case strings.Contains(item, "family="):
itemRule.Family = strings.ReplaceAll(item, "family=", "")
case strings.Contains(item, "address="):
itemRule.Address = strings.ReplaceAll(item, "address=", "")
case strings.Contains(item, "port="):
itemRule.Port = strings.ReplaceAll(item, "port=", "")
case strings.Contains(item, "protocol="):
itemRule.Protocol = strings.ReplaceAll(item, "protocol=", "")
case item == "accept" || item == "drop":
itemRule.Strategy = item
}
}
return itemRule
}

View File

@ -1,7 +1,6 @@
package client
import (
"encoding/json"
"fmt"
"strings"
"testing"
@ -11,41 +10,28 @@ import (
func TestFire(t *testing.T) {
ConnInfo := ssh.ConnInfo{
Addr: "172.16.10.234",
User: "ubuntu",
AuthMode: "password",
Port: 22,
}
output, err := ConnInfo.Run("sudo ufw status numbered")
output, err := ConnInfo.Run("sudo ufw status verbose")
if err != nil {
fmt.Println(err)
}
lines := strings.Split(string(output), "\n")
var rules []UfwRule
var datas []FireInfo
isStart := false
for _, line := range lines {
if line == "" || !strings.HasPrefix(line, "[") {
if strings.HasPrefix(line, "--") {
isStart = true
continue
}
if !isStart {
continue
}
fields := strings.Fields(line)
rule := UfwRule{Status: fields[0], From: fields[1], To: fields[2], Proto: fields[3], Comment: strings.Join(fields[4:], " ")}
rules = append(rules, rule)
}
ufwStatus := UfwStatus{Rules: rules}
ufwStatusJSON, err := json.MarshalIndent(ufwStatus, "", " ")
if err != nil {
fmt.Println("Error:", err)
}
fmt.Println(string(ufwStatusJSON))
}
type UfwRule struct {
Status string `json:"status"`
From string `json:"from"`
To string `json:"to"`
Proto string `json:"proto"`
Comment string `json:"comment"`
}
type UfwStatus struct {
Rules []UfwRule `json:"rules"`
}
fmt.Println(datas)
}

View File

@ -1,16 +1,16 @@
package client
type FireInfo struct {
Family string
Address string
Port string
Protocol string
Strategy string
Family string `json:"family"` // ipv4 ipv6
Address string `json:"address"` // Anywhere
Port string `json:"port"`
Protocol string `json:"protocol"` // tcp udp tcp/upd
Strategy string `json:"strategy"` // accept drop
}
type Forward struct {
Protocol string
Address string
Port string
Target string
Protocol string `json:"protocol"`
Address string `json:"address"`
Port string `json:"port"`
Target string `json:"target"`
}

View File

@ -4,17 +4,25 @@ import (
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/ssh"
)
type Ufw struct{}
type Ufw struct {
Client ssh.ConnInfo
}
func NewUfw() (*Ufw, error) {
return &Ufw{}, nil
ConnInfo := ssh.ConnInfo{
Addr: "172.16.10.234",
User: "ubuntu",
AuthMode: "password",
Port: 22,
}
return &Ufw{Client: ConnInfo}, nil
}
func (f *Ufw) Status() (string, error) {
stdout, err := cmd.Exec("sudo ufw status")
stdout, err := f.Client.Run("sudo ufw status")
if err != nil {
return "", fmt.Errorf("load the firewall status failed, err: %s", stdout)
}
@ -25,7 +33,7 @@ func (f *Ufw) Status() (string, error) {
}
func (f *Ufw) Start() error {
stdout, err := cmd.Exec("sudo ufw enable")
stdout, err := f.Client.Run("sudo ufw enable")
if err != nil {
return fmt.Errorf("enable the firewall failed, err: %s", stdout)
}
@ -33,7 +41,7 @@ func (f *Ufw) Start() error {
}
func (f *Ufw) Stop() error {
stdout, err := cmd.Exec("sudo ufw disable")
stdout, err := f.Client.Run("sudo ufw disable")
if err != nil {
return fmt.Errorf("stop the firewall failed, err: %s", stdout)
}
@ -41,7 +49,7 @@ func (f *Ufw) Stop() error {
}
func (f *Ufw) Reload() error {
stdout, err := cmd.Exec("sudo ufw reload")
stdout, err := f.Client.Run("sudo ufw reload")
if err != nil {
return fmt.Errorf("reload firewall failed, err: %s", stdout)
}
@ -49,51 +57,49 @@ func (f *Ufw) Reload() error {
}
func (f *Ufw) ListPort() ([]FireInfo, error) {
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-ports")
stdout, err := f.Client.Run("sudo ufw status verbose")
if err != nil {
return nil, err
}
ports := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ")
portInfos := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ")
var datas []FireInfo
for _, port := range ports {
var itemPort FireInfo
if strings.Contains(port, "/") {
itemPort.Port = strings.Split(port, "/")[0]
itemPort.Protocol = strings.Split(port, "/")[1]
isStart := false
for _, line := range portInfos {
if strings.HasPrefix(line, "--") {
isStart = true
continue
}
if !isStart {
continue
}
itemFire := f.loadInfo(line, "port")
if len(itemFire.Address) != 0 {
datas = append(datas, itemFire)
}
datas = append(datas, itemPort)
}
return datas, nil
}
func (f *Ufw) ListRichRules() ([]FireInfo, error) {
stdout, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules")
func (f *Ufw) ListAddress() ([]FireInfo, error) {
stdout, err := f.Client.Run("sudo ufw status verbose")
if err != nil {
return nil, err
}
portInfos := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ")
var datas []FireInfo
rules := strings.Split(stdout, "\n")
for _, rule := range rules {
if len(rule) == 0 {
isStart := false
for _, line := range portInfos {
if strings.HasPrefix(line, "--") {
isStart = true
continue
}
var itemRule FireInfo
ruleInfo := strings.Split(strings.ReplaceAll(rule, "\"", ""), " ")
for _, item := range ruleInfo {
switch {
case strings.Contains(item, "family="):
itemRule.Family = strings.ReplaceAll(item, "family=", "")
case strings.Contains(item, "address="):
itemRule.Address = strings.ReplaceAll(item, "address=", "")
case strings.Contains(item, "port="):
itemRule.Port = strings.ReplaceAll(item, "port=", "")
case strings.Contains(item, "protocol="):
itemRule.Protocol = strings.ReplaceAll(item, "protocol=", "")
case item == "accept" || item == "drop":
itemRule.Strategy = item
}
if !isStart {
continue
}
itemFire := f.loadInfo(line, "address")
if len(itemFire.Address) != 0 {
datas = append(datas, itemFire)
}
datas = append(datas, itemRule)
}
return datas, nil
}
@ -108,11 +114,11 @@ func (f *Ufw) Port(port FireInfo, operation string) error {
return fmt.Errorf("unsupport operation %s", operation)
}
command := fmt.Sprintf("ufw %s %s", operation, port.Port)
command := fmt.Sprintf("sudo ufw %s %s", operation, port.Port)
if len(port.Protocol) != 0 {
command += fmt.Sprintf("/%s", port.Protocol)
}
stdout, err := cmd.Exec(command)
stdout, err := f.Client.Run(command)
if err != nil {
return fmt.Errorf("%s port failed, err: %s", operation, stdout)
}
@ -120,25 +126,21 @@ func (f *Ufw) Port(port FireInfo, operation string) error {
}
func (f *Ufw) RichRules(rule FireInfo, operation string) error {
ruleStr := "rule family=ipv4 "
ruleStr := "sudo ufw "
if len(rule.Protocol) != 0 {
ruleStr += fmt.Sprintf("proto %s ", rule.Protocol)
}
if len(rule.Address) != 0 {
ruleStr += fmt.Sprintf("source address=%s ", rule.Address)
ruleStr += fmt.Sprintf("from %s ", rule.Address)
}
if len(rule.Port) != 0 {
ruleStr += fmt.Sprintf("port port=%s ", rule.Port)
ruleStr += fmt.Sprintf("to any port %s ", rule.Port)
}
if len(rule.Protocol) != 0 {
ruleStr += fmt.Sprintf("protocol=%s ", rule.Protocol)
}
ruleStr += rule.Strategy
stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, ruleStr)
stdout, err := f.Client.Run(ruleStr)
if err != nil {
return fmt.Errorf("%s rich rules failed, err: %s", operation, stdout)
}
if err := f.Reload(); err != nil {
return err
}
return nil
}
@ -148,7 +150,7 @@ func (f *Ufw) PortForward(info Forward, operation string) error {
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)
}
stdout, err := cmd.Exec(ruleStr)
stdout, err := f.Client.Run(ruleStr)
if err != nil {
return fmt.Errorf("%s port forward failed, err: %s", operation, stdout)
}
@ -157,3 +159,49 @@ func (f *Ufw) PortForward(info Forward, operation string) error {
}
return nil
}
func (f *Ufw) loadInfo(line string, fireType string) FireInfo {
fields := strings.Fields(line)
var itemInfo FireInfo
if len(fields) < 4 {
return itemInfo
}
if fields[0] == "Anywhere" && fireType == "port" {
itemInfo.Strategy = "drop"
if fields[2] == "ALLOW" {
itemInfo.Strategy = "accept"
}
itemInfo.Address = fields[3]
return itemInfo
}
if strings.Contains(fields[0], "/") {
itemInfo.Port = strings.Split(fields[0], "/")[0]
itemInfo.Protocol = strings.Split(fields[0], "/")[1]
} else {
itemInfo.Port = fields[0]
itemInfo.Protocol = "tcp/udp"
}
if fields[1] == "(v6)" {
if len(fields) < 5 {
return itemInfo
}
itemInfo.Family = "ipv6"
if fields[2] == "ALLOW" {
itemInfo.Strategy = "accept"
} else {
itemInfo.Strategy = "drop"
}
itemInfo.Address = fields[4]
} else {
itemInfo.Family = "ipv4"
if fields[1] == "ALLOW" {
itemInfo.Strategy = "accept"
} else {
itemInfo.Strategy = "drop"
}
itemInfo.Address = fields[3]
}
return itemInfo
}

View File

@ -52,4 +52,29 @@ export namespace Host {
groupID: number;
info?: string;
}
export interface RuleSearch extends ReqPage {
info: string;
type: string;
}
export interface RuleInfo extends ReqPage {
family: string;
address: string;
port: string;
protocol: string;
strategy: string;
}
export interface RulePort {
operation: string;
address: string;
port: string;
source: string;
protocol: string;
strategy: string;
}
export interface RuleIP {
operation: string;
address: string;
strategy: string;
}
}

View File

@ -70,3 +70,14 @@ export const editCommand = (params: Command.CommandOperate) => {
export const deleteCommand = (params: { ids: number[] }) => {
return http.post(`/hosts/command/del`, params);
};
// firewall
export const searchFireRule = (params: Host.RuleSearch) => {
return http.post<ResPage<Host.RuleInfo>>(`/hosts/firewall/search`, params);
};
export const operatePortRule = (params: Host.RulePort) => {
return http.post<Host.RulePort>(`/hosts/firewall/port`, params);
};
export const operateIPRule = (params: Host.RuleIP) => {
return http.post<Host.RuleIP>(`/hosts/firewall/ip`, params);
};

View File

@ -215,7 +215,6 @@ const message = {
container: '容器',
cronjob: '计划任务',
host: '主机',
security: '安全',
files: '文件',
monitor: '监控',
terminal: '终端',
@ -1181,6 +1180,19 @@ const message = {
argsCheck: 'GET 参数校验',
postCheck: 'POST 参数校验',
cookieBlockList: 'Cookie 黑名单',
firewall: '防火墙',
protocol: '协议',
port: '端口',
strategy: '策略',
accept: '允许',
drop: '拒绝',
source: '来源',
anyWhere: '所有 IP',
address: '指定 IP',
allIP: '所有 IP',
portRule: '端口规则',
ipRule: 'IP 规则',
},
};
export default {

View File

@ -29,6 +29,25 @@ const hostRouter = {
requiresAuth: false,
},
},
{
path: '/hosts/firewall/port',
name: 'FirewallPort',
component: () => import('@/views/host/firewall/port/index.vue'),
meta: {
title: 'menu.firewall',
requiresAuth: false,
},
},
{
path: '/hosts/firewall/ip',
name: 'FirewallIP',
component: () => import('@/views/host/firewall/ip/index.vue'),
hidden: true,
meta: {
title: 'menu.toolbox',
requiresAuth: false,
},
},
{
path: '/hosts/terminal',
name: 'Terminal',

View File

@ -0,0 +1,25 @@
<template>
<div>
<RouterButton :buttons="buttons" />
<LayoutContent>
<router-view></router-view>
</LayoutContent>
</div>
</template>
<script lang="ts" setup>
import i18n from '@/lang';
import LayoutContent from '@/layout/layout-content.vue';
import RouterButton from '@/components/router-button/index.vue';
const buttons = [
{
label: i18n.global.t('firewall.portRule'),
path: '/hosts/firewall/port',
},
{
label: i18n.global.t('firewall.ipRule'),
path: '/hosts/firewall/ip',
},
];
</script>

View File

@ -0,0 +1,101 @@
<template>
<div>
<FireRouter />
<LayoutContent v-loading="loading" :title="$t('firewall.firewall')">
<template #toolbar>
<el-row>
<el-col :span="20">
<el-button
class="tag-button"
:class="activeTag === 'port' ? '' : 'no-active'"
@click="changeTag('port')"
:type="activeTag === 'port' ? 'primary' : ''"
:plain="activeTag !== 'port'"
>
{{ $t('firewall.portRule') }}
</el-button>
<el-button
class="tag-button"
:class="activeTag === 'ip' ? '' : 'no-active'"
@click="changeTag('ip')"
:type="activeTag === 'ip' ? 'primary' : ''"
:plain="activeTag !== 'ip'"
>
{{ $t('firewall.ipRule') }}
</el-button>
</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('firewall.protocol')" :min-width="90" prop="protocol" />
<el-table-column :label="$t('firewall.port')" :min-width="120" prop="port" />
<el-table-column :min-width="80" :label="$t('firewall.strategy')" prop="strategy">
<template #default="{ row }">
<el-tag v-if="row.strategy === 'accept'" type="success">{{ $t('firewall.accept') }}</el-tag>
<el-tag v-if="row.strategy === 'drop'" type="danger">{{ $t('firewall.drop') }}</el-tag>
</template>
</el-table-column>
<el-table-column :min-width="80" :label="$t('firewall.address')" prop="address">
<template #default="{ row }">
<span v-if="row.address && row.address !== 'Anywhere'">{{ row.address }}</span>
<span v-else>{{ $t('firewall.allIP') }}</span>
</template>
</el-table-column>
</ComplexTable>
</template>
</LayoutContent>
</div>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import FireRouter from '@/views/host/firewall/index.vue';
import LayoutContent from '@/layout/layout-content.vue';
import { onMounted, reactive, ref } from 'vue';
import { searchFireRule } from '@/api/modules/host';
const loading = ref();
const activeTag = ref('port');
const selects = ref<any>([]);
const data = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const search = async () => {
let params = {
type: activeTag.value,
info: '',
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 changeTag = async (type: string) => {
activeTag.value = type;
};
onMounted(() => {
search();
});
</script>

View File

@ -0,0 +1,114 @@
<template>
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header>
<DrawerHeader :header="$t('firewall.portRule')" :back="handleClose" />
</template>
<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('firewall.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.port')" prop="port">
<el-input clearable v-model.trim="dialogData.rowData!.port" />
</el-form-item>
<el-form-item :label="$t('firewall.source')" prop="source">
<el-radio-group v-model="dialogData.rowData!.source">
<el-radio label="anyWhere">{{ $t('firewall.anyWhere') }}</el-radio>
<el-radio label="address">{{ $t('firewall.address') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
:label="$t('firewall.address')"
v-if="dialogData.rowData!.source === 'address'"
prop="address"
>
<el-input v-model="dialogData.rowData!.address" />
</el-form-item>
<el-form-item :label="$t('firewall.strategy')" prop="strategy">
<el-radio-group v-model="dialogData.rowData!.strategy">
<el-radio label="accept">{{ $t('firewall.accept') }}</el-radio>
<el-radio label="drop">{{ $t('firewall.drop') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisiable = 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 } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgSuccess } from '@/utils/message';
import { Host } from '@/api/interface/host';
import { operatePortRule } from '@/api/modules/host';
interface DialogProps {
title: string;
rowData?: Host.RulePort;
getTableList?: () => Promise<any>;
}
const title = ref<string>('');
const drawerVisiable = ref(false);
const dialogData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
drawerVisiable.value = true;
};
const emit = defineEmits<{ (e: 'search'): void }>();
const handleClose = () => {
drawerVisiable.value = false;
};
const rules = reactive({
protocol: [Rules.requiredSelect],
port: [Rules.requiredInput],
address: [Rules.requiredInput],
});
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;
if (!dialogData.value.rowData) return;
if (dialogData.value.title === 'create') {
await operatePortRule(dialogData.value.rowData);
}
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisiable.value = false;
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -0,0 +1,141 @@
<template>
<div>
<FireRouter />
<LayoutContent v-loading="loading" :title="$t('firewall.firewall')">
<template #toolbar>
<el-row>
<el-col :span="20">
<el-button type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}{{ $t('firewall.portRule') }}
</el-button>
</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('firewall.protocol')" :min-width="90" prop="protocol" />
<el-table-column :label="$t('firewall.port')" :min-width="120" prop="port" />
<el-table-column :min-width="80" :label="$t('firewall.strategy')" prop="strategy">
<template #default="{ row }">
<el-tag v-if="row.strategy === 'accept'" type="success">{{ $t('firewall.accept') }}</el-tag>
<el-tag v-if="row.strategy === 'drop'" type="danger">{{ $t('firewall.drop') }}</el-tag>
</template>
</el-table-column>
<el-table-column :min-width="80" :label="$t('firewall.address')" prop="address">
<template #default="{ row }">
<span v-if="row.address && row.address !== 'Anywhere'">{{ row.address }}</span>
<span v-else>{{ $t('firewall.allIP') }}</span>
</template>
</el-table-column>
<fu-table-operations
width="200px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</template>
</LayoutContent>
<OperatrDialog @search="search" ref="dialogRef" />
</div>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import FireRouter from '@/views/host/firewall/index.vue';
import OperatrDialog from '@/views/host/firewall/port/create/index.vue';
import LayoutContent from '@/layout/layout-content.vue';
import { onMounted, reactive, ref } from 'vue';
import { operatePortRule, searchFireRule } from '@/api/modules/host';
import { Host } from '@/api/interface/host';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { ElMessageBox } from 'element-plus';
const loading = ref();
const activeTag = ref('port');
const selects = ref<any>([]);
const data = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const search = async () => {
let params = {
type: activeTag.value,
info: '',
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.RulePort> = {
protocol: 'tcp',
source: 'anyWhere',
strategy: 'accept',
},
) => {
let params = {
title,
rowData: { ...rowData },
};
dialogRef.value!.acceptParams(params);
};
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'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'warning',
}).then(async () => {
let params = {
operation: 'remove',
address: row.address,
port: row.port,
source: '',
protocol: row.protocol,
strategy: row.strategy,
};
await operatePortRule(params);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
});
};
const buttons = [
{
label: i18n.global.t('commons.button.delete'),
click: (row: Host.RuleInfo) => {
onDelete(row);
},
},
];
onMounted(() => {
search();
});
</script>

View File

@ -1,7 +0,0 @@
<template>
<LayoutContent></LayoutContent>
</template>
<script lang="ts" setup>
import LayoutContent from '@/layout/layout-content.vue';
</script>