mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
feat: support iptables firewall control (#10903)
This commit is contained in:
@@ -38,6 +38,7 @@ var (
|
||||
fileService = service.NewIFileService()
|
||||
sshService = service.NewISSHService()
|
||||
firewallService = service.NewIFirewallService()
|
||||
iptablesService = service.NewIIptablesService()
|
||||
monitorService = service.NewIMonitorService()
|
||||
systemService = service.NewISystemService()
|
||||
|
||||
|
||||
@@ -8,12 +8,19 @@ import (
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Load firewall base info
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200 {object} dto.FirewallBaseInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/base [get]
|
||||
// @Router /hosts/firewall/base [post]
|
||||
func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) {
|
||||
data, err := firewallService.LoadBaseInfo()
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := firewallService.LoadBaseInfo(req.Name)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
@@ -221,3 +228,112 @@ func (b *BaseApi) UpdateAddrRule(c *gin.Context) {
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary search iptables filter rules
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchPageWithType true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/search [post]
|
||||
func (b *BaseApi) SearchFilterRules(c *gin.Context) {
|
||||
var req dto.SearchPageWithType
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := iptablesService.Search(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Operate iptables filter rule
|
||||
// @Accept json
|
||||
// @Param request body dto.IptablesRuleOp true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/rule/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation","chain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] filter规则到 [chain]","formatEN":"[operation] filter rule to [chain]"}
|
||||
func (b *BaseApi) OperateFilterRule(c *gin.Context) {
|
||||
var req dto.IptablesRuleOp
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := iptablesService.OperateRule(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Batch operate iptables filter rules
|
||||
// @Accept json
|
||||
// @Param request body dto.IptablesBatchOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/rule/batch [post]
|
||||
func (b *BaseApi) BatchOperateFilterRule(c *gin.Context) {
|
||||
var req dto.IptablesBatchOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := iptablesService.BatchOperate(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary Apply/Unload/Init iptables filter
|
||||
// @Accept json
|
||||
// @Param request body dto.IptablesOp true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] iptables filter 防火墙","formatEN":"[operate] iptables filter firewall"}
|
||||
func (b *BaseApi) OperateFilterChain(c *gin.Context) {
|
||||
var req dto.IptablesOp
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := iptablesService.Operate(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Firewall
|
||||
// @Summary load chain status with name
|
||||
// @Accept json
|
||||
// @Param request body dto.OperationWithName true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /hosts/firewall/filter/chain/status [post]
|
||||
func (b *BaseApi) LoadChainStatus(c *gin.Context) {
|
||||
var req dto.OperationWithName
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, iptablesService.LoadChainStatus(req))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ type FirewallBaseInfo struct {
|
||||
Name string `json:"name"`
|
||||
IsExist bool `json:"isExist"`
|
||||
IsActive bool `json:"isActive"`
|
||||
IsInit bool `json:"isInit"`
|
||||
IsBind bool `json:"isBind"`
|
||||
Version string `json:"version"`
|
||||
PingStatus string `json:"pingStatus"`
|
||||
}
|
||||
@@ -22,6 +24,7 @@ type FirewallOperation struct {
|
||||
}
|
||||
|
||||
type PortRuleOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Address string `json:"address"`
|
||||
Port string `json:"port" validate:"required"`
|
||||
@@ -55,6 +58,7 @@ type UpdateFirewallDescription struct {
|
||||
}
|
||||
|
||||
type AddrRuleOperate struct {
|
||||
ID uint `json:"id"`
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
Address string `json:"address" validate:"required"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=accept drop"`
|
||||
@@ -76,3 +80,30 @@ type BatchRuleOperate struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Rules []PortRuleOperate `json:"rules"`
|
||||
}
|
||||
|
||||
type IptablesOp struct {
|
||||
Name string `json:"name" validate:"required,oneof=1PANEL_INPUT 1PANEL_OUTPUT 1PANEL_BASIC"`
|
||||
Operate string `json:"operate" validate:"required,oneof=init-base init-forward init-advance bind-base unbind-base bind unbind"`
|
||||
}
|
||||
|
||||
type IptablesRuleOp struct {
|
||||
Operation string `json:"operation" validate:"required,oneof=add remove"`
|
||||
ID uint `json:"id"`
|
||||
Chain string `json:"chain" validate:"required,oneof=1PANEL_INPUT 1PANEL_OUTPUT"`
|
||||
Protocol string `json:"protocol"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
SrcPort uint `json:"srcPort"`
|
||||
DstIP string `json:"dstIP"`
|
||||
DstPort uint `json:"dstPort"`
|
||||
Strategy string `json:"strategy" validate:"required,oneof=ACCEPT DROP REJECT"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type IptablesBatchOperate struct {
|
||||
Rules []IptablesRuleOp `json:"rules"`
|
||||
}
|
||||
|
||||
type IptablesChainStatus struct {
|
||||
IsBind bool `json:"isBind"`
|
||||
DefaultStrategy string `json:"defaultStrategy"`
|
||||
}
|
||||
|
||||
@@ -3,20 +3,16 @@ package model
|
||||
type Firewall struct {
|
||||
BaseModel
|
||||
|
||||
Type string `gorm:"not null" json:"type"`
|
||||
Port string `gorm:"not null" json:"port"`
|
||||
Protocol string `gorm:"not null" json:"protocol"`
|
||||
Address string `gorm:"not null" json:"address"`
|
||||
Type string `json:"type"`
|
||||
Port string `json:"port"` // Deprecated
|
||||
Address string `json:"address"` // Deprecated
|
||||
|
||||
Chain string `json:"chain"`
|
||||
Protocol string `json:"protocol"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
SrcPort string `json:"srcPort"`
|
||||
DstIP string `json:"dstIP"`
|
||||
DstPort string `json:"dstPort"`
|
||||
Strategy string `gorm:"not null" json:"strategy"`
|
||||
Description string `gorm:"not null" json:"description"`
|
||||
}
|
||||
|
||||
type Forward struct {
|
||||
BaseModel
|
||||
|
||||
Protocol string `gorm:"not null" json:"protocol"`
|
||||
Port string `gorm:"not null" json:"port"`
|
||||
TargetIP string `gorm:"not null" json:"targetIP"`
|
||||
TargetPort string `gorm:"not null" json:"targetPort"`
|
||||
Interface string `json:"interface"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type HostRepo struct{}
|
||||
|
||||
type IHostRepo interface {
|
||||
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
|
||||
ListFirewallRecord() ([]model.Firewall, error)
|
||||
ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error)
|
||||
SaveFirewallRecord(firewall *model.Firewall) error
|
||||
DeleteFirewallRecordByID(id uint) error
|
||||
DeleteFirewallRecord(fType, port, protocol, address, strategy string) error
|
||||
|
||||
SyncCert(data []model.RootCert) error
|
||||
GetCert(opts ...DBOption) (model.RootCert, error)
|
||||
@@ -22,6 +22,8 @@ type IHostRepo interface {
|
||||
SaveCert(cert *model.RootCert) error
|
||||
UpdateCert(id uint, vars map[string]interface{}) error
|
||||
DeleteCert(opts ...DBOption) error
|
||||
|
||||
WithByChain(chain string) DBOption
|
||||
}
|
||||
|
||||
func NewIHostRepo() IHostRepo {
|
||||
@@ -38,12 +40,16 @@ func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.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
|
||||
func (h *HostRepo) ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error) {
|
||||
var firewalls []model.Firewall
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return datas, nil
|
||||
if err := global.DB.Find(&firewalls).Error; err != nil {
|
||||
return firewalls, nil
|
||||
}
|
||||
return firewalls, nil
|
||||
}
|
||||
|
||||
func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error {
|
||||
@@ -52,12 +58,12 @@ func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error {
|
||||
}
|
||||
var data model.Firewall
|
||||
if firewall.Type == "port" {
|
||||
_ = global.DB.Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", "port", firewall.Port, firewall.Protocol, firewall.Address, firewall.Strategy).First(&data)
|
||||
_ = global.DB.Where("type = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND strategy = ?", "port", firewall.DstPort, firewall.Protocol, firewall.SrcIP, firewall.Strategy).First(&data)
|
||||
if data.ID != 0 {
|
||||
firewall.ID = data.ID
|
||||
}
|
||||
} else {
|
||||
_ = global.DB.Where("type = ? AND address = ? AND strategy = ?", "address", firewall.Address, firewall.Strategy).First(&data)
|
||||
_ = global.DB.Where("type = ? AND src_ip = ? AND strategy = ?", "address", firewall.SrcIP, firewall.Strategy).First(&data)
|
||||
if data.ID != 0 {
|
||||
firewall.ID = data.ID
|
||||
}
|
||||
@@ -69,10 +75,6 @@ func (h *HostRepo) DeleteFirewallRecordByID(id uint) error {
|
||||
return global.DB.Where("id = ?", id).Delete(&model.Firewall{}).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) DeleteFirewallRecord(fType, port, protocol, address, strategy string) error {
|
||||
return global.DB.Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", fType, port, protocol, address, strategy).Delete(&model.Firewall{}).Error
|
||||
}
|
||||
|
||||
func (u *HostRepo) GetCert(opts ...DBOption) (model.RootCert, error) {
|
||||
var cert model.RootCert
|
||||
db := global.DB
|
||||
@@ -151,3 +153,9 @@ func (u *HostRepo) SyncCert(data []model.RootCert) error {
|
||||
tx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *HostRepo) WithByChain(chain string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("chain = ?", chain)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/controller"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
fireClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/client"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
@@ -27,7 +28,7 @@ const confPath = "/etc/sysctl.conf"
|
||||
type FirewallService struct{}
|
||||
|
||||
type IFirewallService interface {
|
||||
LoadBaseInfo() (dto.FirewallBaseInfo, error)
|
||||
LoadBaseInfo(tab string) (dto.FirewallBaseInfo, error)
|
||||
SearchWithPage(search dto.RuleSearch) (int64, interface{}, error)
|
||||
OperateFirewall(req dto.FirewallOperation) error
|
||||
OperatePortRule(req dto.PortRuleOperate, reload bool) error
|
||||
@@ -43,7 +44,7 @@ func NewIFirewallService() IFirewallService {
|
||||
return &FirewallService{}
|
||||
}
|
||||
|
||||
func (u *FirewallService) LoadBaseInfo() (dto.FirewallBaseInfo, error) {
|
||||
func (u *FirewallService) LoadBaseInfo(tab string) (dto.FirewallBaseInfo, error) {
|
||||
var baseInfo dto.FirewallBaseInfo
|
||||
baseInfo.Version = "-"
|
||||
baseInfo.Name = "-"
|
||||
@@ -57,7 +58,7 @@ func (u *FirewallService) LoadBaseInfo() (dto.FirewallBaseInfo, error) {
|
||||
baseInfo.Name = client.Name()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
wg.Add(4)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
baseInfo.PingStatus = u.pingStatus()
|
||||
@@ -70,6 +71,10 @@ func (u *FirewallService) LoadBaseInfo() (dto.FirewallBaseInfo, error) {
|
||||
defer wg.Done()
|
||||
baseInfo.Version, _ = client.Version()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
baseInfo.IsInit, baseInfo.IsBind = loadInitStatus(baseInfo.Name, tab)
|
||||
}()
|
||||
wg.Wait()
|
||||
return baseInfo, nil
|
||||
}
|
||||
@@ -158,15 +163,17 @@ func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}
|
||||
if req.Type != des.Type {
|
||||
continue
|
||||
}
|
||||
if backDatas[i].Port == des.Port &&
|
||||
if backDatas[i].Port == des.DstPort &&
|
||||
req.Type == "port" &&
|
||||
backDatas[i].Protocol == des.Protocol &&
|
||||
backDatas[i].Strategy == des.Strategy &&
|
||||
backDatas[i].Address == des.Address {
|
||||
backDatas[i].Address == des.SrcIP {
|
||||
backDatas[i].ID = des.ID
|
||||
backDatas[i].Description = des.Description
|
||||
break
|
||||
}
|
||||
if req.Type == "address" && backDatas[i].Strategy == des.Strategy && backDatas[i].Address == des.Address {
|
||||
if req.Type == "address" && backDatas[i].Strategy == des.Strategy && backDatas[i].Address == des.SrcIP {
|
||||
backDatas[i].ID = des.ID
|
||||
backDatas[i].Description = des.Description
|
||||
break
|
||||
}
|
||||
@@ -224,6 +231,10 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chain := ""
|
||||
if client.Name() == "iptables" {
|
||||
chain = iptables.Chain1PanelBasic
|
||||
}
|
||||
protos := strings.Split(req.Protocol, "/")
|
||||
itemAddress := strings.Split(strings.TrimSuffix(req.Address, ","), ",")
|
||||
|
||||
@@ -241,7 +252,7 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
|
||||
return err
|
||||
}
|
||||
req.Port = strings.ReplaceAll(req.Port, ":", "-")
|
||||
if err := u.addPortRecord(req); err != nil {
|
||||
if err := u.addPortRecord(chain, req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -262,7 +273,7 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
|
||||
if len(req.Protocol) == 0 {
|
||||
req.Protocol = "tcp/udp"
|
||||
}
|
||||
if err := u.addPortRecord(req); err != nil {
|
||||
if err := u.addPortRecord(chain, req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -278,7 +289,7 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
|
||||
if err := u.operatePort(client, req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := u.addPortRecord(req); err != nil {
|
||||
if err := u.addPortRecord(chain, req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -295,7 +306,7 @@ func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool)
|
||||
if err := u.operatePort(client, req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := u.addPortRecord(req); err != nil {
|
||||
if err := u.addPortRecord(chain, req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -404,7 +415,10 @@ func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload boo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chain := ""
|
||||
if client.Name() == "iptables" {
|
||||
chain = iptables.Chain1PanelBasic
|
||||
}
|
||||
var fireInfo fireClient.FireInfo
|
||||
if err := copier.Copy(&fireInfo, &req); err != nil {
|
||||
return err
|
||||
@@ -420,7 +434,7 @@ func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload boo
|
||||
return err
|
||||
}
|
||||
req.Address = addressList[i]
|
||||
if err := u.addAddressRecord(req); err != nil {
|
||||
if err := u.addAddressRecord(chain, req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -459,10 +473,16 @@ func (u *FirewallService) UpdateAddrRule(req dto.AddrRuleUpdate) error {
|
||||
}
|
||||
|
||||
func (u *FirewallService) UpdateDescription(req dto.UpdateFirewallDescription) error {
|
||||
var firewall model.Firewall
|
||||
if err := copier.Copy(&firewall, &req); err != nil {
|
||||
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
firewall := model.Firewall{
|
||||
Type: req.Type,
|
||||
Chain: iptables.Chain1PanelBasic,
|
||||
SrcIP: req.Address,
|
||||
DstPort: req.Port,
|
||||
Protocol: req.Protocol,
|
||||
Strategy: req.Strategy,
|
||||
Description: req.Description,
|
||||
}
|
||||
|
||||
return hostRepo.SaveFirewallRecord(&firewall)
|
||||
}
|
||||
|
||||
@@ -562,7 +582,7 @@ func (u *FirewallService) cleanUnUsedData(client firewall.FirewallClient) {
|
||||
}
|
||||
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 {
|
||||
if records[i].DstPort == item.Port && records[i].Protocol == item.Protocol && records[i].Strategy == item.Strategy && records[i].SrcIP == item.Address {
|
||||
records = append(records[:i], records[i+1:]...)
|
||||
}
|
||||
}
|
||||
@@ -659,16 +679,19 @@ func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) er
|
||||
return client.Reload()
|
||||
}
|
||||
|
||||
func (u *FirewallService) addPortRecord(req dto.PortRuleOperate) error {
|
||||
func (u *FirewallService) addPortRecord(chain string, req dto.PortRuleOperate) error {
|
||||
if req.Operation == "remove" {
|
||||
return hostRepo.DeleteFirewallRecord("port", req.Port, req.Protocol, req.Address, req.Strategy)
|
||||
if req.ID != 0 {
|
||||
return hostRepo.DeleteFirewallRecordByID(req.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := hostRepo.SaveFirewallRecord(&model.Firewall{
|
||||
Type: "port",
|
||||
Port: req.Port,
|
||||
Chain: chain,
|
||||
DstPort: req.Port,
|
||||
Protocol: req.Protocol,
|
||||
Address: req.Address,
|
||||
SrcIP: req.Address,
|
||||
Strategy: req.Strategy,
|
||||
Description: req.Description,
|
||||
}); err != nil {
|
||||
@@ -678,13 +701,17 @@ func (u *FirewallService) addPortRecord(req dto.PortRuleOperate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *FirewallService) addAddressRecord(req dto.AddrRuleOperate) error {
|
||||
func (u *FirewallService) addAddressRecord(chain string, req dto.AddrRuleOperate) error {
|
||||
if req.Operation == "remove" {
|
||||
return hostRepo.DeleteFirewallRecord("address", "", "", req.Address, req.Strategy)
|
||||
if req.ID != 0 {
|
||||
return hostRepo.DeleteFirewallRecordByID(req.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := hostRepo.SaveFirewallRecord(&model.Firewall{
|
||||
Type: "address",
|
||||
Address: req.Address,
|
||||
Chain: chain,
|
||||
SrcIP: req.Address,
|
||||
Strategy: req.Strategy,
|
||||
Description: req.Description,
|
||||
}); err != nil {
|
||||
@@ -751,3 +778,90 @@ func checkPortUsed(ports, proto string, apps []portOfApp) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func loadInitStatus(clientName, tab string) (bool, bool) {
|
||||
if clientName != "iptables" && tab != "forward" {
|
||||
return true, true
|
||||
}
|
||||
switch tab {
|
||||
case "base":
|
||||
if isExist, _ := iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelBasicBefore); !isExist {
|
||||
return false, false
|
||||
}
|
||||
if exist := iptables.CheckRuleExist(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.IoRuleIn); !exist {
|
||||
return false, false
|
||||
}
|
||||
if exist := iptables.CheckRuleExist(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.EstablishedRule); !exist {
|
||||
return false, false
|
||||
}
|
||||
if exist, _ := iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelBasic); !exist {
|
||||
return false, false
|
||||
}
|
||||
if exist, _ := iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelBasicAfter); !exist {
|
||||
return false, false
|
||||
}
|
||||
if exist := iptables.CheckRuleExist(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.DropAll); !exist {
|
||||
return false, false
|
||||
}
|
||||
if bind, _ := iptables.CheckChainBind(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore); !bind {
|
||||
return true, false
|
||||
}
|
||||
if bind, _ := iptables.CheckChainBind(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic); !bind {
|
||||
return true, false
|
||||
}
|
||||
if bind, _ := iptables.CheckChainBind(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter); !bind {
|
||||
return true, false
|
||||
}
|
||||
return true, true
|
||||
case "advance":
|
||||
isExist, _ := iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelInput)
|
||||
if !isExist {
|
||||
return false, false
|
||||
}
|
||||
isExist, _ = iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelOutput)
|
||||
if !isExist {
|
||||
return false, false
|
||||
}
|
||||
|
||||
isBind, _ := iptables.CheckChainBind(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelInput)
|
||||
if !isBind {
|
||||
return true, false
|
||||
}
|
||||
isBind, _ = iptables.CheckChainBind(iptables.FilterTab, iptables.ChainOutput, iptables.Chain1PanelOutput)
|
||||
return true, isBind
|
||||
case "forward":
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashC("cat /proc/sys/net/ipv4/ip_forward")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("check /proc/sys/net/ipv4/ip_forward failed, err: %v", err)
|
||||
return false, false
|
||||
}
|
||||
if strings.TrimSpace(stdout) == "0" {
|
||||
return false, false
|
||||
}
|
||||
|
||||
exist, _ := iptables.CheckChainExist(iptables.NatTab, iptables.Chain1PanelPreRouting)
|
||||
if !exist {
|
||||
return false, false
|
||||
}
|
||||
exist, _ = iptables.CheckChainExist(iptables.NatTab, iptables.Chain1PanelPostRouting)
|
||||
if !exist {
|
||||
return false, false
|
||||
}
|
||||
exist, _ = iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelForward)
|
||||
if !exist {
|
||||
return false, false
|
||||
}
|
||||
isBind, _ := iptables.CheckChainBind(iptables.NatTab, "PREROUTING", iptables.Chain1PanelPreRouting)
|
||||
if !isBind {
|
||||
return false, false
|
||||
}
|
||||
isBind, _ = iptables.CheckChainBind(iptables.NatTab, "POSTROUTING", iptables.Chain1PanelPostRouting)
|
||||
if !isBind {
|
||||
return false, false
|
||||
}
|
||||
isBind, _ = iptables.CheckChainBind(iptables.FilterTab, "FORWARD", iptables.Chain1PanelForward)
|
||||
return true, isBind
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
||||
323
agent/app/service/iptables.go
Normal file
323
agent/app/service/iptables.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables"
|
||||
)
|
||||
|
||||
type IIptablesService interface {
|
||||
Search(req dto.SearchPageWithType) (int64, interface{}, error)
|
||||
OperateRule(req dto.IptablesRuleOp) error
|
||||
BatchOperate(req dto.IptablesBatchOperate) error
|
||||
LoadChainStatus(req dto.OperationWithName) dto.IptablesChainStatus
|
||||
|
||||
Operate(req dto.IptablesOp) error
|
||||
}
|
||||
|
||||
type IptablesService struct{}
|
||||
|
||||
func NewIIptablesService() IIptablesService {
|
||||
return &IptablesService{}
|
||||
}
|
||||
|
||||
func (s *IptablesService) Search(req dto.SearchPageWithType) (int64, interface{}, error) {
|
||||
rules, err := iptables.ReadFilterRulesByChain(req.Type)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to read iptables rules: %w", err)
|
||||
}
|
||||
var records []iptables.FilterRules
|
||||
total, start, end := len(rules), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
records = make([]iptables.FilterRules, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
records = rules[start:end]
|
||||
}
|
||||
|
||||
rulesInDB, _ := hostRepo.ListFirewallRecord(hostRepo.WithByChain(req.Type))
|
||||
|
||||
for i := 0; i < len(records); i++ {
|
||||
for _, item := range rulesInDB {
|
||||
if records[i].Strategy == item.Strategy &&
|
||||
records[i].DstIP == item.DstIP &&
|
||||
fmt.Sprintf("%v", records[i].DstPort) == item.DstPort &&
|
||||
records[i].Protocol == item.Protocol &&
|
||||
records[i].SrcIP == item.SrcIP &&
|
||||
fmt.Sprintf("%v", records[i].SrcPort) == item.SrcPort {
|
||||
records[i].ID = item.ID
|
||||
records[i].Description = item.Description
|
||||
}
|
||||
}
|
||||
}
|
||||
return int64(total), records, nil
|
||||
}
|
||||
|
||||
func (s *IptablesService) OperateRule(req dto.IptablesRuleOp) error {
|
||||
policy := iptables.FilterRules{
|
||||
Protocol: req.Protocol,
|
||||
SrcIP: req.SrcIP,
|
||||
SrcPort: req.SrcPort,
|
||||
DstIP: req.DstIP,
|
||||
DstPort: req.DstPort,
|
||||
Strategy: req.Strategy,
|
||||
}
|
||||
|
||||
name := iptables.InputFileName
|
||||
if req.Chain == iptables.Chain1PanelOutput {
|
||||
name = iptables.OutputFileName
|
||||
}
|
||||
switch req.Operation {
|
||||
case "add":
|
||||
if err := s.validateRuleInput(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := iptables.AddFilterRule(req.Chain, policy); err != nil {
|
||||
return fmt.Errorf("failed to add iptables rule: %w", err)
|
||||
}
|
||||
|
||||
rule := &model.Firewall{
|
||||
Chain: req.Chain,
|
||||
Protocol: req.Protocol,
|
||||
SrcIP: req.SrcIP,
|
||||
SrcPort: fmt.Sprintf("%v", req.SrcPort),
|
||||
DstIP: req.DstIP,
|
||||
DstPort: fmt.Sprintf("%v", req.DstPort),
|
||||
Strategy: req.Strategy,
|
||||
Description: req.Description,
|
||||
}
|
||||
|
||||
if err := hostRepo.SaveFirewallRecord(rule); err != nil {
|
||||
return fmt.Errorf("failed to save rule to database: %w", err)
|
||||
}
|
||||
case "remove":
|
||||
if err := iptables.DeleteFilterRule(req.Chain, policy); err != nil {
|
||||
return fmt.Errorf("failed to remove iptables rule: %w", err)
|
||||
}
|
||||
if req.ID != 0 {
|
||||
if err := hostRepo.DeleteFirewallRecordByID(req.ID); err != nil {
|
||||
return fmt.Errorf("failed to delete rule from database: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := iptables.SaveRulesToFile(iptables.FilterTab, req.Chain, name); err != nil {
|
||||
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IptablesService) BatchOperate(req dto.IptablesBatchOperate) error {
|
||||
for _, rule := range req.Rules {
|
||||
if err := s.OperateRule(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IptablesService) Operate(req dto.IptablesOp) error {
|
||||
targetChain := iptables.ChainInput
|
||||
if req.Name == iptables.Chain1PanelOutput {
|
||||
targetChain = iptables.ChainOutput
|
||||
}
|
||||
switch req.Operate {
|
||||
case "init-base":
|
||||
if ok := cmd.Which("iptables"); !ok {
|
||||
return fmt.Errorf("failed to find iptables")
|
||||
}
|
||||
if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelBasicBefore); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelBasic); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelBasicAfter); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := initPreRules(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case "init-forward":
|
||||
return client.EnableIptablesForward()
|
||||
case "init-advance":
|
||||
if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelInput); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
number := loadBindNumber()
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainOutput, iptables.Chain1PanelOutput, number); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelInput, number); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case "bind-base":
|
||||
if err := initPreRules(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case "unbind-base":
|
||||
if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case "bind":
|
||||
if err := iptables.BindChain(iptables.FilterTab, targetChain, req.Name, loadBindNumber()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case "unbind":
|
||||
if err := iptables.UnbindChain(iptables.FilterTab, targetChain, req.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IptablesService) LoadChainStatus(req dto.OperationWithName) dto.IptablesChainStatus {
|
||||
var data dto.IptablesChainStatus
|
||||
var err error
|
||||
data.DefaultStrategy, err = iptables.LoadDefaultStrategy(req.Name)
|
||||
if err != nil {
|
||||
global.LOG.Error(err)
|
||||
}
|
||||
switch req.Name {
|
||||
case iptables.Chain1PanelBasic:
|
||||
data.IsBind, err = iptables.CheckChainBind(iptables.FilterTab, iptables.ChainInput, req.Name)
|
||||
case iptables.Chain1PanelInput:
|
||||
data.IsBind, err = iptables.CheckChainBind(iptables.FilterTab, iptables.ChainInput, req.Name)
|
||||
case iptables.Chain1PanelOutput:
|
||||
data.IsBind, err = iptables.CheckChainBind(iptables.FilterTab, iptables.ChainOutput, req.Name)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (s *IptablesService) validateRuleInput(req *dto.IptablesRuleOp) error {
|
||||
if req.Protocol != "" {
|
||||
validProtocols := map[string]bool{"tcp": true, "udp": true, "icmp": true, "all": true}
|
||||
if !validProtocols[strings.ToLower(req.Protocol)] {
|
||||
return fmt.Errorf("invalid protocol: %s, must be tcp, udp, icmp or all", req.Protocol)
|
||||
}
|
||||
}
|
||||
if req.SrcIP != "" {
|
||||
if err := s.validateIPOrCIDR(req.SrcIP); err != nil {
|
||||
return fmt.Errorf("invalid source IP: %w", err)
|
||||
}
|
||||
}
|
||||
if req.DstIP != "" {
|
||||
if err := s.validateIPOrCIDR(req.DstIP); err != nil {
|
||||
return fmt.Errorf("invalid destination IP: %w", err)
|
||||
}
|
||||
}
|
||||
if req.SrcPort > 65535 {
|
||||
return fmt.Errorf("invalid source port: %d, must be between 1 and 65535", req.SrcPort)
|
||||
}
|
||||
if req.DstPort > 65535 {
|
||||
return fmt.Errorf("invalid destination port: %d, must be between 1 and 65535", req.DstPort)
|
||||
}
|
||||
if (req.SrcPort > 0 || req.DstPort > 0) && req.Protocol == "" {
|
||||
return fmt.Errorf("port specification requires protocol (tcp/udp)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IptablesService) validateIPOrCIDR(ipStr string) error {
|
||||
if strings.Contains(ipStr, "/") {
|
||||
_, _, err := net.ParseCIDR(ipStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid CIDR format: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("invalid IP address format")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadBindNumber() int {
|
||||
number := 1
|
||||
if exist, _ := iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelBasicBefore); exist {
|
||||
number++
|
||||
}
|
||||
if exist, _ := iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelBasic); exist {
|
||||
number++
|
||||
}
|
||||
return number
|
||||
}
|
||||
|
||||
func initPreRules() error {
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.IoRuleIn); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.EstablishedRule); err != nil {
|
||||
return err
|
||||
}
|
||||
panelPort := ""
|
||||
if !global.IsMaster {
|
||||
panelPort = global.CONF.Base.Port
|
||||
} else {
|
||||
var portSetting model.Setting
|
||||
_ = global.CoreDB.Where("key = ?", "ServerPort").First(&portSetting).Error
|
||||
if len(portSetting.Value) != 0 {
|
||||
panelPort = portSetting.Value
|
||||
}
|
||||
}
|
||||
if len(panelPort) == 0 {
|
||||
return errors.New("find 1panel service port failed")
|
||||
}
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.AllowSSH); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, fmt.Sprintf("-p tcp -m tcp --dport %s -j ACCEPT", panelPort)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.DropAll); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -47,6 +47,7 @@ type SystemDir struct {
|
||||
McpDir string
|
||||
ConvertLogDir string
|
||||
TensorRTLLMDir string
|
||||
FirewallDir string
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
|
||||
@@ -2,15 +2,10 @@ package app
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
go func() {
|
||||
_ = docker.CreateDefaultDockerNetwork()
|
||||
|
||||
if f, err := firewall.NewFirewallClient(); err == nil {
|
||||
_ = f.EnableForward()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -34,4 +34,5 @@ func Init() {
|
||||
global.Dir.McpDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/mcp"))
|
||||
global.Dir.ConvertLogDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/log/convert"))
|
||||
global.Dir.TensorRTLLMDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/ai/tensorrt_llm"))
|
||||
global.Dir.FirewallDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/firewall"))
|
||||
}
|
||||
|
||||
24
agent/init/firewall/firwall.go
Normal file
24
agent/init/firewall/firwall.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
clientName := client.Name()
|
||||
if clientName == "ufw" || clientName == "iptables" {
|
||||
_ = iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelForward, iptables.ForwardFileName)
|
||||
_ = iptables.LoadRulesFromFile(iptables.NatTab, iptables.Chain1PanelPreRouting, iptables.ForwardFileName1)
|
||||
_ = iptables.LoadRulesFromFile(iptables.NatTab, iptables.Chain1PanelPostRouting, iptables.ForwardFileName2)
|
||||
}
|
||||
if clientName == "iptables" {
|
||||
_ = iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelBasic, iptables.BasicFileName)
|
||||
_ = iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelInput, iptables.InputFileName)
|
||||
_ = iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelOutput, iptables.OutputFileName)
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ func InitAgentDB() {
|
||||
migrations.AddMonitorProcess,
|
||||
migrations.UpdateCronJob,
|
||||
migrations.UpdateTensorrtLLM,
|
||||
migrations.AddIptablesFilterRuleTable,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
|
||||
@@ -48,7 +49,6 @@ var AddTable = &gormigrate.Migration{
|
||||
&model.DatabaseMysql{},
|
||||
&model.DatabasePostgresql{},
|
||||
&model.Favorite{},
|
||||
&model.Forward{},
|
||||
&model.Firewall{},
|
||||
&model.Ftp{},
|
||||
&model.ImageRepo{},
|
||||
@@ -660,6 +660,31 @@ var UpdateMonitorInterval = &gormigrate.Migration{
|
||||
},
|
||||
}
|
||||
|
||||
var AddIptablesFilterRuleTable = &gormigrate.Migration{
|
||||
ID: "20251106-add-iptables-filter-rule-table",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Firewall{}); err != nil {
|
||||
return err
|
||||
}
|
||||
var firewalls []model.Firewall
|
||||
_ = tx.Where("1 = 1").Find(&firewalls).Error
|
||||
|
||||
firewallType := ""
|
||||
client, err := firewall.NewFirewallClient()
|
||||
if err == nil {
|
||||
firewallType = client.Name()
|
||||
}
|
||||
for _, item := range firewalls {
|
||||
if err := tx.Model(&model.Firewall{}).
|
||||
Where("id = ?", item.ID).
|
||||
Updates(map[string]interface{}{"dst_port": item.Port, "src_ip": item.Address, "firewall_type": firewallType}); err != nil {
|
||||
global.LOG.Errorf("update firewall failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddMonitorProcess = &gormigrate.Migration{
|
||||
ID: "20251030-add-monitor-process",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
||||
@@ -11,7 +11,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter := Router.Group("hosts")
|
||||
baseApi := v2.ApiGroupApp.BaseApi
|
||||
{
|
||||
hostRouter.GET("/firewall/base", baseApi.LoadFirewallBaseInfo)
|
||||
hostRouter.POST("/firewall/base", baseApi.LoadFirewallBaseInfo)
|
||||
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
|
||||
hostRouter.POST("/firewall/operate", baseApi.OperateFirewall)
|
||||
hostRouter.POST("/firewall/port", baseApi.OperatePortRule)
|
||||
@@ -22,6 +22,12 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter.POST("/firewall/update/addr", baseApi.UpdateAddrRule)
|
||||
hostRouter.POST("/firewall/update/description", baseApi.UpdateFirewallDescription)
|
||||
|
||||
hostRouter.POST("/firewall/filter/rule/search", baseApi.SearchFilterRules)
|
||||
hostRouter.POST("/firewall/filter/rule/operate", baseApi.OperateFilterRule)
|
||||
hostRouter.POST("/firewall/filter/rule/batch", baseApi.BatchOperateFilterRule)
|
||||
hostRouter.POST("/firewall/filter/operate", baseApi.OperateFilterChain)
|
||||
hostRouter.POST("/firewall/filter/chain/status", baseApi.LoadChainStatus)
|
||||
|
||||
hostRouter.POST("/monitor/search", baseApi.LoadMonitor)
|
||||
hostRouter.POST("/monitor/clean", baseApi.CleanMonitor)
|
||||
hostRouter.GET("/monitor/netoptions", baseApi.GetNetworkOptions)
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/cron"
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/init/cache"
|
||||
"github.com/1Panel-dev/1Panel/agent/init/db"
|
||||
"github.com/1Panel-dev/1Panel/agent/init/dir"
|
||||
"github.com/1Panel-dev/1Panel/agent/init/firewall"
|
||||
"github.com/1Panel-dev/1Panel/agent/init/hook"
|
||||
"github.com/1Panel-dev/1Panel/agent/init/lang"
|
||||
"github.com/1Panel-dev/1Panel/agent/init/log"
|
||||
@@ -38,8 +40,12 @@ func Start() {
|
||||
i18n.Init()
|
||||
cache.Init()
|
||||
app.Init()
|
||||
firewall.Init()
|
||||
lang.Init()
|
||||
validator.Init()
|
||||
if os.Getenv("GIN_MODE") == "" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
cron.Run()
|
||||
hook.Init()
|
||||
InitOthers()
|
||||
|
||||
@@ -185,6 +185,7 @@ func loadFromPredefined(mgr Controller, keyword string) string {
|
||||
"1panel-core": {"1panel-core.service"},
|
||||
"1panel-agent": {"1panel-agent.service"},
|
||||
"docker": {"docker.service", "dockerd"},
|
||||
"iptables": {"iptables", "iptables-services"},
|
||||
}
|
||||
if val, ok := predefinedMap[keyword]; ok {
|
||||
for _, item := range val {
|
||||
|
||||
@@ -5,14 +5,15 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/loader"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/joho/godotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ComposeService struct {
|
||||
|
||||
@@ -34,12 +34,16 @@ func NewFirewallClient() (FirewallClient, error) {
|
||||
if firewalld && ufw {
|
||||
return nil, errors.New("It is detected that the system has both firewalld and ufw services. To avoid conflicts, please uninstall and try again!")
|
||||
}
|
||||
|
||||
if firewalld {
|
||||
return client.NewFirewalld()
|
||||
}
|
||||
if ufw {
|
||||
return client.NewUfw()
|
||||
}
|
||||
return nil, errors.New("No system firewalld or ufw service detected, please check and try again!")
|
||||
|
||||
iptables := cmd.Which("iptables")
|
||||
if iptables {
|
||||
return client.NewIptables()
|
||||
}
|
||||
return nil, errors.New("No system firewall service detected (firewalld/ufw/iptables), please check and try again!")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package client
|
||||
|
||||
type FireInfo struct {
|
||||
ID uint `json:"uint"`
|
||||
Family string `json:"family"` // ipv4 ipv6
|
||||
Address string `json:"address"` // Anywhere
|
||||
Port string `json:"port"`
|
||||
@@ -24,25 +25,3 @@ type Forward struct {
|
||||
TargetPort string `json:"targetPort"`
|
||||
Interface string `json:"interface"`
|
||||
}
|
||||
|
||||
type IptablesNatInfo struct {
|
||||
Num string `json:"num"`
|
||||
Target string `json:"target"`
|
||||
Protocol string `json:"protocol"`
|
||||
InIface string `json:"inIface"`
|
||||
OutIface string `json:"outIface"`
|
||||
Opt string `json:"opt"`
|
||||
Source string `json:"source"`
|
||||
Destination string `json:"destination"`
|
||||
SrcPort string `json:"srcPort"`
|
||||
DestPort string `json:"destPort"`
|
||||
}
|
||||
|
||||
type IptablesFilterInfo 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"`
|
||||
}
|
||||
|
||||
@@ -3,252 +3,404 @@ package client
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables"
|
||||
)
|
||||
|
||||
const (
|
||||
PreRoutingChain = "1PANEL_PREROUTING"
|
||||
PostRoutingChain = "1PANEL_POSTROUTING"
|
||||
ForwardChain = "1PANEL_FORWARD"
|
||||
)
|
||||
var portRuleRegex = regexp.MustCompile(`-A\s+INPUT\s+-p\s+(\w+)(?:\s+-m\s+\w+)*\s+--dport\s+(\d+(?::\d+)?)\s+-j\s+(\w+)`)
|
||||
var addressRuleRegex = regexp.MustCompile(`-A\s+(INPUT|OUTPUT)\s+-s\s+(\S+)\s+-j\s+(\w+)`)
|
||||
|
||||
const (
|
||||
FilterTab = "filter"
|
||||
NatTab = "nat"
|
||||
)
|
||||
|
||||
const NatChain = "1PANEL"
|
||||
|
||||
var (
|
||||
natListRegex = regexp.MustCompile(`^(\d+)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)(?:\s+(.+?) .+?:(\d{1,5}(?::\d+)?).+?[ :](.+-.+|(?:.+:)?\d{1,5}(?:-\d{1,5})?))?$`)
|
||||
)
|
||||
|
||||
type Iptables struct {
|
||||
CmdStr string
|
||||
}
|
||||
type Iptables struct{}
|
||||
|
||||
func NewIptables() (*Iptables, error) {
|
||||
iptables := new(Iptables)
|
||||
iptables.CmdStr = cmd.SudoHandleCmd()
|
||||
|
||||
return iptables, nil
|
||||
return &Iptables{}, nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) out(tab, rule string) (string, error) {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -t %s %s", iptables.CmdStr, tab, rule)
|
||||
func (i *Iptables) Name() string {
|
||||
return "iptables"
|
||||
}
|
||||
|
||||
func (i *Iptables) Status() (bool, error) {
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashC("iptables -L -n | head -1")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("iptables failed, %v", err)
|
||||
return false, err
|
||||
}
|
||||
return stdout, nil
|
||||
return strings.Contains(stdout, "Chain"), nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) run(tab, rule string) error {
|
||||
if _, err := iptables.out(tab, rule); err != nil {
|
||||
return err
|
||||
}
|
||||
func (i *Iptables) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) Check() error {
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashC("cat /proc/sys/net/ipv4/ip_forward")
|
||||
if err != nil {
|
||||
return fmt.Errorf("check ip_forward failed, %v", err)
|
||||
}
|
||||
if strings.TrimSpace(stdout) == "0" {
|
||||
return fmt.Errorf("ipv4 forward disabled")
|
||||
}
|
||||
|
||||
chain, err := iptables.out(NatTab, fmt.Sprintf("-L -n | grep 'Chain %s'", PreRoutingChain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check chain: %w", err)
|
||||
}
|
||||
if strings.TrimSpace(chain) != "" {
|
||||
return fmt.Errorf("chain %s already exists", PreRoutingChain)
|
||||
}
|
||||
|
||||
func (i *Iptables) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) NewChain(tab, chain string) error {
|
||||
return iptables.run(tab, "-N "+chain)
|
||||
func (i *Iptables) Restart() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) AppendChain(tab string, chain, chain1 string) error {
|
||||
return iptables.run(tab, fmt.Sprintf("-A %s -j %s", chain, chain1))
|
||||
func (i *Iptables) Reload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) NatList(chain ...string) ([]IptablesNatInfo, error) {
|
||||
if len(chain) == 0 {
|
||||
chain = append(chain, PreRoutingChain)
|
||||
}
|
||||
stdout, err := iptables.out(NatTab, fmt.Sprintf("-nvL %s --line-numbers", chain[0]))
|
||||
func (i *Iptables) Version() (string, error) {
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashC("iptables --version")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", fmt.Errorf("failed to get iptables version: %w", err)
|
||||
}
|
||||
parts := strings.Fields(stdout)
|
||||
if len(parts) >= 2 {
|
||||
return strings.TrimPrefix(parts[1], "v"), nil
|
||||
}
|
||||
return strings.TrimSpace(stdout), nil
|
||||
}
|
||||
|
||||
func (i *Iptables) ListPort() ([]FireInfo, error) {
|
||||
stdout, err := iptables.RunWithStd(iptables.FilterTab, fmt.Sprintf("-S %s", iptables.Chain1PanelBasic))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list 1PANEL_BASIC rules: %w", 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[13], ":") {
|
||||
match[13] = fmt.Sprintf(":%s", match[13])
|
||||
var datas []FireInfo
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
chainPortRegex := regexp.MustCompile(fmt.Sprintf(`-A\s+%s\s+(?:-s\s+(\S+)\s+)?-p\s+(\w+)(?:\s+-m\s+\w+)*\s+--dport\s+(\d+(?::\d+)?)\s+-j\s+(\w+)`, iptables.Chain1PanelBasic))
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, fmt.Sprintf("-A %s", iptables.Chain1PanelBasic)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if matches := chainPortRegex.FindStringSubmatch(line); len(matches) == 5 {
|
||||
address := matches[1]
|
||||
protocol := matches[2]
|
||||
port := strings.ReplaceAll(matches[3], ":", "-")
|
||||
action := strings.ToLower(matches[4])
|
||||
|
||||
strategy := "accept"
|
||||
if action == "drop" || action == "reject" {
|
||||
strategy = "drop"
|
||||
}
|
||||
forwardList = append(forwardList, IptablesNatInfo{
|
||||
Num: match[1],
|
||||
Target: match[4],
|
||||
Protocol: match[11],
|
||||
InIface: match[7],
|
||||
OutIface: match[8],
|
||||
Opt: match[6],
|
||||
Source: match[9],
|
||||
Destination: match[10],
|
||||
SrcPort: match[12],
|
||||
DestPort: match[13],
|
||||
|
||||
datas = append(datas, FireInfo{
|
||||
Address: address,
|
||||
Protocol: protocol,
|
||||
Port: port,
|
||||
Strategy: strategy,
|
||||
Family: "ipv4",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return forwardList, nil
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) NatAdd(protocol, srcPort, dest, destPort, iface string, save bool) error {
|
||||
if dest != "" && dest != "127.0.0.1" && dest != "localhost" {
|
||||
iptablesArg := fmt.Sprintf("-A %s", PreRoutingChain)
|
||||
if iface != "" {
|
||||
iptablesArg += fmt.Sprintf(" -i %s", iface)
|
||||
}
|
||||
iptablesArg += fmt.Sprintf(" -p %s --dport %s -j DNAT --to-destination %s:%s", protocol, srcPort, dest, destPort)
|
||||
if err := iptables.run(NatTab, iptablesArg); err != nil {
|
||||
return err
|
||||
func (i *Iptables) ListAddress() ([]FireInfo, error) {
|
||||
stdout, err := iptables.RunWithStd(iptables.FilterTab, fmt.Sprintf("-S %s", iptables.Chain1PanelBasic))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list 1PANEL_BASIC address rules: %w", err)
|
||||
}
|
||||
|
||||
var datas []FireInfo
|
||||
lines := strings.Split(stdout, "\n")
|
||||
addressMap := make(map[string]FireInfo)
|
||||
|
||||
chainAddressRegex := regexp.MustCompile(fmt.Sprintf(`-A\s+%s\s+(?:-s\s+(\S+)|(?:-d\s+(\S+)))?\s+-j\s+(\w+)`, iptables.Chain1PanelBasic))
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, fmt.Sprintf("-A %s", iptables.Chain1PanelBasic)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := iptables.run(NatTab, fmt.Sprintf(
|
||||
"-A %s -d %s -p %s --dport %s -j MASQUERADE",
|
||||
PostRoutingChain,
|
||||
dest,
|
||||
protocol,
|
||||
destPort,
|
||||
)); err != nil {
|
||||
return err
|
||||
}
|
||||
if matches := chainAddressRegex.FindStringSubmatch(line); len(matches) >= 4 {
|
||||
address := matches[1]
|
||||
if address == "" {
|
||||
address = matches[2]
|
||||
}
|
||||
if address == "" {
|
||||
continue
|
||||
}
|
||||
action := strings.ToLower(matches[3])
|
||||
|
||||
if err := iptables.run(FilterTab, fmt.Sprintf(
|
||||
"-A %s -d %s -p %s --dport %s -j ACCEPT",
|
||||
ForwardChain,
|
||||
dest,
|
||||
protocol,
|
||||
destPort,
|
||||
)); err != nil {
|
||||
return err
|
||||
}
|
||||
strategy := "accept"
|
||||
if action == "drop" || action == "reject" {
|
||||
strategy = "drop"
|
||||
}
|
||||
|
||||
if err := iptables.run(FilterTab, fmt.Sprintf(
|
||||
"-A %s -s %s -p %s --sport %s -j ACCEPT",
|
||||
ForwardChain,
|
||||
dest,
|
||||
protocol,
|
||||
destPort,
|
||||
)); err != nil {
|
||||
if _, exists := addressMap[address]; !exists {
|
||||
addressMap[address] = FireInfo{
|
||||
Address: address,
|
||||
Strategy: strategy,
|
||||
Family: "ipv4",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, info := range addressMap {
|
||||
datas = append(datas, info)
|
||||
}
|
||||
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (i *Iptables) Port(port FireInfo, operation string) error {
|
||||
if operation != "add" && operation != "remove" {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
|
||||
portSpec, err := normalizePortSpec(port.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protocol := port.Protocol
|
||||
if protocol == "" {
|
||||
protocol = "tcp"
|
||||
}
|
||||
|
||||
action := "ACCEPT"
|
||||
if port.Strategy == "drop" {
|
||||
action = "DROP"
|
||||
}
|
||||
|
||||
ruleArgs := []string{fmt.Sprintf("-p %s", protocol)}
|
||||
if protocol == "tcp" || protocol == "udp" {
|
||||
ruleArgs = append(ruleArgs, fmt.Sprintf("-m %s", protocol))
|
||||
}
|
||||
ruleArgs = append(ruleArgs, fmt.Sprintf("--dport %s", portSpec), fmt.Sprintf("-j %s", action))
|
||||
ruleSpec := strings.Join(ruleArgs, " ")
|
||||
if operation == "add" {
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasic, ruleSpec); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
iptablesArg := fmt.Sprintf("-A %s", PreRoutingChain)
|
||||
if iface != "" {
|
||||
iptablesArg += fmt.Sprintf(" -i %s", iface)
|
||||
}
|
||||
iptablesArg += fmt.Sprintf(" -p %s --dport %s -j REDIRECT --to-port %s", protocol, srcPort, destPort)
|
||||
if err := iptables.run(NatTab, iptablesArg); err != nil {
|
||||
if err := iptables.DeleteRule(iptables.FilterTab, iptables.Chain1PanelBasic, ruleSpec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if save {
|
||||
return global.DB.Save(&model.Forward{
|
||||
Protocol: protocol,
|
||||
Port: srcPort,
|
||||
TargetIP: dest,
|
||||
TargetPort: destPort,
|
||||
Interface: iface,
|
||||
}).Error
|
||||
if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelBasic, iptables.BasicFileName); err != nil {
|
||||
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) NatRemove(num string, protocol, srcPort, dest, destPort, iface string) error {
|
||||
if err := iptables.run(NatTab, fmt.Sprintf("-D %s %s", PreRoutingChain, num)); err != nil {
|
||||
return err
|
||||
func (i *Iptables) RichRules(rule FireInfo, operation string) error {
|
||||
if operation != "add" && operation != "remove" {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
|
||||
if dest != "" && dest != "127.0.0.1" && dest != "localhost" {
|
||||
if err := iptables.run(NatTab, fmt.Sprintf(
|
||||
"-D %s -d %s -p %s --dport %s -j MASQUERADE",
|
||||
PostRoutingChain,
|
||||
dest,
|
||||
protocol,
|
||||
destPort,
|
||||
)); err != nil {
|
||||
address := strings.TrimSpace(rule.Address)
|
||||
if strings.EqualFold(address, "Anywhere") {
|
||||
address = ""
|
||||
}
|
||||
|
||||
action := "ACCEPT"
|
||||
if rule.Strategy == "drop" {
|
||||
action = "DROP"
|
||||
}
|
||||
|
||||
var ruleArgs []string
|
||||
if address != "" {
|
||||
ruleArgs = append(ruleArgs, fmt.Sprintf("-s %s", address))
|
||||
}
|
||||
|
||||
protocol := strings.TrimSpace(rule.Protocol)
|
||||
if rule.Port != "" && protocol == "" {
|
||||
protocol = "tcp"
|
||||
}
|
||||
|
||||
if protocol != "" {
|
||||
ruleArgs = append(ruleArgs, fmt.Sprintf("-p %s", protocol))
|
||||
}
|
||||
|
||||
if rule.Port != "" {
|
||||
portSegment, err := normalizePortSpec(rule.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if protocol == "" {
|
||||
return fmt.Errorf("protocol is required when specifying a port")
|
||||
}
|
||||
if protocol == "tcp" || protocol == "udp" {
|
||||
ruleArgs = append(ruleArgs, fmt.Sprintf("-m %s", protocol))
|
||||
}
|
||||
ruleArgs = append(ruleArgs, fmt.Sprintf("--dport %s", portSegment))
|
||||
}
|
||||
|
||||
if err := iptables.run(FilterTab, fmt.Sprintf(
|
||||
"-D %s -d %s -p %s --dport %s -j ACCEPT",
|
||||
ForwardChain,
|
||||
dest,
|
||||
protocol,
|
||||
destPort,
|
||||
)); err != nil {
|
||||
ruleArgs = append(ruleArgs, fmt.Sprintf("-j %s", action))
|
||||
ruleSpec := strings.Join(ruleArgs, " ")
|
||||
if operation == "add" {
|
||||
if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasic, ruleSpec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := iptables.run(FilterTab, fmt.Sprintf(
|
||||
"-D %s -s %s -p %s --sport %s -j ACCEPT",
|
||||
ForwardChain,
|
||||
dest,
|
||||
protocol,
|
||||
destPort,
|
||||
)); err != nil {
|
||||
} else {
|
||||
if err := iptables.DeleteRule(iptables.FilterTab, iptables.Chain1PanelBasic, ruleSpec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
global.DB.Where(
|
||||
"protocol = ? AND port = ? AND target_ip = ? AND target_port = ? AND (interface = ? OR (interface IS NULL AND ? = ''))",
|
||||
protocol,
|
||||
srcPort,
|
||||
dest,
|
||||
destPort,
|
||||
iface,
|
||||
iface,
|
||||
).Delete(&model.Forward{})
|
||||
if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelBasic, iptables.BasicFileName); err != nil {
|
||||
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iptables *Iptables) Reload() error {
|
||||
if err := iptables.run(NatTab, "-F "+PreRoutingChain); err != nil {
|
||||
func (i *Iptables) PortForward(info Forward, operation string) error {
|
||||
return iptablesPortForward(info, operation)
|
||||
}
|
||||
|
||||
func (i *Iptables) EnableForward() error {
|
||||
return EnableIptablesForward()
|
||||
}
|
||||
|
||||
func (i *Iptables) ListForward() ([]FireInfo, error) {
|
||||
return iptablesListForward()
|
||||
}
|
||||
|
||||
func EnableIptablesForward() error {
|
||||
if err := cmd.RunDefaultBashC("echo 1 > /proc/sys/net/ipv4/ip_forward"); err != nil {
|
||||
return fmt.Errorf("failed to enable IP forwarding: %w", err)
|
||||
}
|
||||
_ = cmd.RunDefaultBashC("grep -q '^net.ipv4.ip_forward' /etc/sysctl.conf || echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf")
|
||||
_ = cmd.RunDefaultBashC("sysctl -p")
|
||||
|
||||
if err := iptables.AddChainWithAppend(iptables.NatTab, "PREROUTING", iptables.Chain1PanelPreRouting); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.run(NatTab, "-F "+PostRoutingChain); err != nil {
|
||||
if err := iptables.AddChainWithAppend(iptables.NatTab, "POSTROUTING", iptables.Chain1PanelPostRouting); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iptables.run(FilterTab, "-F "+ForwardChain); err != nil {
|
||||
if err := iptables.AddChainWithAppend(iptables.FilterTab, "FORWARD", iptables.Chain1PanelForward); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var rules []model.Forward
|
||||
global.DB.Find(&rules)
|
||||
for _, forward := range rules {
|
||||
if err := iptables.NatAdd(forward.Protocol, forward.Port, forward.TargetIP, forward.TargetPort, forward.Interface, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func iptablesPortForward(info Forward, operation string) error {
|
||||
if operation != "add" && operation != "remove" {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
if info.Protocol == "" || info.Port == "" || info.TargetPort == "" {
|
||||
return fmt.Errorf("protocol, port, and target port are required")
|
||||
}
|
||||
if operation == "add" {
|
||||
if err := iptables.AddForward(info.Protocol, info.Port, info.TargetIP, info.TargetPort, info.Interface, true); err != nil {
|
||||
return err
|
||||
}
|
||||
forwardPersistence()
|
||||
}
|
||||
natList, err := iptables.ListForward(iptables.Chain1PanelPreRouting)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list NAT rules: %w", err)
|
||||
}
|
||||
|
||||
for _, nat := range natList {
|
||||
if nat.Protocol == info.Protocol &&
|
||||
strings.TrimPrefix(nat.SrcPort, ":") == info.Port &&
|
||||
strings.TrimPrefix(nat.DestPort, ":") == info.TargetPort {
|
||||
targetIP := info.TargetIP
|
||||
if targetIP == "" {
|
||||
targetIP = "127.0.0.1"
|
||||
}
|
||||
|
||||
if err := iptables.DeleteForward(nat.Num, info.Protocol, info.Port, targetIP, info.TargetPort, info.Interface); err != nil {
|
||||
return err
|
||||
}
|
||||
forwardPersistence()
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("forward rule not found")
|
||||
}
|
||||
|
||||
func forwardPersistence() {
|
||||
if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelForward, iptables.ForwardFileName); err != nil {
|
||||
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelForward, err)
|
||||
}
|
||||
if err := iptables.SaveRulesToFile(iptables.NatTab, iptables.Chain1PanelPreRouting, iptables.ForwardFileName1); err != nil {
|
||||
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelPreRouting, err)
|
||||
}
|
||||
if err := iptables.SaveRulesToFile(iptables.NatTab, iptables.Chain1PanelPostRouting, iptables.ForwardFileName2); err != nil {
|
||||
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelPostRouting, err)
|
||||
}
|
||||
}
|
||||
|
||||
func iptablesListForward() ([]FireInfo, error) {
|
||||
natList, err := iptables.ListForward(iptables.Chain1PanelPreRouting)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list NAT rules: %w", err)
|
||||
}
|
||||
|
||||
var datas []FireInfo
|
||||
for _, nat := range natList {
|
||||
datas = append(datas, FireInfo{
|
||||
Num: nat.Num,
|
||||
Protocol: nat.Protocol,
|
||||
Port: strings.TrimPrefix(nat.SrcPort, ":"),
|
||||
TargetIP: nat.Destination,
|
||||
TargetPort: strings.TrimPrefix(nat.DestPort, ":"),
|
||||
Interface: nat.InIface,
|
||||
})
|
||||
}
|
||||
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func parsePort(portStr string) (int, error) {
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid port number: %s", portStr)
|
||||
}
|
||||
if port < 1 || port > 65535 {
|
||||
return 0, fmt.Errorf("port out of range: %d", port)
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func normalizePortSpec(port string) (string, error) {
|
||||
value := strings.TrimSpace(port)
|
||||
if value == "" {
|
||||
return "", fmt.Errorf("port is required")
|
||||
}
|
||||
|
||||
separator := ""
|
||||
if strings.Contains(value, "-") {
|
||||
separator = "-"
|
||||
} else if strings.Contains(value, ":") {
|
||||
separator = ":"
|
||||
}
|
||||
|
||||
if separator != "" {
|
||||
parts := strings.Split(value, separator)
|
||||
if len(parts) != 2 {
|
||||
return "", fmt.Errorf("invalid port range: %s", port)
|
||||
}
|
||||
start, err := parsePort(strings.TrimSpace(parts[0]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
end, err := parsePort(strings.TrimSpace(parts[1]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if start > end {
|
||||
return "", fmt.Errorf("invalid port range: %d-%d", start, end)
|
||||
}
|
||||
return fmt.Sprintf("%d:%d", start, end), nil
|
||||
}
|
||||
|
||||
single, err := parsePort(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%d", single), nil
|
||||
}
|
||||
|
||||
198
agent/utils/firewall/client/iptables/common.go
Normal file
198
agent/utils/firewall/client/iptables/common.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
Chain1PanelPreRouting = "1PANEL_PREROUTING"
|
||||
Chain1PanelPostRouting = "1PANEL_POSTROUTING"
|
||||
Chain1PanelForward = "1PANEL_FORWARD"
|
||||
ChainInput = "INPUT"
|
||||
ChainOutput = "OUTPUT"
|
||||
Chain1PanelInput = "1PANEL_INPUT"
|
||||
Chain1PanelOutput = "1PANEL_OUTPUT"
|
||||
Chain1PanelBasicBefore = "1PANEL_BASIC_BEFORE"
|
||||
Chain1PanelBasic = "1PANEL_BASIC"
|
||||
Chain1PanelBasicAfter = "1PANEL_BASIC_AFTER"
|
||||
)
|
||||
|
||||
const (
|
||||
EstablishedRule = "-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -m comment --comment 'ESTABLISHED Whitelist'"
|
||||
IoRuleIn = "-i lo -j ACCEPT -m comment --comment 'Loopback Whitelist'"
|
||||
DropAll = "-j DROP"
|
||||
AllowSSH = "-p tcp --dport ssh -j ACCEPT"
|
||||
)
|
||||
|
||||
var (
|
||||
natListRegex = regexp.MustCompile(`^(\d+)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)(?:\s+(.+?) .+?:(\d{1,5}(?::\d+)?).+?[ :](.+-.+|(?:.+:)?\d{1,5}(?:-\d{1,5})?))?$`)
|
||||
)
|
||||
|
||||
const (
|
||||
ACCEPT = "ACCEPT"
|
||||
DROP = "DROP"
|
||||
REJECT = "REJECT"
|
||||
ANYWHERE = "anywhere"
|
||||
)
|
||||
|
||||
const (
|
||||
FilterTab = "filter"
|
||||
NatTab = "nat"
|
||||
)
|
||||
|
||||
func RunWithStd(tab, rule string) (string, error) {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -t %s %s", cmd.SudoHandleCmd(), tab, rule)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("iptables command failed [table=%s, rule=%s]: %v", tab, rule, err)
|
||||
return stdout, err
|
||||
}
|
||||
return stdout, nil
|
||||
}
|
||||
func RunWithoutIgnrore(tab, rule string) (string, error) {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -t %s %s", cmd.SudoHandleCmd(), tab, rule)
|
||||
if err != nil {
|
||||
return stdout, err
|
||||
}
|
||||
return stdout, nil
|
||||
}
|
||||
func Run(tab, rule string) error {
|
||||
if _, err := RunWithStd(tab, rule); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewChain(tab, chain string) error {
|
||||
return Run(tab, "-N "+chain)
|
||||
}
|
||||
|
||||
func ClearChain(tab, chain string) error {
|
||||
return Run(tab, "-F "+chain)
|
||||
}
|
||||
|
||||
func AddRule(tab, chain, rule string) error {
|
||||
if CheckRuleExist(tab, chain, rule) {
|
||||
return nil
|
||||
}
|
||||
return Run(tab, fmt.Sprintf("-A %s %s", chain, rule))
|
||||
}
|
||||
func DeleteRule(tab, chain, rule string) error {
|
||||
return Run(tab, fmt.Sprintf("-D %s %s", chain, rule))
|
||||
}
|
||||
|
||||
func CheckChainExist(tab, chain string) (bool, error) {
|
||||
stdout, err := RunWithStd(tab, fmt.Sprintf("-S | grep -w 'N %s'", chain))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("check chain %s from tab %s exist failed, err: %v", chain, tab, err)
|
||||
return false, fmt.Errorf("check chain %s from tab %s exist failed, err: %v", chain, tab, err)
|
||||
}
|
||||
if strings.TrimSpace(stdout) == "" {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
func CheckChainBind(tab, parentChain, chain string) (bool, error) {
|
||||
stdout, err := RunWithStd(tab, fmt.Sprintf("-L %s | grep -w %s", parentChain, chain))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("check chain %s from tab %s is bind to %s failed, err: %v", chain, tab, parentChain, err)
|
||||
return false, fmt.Errorf("check chain %s from tab %s is bind to %s failed, err: %v", chain, tab, parentChain, err)
|
||||
}
|
||||
if strings.TrimSpace(stdout) == "" {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
func CheckRuleExist(tab, chain, rule string) bool {
|
||||
_, err := RunWithoutIgnrore(tab, fmt.Sprintf("-C %s %s", chain, rule))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func AddChain(tab, chain string) error {
|
||||
exists, err := CheckChainExist(tab, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check chain %s exist from tab %s failed, err: %w", chain, tab, err)
|
||||
}
|
||||
if !exists {
|
||||
if err := NewChain(tab, chain); err != nil {
|
||||
return fmt.Errorf("add chain %s for tab %s failed, err: %w", tab, chain, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func BindChain(tab, targetChain, chain string, position int) error {
|
||||
line, err := FindChainNum(tab, targetChain, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("find chain %s number from %s failed, err: %w", chain, targetChain, err)
|
||||
}
|
||||
if line == 0 {
|
||||
if err := Run(tab, fmt.Sprintf("-I %s %d -j %s", targetChain, position, chain)); err != nil {
|
||||
return fmt.Errorf("bind chain %s to %s failed, err: %w", chain, targetChain, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func UnbindChain(tab, targetChain, chain string) error {
|
||||
line, err := FindChainNum(tab, targetChain, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("find chain %s number from %s failed, err: %w", chain, targetChain, err)
|
||||
}
|
||||
if line != 0 {
|
||||
return Run(tab, fmt.Sprintf("-D %s %v", targetChain, line))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindChainNum(tab, targetChain, chain string) (int, error) {
|
||||
stdout, err := RunWithStd(tab, fmt.Sprintf("-L %s --line-numbers -n | grep -w %s", targetChain, chain))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to list rules in chain %s: %w", targetChain, err)
|
||||
}
|
||||
|
||||
lineItem := strings.TrimSpace(stdout)
|
||||
lines := strings.Split(lineItem, "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
if fields[1] == chain {
|
||||
itemNum, err := strconv.Atoi(fields[0])
|
||||
return itemNum, err
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func AddChainWithAppend(tab, parentChain, chain string) error {
|
||||
exists, err := CheckChainExist(tab, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check chain %s: %w", chain, err)
|
||||
}
|
||||
if !exists {
|
||||
if err := NewChain(tab, chain); err != nil {
|
||||
return fmt.Errorf("failed to create chain %s: %w", chain, err)
|
||||
}
|
||||
}
|
||||
isBind, err := CheckChainBind(tab, parentChain, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check chain %s bind to %s failed, err: %w", parentChain, chain, err)
|
||||
}
|
||||
if !isBind {
|
||||
if err := AppendChain(tab, parentChain, chain); err != nil {
|
||||
return fmt.Errorf("failed to append %s to %s: %w", chain, parentChain, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func AppendChain(tab string, parentChain, chain string) error {
|
||||
return Run(tab, fmt.Sprintf("-A %s -j %s", parentChain, chain))
|
||||
}
|
||||
155
agent/utils/firewall/client/iptables/filter.go
Normal file
155
agent/utils/firewall/client/iptables/filter.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
type FilterRules struct {
|
||||
ID uint `json:"id"`
|
||||
Protocol string `json:"protocol"`
|
||||
SrcPort uint `json:"srcPort"`
|
||||
DstPort uint `json:"dstPort"`
|
||||
SrcIP string `json:"srcIP"`
|
||||
DstIP string `json:"dstIP"`
|
||||
Strategy string `json:"strategy"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func AddFilterRule(chain string, policy FilterRules) error {
|
||||
if err := validateRuleSafety(policy, chain); err != nil {
|
||||
return err
|
||||
}
|
||||
iptablesArg := fmt.Sprintf("-A %s", chain)
|
||||
if policy.Protocol != "" {
|
||||
iptablesArg += fmt.Sprintf(" -p %s", policy.Protocol)
|
||||
}
|
||||
if policy.SrcPort != 0 {
|
||||
iptablesArg += fmt.Sprintf(" --sport %d", policy.SrcPort)
|
||||
}
|
||||
if policy.DstPort != 0 {
|
||||
iptablesArg += fmt.Sprintf(" --dport %d", policy.DstPort)
|
||||
}
|
||||
if policy.SrcIP != "" {
|
||||
iptablesArg += fmt.Sprintf(" -s %s", policy.SrcIP)
|
||||
}
|
||||
if policy.DstIP != "" {
|
||||
iptablesArg += fmt.Sprintf(" -d %s", policy.DstIP)
|
||||
}
|
||||
iptablesArg += fmt.Sprintf(" -j %s", policy.Strategy)
|
||||
|
||||
return Run(FilterTab, iptablesArg)
|
||||
}
|
||||
|
||||
func DeleteFilterRule(chain string, policy FilterRules) error {
|
||||
iptablesArg := fmt.Sprintf("-D %s", chain)
|
||||
if policy.Protocol != "" {
|
||||
iptablesArg += fmt.Sprintf(" -p %s", policy.Protocol)
|
||||
}
|
||||
if policy.SrcPort != 0 {
|
||||
iptablesArg += fmt.Sprintf(" --sport %d", policy.SrcPort)
|
||||
}
|
||||
if policy.DstPort != 0 {
|
||||
iptablesArg += fmt.Sprintf(" --dport %d", policy.DstPort)
|
||||
}
|
||||
if policy.SrcIP != "" {
|
||||
iptablesArg += fmt.Sprintf(" -s %s", policy.SrcIP)
|
||||
}
|
||||
if policy.DstIP != "" {
|
||||
iptablesArg += fmt.Sprintf(" -d %s", policy.DstIP)
|
||||
}
|
||||
iptablesArg += fmt.Sprintf(" -j %s", policy.Strategy)
|
||||
|
||||
return Run(FilterTab, iptablesArg)
|
||||
}
|
||||
|
||||
func ReadFilterRulesByChain(chain string) ([]FilterRules, error) {
|
||||
var rules []FilterRules
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -t %s -L %s", cmd.SudoHandleCmd(), FilterTab, chain)
|
||||
if err != nil {
|
||||
return rules, fmt.Errorf("load filter fules by chain %s failed, %v", chain, err)
|
||||
}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
fields := strings.Fields(lines[i])
|
||||
if len(fields) < 7 {
|
||||
continue
|
||||
}
|
||||
itemRule := FilterRules{
|
||||
Protocol: fields[1],
|
||||
SrcPort: loadPort("src", fields[6]),
|
||||
DstPort: loadPort("dst", fields[6]),
|
||||
SrcIP: loadIP(fields[3]),
|
||||
DstIP: loadIP(fields[4]),
|
||||
Strategy: fields[0],
|
||||
}
|
||||
rules = append(rules, itemRule)
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func LoadDefaultStrategy(chain string) (string, error) {
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second))
|
||||
stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -t %s -L %s", cmd.SudoHandleCmd(), FilterTab, chain)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("load filter fules by chain %s failed, %v", chain, err)
|
||||
}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for i := len(lines) - 1; i > 0; i-- {
|
||||
fields := strings.Fields(lines[i])
|
||||
if len(fields) < 5 {
|
||||
continue
|
||||
}
|
||||
if fields[0] == "DROP" && fields[1] == "all" && fields[3] == ANYWHERE && fields[4] == ANYWHERE {
|
||||
return DROP, nil
|
||||
}
|
||||
}
|
||||
return ACCEPT, nil
|
||||
}
|
||||
|
||||
func loadPort(position, portStr string) uint {
|
||||
var portItem string
|
||||
if strings.Contains(portStr, "spt:") && position == "src" {
|
||||
portItem = strings.ReplaceAll(portStr, "spt:", "")
|
||||
}
|
||||
if strings.Contains(portStr, "dpt:") && position == "dst" {
|
||||
portItem = strings.ReplaceAll(portStr, "dpt:", "")
|
||||
}
|
||||
if len(portItem) == 0 {
|
||||
return 0
|
||||
}
|
||||
port, _ := strconv.Atoi(portItem)
|
||||
return uint(port)
|
||||
}
|
||||
|
||||
func loadIP(ipStr string) string {
|
||||
if ipStr == ANYWHERE {
|
||||
return ""
|
||||
}
|
||||
return ipStr
|
||||
}
|
||||
|
||||
func validateRuleSafety(rule FilterRules, chain string) error {
|
||||
if strings.ToUpper(rule.Strategy) != "DROP" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if chain == ChainInput || chain == Chain1PanelInput || chain == Chain1PanelBasic {
|
||||
if rule.SrcIP == "0.0.0.0/0" && rule.SrcPort == 0 && rule.DstPort == 0 {
|
||||
return fmt.Errorf("unsafe DROP is not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
if chain == ChainOutput || chain == Chain1PanelOutput || chain == Chain1PanelBasicAfter {
|
||||
if rule.DstIP == "0.0.0.0/0" && rule.DstPort == 0 && rule.SrcPort == 0 {
|
||||
return fmt.Errorf("unsafe DROP is not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
112
agent/utils/firewall/client/iptables/forward.go
Normal file
112
agent/utils/firewall/client/iptables/forward.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func AddForward(protocol, srcPort, dest, destPort, iface string, save bool) error {
|
||||
if dest != "" && dest != "127.0.0.1" && dest != "localhost" {
|
||||
iptablesArg := fmt.Sprintf("-A %s", Chain1PanelPreRouting)
|
||||
if iface != "" {
|
||||
iptablesArg += fmt.Sprintf(" -i %s", iface)
|
||||
}
|
||||
iptablesArg += fmt.Sprintf(" -p %s --dport %s -j DNAT --to-destination %s:%s", protocol, srcPort, dest, destPort)
|
||||
if err := Run(NatTab, iptablesArg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Run(NatTab, fmt.Sprintf("-A %s -d %s -p %s --dport %s -j MASQUERADE", Chain1PanelPostRouting, dest, protocol, destPort)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Run(FilterTab, fmt.Sprintf("-A %s -d %s -p %s --dport %s -j ACCEPT", Chain1PanelForward, dest, protocol, destPort)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Run(FilterTab, fmt.Sprintf("-A %s -s %s -p %s --sport %s -j ACCEPT", Chain1PanelForward, dest, protocol, destPort)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
iptablesArg := fmt.Sprintf("-A %s", Chain1PanelPreRouting)
|
||||
if iface != "" {
|
||||
iptablesArg += fmt.Sprintf(" -i %s", iface)
|
||||
}
|
||||
iptablesArg += fmt.Sprintf(" -p %s --dport %s -j REDIRECT --to-port %s", protocol, srcPort, destPort)
|
||||
if err := Run(NatTab, iptablesArg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteForward(num string, protocol, srcPort, dest, destPort, iface string) error {
|
||||
if err := Run(NatTab, fmt.Sprintf("-D %s %s", Chain1PanelPreRouting, num)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dest != "" && dest != "127.0.0.1" && dest != "localhost" {
|
||||
if err := Run(NatTab, fmt.Sprintf("-D %s -d %s -p %s --dport %s -j MASQUERADE", Chain1PanelPostRouting, dest, protocol, destPort)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Run(FilterTab, fmt.Sprintf("-D %s -d %s -p %s --dport %s -j ACCEPT", Chain1PanelForward, dest, protocol, destPort)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Run(FilterTab, fmt.Sprintf("-D %s -s %s -p %s --sport %s -j ACCEPT", Chain1PanelForward, dest, protocol, destPort)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ListForward(chain ...string) ([]IptablesNatInfo, error) {
|
||||
if len(chain) == 0 {
|
||||
chain = append(chain, Chain1PanelPreRouting)
|
||||
}
|
||||
stdout, err := RunWithStd(NatTab, fmt.Sprintf("-nvL %s --line-numbers", chain[0]))
|
||||
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[13], ":") {
|
||||
match[13] = fmt.Sprintf(":%s", match[13])
|
||||
}
|
||||
forwardList = append(forwardList, IptablesNatInfo{
|
||||
Num: match[1],
|
||||
Target: match[4],
|
||||
Protocol: match[11],
|
||||
InIface: match[7],
|
||||
OutIface: match[8],
|
||||
Opt: match[6],
|
||||
Source: match[9],
|
||||
Destination: match[10],
|
||||
SrcPort: match[12],
|
||||
DestPort: match[13],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return forwardList, nil
|
||||
}
|
||||
|
||||
type IptablesNatInfo struct {
|
||||
Num string `json:"num"`
|
||||
Target string `json:"target"`
|
||||
Protocol string `json:"protocol"`
|
||||
InIface string `json:"inIface"`
|
||||
OutIface string `json:"outIface"`
|
||||
Opt string `json:"opt"`
|
||||
Source string `json:"source"`
|
||||
Destination string `json:"destination"`
|
||||
SrcPort string `json:"srcPort"`
|
||||
DestPort string `json:"destPort"`
|
||||
}
|
||||
103
agent/utils/firewall/client/iptables/persistence.go
Normal file
103
agent/utils/firewall/client/iptables/persistence.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
const (
|
||||
BasicFileName = "1panel_basic.rules"
|
||||
InputFileName = "1panel_input.rules"
|
||||
OutputFileName = "1panel_out.rules"
|
||||
ForwardFileName = "1panel_forward.rules"
|
||||
ForwardFileName1 = "1panel_forward_pre.rules"
|
||||
ForwardFileName2 = "1panel_forward_post.rules"
|
||||
)
|
||||
|
||||
func SaveRulesToFile(tab, chain, fileName string) error {
|
||||
rulesFile := path.Join(global.Dir.FirewallDir, fileName)
|
||||
|
||||
stdout, err := RunWithStd(tab, fmt.Sprintf("-S %s", chain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list %s rules: %w", chain, err)
|
||||
}
|
||||
var rules []string
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, fmt.Sprintf("-A %s", chain)) {
|
||||
rules = append(rules, line)
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(rulesFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create rules file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
writer := bufio.NewWriter(file)
|
||||
for _, rule := range rules {
|
||||
_, err := writer.WriteString(rule + "\n")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write rule to file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writer.Flush(); err != nil {
|
||||
return fmt.Errorf("failed to flush rules to file: %w", err)
|
||||
}
|
||||
|
||||
global.LOG.Infof("persistence rules to %s successful", rulesFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadRulesFromFile(tab, chain, fileName string) error {
|
||||
rulesFile := path.Join(global.Dir.FirewallDir, fileName)
|
||||
if _, err := os.Stat(rulesFile); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(rulesFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open rules file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var rules []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
rules = append(rules, line)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("failed to read rules file: %w", err)
|
||||
}
|
||||
|
||||
if err := ClearChain(tab, chain); err != nil {
|
||||
global.LOG.Warnf("Failed to clear existing rules from %s: %v", chain, err)
|
||||
}
|
||||
|
||||
appliedCount := 0
|
||||
for _, rule := range rules {
|
||||
if strings.HasPrefix(rule, fmt.Sprintf("-A %s", chain)) {
|
||||
ruleArgs := strings.TrimPrefix(rule, "-A ")
|
||||
if err := Run(tab, "-A "+ruleArgs); err != nil {
|
||||
global.LOG.Errorf("Failed to apply rule '%s': %v", rule, err)
|
||||
continue
|
||||
}
|
||||
appliedCount++
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
@@ -97,40 +96,6 @@ func (f *Ufw) ListPort() ([]FireInfo, error) {
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (f *Ufw) ListForward() ([]FireInfo, error) {
|
||||
if err := f.EnableForward(); err != nil {
|
||||
global.LOG.Errorf("init port forward failed, err: %v", err)
|
||||
}
|
||||
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.Split(rule.DestPort, ":")
|
||||
if len(dest) < 2 {
|
||||
continue
|
||||
}
|
||||
if len(dest[0]) == 0 {
|
||||
dest[0] = "127.0.0.1"
|
||||
}
|
||||
list = append(list, FireInfo{
|
||||
Num: rule.Num,
|
||||
Protocol: rule.Protocol,
|
||||
Interface: rule.InIface,
|
||||
Port: rule.SrcPort,
|
||||
TargetIP: dest[0],
|
||||
TargetPort: dest[1],
|
||||
})
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (f *Ufw) ListAddress() ([]FireInfo, error) {
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf("%s status verbose", f.CmdStr)
|
||||
if err != nil {
|
||||
@@ -232,20 +197,15 @@ func (f *Ufw) RichRules(rule FireInfo, operation string) error {
|
||||
}
|
||||
|
||||
func (f *Ufw) PortForward(info Forward, operation string) error {
|
||||
iptables, err := NewIptables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return iptablesPortForward(info, operation)
|
||||
}
|
||||
|
||||
if operation == "add" {
|
||||
err = iptables.NatAdd(info.Protocol, info.Port, info.TargetIP, info.TargetPort, info.Interface, true)
|
||||
} else {
|
||||
err = iptables.NatRemove(info.Num, info.Protocol, info.Port, info.TargetIP, info.TargetPort, info.Interface)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s port forward failed, err: %s", operation, err)
|
||||
}
|
||||
return nil
|
||||
func (f *Ufw) EnableForward() error {
|
||||
return EnableIptablesForward()
|
||||
}
|
||||
|
||||
func (f *Ufw) ListForward() ([]FireInfo, error) {
|
||||
return iptablesListForward()
|
||||
}
|
||||
|
||||
func (f *Ufw) loadInfo(line string, fireType string) FireInfo {
|
||||
@@ -292,39 +252,3 @@ func (f *Ufw) loadInfo(line string, fireType string) FireInfo {
|
||||
|
||||
return itemInfo
|
||||
}
|
||||
|
||||
func (f *Ufw) EnableForward() error {
|
||||
iptables, err := NewIptables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = iptables.Check(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = iptables.NewChain(NatTab, PreRoutingChain)
|
||||
_ = iptables.NewChain(NatTab, PostRoutingChain)
|
||||
_ = iptables.NewChain(FilterTab, ForwardChain)
|
||||
|
||||
if err = f.enableChain(iptables); err != nil {
|
||||
return err
|
||||
}
|
||||
return iptables.Reload()
|
||||
}
|
||||
|
||||
func (f *Ufw) enableChain(iptables *Iptables) error {
|
||||
rules, err := iptables.NatList("PREROUTING")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if rule.Target == PreRoutingChain {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
_ = iptables.AppendChain(NatTab, "PREROUTING", PreRoutingChain)
|
||||
_ = iptables.AppendChain(NatTab, "POSTROUTING", PostRoutingChain)
|
||||
_ = iptables.AppendChain(FilterTab, "FORWARD", ForwardChain)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/core/init/proxy"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -15,7 +13,9 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/core/init/geo"
|
||||
"github.com/1Panel-dev/1Panel/core/init/log"
|
||||
"github.com/1Panel-dev/1Panel/core/init/migration"
|
||||
"github.com/1Panel-dev/1Panel/core/init/proxy"
|
||||
"github.com/1Panel-dev/1Panel/core/init/run"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
@@ -40,6 +40,9 @@ func Start() {
|
||||
gob.Register(psession.SessionUser{})
|
||||
cron.Init()
|
||||
session.Init()
|
||||
if os.Getenv("GIN_MODE") == "" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
hook.Init()
|
||||
InitOthers()
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ export namespace Host {
|
||||
name: string;
|
||||
isExist: boolean;
|
||||
isActive: boolean;
|
||||
isInit: boolean;
|
||||
isBind: boolean;
|
||||
version: string;
|
||||
pingStatus: string;
|
||||
}
|
||||
@@ -263,4 +265,41 @@ export namespace Host {
|
||||
path: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
// Iptables Filter
|
||||
export interface IptablesFilterRuleSearch extends ReqPage {
|
||||
info: string;
|
||||
type: string;
|
||||
}
|
||||
export interface IptablesData {
|
||||
items: IptablesRules[];
|
||||
total: number;
|
||||
defaultStrategy: string;
|
||||
}
|
||||
export interface IptablesRules {
|
||||
id: number;
|
||||
protocol: string;
|
||||
srcPort: number;
|
||||
dstPort: number;
|
||||
srcIP: string;
|
||||
dstIP: string;
|
||||
strategy: string;
|
||||
description: string;
|
||||
}
|
||||
export interface ChainStatus {
|
||||
isBind: boolean;
|
||||
defaultStrategy: string;
|
||||
}
|
||||
export interface IptablesFilterRuleOp {
|
||||
operation: string;
|
||||
id?: number;
|
||||
chain: string;
|
||||
protocol: string;
|
||||
srcIP?: string;
|
||||
srcPort?: number;
|
||||
dstIP?: string;
|
||||
dstPort?: number;
|
||||
strategy: string;
|
||||
description?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import { deepCopy } from '@/utils/util';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
// firewall
|
||||
export const loadFireBaseInfo = () => {
|
||||
return http.get<Host.FirewallBase>(`/hosts/firewall/base`);
|
||||
export const loadFireBaseInfo = (tab: string) => {
|
||||
return http.post<Host.FirewallBase>(`/hosts/firewall/base`, { name: tab }, TimeoutEnum.T_40S);
|
||||
};
|
||||
export const searchFireRule = (params: Host.RuleSearch) => {
|
||||
return http.post<ResPage<Host.RuleInfo>>(`/hosts/firewall/search`, params, TimeoutEnum.T_40S);
|
||||
@@ -44,6 +44,23 @@ export const batchOperateRule = (params: Host.BatchRule) => {
|
||||
return http.post(`/hosts/firewall/batch`, params, TimeoutEnum.T_60S);
|
||||
};
|
||||
|
||||
// Iptables Filter
|
||||
export const searchFilterRules = (params: Host.IptablesFilterRuleSearch) => {
|
||||
return http.post<Host.IptablesData>(`/hosts/firewall/filter/rule/search`, params);
|
||||
};
|
||||
export const loadChainStatus = (name: string) => {
|
||||
return http.post<Host.ChainStatus>(`/hosts/firewall/filter/chain/status`, { name: name }, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const operateFilterRule = (params: Host.IptablesFilterRuleOp) => {
|
||||
return http.post(`/hosts/firewall/filter/rule/operate`, params, TimeoutEnum.T_40S);
|
||||
};
|
||||
export const batchOperateFilterRule = (params: { rules: Host.IptablesFilterRuleOp[] }) => {
|
||||
return http.post(`/hosts/firewall/filter/rule/batch`, params, TimeoutEnum.T_40S);
|
||||
};
|
||||
export const operateFilterChain = (name: string, op: string) => {
|
||||
return http.post(`/hosts/firewall/filter/operate`, { name: name, operate: op }, TimeoutEnum.T_60S);
|
||||
};
|
||||
|
||||
// monitors
|
||||
export const loadMonitor = (param: Host.MonitorSearch) => {
|
||||
return http.post<Array<Host.MonitorData>>(`/hosts/monitor/search`, param);
|
||||
|
||||
@@ -374,6 +374,7 @@ const message = {
|
||||
config: 'Configuration | Configurations',
|
||||
ssh: 'SSH Settings',
|
||||
firewall: 'Firewall',
|
||||
filter: 'Filter',
|
||||
ssl: 'Certificate | Certificates',
|
||||
database: 'Database | Databases',
|
||||
aiTools: 'AI',
|
||||
@@ -2845,6 +2846,8 @@ const message = {
|
||||
firewall: {
|
||||
create: 'Create rule',
|
||||
edit: 'Edit rule',
|
||||
advancedControl: 'Advanced Control',
|
||||
advancedControlNotAvailable: 'Currently using {0} firewall, advanced rules only support iptables',
|
||||
ccDeny: 'CC Protection',
|
||||
ipWhiteList: 'IP allowlist',
|
||||
ipBlockList: 'IP blocklist',
|
||||
@@ -2885,7 +2888,7 @@ const message = {
|
||||
portHelper2: 'Range port, e.g. 8080-8089',
|
||||
changeStrategyHelper:
|
||||
'Change [{1}] {0} strategy to [{2}]. After setting, {0} will access {2} externally. Do you want to continue?',
|
||||
portHelper: 'Multiple ports can be entered, e.g. 80,81, or range ports, e.g. 80-88',
|
||||
|
||||
strategy: 'Strategy',
|
||||
accept: 'Accept',
|
||||
drop: 'Drop',
|
||||
@@ -2915,6 +2918,37 @@ const message = {
|
||||
exportHelper: 'About to export {0} firewall rules. Continue?',
|
||||
importSuccess: 'Successfully imported {0} rules',
|
||||
importPartialSuccess: 'Import completed: {0} succeeded, {1} failed',
|
||||
|
||||
basicStatus: 'Current chain {0} status is unbound. Added firewall rules will take effect after binding!',
|
||||
baseIptables: 'Iptables Service',
|
||||
forwardIptables: 'Iptables Port Forwarding Service',
|
||||
advanceIptables: 'Iptables Advanced Configuration Service',
|
||||
initMsg: 'About to initialize {0}, continue?',
|
||||
initHelper:
|
||||
'Detected that {0} is not initialized. Please click the initialization button in the top status bar to configure!',
|
||||
bindHelper: 'Bind - Firewall rules will only take effect when the status is bound. Confirm?',
|
||||
unbindHelper:
|
||||
'Unbind - When unbound, all added firewall rules will become invalid. Proceed with caution. Confirm?',
|
||||
defaultStrategy: 'Default policy for current chain {0} is {1}',
|
||||
defaultStrategy2:
|
||||
'Default policy for current chain {0} is {1}, current status is unbound. Added firewall rules will take effect after binding!',
|
||||
filterRule: 'Filter Rule',
|
||||
filterHelper:
|
||||
'Filter rules allow you to control network traffic at the INPUT/OUTPUT level. Configure carefully to avoid locking the system.',
|
||||
chain: 'Chain',
|
||||
targetChain: 'Target Chain',
|
||||
sourceIP: 'Source IP',
|
||||
destIP: 'Destination IP',
|
||||
inboundDirection: 'Inbound Direction',
|
||||
outboundDirection: 'Outbound Direction',
|
||||
destPort: 'Destination Port',
|
||||
action: 'Action',
|
||||
reject: 'Reject',
|
||||
sourceIPHelper: 'CIDR format, e.g., 192.168.1.0/24. Leave empty for all addresses',
|
||||
destIPHelper: 'CIDR format, e.g., 10.0.0.0/8. Leave empty for all addresses',
|
||||
portHelper: '0 means any port',
|
||||
allPorts: 'All Ports',
|
||||
deleteRuleConfirm: 'Will delete {0} rules. Continue?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: 'Runtime',
|
||||
|
||||
@@ -2861,7 +2861,7 @@ const message = {
|
||||
portHelper2: 'Rango de puertos, ej. 8080-8089',
|
||||
changeStrategyHelper:
|
||||
'Cambiar estrategia de {0} [{1}] a [{2}]. Después de configurarla, {0} tendrá acceso externo como {2}. ¿Deseas continuar?',
|
||||
portHelper: 'Se pueden ingresar múltiples puertos, ej. 80,81, o rangos, ej. 80-88',
|
||||
|
||||
strategy: 'Estrategia',
|
||||
accept: 'Aceptar',
|
||||
drop: 'Rechazar',
|
||||
@@ -2891,6 +2891,39 @@ const message = {
|
||||
exportHelper: 'A punto de exportar {0} reglas de firewall. ¿Continuar?',
|
||||
importSuccess: 'Se importaron correctamente {0} reglas',
|
||||
importPartialSuccess: 'Importación completada: {0} correctas, {1} fallidas',
|
||||
|
||||
basicStatus:
|
||||
'El estado actual de la cadena {0} es no vinculado. ¡Las reglas de firewall agregadas surtirán efecto después de la vinculación!',
|
||||
baseIptables: 'Servicio Iptables',
|
||||
forwardIptables: 'Servicio de Reenvío de Puertos Iptables',
|
||||
advanceIptables: 'Servicio de Configuración Avanzada de Iptables',
|
||||
initMsg: 'A punto de inicializar {0}, ¿continuar?',
|
||||
initHelper:
|
||||
'Se detectó que {0} no está inicializado. ¡Haga clic en el botón de inicialización en la barra de estado superior para configurar!',
|
||||
bindHelper:
|
||||
'Vincular: las reglas de firewall solo surtirán efecto cuando el estado esté vinculado. ¿Confirmar?',
|
||||
unbindHelper:
|
||||
'Desvincular: al desvincular, todas las reglas de firewall agregadas se volverán inválidas. Proceda con precaución. ¿Confirmar?',
|
||||
defaultStrategy: 'La política predeterminada para la cadena actual {0} es {1}',
|
||||
defaultStrategy2:
|
||||
'La política predeterminada para la cadena actual {0} es {1}, el estado actual es no vinculado. ¡Las reglas de firewall agregadas surtirán efecto después de la vinculación!',
|
||||
filterRule: 'Regla de Filtro',
|
||||
filterHelper:
|
||||
'Las reglas de filtro le permiten controlar el tráfico de red a nivel INPUT/OUTPUT. Configure con cuidado para evitar bloquear el sistema.',
|
||||
chain: 'Cadena',
|
||||
targetChain: 'Cadena de Destino',
|
||||
sourceIP: 'IP de Origen',
|
||||
destIP: 'IP de Destino',
|
||||
inboundDirection: 'Dirección de Entrada',
|
||||
outboundDirection: 'Dirección de Salida',
|
||||
destPort: 'Puerto de Destino',
|
||||
action: 'Acción',
|
||||
reject: 'Rechazar',
|
||||
sourceIPHelper: 'Formato CIDR, ej. 192.168.1.0/24. Dejar vacío para todas las direcciones',
|
||||
destIPHelper: 'Formato CIDR, ej. 10.0.0.0/8. Dejar vacío para todas las direcciones',
|
||||
portHelper: '0 significa cualquier puerto',
|
||||
allPorts: 'Todos los Puertos',
|
||||
deleteRuleConfirm: 'Se eliminarán {0} reglas. ¿Continuar?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: 'Runtime',
|
||||
|
||||
@@ -2804,7 +2804,7 @@ const message = {
|
||||
portHelper2: '範囲ポート、例えば8080-8089',
|
||||
changeStrategyHelper:
|
||||
'[{1}] {0}戦略を[{2}]に変更します。設定後、{0}は外部から{2}にアクセスします。続けたいですか?',
|
||||
portHelper: '複数のポートを入力できます。80,81、または範囲ポート、例えば80-88',
|
||||
|
||||
strategy: '戦略',
|
||||
accept: '受け入れる',
|
||||
drop: '落とす',
|
||||
@@ -2834,6 +2834,38 @@ const message = {
|
||||
exportHelper: '{0} 件のファイアウォールルールをエクスポートします。続行しますか?',
|
||||
importSuccess: '{0} 件のルールを正常にインポートしました',
|
||||
importPartialSuccess: 'インポート完了: {0} 件成功、{1} 件失敗',
|
||||
|
||||
basicStatus:
|
||||
'現在のチェーン {0} の状態は未バインドです。追加されたファイアウォールルールはバインド後に有効になります!',
|
||||
baseIptables: 'Iptables サービス',
|
||||
forwardIptables: 'Iptables ポート転送サービス',
|
||||
advanceIptables: 'Iptables 高度な設定サービス',
|
||||
initMsg: '{0} を初期化します。続行しますか?',
|
||||
initHelper:
|
||||
'{0} が初期化されていないことを検出しました。上部ステータスバーの初期化ボタンをクリックして設定してください!',
|
||||
bindHelper: 'バインド - ファイアウォールルールは状態がバインドされている場合のみ有効になります。確認しますか?',
|
||||
unbindHelper:
|
||||
'アンバインド - アンバインドすると、追加されたすべてのファイアウォールルールが無効になります。注意して操作してください。確認しますか?',
|
||||
defaultStrategy: '現在のチェーン {0} のデフォルトポリシーは {1} です',
|
||||
defaultStrategy2:
|
||||
'現在のチェーン {0} のデフォルトポリシーは {1} です。現在の状態は未バインドです。追加されたファイアウォールルールはバインド後に有効になります!',
|
||||
filterRule: 'フィルタールール',
|
||||
filterHelper:
|
||||
'フィルタールールを使用すると、INPUT/OUTPUT レベルでネットワークトラフィックを制御できます。システムをロックしないように注意して設定してください。',
|
||||
chain: 'チェーン',
|
||||
targetChain: 'ターゲットチェーン',
|
||||
sourceIP: '送信元 IP',
|
||||
destIP: '宛先 IP',
|
||||
inboundDirection: 'インバウンド方向',
|
||||
outboundDirection: 'アウトバウンド方向',
|
||||
destPort: '宛先ポート',
|
||||
action: 'アクション',
|
||||
reject: '拒否',
|
||||
sourceIPHelper: 'CIDR 形式、例: 192.168.1.0/24。すべてのアドレスの場合は空のまま',
|
||||
destIPHelper: 'CIDR 形式、例: 10.0.0.0/8。すべてのアドレスの場合は空のまま',
|
||||
portHelper: '0 は任意のポートを意味します',
|
||||
allPorts: 'すべてのポート',
|
||||
deleteRuleConfirm: '{0} 個のルールを削除します。続行しますか?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: 'ランタイム',
|
||||
|
||||
@@ -2755,7 +2755,7 @@ const message = {
|
||||
portHelper2: '포트 범위, 예: 8080-8089',
|
||||
changeStrategyHelper:
|
||||
'[{1}] {0} 전략을 [{2}]로 변경합니다. 설정 후 {0}은(는) {2}로 외부 접근을 허용합니다. 계속하시겠습니까?',
|
||||
portHelper: '여러 포트를 입력할 수 있습니다. 예: 80, 81 또는 포트 범위, 예: 80-88',
|
||||
|
||||
strategy: '전략',
|
||||
accept: '허용',
|
||||
drop: '차단',
|
||||
@@ -2785,6 +2785,36 @@ const message = {
|
||||
exportHelper: '{0}개의 방화벽 규칙을 내보내려고 합니다. 계속하시겠습니까?',
|
||||
importSuccess: '{0}개의 규칙을 성공적으로 가져왔습니다',
|
||||
importPartialSuccess: '가져오기 완료: 성공 {0}건, 실패 {1}건',
|
||||
|
||||
basicStatus: '현재 체인 {0} 상태가 바인딩되지 않았습니다. 추가된 방화벽 규칙은 바인딩 후에 효과가 발생합니다!',
|
||||
baseIptables: 'Iptables 서비스',
|
||||
forwardIptables: 'Iptables 포트 포워딩 서비스',
|
||||
advanceIptables: 'Iptables 고급 구성 서비스',
|
||||
initMsg: '{0}을(를) 초기화하려고 합니다. 계속하시겠습니까?',
|
||||
initHelper: '{0}이(가) 초기화되지 않았습니다. 상단 상태 표시줄의 초기화 버튼을 클릭하여 구성하세요!',
|
||||
bindHelper: '바인딩 - 방화벽 규칙은 상태가 바인딩된 경우에만 효과가 있습니다. 확인하시겠습니까?',
|
||||
unbindHelper:
|
||||
'바인딩 해제 - 바인딩 해제 시 추가된 모든 방화벽 규칙이 무효화됩니다. 주의하여 진행하세요. 확인하시겠습니까?',
|
||||
defaultStrategy: '현재 체인 {0}의 기본 정책은 {1}입니다',
|
||||
defaultStrategy2:
|
||||
'현재 체인 {0}의 기본 정책은 {1}입니다. 현재 상태는 바인딩되지 않았습니다. 추가된 방화벽 규칙은 바인딩 후에 효과가 발생합니다!',
|
||||
filterRule: '필터 규칙',
|
||||
filterHelper:
|
||||
'필터 규칙을 사용하면 INPUT/OUTPUT 수준에서 네트워크 트래픽을 제어할 수 있습니다. 시스템 잠금을 방지하기 위해 주의하여 구성하세요.',
|
||||
chain: '체인',
|
||||
targetChain: '대상 체인',
|
||||
sourceIP: '소스 IP',
|
||||
destIP: '대상 IP',
|
||||
inboundDirection: '인바운드 방향',
|
||||
outboundDirection: '아웃바운드 방향',
|
||||
destPort: '대상 포트',
|
||||
action: '동작',
|
||||
reject: '거부',
|
||||
sourceIPHelper: 'CIDR 형식, 예: 192.168.1.0/24. 모든 주소의 경우 비워 둠',
|
||||
destIPHelper: 'CIDR 형식, 예: 10.0.0.0/8. 모든 주소의 경우 비워 둠',
|
||||
portHelper: '0은 모든 포트를 의미합니다',
|
||||
allPorts: '모든 포트',
|
||||
deleteRuleConfirm: '{0}개의 규칙을 삭제합니다. 계속하시겠습니까?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: '실행 환경',
|
||||
|
||||
@@ -2869,7 +2869,7 @@ const message = {
|
||||
portHelper2: 'Port rentang, contohnya 8080-8089',
|
||||
changeStrategyHelper:
|
||||
'Tukar strategi {0} [{1}] kepada [{2}]. Selepas tetapan, {0} akan mengakses {2} secara luaran. Adakah anda mahu meneruskan?',
|
||||
portHelper: 'Pelbagai port boleh dimasukkan, contohnya 80,81, atau rentang port, contohnya 80-88',
|
||||
|
||||
strategy: 'Strategi',
|
||||
accept: 'Terima',
|
||||
drop: 'Lumpuhkan',
|
||||
@@ -2899,6 +2899,37 @@ const message = {
|
||||
exportHelper: 'Akan mengeksport {0} peraturan firewall. Teruskan?',
|
||||
importSuccess: '{0} peraturan berjaya diimport',
|
||||
importPartialSuccess: 'Import selesai: {0} berjaya, {1} gagal',
|
||||
|
||||
basicStatus:
|
||||
'Status rantaian semasa {0} adalah tidak terikat. Peraturan firewall yang ditambah akan berkuat kuasa selepas pengikatan!',
|
||||
baseIptables: 'Perkhidmatan Iptables',
|
||||
forwardIptables: 'Perkhidmatan Penerusan Port Iptables',
|
||||
advanceIptables: 'Perkhidmatan Konfigurasi Lanjutan Iptables',
|
||||
initMsg: 'Akan memulakan {0}, teruskan?',
|
||||
initHelper: 'Mengesan {0} tidak dimulakan. Sila klik butang pemulaan di bar status atas untuk mengkonfigurasi!',
|
||||
bindHelper: 'Ikat - Peraturan firewall hanya akan berkuat kuasa apabila status terikat. Sahkan?',
|
||||
unbindHelper:
|
||||
'Nyahikat - Apabila tidak terikat, semua peraturan firewall yang ditambah akan menjadi tidak sah. Teruskan dengan berhati-hati. Sahkan?',
|
||||
defaultStrategy: 'Dasar lalai untuk rantaian semasa {0} adalah {1}',
|
||||
defaultStrategy2:
|
||||
'Dasar lalai untuk rantaian semasa {0} adalah {1}, status semasa adalah tidak terikat. Peraturan firewall yang ditambah akan berkuat kuasa selepas pengikatan!',
|
||||
filterRule: 'Peraturan Penapis',
|
||||
filterHelper:
|
||||
'Peraturan penapis membolehkan anda mengawal trafik rangkaian pada tahap INPUT/OUTPUT. Konfigurasikan dengan berhati-hati untuk mengelakkan mengunci sistem.',
|
||||
chain: 'Rantai',
|
||||
targetChain: 'Rantai Sasaran',
|
||||
sourceIP: 'IP Sumber',
|
||||
destIP: 'IP Destinasi',
|
||||
inboundDirection: 'Arah Masuk',
|
||||
outboundDirection: 'Arah Keluar',
|
||||
destPort: 'Port Destinasi',
|
||||
action: 'Tindakan',
|
||||
reject: 'Tolak',
|
||||
sourceIPHelper: 'Format CIDR, cth. 192.168.1.0/24. Biarkan kosong untuk semua alamat',
|
||||
destIPHelper: 'Format CIDR, cth. 10.0.0.0/8. Biarkan kosong untuk semua alamat',
|
||||
portHelper: '0 bermaksud mana-mana port',
|
||||
allPorts: 'Semua Port',
|
||||
deleteRuleConfirm: 'Akan memadam {0} peraturan. Teruskan?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: 'Runtime',
|
||||
|
||||
@@ -2874,7 +2874,7 @@ const message = {
|
||||
portHelper2: 'Faixa de portas, ex.: 8080-8089',
|
||||
changeStrategyHelper:
|
||||
'Alterar a estratégia [{1}] {0} para [{2}]. Após a definição, {0} acessará {2} externamente. Deseja continuar?',
|
||||
portHelper: 'Várias portas podem ser inseridas, ex.: 80,81, ou faixas de portas, ex.: 80-88',
|
||||
|
||||
strategy: 'Estratégia',
|
||||
accept: 'Aceitar',
|
||||
drop: 'Bloquear',
|
||||
@@ -2905,6 +2905,39 @@ const message = {
|
||||
exportHelper: 'Prestes a exportar {0} regras de firewall. Continuar?',
|
||||
importSuccess: '{0} regras importadas com sucesso',
|
||||
importPartialSuccess: 'Importação concluída: {0} sucesso, {1} falha',
|
||||
|
||||
basicStatus:
|
||||
'O status atual da cadeia {0} é não vinculado. As regras de firewall adicionadas entrarão em vigor após a vinculação!',
|
||||
baseIptables: 'Serviço Iptables',
|
||||
forwardIptables: 'Serviço de Encaminhamento de Porta Iptables',
|
||||
advanceIptables: 'Serviço de Configuração Avançada do Iptables',
|
||||
initMsg: 'Prestes a inicializar {0}, continuar?',
|
||||
initHelper:
|
||||
'Detectado que {0} não está inicializado. Clique no botão de inicialização na barra de status superior para configurar!',
|
||||
bindHelper:
|
||||
'Vincular - As regras de firewall só entrarão em vigor quando o status estiver vinculado. Confirmar?',
|
||||
unbindHelper:
|
||||
'Desvincular - Quando desvinculado, todas as regras de firewall adicionadas se tornarão inválidas. Prossiga com cautela. Confirmar?',
|
||||
defaultStrategy: 'A política padrão para a cadeia atual {0} é {1}',
|
||||
defaultStrategy2:
|
||||
'A política padrão para a cadeia atual {0} é {1}, o status atual é não vinculado. As regras de firewall adicionadas entrarão em vigor após a vinculação!',
|
||||
filterRule: 'Regra de Filtro',
|
||||
filterHelper:
|
||||
'As regras de filtro permitem controlar o tráfego de rede no nível INPUT/OUTPUT. Configure com cuidado para evitar bloquear o sistema.',
|
||||
chain: 'Cadeia',
|
||||
targetChain: 'Cadeia de Destino',
|
||||
sourceIP: 'IP de Origem',
|
||||
destIP: 'IP de Destino',
|
||||
inboundDirection: 'Direção de Entrada',
|
||||
outboundDirection: 'Direção de Saída',
|
||||
destPort: 'Porta de Destino',
|
||||
action: 'Ação',
|
||||
reject: 'Rejeitar',
|
||||
sourceIPHelper: 'Formato CIDR, ex. 192.168.1.0/24. Deixe vazio para todos os endereços',
|
||||
destIPHelper: 'Formato CIDR, ex. 10.0.0.0/8. Deixe vazio para todos os endereços',
|
||||
portHelper: '0 significa qualquer porta',
|
||||
allPorts: 'Todas as Portas',
|
||||
deleteRuleConfirm: 'Excluirá {0} regras. Continuar?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: 'Runtime',
|
||||
|
||||
@@ -2868,7 +2868,7 @@ const message = {
|
||||
portHelper2: 'Диапазон портов, например 8080-8089',
|
||||
changeStrategyHelper:
|
||||
'Изменить стратегию {0} [{1}] на [{2}]. После установки {0} будет иметь внешний доступ {2}. Хотите продолжить?',
|
||||
portHelper: 'Можно ввести несколько портов, например 80,81, или диапазон портов, например 80-88',
|
||||
|
||||
strategy: 'Стратегия',
|
||||
accept: 'Принять',
|
||||
drop: 'Отбросить',
|
||||
@@ -2899,6 +2899,38 @@ const message = {
|
||||
exportHelper: 'Собираюсь экспортировать {0} правил брандмауэра. Продолжить?',
|
||||
importSuccess: 'Успешно импортировано {0} правил',
|
||||
importPartialSuccess: 'Импорт завершён: {0} успешно, {1} с ошибкой',
|
||||
|
||||
basicStatus:
|
||||
'Текущий статус цепочки {0} - не привязан. Добавленные правила брандмауэра вступят в силу после привязки!',
|
||||
baseIptables: 'Сервис Iptables',
|
||||
forwardIptables: 'Сервис Переадресации Порта Iptables',
|
||||
advanceIptables: 'Сервис Расширенной Конфигурации Iptables',
|
||||
initMsg: 'Собираюсь инициализировать {0}, продолжить?',
|
||||
initHelper:
|
||||
'Обнаружено, что {0} не инициализирован. Нажмите кнопку инициализации в верхней строке состояния для настройки!',
|
||||
bindHelper: 'Привязать - Правила брандмауэра вступят в силу только когда статус привязан. Подтвердить?',
|
||||
unbindHelper:
|
||||
'Отвязать - При отвязке все добавленные правила брандмауэра станут недействительными. Действуйте осторожно. Подтвердить?',
|
||||
defaultStrategy: 'Политика по умолчанию для текущей цепочки {0} - {1}',
|
||||
defaultStrategy2:
|
||||
'Политика по умолчанию для текущей цепочки {0} - {1}, текущий статус - не привязан. Добавленные правила брандмауэра вступят в силу после привязки!',
|
||||
filterRule: 'Правило Фильтра',
|
||||
filterHelper:
|
||||
'Правила фильтра позволяют управлять сетевым трафиком на уровне INPUT/OUTPUT. Настраивайте осторожно, чтобы избежать блокировки системы.',
|
||||
chain: 'Цепочка',
|
||||
targetChain: 'Целевая Цепочка',
|
||||
sourceIP: 'Исходный IP',
|
||||
destIP: 'Целевой IP',
|
||||
inboundDirection: 'Входящее Направление',
|
||||
outboundDirection: 'Исходящее Направление',
|
||||
destPort: 'Целевой Порт',
|
||||
action: 'Действие',
|
||||
reject: 'Отклонить',
|
||||
sourceIPHelper: 'Формат CIDR, напр. 192.168.1.0/24. Оставьте пустым для всех адресов',
|
||||
destIPHelper: 'Формат CIDR, напр. 10.0.0.0/8. Оставьте пустым для всех адресов',
|
||||
portHelper: '0 означает любой порт',
|
||||
allPorts: 'Все Порта',
|
||||
deleteRuleConfirm: 'Удалит {0} правил. Продолжить?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: 'Среда выполнения',
|
||||
|
||||
@@ -2927,7 +2927,6 @@ const message = {
|
||||
portHelper2: 'Aralık portu, ör. 8080-8089',
|
||||
changeStrategyHelper:
|
||||
'[{1}] {0} stratejisini [{2}] olarak değiştirin. Ayar yapıldıktan sonra {0}, dışarıdan {2} erişimi sağlayacak. Devam etmek istiyor musunuz?',
|
||||
portHelper: 'Birden fazla port girilebilir, ör. 80,81 veya aralık portları, ör. 80-88',
|
||||
strategy: 'Strateji',
|
||||
accept: 'Kabul Et',
|
||||
drop: 'Reddet',
|
||||
@@ -2957,6 +2956,38 @@ const message = {
|
||||
exportHelper: '{0} güvenlik duvarı kuralını dışa aktarmak üzere. Devam etmek istiyor musunuz?',
|
||||
importSuccess: '{0} kural başarıyla içe aktarıldı',
|
||||
importPartialSuccess: 'İçe aktarma tamamlandı: {0} başarılı, {1} başarısız',
|
||||
|
||||
basicStatus:
|
||||
'Mevcut zincir {0} durumu bağlı değil. Eklenen güvenlik duvarı kuralları bağlandıktan sonra etkili olacak!',
|
||||
baseIptables: 'Iptables Servisi',
|
||||
forwardIptables: 'Iptables Port Yönlendirme Servisi',
|
||||
advanceIptables: 'Iptables Gelişmiş Yapılandırma Servisi',
|
||||
initMsg: '{0} başlatılmak üzere, devam etmek istiyor musunuz?',
|
||||
initHelper:
|
||||
'{0} başlatılmadığı tespit edildi. Lütfen üst durum çubuğundaki başlatma düğmesine tıklayarak yapılandırın!',
|
||||
bindHelper: 'Bağla - Güvenlik duvarı kuralları yalnızca durum bağlı olduğunda etkili olur. Onaylıyor musunuz?',
|
||||
unbindHelper:
|
||||
'Bağlantıyı Kaldır - Bağlantı kaldırıldığında, eklenen tüm güvenlik duvarı kuralları geçersiz olacaktır. Dikkatli ilerleyin. Onaylıyor musunuz?',
|
||||
defaultStrategy: 'Mevcut zincir {0} için varsayılan politika {1}',
|
||||
defaultStrategy2:
|
||||
'Mevcut zincir {0} için varsayılan politika {1}, mevcut durum bağlı değil. Eklenen güvenlik duvarı kuralları bağlandıktan sonra etkili olacak!',
|
||||
filterRule: 'Filtre Kuralı',
|
||||
filterHelper:
|
||||
'Filtre kuralları, INPUT/OUTPUT seviyesinde ağ trafiğini kontrol etmenize izin verir. Sistemi kilitlememek için dikkatli yapılandırın.',
|
||||
chain: 'Zincir',
|
||||
targetChain: 'Hedef Zincir',
|
||||
sourceIP: 'Kaynak IP',
|
||||
destIP: 'Hedef IP',
|
||||
inboundDirection: 'Gelen Yön',
|
||||
outboundDirection: 'Giden Yön',
|
||||
destPort: 'Hedef Port',
|
||||
action: 'Eylem',
|
||||
reject: 'Reddet',
|
||||
sourceIPHelper: 'CIDR formatı, örn. 192.168.1.0/24. Tüm adresler için boş bırakın',
|
||||
destIPHelper: 'CIDR formatı, örn. 10.0.0.0/8. Tüm adresler için boş bırakın',
|
||||
portHelper: '0 herhangi bir port anlamına gelir',
|
||||
allPorts: 'Tüm Portlar',
|
||||
deleteRuleConfirm: '{0} kural silinecek. Devam etmek istiyor musunuz?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: 'Çalışma Zamanı',
|
||||
|
||||
@@ -2708,6 +2708,33 @@ const message = {
|
||||
exportHelper: '即將導出 {0} 條防火牆規則,是否繼續?',
|
||||
importSuccess: '成功匯入 {0} 條規則',
|
||||
importPartialSuccess: '匯入完成:成功 {0} 條,失敗 {1} 條',
|
||||
|
||||
basicStatus: '目前鏈 {0} 狀態為未綁定,已新增的防火牆規則需要綁定後生效!',
|
||||
baseIptables: 'Iptables 服務',
|
||||
forwardIptables: 'Iptables 端口轉發服務',
|
||||
advanceIptables: 'Iptables 進階設定服務',
|
||||
initMsg: '即將初始化 {0}, 是否繼續?',
|
||||
initHelper: '偵測到 {0} 未初始化,請點選頂部狀態列的初始化按鈕進行設定!',
|
||||
bindHelper: '綁定 僅當狀態為綁定時,防火牆規則才能生效,是否確認?',
|
||||
unbindHelper: '解除綁定 解除綁定時,已新增的所有防火牆規則將失效,請謹慎操作,是否確認?',
|
||||
defaultStrategy: '目前鏈 {0} 的預設策略為 {1}',
|
||||
defaultStrategy2: '目前鏈 {0} 的預設策略為 {1},目前狀態為未綁定,已新增的防火牆規則需要綁定後生效!',
|
||||
filterRule: 'Filter 規則',
|
||||
filterHelper: 'Filter 規則允許您在 INPUT/OUTPUT 層級控制網路流量。請謹慎設定,避免鎖定系統。',
|
||||
chain: '鏈',
|
||||
targetChain: '目標鏈',
|
||||
sourceIP: '來源 IP',
|
||||
destIP: '目標 IP',
|
||||
inboundDirection: '入站方向',
|
||||
outboundDirection: '出站方向',
|
||||
destPort: '目標端口',
|
||||
action: '動作',
|
||||
reject: '拒絕',
|
||||
sourceIPHelper: 'CIDR 格式,如 192.168.1.0/24,留空表示所有地址',
|
||||
destIPHelper: 'CIDR 格式,如 10.0.0.0/8,留空表示所有地址',
|
||||
portHelper: '0 表示任意端口',
|
||||
allPorts: '所有端口',
|
||||
deleteRuleConfirm: '將刪除 {0} 條規則,是否繼續?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: '執行環境',
|
||||
|
||||
@@ -362,6 +362,7 @@ const message = {
|
||||
config: '配置',
|
||||
ssh: 'SSH 管理',
|
||||
firewall: '防火墙',
|
||||
filter: '过滤器',
|
||||
ssl: '证书',
|
||||
database: '数据库',
|
||||
aiTools: 'AI',
|
||||
@@ -2641,6 +2642,8 @@ const message = {
|
||||
firewall: {
|
||||
create: '创建规则',
|
||||
edit: '编辑规则',
|
||||
advancedControl: '高级控制',
|
||||
advancedControlNotAvailable: '当前使用 {0} 防火墙,高级规则仅支持 iptables',
|
||||
ccDeny: 'CC 防护',
|
||||
ipWhiteList: 'IP 白名单',
|
||||
ipBlockList: 'IP 黑名单',
|
||||
@@ -2704,6 +2707,33 @@ const message = {
|
||||
exportHelper: '即将导出 {0} 条防火墙规则,是否继续?',
|
||||
importSuccess: '成功导入 {0} 条规则',
|
||||
importPartialSuccess: '导入完成:成功 {0} 条,失败 {1} 条',
|
||||
|
||||
basicStatus: '当前链 {0} 状态为未绑定,已添加的防火墙规则需要绑定后生效!',
|
||||
baseIptables: 'Iptables 服务',
|
||||
forwardIptables: 'Iptables 端口转发服务',
|
||||
advanceIptables: 'Iptables 高级配置服务',
|
||||
initMsg: '即将初始化 {0}, 是否继续?',
|
||||
initHelper: '检测到 {0} 未初始化,请点击顶部状态栏的初始化按钮进行配置!',
|
||||
bindHelper: '绑定 仅当状态为绑定时,防火墙规则才能生效,是否确认?',
|
||||
unbindHelper: '解绑 解除绑定时,已添加的所有防火墙规则将失效,请谨慎操作,是否确认?',
|
||||
defaultStrategy: '当前链 {0} 的默认策略为 {1}',
|
||||
defaultStrategy2: '当前链 {0} 的默认策略为 {1},当前状态为未绑定,已添加的防火墙规则需要绑定后生效!',
|
||||
filterRule: 'Filter 规则',
|
||||
filterHelper: 'Filter 规则允许您在 INPUT/OUTPUT 级别控制网络流量。请谨慎配置,避免锁定系统。',
|
||||
chain: '链',
|
||||
targetChain: '目标链',
|
||||
sourceIP: '源 IP',
|
||||
destIP: '目标 IP',
|
||||
inboundDirection: '入站方向',
|
||||
outboundDirection: '出站方向',
|
||||
destPort: '目标端口',
|
||||
action: '动作',
|
||||
reject: '拒绝',
|
||||
sourceIPHelper: 'CIDR 格式,如 192.168.1.0/24,留空表所有地址',
|
||||
destIPHelper: 'CIDR 格式,如 10.0.0.0/留空表所有地址',
|
||||
portHelper: '0 表示任意端口',
|
||||
allPorts: '所有端口',
|
||||
deleteRuleConfirm: '将删除 {0} 条规则,是否继续?',
|
||||
},
|
||||
runtime: {
|
||||
runtime: '运行环境',
|
||||
|
||||
@@ -80,6 +80,18 @@ const hostRouter = {
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/hosts/firewall/advance',
|
||||
name: 'FirewallAdvance',
|
||||
component: () => import('@/views/host/firewall/advance/index.vue'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/hosts/firewall/port',
|
||||
parent: 'menu.firewall',
|
||||
title: 'firewall.advancedControl',
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/hosts/disk',
|
||||
name: 'Disk',
|
||||
|
||||
337
frontend/src/views/host/firewall/advance/index.vue
Normal file
337
frontend/src/views/host/firewall/advance/index.vue
Normal file
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<div>
|
||||
<FireRouter />
|
||||
|
||||
<div v-loading="loading">
|
||||
<FireStatus
|
||||
ref="fireStatusRef"
|
||||
@search="search"
|
||||
v-model:loading="loading"
|
||||
v-model:mask-show="maskShow"
|
||||
v-model:is-active="isActive"
|
||||
v-model:name="fireName"
|
||||
current-tab="advance"
|
||||
/>
|
||||
<div v-if="fireName !== '-' && fireName !== 'iptables'">
|
||||
<LayoutContent :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ $t('firewall.advancedControlNotAvailable', [firewallName]) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
</div>
|
||||
|
||||
<div v-if="fireName === 'iptables'">
|
||||
<el-card v-if="!isActive && maskShow" class="mask-prompt">
|
||||
<span>{{ $t('firewall.firewallNotStart') }}</span>
|
||||
</el-card>
|
||||
<LayoutContent :title="$t('firewall.filterRule')" :class="{ mask: !isActive }">
|
||||
<template #prompt>
|
||||
<el-alert type="info" :closable="false" :title="loadPrompt()" />
|
||||
</template>
|
||||
<template #leftToolBar>
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('firewall.create') }}
|
||||
</el-button>
|
||||
<el-button v-if="isBind" plain @click="onUnBind">
|
||||
{{ $t('commons.button.unbind') }}
|
||||
</el-button>
|
||||
<el-button v-if="!isBind" plain @click="onBind">
|
||||
{{ $t('commons.button.bind') }}
|
||||
</el-button>
|
||||
<el-button @click="onDelete(null)" plain :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #rightToolBar>
|
||||
<el-select v-model="selectedChain" @change="search()" clearable class="p-w-200">
|
||||
<template #prefix>{{ $t('firewall.chain') }}</template>
|
||||
<el-option :label="$t('firewall.inboundDirection')" value="1PANEL_INPUT"></el-option>
|
||||
<el-option :label="$t('firewall.outboundDirection')" value="1PANEL_OUTPUT"></el-option>
|
||||
</el-select>
|
||||
<TableRefresh @search="search()" />
|
||||
<TableSetting title="firewall-filter-refresh" @search="search()" />
|
||||
</template>
|
||||
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
:pagination-config="paginationConfig"
|
||||
v-model:selects="selects"
|
||||
@search="search"
|
||||
:data="data"
|
||||
:heightDiff="220"
|
||||
>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column :label="$t('commons.table.protocol')" :min-width="80" prop="protocol">
|
||||
<template #default="{ row }">
|
||||
{{ row.protocol === '' ? 'ALL' : row.protocol }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('firewall.sourceIP')" :min-width="120" prop="srcIP">
|
||||
<template #default="{ row }">
|
||||
{{ formatIP(row.srcIP) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('firewall.sourcePort')" :min-width="100" prop="sourcePort">
|
||||
<template #default="{ row }">
|
||||
{{ formatPort(row.srcPort) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('firewall.destIP')" :min-width="120" prop="dstIP">
|
||||
<template #default="{ row }">
|
||||
{{ formatIP(row.dstIP) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('firewall.destPort')" :min-width="100" prop="dstPort">
|
||||
<template #default="{ row }">
|
||||
{{ formatPort(row.dstPort) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :min-width="100" :label="$t('firewall.action')" prop="strategy">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.strategy === 'ACCEPT'" type="success">{{ row.strategy }}</el-tag>
|
||||
<el-tag v-else-if="row.strategy === 'DROP'" type="danger">
|
||||
{{ row.strategy }}
|
||||
</el-tag>
|
||||
<el-tag v-else-if="row.strategy === 'REJECT'" type="warning">
|
||||
{{ row.strategy }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="info">{{ row.strategy }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:min-width="150"
|
||||
:label="$t('commons.table.description')"
|
||||
prop="description"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<fu-table-operations
|
||||
width="120px"
|
||||
:buttons="buttons"
|
||||
:ellipsis="10"
|
||||
:label="$t('commons.table.operate')"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</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 FireStatus from '@/views/host/firewall/status/index.vue';
|
||||
import OperateDialog from '@/views/host/firewall/advance/operate/index.vue';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { searchFilterRules, batchOperateFilterRule, loadChainStatus, operateFilterChain } from '@/api/modules/host';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const loading = ref();
|
||||
const selects = ref<any>([]);
|
||||
const selectedChain = ref('1PANEL_INPUT');
|
||||
const defaultStrategy = ref('ACCEPT');
|
||||
const firewallName = ref('');
|
||||
|
||||
const maskShow = ref(true);
|
||||
const isActive = ref(false);
|
||||
const isBind = ref(false);
|
||||
const fireName = ref();
|
||||
const fireStatusRef = ref();
|
||||
|
||||
const opRef = ref();
|
||||
|
||||
const data = ref();
|
||||
|
||||
const formatPort = (port?: number | null | string) => {
|
||||
if (port === 0 || port === '0') {
|
||||
return i18n.global.t('firewall.allPorts');
|
||||
}
|
||||
if (port === undefined || port === null || port === '') {
|
||||
return '-';
|
||||
}
|
||||
return port;
|
||||
};
|
||||
const formatIP = (ip?: null | string) => {
|
||||
if (ip) {
|
||||
return ip === 'anywhere' ? i18n.global.t('firewall.anyWhere') : ip;
|
||||
}
|
||||
return i18n.global.t('firewall.anyWhere');
|
||||
};
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'firewall-filter-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: Number(localStorage.getItem('firewall-filter-page-size')) || 20,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const search = async () => {
|
||||
if (!isActive.value) {
|
||||
loading.value = false;
|
||||
paginationConfig.total = 0;
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
type: selectedChain.value,
|
||||
info: '',
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
loading.value = true;
|
||||
loadStatus();
|
||||
await searchFilterRules(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
data.value = res.data.items || [];
|
||||
paginationConfig.total = res.data.total;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const loadPrompt = () => {
|
||||
if (isBind.value) {
|
||||
return i18n.global.t('firewall.defaultStrategy', [selectedChain.value, defaultStrategy.value]);
|
||||
}
|
||||
return i18n.global.t('firewall.defaultStrategy2', [selectedChain.value, defaultStrategy.value]);
|
||||
};
|
||||
|
||||
const loadStatus = async () => {
|
||||
await loadChainStatus(selectedChain.value).then((res) => {
|
||||
isBind.value = res.data.isBind;
|
||||
defaultStrategy.value = res.data.defaultStrategy || 'ACCEPT';
|
||||
});
|
||||
};
|
||||
const onBind = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('firewall.bindHelper'), i18n.global.t('commons.button.bind'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await operateFilterChain(selectedChain.value, 'bind').then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadStatus();
|
||||
});
|
||||
});
|
||||
};
|
||||
const onUnBind = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('firewall.unbindHelper'), i18n.global.t('commons.button.unbind'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await operateFilterChain(selectedChain.value, 'unbind').then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadStatus();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const dialogRef = ref();
|
||||
const onOpenDialog = async (title: string, rowData?: Host.IptablesFilterRuleOp) => {
|
||||
const params = {
|
||||
title,
|
||||
rowData: rowData || {
|
||||
chain: selectedChain.value,
|
||||
protocol: 'tcp',
|
||||
strategy: 'ACCEPT',
|
||||
srcPort: 0,
|
||||
dstPort: 0,
|
||||
},
|
||||
};
|
||||
dialogRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const onDelete = async (row: Host.IptablesFilterRuleOp | null) => {
|
||||
let names = [];
|
||||
let rules = [];
|
||||
if (row) {
|
||||
rules.push({
|
||||
operation: 'remove',
|
||||
id: row.id,
|
||||
chain: selectedChain.value,
|
||||
srcPort: row.srcPort,
|
||||
dstPort: row.dstPort,
|
||||
srcIP: row.srcIP === 'anywhere' ? '' : row.srcIP,
|
||||
dstIP: row.dstIP === 'anywhere' ? '' : row.dstIP,
|
||||
protocol: row.protocol,
|
||||
strategy: row.strategy,
|
||||
});
|
||||
names = [
|
||||
`${row.protocol} ${row.srcIP || '*'}:${row.srcPort || '*'} -> ${row.dstIP || '*'}:${row.dstPort || '*'}`,
|
||||
];
|
||||
} else {
|
||||
for (const item of selects.value) {
|
||||
names.push(
|
||||
`${item.protocol} ${item.srcIP || '*'}:${item.srcPort || '*'} -> ${item.dstIP || '*'}:${
|
||||
item.dstPort || '*'
|
||||
}`,
|
||||
);
|
||||
rules.push({
|
||||
operation: 'remove',
|
||||
id: item.id,
|
||||
chain: selectedChain.value,
|
||||
srcPort: item.srcPort,
|
||||
dstPort: item.dstPort,
|
||||
srcIP: item.srcIP === 'anywhere' ? '' : item.srcIP,
|
||||
dstIP: item.dstIP === 'anywhere' ? '' : item.dstIP,
|
||||
protocol: item.protocol,
|
||||
strategy: item.strategy,
|
||||
});
|
||||
}
|
||||
}
|
||||
opRef.value.acceptParams({
|
||||
title: i18n.global.t('commons.button.delete'),
|
||||
names: names,
|
||||
msg: i18n.global.t('firewall.deleteRuleConfirm', [rules.length]),
|
||||
api: batchOperateFilterRule,
|
||||
params: { rules: rules },
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: (row: Host.IptablesFilterRuleOp) => {
|
||||
onDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
if (fireName.value !== '-') {
|
||||
loading.value = true;
|
||||
fireStatusRef.value.acceptParams();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chain-card {
|
||||
.chain-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.chain-policy {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
166
frontend/src/views/host/firewall/advance/operate/index.vue
Normal file
166
frontend/src/views/host/firewall/advance/operate/index.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<DrawerPro v-model="drawerVisible" :header="title" @close="handleClose" size="large">
|
||||
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules" v-loading="loading">
|
||||
<el-form-item :label="$t('commons.table.protocol')" prop="protocol">
|
||||
<el-select class="w-full" v-model="dialogData.rowData!.protocol" @change="changeProtocol">
|
||||
<el-option value="all" label="all" />
|
||||
<el-option value="tcp" label="tcp" />
|
||||
<el-option value="udp" label="udp" />
|
||||
<el-option value="icmp" label="icmp" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData?.chain === '1PANEL_INPUT'"
|
||||
:label="$t('firewall.sourceIP')"
|
||||
prop="srcIP"
|
||||
>
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.srcIP" placeholder="0.0.0.0/0" />
|
||||
<span class="input-help">{{ $t('firewall.sourceIPHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData?.chain === '1PANEL_OUTPUT'"
|
||||
:label="$t('firewall.destIP')"
|
||||
prop="dstIP"
|
||||
>
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.dstIP" placeholder="0.0.0.0/0" />
|
||||
<span class="input-help">{{ $t('firewall.destIPHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('firewall.destPort')" prop="dstPort">
|
||||
<el-input-number
|
||||
class="w-full"
|
||||
v-model="dialogData.rowData!.dstPort"
|
||||
:min="0"
|
||||
:max="65535"
|
||||
:disabled="dialogData.rowData?.protocol === 'all'"
|
||||
/>
|
||||
<span class="input-help">{{ $t('firewall.portHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('firewall.action')" prop="strategy">
|
||||
<el-radio-group v-model="dialogData.rowData!.strategy">
|
||||
<el-radio value="ACCEPT">{{ $t('firewall.accept') }}</el-radio>
|
||||
<el-radio value="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-form>
|
||||
<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>
|
||||
</DrawerPro>
|
||||
</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 { MsgSuccess } from '@/utils/message';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { operateFilterRule } from '@/api/modules/host';
|
||||
import { checkCidr, checkCidrV6, checkIpV4V6 } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
|
||||
interface DialogProps {
|
||||
title: string;
|
||||
rowData?: Host.IptablesFilterRuleOp;
|
||||
}
|
||||
const title = ref<string>('');
|
||||
const drawerVisible = ref(false);
|
||||
const dialogData = ref<DialogProps>({
|
||||
title: '',
|
||||
});
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
title.value = i18n.global.t('firewall.' + dialogData.value.title);
|
||||
if (dialogData.value.rowData.chain === '1PANEL_INPUT') {
|
||||
dialogData.value.rowData.dstIP = '';
|
||||
} else if (dialogData.value.rowData.chain === '1PANEL_OUTPUT') {
|
||||
dialogData.value.rowData.srcIP = '';
|
||||
}
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
chain: [Rules.requiredSelect],
|
||||
protocol: [Rules.requiredSelect],
|
||||
strategy: [Rules.requiredSelect],
|
||||
srcIP: [{ validator: checkIPAddress, trigger: 'blur' }],
|
||||
dstIP: [{ validator: checkIPAddress, trigger: 'blur' }],
|
||||
});
|
||||
|
||||
function checkIPAddress(_rule: any, value: any, callback: any) {
|
||||
if (!value) {
|
||||
return callback();
|
||||
}
|
||||
if (value.indexOf('/') !== -1) {
|
||||
if (value.indexOf(':') !== -1) {
|
||||
if (checkCidrV6(value)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
} else {
|
||||
if (checkCidr(value)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (checkIpV4V6(value)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
const changeProtocol = () => {
|
||||
if (dialogData.value.rowData.protocol === 'all') {
|
||||
dialogData.value.rowData.srcPort = 0;
|
||||
dialogData.value.rowData.dstPort = 0;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
dialogData.value.rowData.operation = 'add';
|
||||
if (!dialogData.value.rowData) return;
|
||||
|
||||
loading.value = true;
|
||||
await operateFilterRule(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisible.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
@@ -10,6 +10,7 @@
|
||||
v-model:mask-show="maskShow"
|
||||
v-model:is-active="isActive"
|
||||
v-model:name="fireName"
|
||||
current-tab="forward"
|
||||
/>
|
||||
<div v-if="fireName !== '-'">
|
||||
<el-card v-if="!isActive && maskShow" class="mask-prompt">
|
||||
|
||||
@@ -23,5 +23,9 @@ const buttons = [
|
||||
label: i18n.global.t('firewall.ipRule', 2),
|
||||
path: '/hosts/firewall/ip',
|
||||
},
|
||||
{
|
||||
label: 'iptables ' + i18n.global.t('firewall.advancedControl'),
|
||||
path: '/hosts/firewall/advance',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
v-model:name="fireName"
|
||||
v-model:mask-show="maskShow"
|
||||
v-model:is-active="isActive"
|
||||
current-tab="base"
|
||||
/>
|
||||
|
||||
<div v-if="fireName !== '-'">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
v-model:mask-show="maskShow"
|
||||
v-model:is-active="isActive"
|
||||
v-model:name="fireName"
|
||||
current-tab="base"
|
||||
/>
|
||||
<div v-if="fireName !== '-'">
|
||||
<el-card v-if="!isActive && maskShow" class="mask-prompt">
|
||||
@@ -33,6 +34,14 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div v-if="!isBind" class="mt-2">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
:title="$t('firewall.basicStatus', ['1PANEL_BASIC'])"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #leftToolBar>
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
@@ -160,7 +169,13 @@ import OperateDialog from '@/views/host/firewall/port/operate/index.vue';
|
||||
import ImportDialog from '@/views/host/firewall/port/import/index.vue';
|
||||
import FireStatus from '@/views/host/firewall/status/index.vue';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { batchOperateRule, searchFireRule, updateFirewallDescription, updatePortRule } from '@/api/modules/host';
|
||||
import {
|
||||
batchOperateRule,
|
||||
loadChainStatus,
|
||||
searchFireRule,
|
||||
updateFirewallDescription,
|
||||
updatePortRule,
|
||||
} from '@/api/modules/host';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
@@ -177,6 +192,7 @@ const searchStrategy = ref('');
|
||||
|
||||
const maskShow = ref(true);
|
||||
const isActive = ref(false);
|
||||
const isBind = ref(false);
|
||||
const fireName = ref();
|
||||
const fireStatusRef = ref();
|
||||
|
||||
@@ -207,6 +223,7 @@ const search = async () => {
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
loading.value = true;
|
||||
loadStatus();
|
||||
await searchFireRule(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
@@ -289,6 +306,12 @@ const onChange = async (info: any) => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
};
|
||||
|
||||
const loadStatus = async () => {
|
||||
await loadChainStatus('1PANEL_BASIC').then((res) => {
|
||||
isBind.value = res.data.isBind;
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = async (row: Host.RuleInfo | null) => {
|
||||
let names = [];
|
||||
let rules = [];
|
||||
|
||||
@@ -9,16 +9,33 @@
|
||||
<el-tag>{{ $t('app.version') }}: {{ baseInfo.version }}</el-tag>
|
||||
</div>
|
||||
<div class="mt-0.5">
|
||||
<el-button type="primary" v-if="baseInfo.isActive" @click="onOperate('stop')" link>
|
||||
{{ $t('commons.button.stop') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-if="!baseInfo.isActive" @click="onOperate('start')" link>
|
||||
{{ $t('commons.button.start') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" @click="onOperate('restart')" link>
|
||||
{{ $t('commons.button.restart') }}
|
||||
</el-button>
|
||||
<template v-if="baseInfo.name !== 'iptables'">
|
||||
<el-button type="primary" v-if="baseInfo.isActive" @click="onOperate('stop')" link>
|
||||
{{ $t('commons.button.stop') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-if="!baseInfo.isActive" @click="onOperate('start')" link>
|
||||
{{ $t('commons.button.start') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" @click="onOperate('restart')" link>
|
||||
{{ $t('commons.button.restart') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-if="!baseInfo.isInit">
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="onInit">
|
||||
{{ $t('commons.button.init') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-if="baseInfo.isInit && props.currentTab == 'base'">
|
||||
<el-divider direction="vertical" />
|
||||
<el-button v-if="baseInfo.isBind" type="primary" link @click="onUnBind">
|
||||
{{ $t('commons.button.unbind') }}
|
||||
</el-button>
|
||||
<el-button v-if="!baseInfo.isBind" type="primary" link @click="onBind">
|
||||
{{ $t('commons.button.bind') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<span v-if="onPing !== 'None'">
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button>
|
||||
@@ -35,7 +52,21 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<NoSuchService v-if="!baseInfo.isExist" name="Firewalld / Ufw" />
|
||||
<NoSuchService v-else name="Firewalld / Ufw" />
|
||||
|
||||
<LayoutContent :divider="true" v-if="!baseInfo.isInit">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flex flex-col gap-2 items-center justify-center w-full sm:flex-row">
|
||||
<span>{{ loadInitMsg() }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<DockerRestart
|
||||
ref="dockerRef"
|
||||
v-model:withDockerRestart="withDockerRestart"
|
||||
@@ -51,7 +82,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { loadFireBaseInfo, operateFire } from '@/api/modules/host';
|
||||
import { loadFireBaseInfo, operateFilterChain, operateFire } from '@/api/modules/host';
|
||||
import i18n from '@/lang';
|
||||
import NoSuchService from '@/components/layout-content/no-such-service.vue';
|
||||
import DockerRestart from '@/components/docker-proxy/docker-restart.vue';
|
||||
@@ -59,7 +90,19 @@ import { MsgSuccess } from '@/utils/message';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const baseInfo = ref<Host.FirewallBase>({ isActive: false, isExist: true, name: '', version: '', pingStatus: '' });
|
||||
const props = defineProps({
|
||||
currentTab: String,
|
||||
});
|
||||
|
||||
const baseInfo = ref<Host.FirewallBase>({
|
||||
isActive: false,
|
||||
isExist: true,
|
||||
isInit: false,
|
||||
isBind: false,
|
||||
name: '',
|
||||
version: '',
|
||||
pingStatus: '',
|
||||
});
|
||||
const onPing = ref('Disable');
|
||||
const oldStatus = ref();
|
||||
const dockerRef = ref();
|
||||
@@ -72,13 +115,18 @@ const acceptParams = (): void => {
|
||||
const emit = defineEmits(['search', 'update:is-active', 'update:loading', 'update:maskShow', 'update:name']);
|
||||
|
||||
const loadBaseInfo = async (search: boolean) => {
|
||||
await loadFireBaseInfo()
|
||||
.then((res) => {
|
||||
await loadFireBaseInfo(props.currentTab)
|
||||
.then(async (res) => {
|
||||
baseInfo.value = res.data;
|
||||
onPing.value = baseInfo.value.pingStatus;
|
||||
oldStatus.value = onPing.value;
|
||||
emit('update:name', baseInfo.value.name);
|
||||
if (baseInfo.value.isInit) {
|
||||
emit('update:name', baseInfo.value.name);
|
||||
} else {
|
||||
emit('update:name', '-');
|
||||
}
|
||||
emit('update:is-active', baseInfo.value.isActive);
|
||||
|
||||
if (search) {
|
||||
emit('search');
|
||||
} else {
|
||||
@@ -92,9 +140,81 @@ const loadBaseInfo = async (search: boolean) => {
|
||||
});
|
||||
};
|
||||
|
||||
const loadInitMsg = () => {
|
||||
switch (props.currentTab) {
|
||||
case 'base':
|
||||
return i18n.global.t('firewall.initHelper', [i18n.global.t('firewall.baseIptables')]);
|
||||
case 'forward':
|
||||
return i18n.global.t('firewall.initHelper', [i18n.global.t('firewall.forwardIptables')]);
|
||||
case 'advance':
|
||||
return i18n.global.t('firewall.initHelper', [i18n.global.t('firewall.advanceIptables')]);
|
||||
}
|
||||
};
|
||||
|
||||
const onInit = async () => {
|
||||
let chainName = '';
|
||||
let msg = '';
|
||||
switch (props.currentTab) {
|
||||
case 'base':
|
||||
chainName = '1PANEL_BASIC';
|
||||
msg = i18n.global.t('firewall.initMsg', [i18n.global.t('firewall.baseIptables')]);
|
||||
case 'forward':
|
||||
chainName = '1PANEL_FORWARD';
|
||||
msg = i18n.global.t('firewall.initMsg', [i18n.global.t('firewall.forwardIptables')]);
|
||||
case 'advance':
|
||||
chainName = '1PANEL_INPUT';
|
||||
msg = i18n.global.t('firewall.initMsg', [i18n.global.t('firewall.advanceIptables')]);
|
||||
}
|
||||
ElMessageBox.confirm(msg, i18n.global.t('commons.button.init'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await operateFilterChain(chainName, 'init-' + props.currentTab).then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onBind = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('firewall.bindHelper'), i18n.global.t('commons.button.bind'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await operateFilterChain('1PANEL_BASIC', 'bind-base').then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
const onUnBind = async () => {
|
||||
ElMessageBox.confirm(i18n.global.t('firewall.unbindHelper'), i18n.global.t('commons.button.unbind'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await operateFilterChain('1PANEL_BASIC', 'unbind-base').then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onOperate = async (op: string) => {
|
||||
operation.value = op;
|
||||
dockerRef.value.acceptParams({ title: i18n.global.t('firewall.dockerRestart') });
|
||||
if (baseInfo.value.name === 'iptables') {
|
||||
emit('update:loading', true);
|
||||
emit('update:maskShow', true);
|
||||
await operateFire(operation.value, false)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loadBaseInfo(true);
|
||||
})
|
||||
.catch(() => {
|
||||
loadBaseInfo(true);
|
||||
});
|
||||
} else {
|
||||
dockerRef.value.acceptParams({ title: i18n.global.t('firewall.dockerRestart') });
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
|
||||
Reference in New Issue
Block a user