fix: Fix the issue where deleting rules in iptables advanced settings… (#10942)

… fails
This commit is contained in:
ssongliu 2025-11-13 14:38:47 +08:00 committed by GitHub
parent c4bd4734a5
commit 55cbf572e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 108 additions and 97 deletions

View file

@ -727,6 +727,9 @@ func (u *FirewallService) addPortRecord(req dto.PortRuleOperate) error {
return nil return nil
} }
if len(req.Description) == 0 {
return nil
}
if err := hostRepo.SaveFirewallRecord(&model.Firewall{ if err := hostRepo.SaveFirewallRecord(&model.Firewall{
Type: "port", Type: "port",
Chain: req.Chain, Chain: req.Chain,

View file

@ -64,11 +64,15 @@ func (s *IptablesService) Search(req dto.SearchPageWithType) (int64, interface{}
} }
func (s *IptablesService) OperateRule(req dto.IptablesRuleOp) error { func (s *IptablesService) OperateRule(req dto.IptablesRuleOp) error {
action := "ACCEPT"
if req.Strategy == "drop" {
action = "DROP"
}
policy := iptables.FilterRules{ policy := iptables.FilterRules{
Protocol: req.Protocol, Protocol: req.Protocol,
SrcIP: req.SrcIP, SrcIP: req.SrcIP,
DstIP: req.DstIP, DstIP: req.DstIP,
Strategy: req.Strategy, Strategy: action,
} }
if req.SrcPort != 0 { if req.SrcPort != 0 {
policy.SrcPort = fmt.Sprintf("%v", req.SrcPort) policy.SrcPort = fmt.Sprintf("%v", req.SrcPort)
@ -91,6 +95,7 @@ func (s *IptablesService) OperateRule(req dto.IptablesRuleOp) error {
return fmt.Errorf("failed to add iptables rule: %w", err) return fmt.Errorf("failed to add iptables rule: %w", err)
} }
if len(req.Description) != 0 {
rule := &model.Firewall{ rule := &model.Firewall{
Chain: req.Chain, Chain: req.Chain,
Protocol: req.Protocol, Protocol: req.Protocol,
@ -105,6 +110,7 @@ func (s *IptablesService) OperateRule(req dto.IptablesRuleOp) error {
if err := hostRepo.SaveFirewallRecord(rule); err != nil { if err := hostRepo.SaveFirewallRecord(rule); err != nil {
return fmt.Errorf("failed to save rule to database: %w", err) return fmt.Errorf("failed to save rule to database: %w", err)
} }
}
case "remove": case "remove":
if err := iptables.DeleteFilterRule(req.Chain, policy); err != nil { if err := iptables.DeleteFilterRule(req.Chain, policy); err != nil {
return fmt.Errorf("failed to remove iptables rule: %w", err) return fmt.Errorf("failed to remove iptables rule: %w", err)

View file

@ -72,11 +72,6 @@ func (i *Iptables) ListPort() ([]FireInfo, error) {
if item.Strategy == "drop" || item.Strategy == "reject" { if item.Strategy == "drop" || item.Strategy == "reject" {
item.Strategy = "drop" item.Strategy = "drop"
} }
if item.Protocol == "6" {
item.Protocol = "tcp"
} else if item.Protocol == "17" {
item.Protocol = "udp"
}
datas = append(datas, FireInfo{ datas = append(datas, FireInfo{
Chain: item.Chain, Chain: item.Chain,
@ -263,30 +258,13 @@ func iptablesPortForward(info Forward, operation string) error {
if err := iptables.AddForward(info.Protocol, info.Port, info.TargetIP, info.TargetPort, info.Interface, true); err != nil { if err := iptables.AddForward(info.Protocol, info.Port, info.TargetIP, info.TargetPort, info.Interface, true); err != nil {
return err return err
} }
forwardPersistence() } else {
return nil if err := iptables.DeleteForward(info.Num, info.Protocol, info.Port, info.TargetIP, info.TargetPort, info.Interface); err != 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 return err
} }
}
forwardPersistence() forwardPersistence()
} return nil
}
return fmt.Errorf("forward rule not found")
} }
func forwardPersistence() { func forwardPersistence() {

View file

@ -86,7 +86,7 @@ func ReadFilterRulesByChain(chain string) ([]FilterRules, error) {
} }
itemRule := FilterRules{ itemRule := FilterRules{
Chain: chain, Chain: chain,
Protocol: fields[1], Protocol: loadProtocol(fields[1]),
SrcPort: loadPort("src", fields), SrcPort: loadPort("src", fields),
DstPort: loadPort("dst", fields), DstPort: loadPort("dst", fields),
SrcIP: loadIP(fields[3]), SrcIP: loadIP(fields[3]),
@ -135,6 +135,7 @@ func loadPort(position string, portStr []string) string {
if strings.Contains(portStr[6], "dpts:") && position == "dst" { if strings.Contains(portStr[6], "dpts:") && position == "dst" {
portItem = strings.ReplaceAll(portStr[6], "dpts:", "") portItem = strings.ReplaceAll(portStr[6], "dpts:", "")
} }
portItem = strings.ReplaceAll(portItem, ":", "-")
return portItem return portItem
} }
@ -145,6 +146,21 @@ func loadIP(ipStr string) string {
return ipStr return ipStr
} }
func loadProtocol(protocol string) string {
switch protocol {
case "0":
return "all"
case "1":
return "icmp"
case "6":
return "tcp"
case "17":
return "udp"
default:
return protocol
}
}
func validateRuleSafety(rule FilterRules, chain string) error { func validateRuleSafety(rule FilterRules, chain string) error {
if strings.ToUpper(rule.Strategy) != "DROP" { if strings.ToUpper(rule.Strategy) != "DROP" {
return nil return nil

View file

@ -3,8 +3,6 @@ package iptables
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/1Panel-dev/1Panel/agent/utils/re"
) )
func AddForward(protocol, srcPort, dest, destPort, iface string, save bool) error { func AddForward(protocol, srcPort, dest, destPort, iface string, save bool) error {
@ -71,42 +69,57 @@ func ListForward(chain ...string) ([]IptablesNatInfo, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
natListRegex := re.GetRegex(re.IptablesNatListPattern)
var forwardList []IptablesNatInfo var forwardList []IptablesNatInfo
for _, line := range strings.Split(stdout, "\n") { lines := strings.Split(stdout, "\n")
line = strings.TrimFunc(line, func(r rune) bool { for i := 0; i < len(lines); i++ {
return r <= 32 fields := strings.Fields(lines[i])
}) if len(fields) < 13 {
if natListRegex.MatchString(line) { continue
match := natListRegex.FindStringSubmatch(line)
if !strings.Contains(match[13], ":") {
match[13] = fmt.Sprintf(":%s", match[13])
} }
forwardList = append(forwardList, IptablesNatInfo{ item := IptablesNatInfo{
Num: match[1], Num: fields[0],
Target: match[4], Protocol: loadProtocol(fields[4]),
Protocol: match[11], InIface: fields[6],
InIface: match[7], OutIface: fields[7],
OutIface: match[8], Source: fields[8],
Opt: match[6], SrcPort: loadNatSrcPort(fields[11]),
Source: match[9],
Destination: match[10],
SrcPort: match[12],
DestPort: match[13],
})
} }
if len(fields) == 15 && fields[13] == "ports" {
item.DestPort = fields[14]
}
if len(fields) == 13 && strings.HasPrefix(fields[12], "to:") {
parts := strings.Split(fields[12], ":")
if len(parts) > 2 {
item.DestPort = parts[2]
item.Destination = parts[1]
}
}
if len(item.Destination) == 0 {
item.Destination = "127.0.0.1"
}
forwardList = append(forwardList, item)
} }
return forwardList, nil return forwardList, nil
} }
func loadNatSrcPort(portStr string) string {
var portItem string
if strings.Contains(portStr, "dpt:") {
portItem = strings.ReplaceAll(portStr, "dpt:", "")
}
if strings.Contains(portStr, "dpts:") {
portItem = strings.ReplaceAll(portStr, "dpts:", "")
}
portItem = strings.ReplaceAll(portItem, ":", "-")
return portItem
}
type IptablesNatInfo struct { type IptablesNatInfo struct {
Num string `json:"num"` Num string `json:"num"`
Target string `json:"target"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
InIface string `json:"inIface"` InIface string `json:"inIface"`
OutIface string `json:"outIface"` OutIface string `json:"outIface"`
Opt string `json:"opt"`
Source string `json:"source"` Source string `json:"source"`
Destination string `json:"destination"` Destination string `json:"destination"`
SrcPort string `json:"srcPort"` SrcPort string `json:"srcPort"`

View file

@ -11,7 +11,6 @@ const (
ComposeEnvVarPattern = `\$\{([^}]+)\}` ComposeEnvVarPattern = `\$\{([^}]+)\}`
DiskKeyValuePattern = `([A-Za-z0-9_]+)=("([^"\\]|\\.)*"|[^ \t]+)` DiskKeyValuePattern = `([A-Za-z0-9_]+)=("([^"\\]|\\.)*"|[^ \t]+)`
FirewalldForwardPattern = `^port=(\d{1,5}):proto=(.+?):toport=(\d{1,5}):toaddr=(.*)$` FirewalldForwardPattern = `^port=(\d{1,5}):proto=(.+?):toport=(\d{1,5}):toaddr=(.*)$`
IptablesNatListPattern = `^(\d+)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)\s+(.+?)(?:\s+(.+?) .+?:(\d{1,5}(?::\d+)?).+?[ :](.+-.+|(?:.+:)?\d{1,5}(?:-\d{1,5})?))?$`
ValidatorNamePattern = `^[a-zA-Z\p{Han}]{1}[a-zA-Z0-9_\p{Han}]{0,30}$` ValidatorNamePattern = `^[a-zA-Z\p{Han}]{1}[a-zA-Z0-9_\p{Han}]{0,30}$`
ValidatorIPPattern = `^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$` ValidatorIPPattern = `^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$`
DomainPattern = `^([\w\p{Han}\-\*]{1,100}\.){1,10}([\w\p{Han}\-]{1,24}|[\w\p{Han}\-]{1,24}\.[\w\p{Han}\-]{1,24})(:\d{1,5})?$` DomainPattern = `^([\w\p{Han}\-\*]{1,100}\.){1,10}([\w\p{Han}\-]{1,24}|[\w\p{Han}\-]{1,24}\.[\w\p{Han}\-]{1,24})(:\d{1,5})?$`
@ -42,7 +41,6 @@ func Init() {
ComposeEnvVarPattern, ComposeEnvVarPattern,
DiskKeyValuePattern, DiskKeyValuePattern,
FirewalldForwardPattern, FirewalldForwardPattern,
IptablesNatListPattern,
ValidatorNamePattern, ValidatorNamePattern,
ValidatorIPPattern, ValidatorIPPattern,
DomainPattern, DomainPattern,

View file

@ -95,6 +95,7 @@ var WebUrlMap = map[string]struct{}{
"/hosts/firewall/port": {}, "/hosts/firewall/port": {},
"/hosts/firewall/forward": {}, "/hosts/firewall/forward": {},
"/hosts/firewall/ip": {}, "/hosts/firewall/ip": {},
"/hosts/firewall/advance": {},
"/hosts/process/process": {}, "/hosts/process/process": {},
"/hosts/process/network": {}, "/hosts/process/network": {},
"/hosts/ssh/ssh": {}, "/hosts/ssh/ssh": {},

View file

@ -280,8 +280,8 @@ export namespace Host {
export interface IptablesRules { export interface IptablesRules {
id: number; id: number;
protocol: string; protocol: string;
srcPort: number; srcPort: string;
dstPort: number; dstPort: string;
srcIP: string; srcIP: string;
dstIP: string; dstIP: string;
strategy: string; strategy: string;

View file

@ -157,10 +157,10 @@ const opRef = ref();
const data = ref(); const data = ref();
const formatPort = (port?: number | null | string) => { const formatPort = (port?: number | null | string) => {
if (port === 0 || port === '0') { if (port === '' || port === 0 || port === '0') {
return i18n.global.t('firewall.allPorts'); return i18n.global.t('firewall.allPorts');
} }
if (port === undefined || port === null || port === '') { if (port === undefined || port === null) {
return '-'; return '-';
} }
return port; return port;
@ -255,7 +255,7 @@ const onOpenDialog = async (title: string, rowData?: Host.IptablesFilterRuleOp)
dialogRef.value!.acceptParams(params); dialogRef.value!.acceptParams(params);
}; };
const onDelete = async (row: Host.IptablesFilterRuleOp | null) => { const onDelete = async (row: Host.IptablesRules | null) => {
let names = []; let names = [];
let rules = []; let rules = [];
if (row) { if (row) {
@ -263,8 +263,8 @@ const onDelete = async (row: Host.IptablesFilterRuleOp | null) => {
operation: 'remove', operation: 'remove',
id: row.id, id: row.id,
chain: selectedChain.value, chain: selectedChain.value,
srcPort: row.srcPort, srcPort: Number(row.srcPort),
dstPort: row.dstPort, dstPort: Number(row.dstPort),
srcIP: row.srcIP === 'anywhere' ? '' : row.srcIP, srcIP: row.srcIP === 'anywhere' ? '' : row.srcIP,
dstIP: row.dstIP === 'anywhere' ? '' : row.dstIP, dstIP: row.dstIP === 'anywhere' ? '' : row.dstIP,
protocol: row.protocol, protocol: row.protocol,
@ -284,8 +284,8 @@ const onDelete = async (row: Host.IptablesFilterRuleOp | null) => {
operation: 'remove', operation: 'remove',
id: item.id, id: item.id,
chain: selectedChain.value, chain: selectedChain.value,
srcPort: item.srcPort, srcPort: Number(item.srcPort),
dstPort: item.dstPort, dstPort: Number(item.dstPort),
srcIP: item.srcIP === 'anywhere' ? '' : item.srcIP, srcIP: item.srcIP === 'anywhere' ? '' : item.srcIP,
dstIP: item.dstIP === 'anywhere' ? '' : item.dstIP, dstIP: item.dstIP === 'anywhere' ? '' : item.dstIP,
protocol: item.protocol, protocol: item.protocol,
@ -305,7 +305,7 @@ const onDelete = async (row: Host.IptablesFilterRuleOp | null) => {
const buttons = [ const buttons = [
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
click: (row: Host.IptablesFilterRuleOp) => { click: (row: Host.IptablesRules) => {
onDelete(row); onDelete(row);
}, },
}, },

View file

@ -26,7 +26,6 @@
<el-input clearable v-model.trim="dialogData.rowData!.targetPort" /> <el-input clearable v-model.trim="dialogData.rowData!.targetPort" />
</el-form-item> </el-form-item>
<template v-if="dialogData.fireName === 'ufw'">
<el-form-item :label="$t('firewall.forwardInboundInterface')" prop="interface"> <el-form-item :label="$t('firewall.forwardInboundInterface')" prop="interface">
<el-select class="w-full" v-model="dialogData.rowData!.interface"> <el-select class="w-full" v-model="dialogData.rowData!.interface">
<el-option <el-option
@ -37,7 +36,6 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</template>
</el-form> </el-form>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@ -81,12 +79,10 @@ const acceptParams = (params: DialogProps): void => {
if (dialogData.value.title === 'edit') { if (dialogData.value.title === 'edit') {
oldRule.value = deepCopy(params.rowData); oldRule.value = deepCopy(params.rowData);
} }
if (dialogData.value.fireName === 'ufw') {
getNetworkOptions().then((res) => { getNetworkOptions().then((res) => {
interfaceOptions.value = res.data.map((item) => ({ label: item, value: item })); interfaceOptions.value = res.data.map((item) => ({ label: item, value: item }));
dialogData.value.rowData!.interface = dialogData.value.rowData!.interface || 'all'; dialogData.value.rowData!.interface = dialogData.value.rowData!.interface || 'all';
}); });
}
title.value = i18n.global.t('firewall.' + dialogData.value.title); title.value = i18n.global.t('firewall.' + dialogData.value.title);
drawerVisible.value = true; drawerVisible.value = true;
}; };