diff --git a/agent/app/api/v2/entry.go b/agent/app/api/v2/entry.go index aa403c95cbe8..ef1ee05dd589 100644 --- a/agent/app/api/v2/entry.go +++ b/agent/app/api/v2/entry.go @@ -38,6 +38,7 @@ var ( fileService = service.NewIFileService() sshService = service.NewISSHService() firewallService = service.NewIFirewallService() + iptablesService = service.NewIIptablesService() monitorService = service.NewIMonitorService() systemService = service.NewISystemService() diff --git a/agent/app/api/v2/firewall.go b/agent/app/api/v2/firewall.go index 62e2a44a740d..8c917f6a97d5 100644 --- a/agent/app/api/v2/firewall.go +++ b/agent/app/api/v2/firewall.go @@ -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)) +} diff --git a/agent/app/dto/firewall.go b/agent/app/dto/firewall.go index 99920d929232..d819733d2ea6 100644 --- a/agent/app/dto/firewall.go +++ b/agent/app/dto/firewall.go @@ -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"` +} diff --git a/agent/app/model/firewall.go b/agent/app/model/firewall.go index 622203d058c9..fc79023494d0 100644 --- a/agent/app/model/firewall.go +++ b/agent/app/model/firewall.go @@ -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"` - Strategy string `gorm:"not null" json:"strategy"` - Description string `gorm:"not null" json:"description"` -} + Type string `json:"type"` + Port string `json:"port"` // Deprecated + Address string `json:"address"` // Deprecated -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"` + 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 `json:"description"` } diff --git a/agent/app/repo/host.go b/agent/app/repo/host.go index 3160a1ec3d94..0d399229baad 100644 --- a/agent/app/repo/host.go +++ b/agent/app/repo/host.go @@ -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) + } + if err := global.DB.Find(&firewalls).Error; err != nil { + return firewalls, nil } - return datas, 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) + } +} diff --git a/agent/app/service/firewall.go b/agent/app/service/firewall.go index 679cc2377625..788e4809ce54 100644 --- a/agent/app/service/firewall.go +++ b/agent/app/service/firewall.go @@ -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 + } +} diff --git a/agent/app/service/iptables.go b/agent/app/service/iptables.go new file mode 100644 index 000000000000..f1ee7fdffbb9 --- /dev/null +++ b/agent/app/service/iptables.go @@ -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 +} diff --git a/agent/global/config.go b/agent/global/config.go index 20d1dad627e2..27fbe4c34657 100644 --- a/agent/global/config.go +++ b/agent/global/config.go @@ -47,6 +47,7 @@ type SystemDir struct { McpDir string ConvertLogDir string TensorRTLLMDir string + FirewallDir string } type LogConfig struct { diff --git a/agent/init/app/app.go b/agent/init/app/app.go index a8aae80ee51b..800eabe4614e 100644 --- a/agent/init/app/app.go +++ b/agent/init/app/app.go @@ -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() - } }() } diff --git a/agent/init/dir/dir.go b/agent/init/dir/dir.go index fe2dc51ba178..651f78601639 100644 --- a/agent/init/dir/dir.go +++ b/agent/init/dir/dir.go @@ -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")) } diff --git a/agent/init/firewall/firwall.go b/agent/init/firewall/firwall.go new file mode 100644 index 000000000000..e1d1ab9e9d51 --- /dev/null +++ b/agent/init/firewall/firwall.go @@ -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) + } +} diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 66d1085b3828..208dee79674e 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -51,6 +51,7 @@ func InitAgentDB() { migrations.AddMonitorProcess, migrations.UpdateCronJob, migrations.UpdateTensorrtLLM, + migrations.AddIptablesFilterRuleTable, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 9763b498a384..651cd1bf0359 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -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 { diff --git a/agent/router/ro_host.go b/agent/router/ro_host.go index 7a5a845c23a0..5ac8d30f8ae1 100644 --- a/agent/router/ro_host.go +++ b/agent/router/ro_host.go @@ -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) diff --git a/agent/server/server.go b/agent/server/server.go index 2bf3ed917ac2..63b96512af16 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -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() diff --git a/agent/utils/controller/controller.go b/agent/utils/controller/controller.go index 605adbee37e2..f16031868725 100644 --- a/agent/utils/controller/controller.go +++ b/agent/utils/controller/controller.go @@ -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 { diff --git a/agent/utils/docker/compose.go b/agent/utils/docker/compose.go index 2f12d36eb13e..a2e43ac7ec19 100644 --- a/agent/utils/docker/compose.go +++ b/agent/utils/docker/compose.go @@ -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 { diff --git a/agent/utils/firewall/client.go b/agent/utils/firewall/client.go index 447cd8697572..97b481b8c72f 100644 --- a/agent/utils/firewall/client.go +++ b/agent/utils/firewall/client.go @@ -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!") } diff --git a/agent/utils/firewall/client/info.go b/agent/utils/firewall/client/info.go index ba1603738018..76069b8c5cbd 100644 --- a/agent/utils/firewall/client/info.go +++ b/agent/utils/firewall/client/info.go @@ -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"` -} diff --git a/agent/utils/firewall/client/iptables.go b/agent/utils/firewall/client/iptables.go index dc3610a0b301..cb1718a88754 100644 --- a/agent/utils/firewall/client/iptables.go +++ b/agent/utils/firewall/client/iptables.go @@ -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" -) +type Iptables struct{} -const NatChain = "1PANEL" +func NewIptables() (*Iptables, error) { + return &Iptables{}, nil +} -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})?))?$`) -) +func (i *Iptables) Name() string { + return "iptables" +} -type Iptables struct { - CmdStr string +func (i *Iptables) Status() (bool, error) { + stdout, err := cmd.RunDefaultWithStdoutBashC("iptables -L -n | head -1") + if err != nil { + return false, err + } + return strings.Contains(stdout, "Chain"), nil } -func NewIptables() (*Iptables, error) { - iptables := new(Iptables) - iptables.CmdStr = cmd.SudoHandleCmd() +func (i *Iptables) Start() error { + return nil +} - return iptables, nil +func (i *Iptables) Stop() error { + return 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) - if err != nil { - global.LOG.Errorf("iptables failed, %v", err) - } - return stdout, nil +func (i *Iptables) Restart() error { + return nil } -func (iptables *Iptables) run(tab, rule string) error { - if _, err := iptables.out(tab, rule); err != nil { - return err - } +func (i *Iptables) Reload() error { return nil } -func (iptables *Iptables) Check() error { - stdout, err := cmd.RunDefaultWithStdoutBashC("cat /proc/sys/net/ipv4/ip_forward") +func (i *Iptables) Version() (string, error) { + stdout, err := cmd.RunDefaultWithStdoutBashC("iptables --version") if err != nil { - return fmt.Errorf("check ip_forward failed, %v", err) + return "", fmt.Errorf("failed to get iptables version: %w", err) } - if strings.TrimSpace(stdout) == "0" { - return fmt.Errorf("ipv4 forward disabled") + parts := strings.Fields(stdout) + if len(parts) >= 2 { + return strings.TrimPrefix(parts[1], "v"), nil } + return strings.TrimSpace(stdout), nil +} - chain, err := iptables.out(NatTab, fmt.Sprintf("-L -n | grep 'Chain %s'", PreRoutingChain)) +func (i *Iptables) ListPort() ([]FireInfo, error) { + stdout, err := iptables.RunWithStd(iptables.FilterTab, fmt.Sprintf("-S %s", iptables.Chain1PanelBasic)) if err != nil { - return fmt.Errorf("failed to check chain: %w", err) - } - if strings.TrimSpace(chain) != "" { - return fmt.Errorf("chain %s already exists", PreRoutingChain) + return nil, fmt.Errorf("failed to list 1PANEL_BASIC rules: %w", err) } - return nil -} + var datas []FireInfo + lines := strings.Split(stdout, "\n") -func (iptables *Iptables) NewChain(tab, chain string) error { - return iptables.run(tab, "-N "+chain) -} + 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 + } -func (iptables *Iptables) AppendChain(tab string, chain, chain1 string) error { - return iptables.run(tab, fmt.Sprintf("-A %s -j %s", chain, chain1)) -} + 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" + } -func (iptables *Iptables) NatList(chain ...string) ([]IptablesNatInfo, error) { - if len(chain) == 0 { - chain = append(chain, PreRoutingChain) + datas = append(datas, FireInfo{ + Address: address, + Protocol: protocol, + Port: port, + Strategy: strategy, + Family: "ipv4", + }) + } } - stdout, err := iptables.out(NatTab, fmt.Sprintf("-nvL %s --line-numbers", chain[0])) + + return datas, nil +} + +func (i *Iptables) ListAddress() ([]FireInfo, error) { + stdout, err := iptables.RunWithStd(iptables.FilterTab, fmt.Sprintf("-S %s", iptables.Chain1PanelBasic)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to list 1PANEL_BASIC address 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") + 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 matches := chainAddressRegex.FindStringSubmatch(line); len(matches) >= 4 { + address := matches[1] + if address == "" { + address = matches[2] + } + if address == "" { + continue + } + action := strings.ToLower(matches[3]) + + strategy := "accept" + if action == "drop" || action == "reject" { + strategy = "drop" + } + + if _, exists := addressMap[address]; !exists { + addressMap[address] = FireInfo{ + Address: address, + Strategy: strategy, + Family: "ipv4", + } } - 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 + for _, info := range addressMap { + datas = append(datas, info) + } + + 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) Port(port FireInfo, operation string) error { + if operation != "add" && operation != "remove" { + return buserr.New("ErrCmdIllegal") + } - 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 - } + portSpec, err := normalizePortSpec(port.Port) + if err != nil { + return err + } - 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 - } + protocol := port.Protocol + if protocol == "" { + protocol = "tcp" + } + + action := "ACCEPT" + if port.Strategy == "drop" { + action = "DROP" + } - if err := iptables.run(FilterTab, fmt.Sprintf( - "-A %s -s %s -p %s --sport %s -j ACCEPT", - ForwardChain, - dest, - protocol, - destPort, - )); err != nil { + 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") + } + + address := strings.TrimSpace(rule.Address) + if strings.EqualFold(address, "Anywhere") { + address = "" + } + + action := "ACCEPT" + if rule.Strategy == "drop" { + action = "DROP" } - 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 { + 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 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() } - return nil + 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 } diff --git a/agent/utils/firewall/client/iptables/common.go b/agent/utils/firewall/client/iptables/common.go new file mode 100644 index 000000000000..742c0f1cc1d7 --- /dev/null +++ b/agent/utils/firewall/client/iptables/common.go @@ -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)) +} diff --git a/agent/utils/firewall/client/iptables/filter.go b/agent/utils/firewall/client/iptables/filter.go new file mode 100644 index 000000000000..60efbd323476 --- /dev/null +++ b/agent/utils/firewall/client/iptables/filter.go @@ -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 +} diff --git a/agent/utils/firewall/client/iptables/forward.go b/agent/utils/firewall/client/iptables/forward.go new file mode 100644 index 000000000000..bfb80ca0a31f --- /dev/null +++ b/agent/utils/firewall/client/iptables/forward.go @@ -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"` +} diff --git a/agent/utils/firewall/client/iptables/persistence.go b/agent/utils/firewall/client/iptables/persistence.go new file mode 100644 index 000000000000..c2f0b7f2e5b6 --- /dev/null +++ b/agent/utils/firewall/client/iptables/persistence.go @@ -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 +} diff --git a/agent/utils/firewall/client/ufw.go b/agent/utils/firewall/client/ufw.go index 80be44c59b3a..97da61e2faa5 100644 --- a/agent/utils/firewall/client/ufw.go +++ b/agent/utils/firewall/client/ufw.go @@ -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 -} diff --git a/core/server/server.go b/core/server/server.go index 2f691aaa5004..d640a8b30ed5 100644 --- a/core/server/server.go +++ b/core/server/server.go @@ -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() diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts index a39fcd95dea7..622beaaa116f 100644 --- a/frontend/src/api/interface/host.ts +++ b/frontend/src/api/interface/host.ts @@ -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; + } } diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index f394e38859d5..500cfdc35e67 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -6,8 +6,8 @@ import { deepCopy } from '@/utils/util'; import { Base64 } from 'js-base64'; // firewall -export const loadFireBaseInfo = () => { - return http.get(`/hosts/firewall/base`); +export const loadFireBaseInfo = (tab: string) => { + return http.post(`/hosts/firewall/base`, { name: tab }, TimeoutEnum.T_40S); }; export const searchFireRule = (params: Host.RuleSearch) => { return http.post>(`/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(`/hosts/firewall/filter/rule/search`, params); +}; +export const loadChainStatus = (name: string) => { + return http.post(`/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>(`/hosts/monitor/search`, param); diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 278cd3ec9e3a..ae59f35557ec 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 26436be7397d..5a850dca946f 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -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', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 8ccfcc80c982..45fa0a6e0ab6 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -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: 'ランタイム', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index dcbe63902a40..f3a8606887c5 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -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: '실행 환경', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 8d820c3ac819..f3bf78d653e5 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -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', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 5ef88e7124ac..723e6b406a4f 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -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', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index f7f9290c7d93..dd249e20cc5d 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -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: 'Среда выполнения', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index b08f4f3501af..86897069688f 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -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ı', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 7846a019b566..8300c251f9ad 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -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: '執行環境', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 250652d8afab..81b416032e35 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -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: '运行环境', diff --git a/frontend/src/routers/modules/host.ts b/frontend/src/routers/modules/host.ts index eab0284102df..a0996b66ac7f 100644 --- a/frontend/src/routers/modules/host.ts +++ b/frontend/src/routers/modules/host.ts @@ -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', diff --git a/frontend/src/views/host/firewall/advance/index.vue b/frontend/src/views/host/firewall/advance/index.vue new file mode 100644 index 000000000000..07f9e49db32d --- /dev/null +++ b/frontend/src/views/host/firewall/advance/index.vue @@ -0,0 +1,337 @@ + + + + + diff --git a/frontend/src/views/host/firewall/advance/operate/index.vue b/frontend/src/views/host/firewall/advance/operate/index.vue new file mode 100644 index 000000000000..189094ebec24 --- /dev/null +++ b/frontend/src/views/host/firewall/advance/operate/index.vue @@ -0,0 +1,166 @@ + + + diff --git a/frontend/src/views/host/firewall/forward/index.vue b/frontend/src/views/host/firewall/forward/index.vue index 346237b874ee..9c7a05d83cde 100644 --- a/frontend/src/views/host/firewall/forward/index.vue +++ b/frontend/src/views/host/firewall/forward/index.vue @@ -10,6 +10,7 @@ v-model:mask-show="maskShow" v-model:is-active="isActive" v-model:name="fireName" + current-tab="forward" />
diff --git a/frontend/src/views/host/firewall/index.vue b/frontend/src/views/host/firewall/index.vue index ec41d14bdb81..790c647767d3 100644 --- a/frontend/src/views/host/firewall/index.vue +++ b/frontend/src/views/host/firewall/index.vue @@ -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', + }, ]; diff --git a/frontend/src/views/host/firewall/ip/index.vue b/frontend/src/views/host/firewall/ip/index.vue index 5351dac5a722..a2b2882428b2 100644 --- a/frontend/src/views/host/firewall/ip/index.vue +++ b/frontend/src/views/host/firewall/ip/index.vue @@ -10,6 +10,7 @@ v-model:name="fireName" v-model:mask-show="maskShow" v-model:is-active="isActive" + current-tab="base" />
diff --git a/frontend/src/views/host/firewall/port/index.vue b/frontend/src/views/host/firewall/port/index.vue index 8c6b725636d3..9f66ffe69da4 100644 --- a/frontend/src/views/host/firewall/port/index.vue +++ b/frontend/src/views/host/firewall/port/index.vue @@ -10,6 +10,7 @@ v-model:mask-show="maskShow" v-model:is-active="isActive" v-model:name="fireName" + current-tab="base" />
@@ -33,6 +34,14 @@ + +
+ +