Files
yakpanel-core/script/upgrade_firewall.py
2026-04-07 02:04:22 +05:30

379 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# coding: utf-8
# -------------------------------------------------------------------
# yakpanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 yakpanel(http://www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
import os
import sys
os.chdir('/www/server/panel/')
sys.path.insert(0, "class/")
sys.path.insert(0, "class_v2/")
sys.path.insert(0, "/www/server/panel/")
import public
__isFirewalld = False
__isUfw = False
if os.path.exists('/usr/sbin/firewalld') and os.path.exists('/usr/bin/yum'):
__isFirewalld = True
if os.path.exists('/usr/sbin/ufw') and os.path.exists('/usr/bin/apt-get'):
__isUfw = True
def check_ipset_exist(ipset_name):
cmd = "ipset list {}|grep Name".format(ipset_name)
res, err = public.ExecShell(cmd)
if err != "":
return False
return True
def firewalld_process_zone_file(zone_path, rule_dict, zone_name):
"""
处理zone文件public.xml或trusted.xml删除匹配的规则。
参数:
zone_path (str): zone文件路径
rule_dict (dict): 规则字典
zone_name (str): Zone名称'public''trusted'
"""
if not os.path.exists(zone_path):
return
import xml.etree.ElementTree as ET
tree = ET.parse(zone_path)
root = tree.getroot()
# 处理<rule>标签
for rule in root.findall('rule'):
source = rule.find('source')
if source is not None:
ip = source.get('address')
action = None
if rule.find('accept') is not None:
action = 'ACCEPT'
elif rule.find('drop') is not None:
action = 'DROP'
if (ip in rule_dict and
rule_dict[ip]['Chain'].upper() == 'INPUT' and
rule_dict[ip]['Zone'] == zone_name and
rule_dict[ip]['Strategy'].upper() == action.upper()):
root.remove(rule)
if zone_name == 'trusted':
for source in root.findall('source'):
ip = source.get('address')
if (ip in rule_dict and
rule_dict[ip]['Chain'].upper() == 'INPUT' and
rule_dict[ip]['Zone'] == 'trusted' and
rule_dict[ip]['Strategy'].upper() == 'ACCEPT'):
root.remove(source)
tree.write(zone_path)
def firewalld_process_direct_file(direct_path, rule_dict):
"""
处理direct.xml文件删除匹配的OUTPUT规则。
参数:
direct_path (str): direct.xml文件路径
rule_dict (dict): 规则字典
"""
if not os.path.exists(direct_path):
return
import xml.etree.ElementTree as ET
tree = ET.parse(direct_path)
root = tree.getroot()
for rule in root.findall('rule'):
if rule.get('chain') == 'OUTPUT':
rule_text = rule.text.strip()
if rule_text.startswith('-d '):
parts = rule_text.split()
ip = parts[1]
action = parts[-1]
if (ip in rule_dict and
rule_dict[ip]['Chain'].upper() == 'OUTPUT' and
rule_dict[ip]['Strategy'].upper() == action.upper()):
root.remove(rule)
tree.write(direct_path)
def firewalld_batch_remove_ip_rule(rule_dict):
direct_path = '/etc/firewalld/direct.xml'
public_path = '/etc/firewalld/zones/public.xml'
trusted_path = '/etc/firewalld/zones/trusted.xml'
import shutil
if os.path.exists(public_path):
shutil.copyfile(public_path, public_path + '.bak')
firewalld_process_zone_file(public_path, rule_dict, 'public')
if os.path.exists(trusted_path):
shutil.copyfile(trusted_path, trusted_path + '.bak')
firewalld_process_zone_file(trusted_path, rule_dict, 'trusted')
if os.path.exists(direct_path):
shutil.copyfile(direct_path, direct_path + '.bak')
firewalld_process_direct_file(direct_path, rule_dict)
def ufw_batch_remove_ip_rule(rule_dict):
"""
UFW批量删除规则
"""
ufw_config = '/etc/ufw/user.rules'
import shutil
shutil.copy(ufw_config, ufw_config + ".bak" + public.format_date())
lines = public.readFile(ufw_config)
lines = lines.splitlines()
new_lines = []
i = 0
while i < len(lines):
line = lines[i]
if line.startswith('### tuple ###'):
parts = line.split()
action = parts[3] # deny 或 allow
direction = parts[-1] # in 或 out
ip = parts[-2] # IP 地址
if ip == '0.0.0.0/0':
new_lines.extend(lines[i:i + 3]) # 保留这三行
i += 3
continue
chain = 'INPUT' if direction == 'in' else 'OUTPUT' if direction == 'out' else None
strategy = 'DROP' if action == 'deny' else 'ACCEPT' if action == 'allow' else None
if chain and strategy and all([
ip in rule_dict,
chain.upper() == rule_dict[ip]['Chain'].upper(),
strategy.upper() == rule_dict[ip]['Strategy'].upper(),
]):
# 如果匹配,跳过这三行(即删除)
i += 3
else:
# 如果不匹配,保留这三行
new_lines.extend(lines[i:i + 3])
i += 3
else:
# 如果不是规则的开始行,直接保留
new_lines.append(lines[i])
i += 1
with open(ufw_config, 'w') as file:
file.writelines(line if line.endswith('\n') else line + '\n' for line in new_lines)
# 9.5.0-9.6.0
def upgrade_iprule(commodel):
list_address = commodel.firewall.list_address()
if len(list_address) == 0:
return
rule_dict = {}
print("-* IP rule data being migrated...")
for item in list_address:
print("Migration IP rules:{}".format(item))
commodel.iptables.set_chain_rich_ip(item, 'add', item['Chain'])
rule_dict[item['Address']] = {
"Address": item['Address'],
"Family": item['Family'],
"Strategy": item['Strategy'],
"Zone": item.get('Zone'),
"Chain": item['Chain']
}
if __isFirewalld:
firewalld_batch_remove_ip_rule(rule_dict)
elif __isUfw:
ufw_batch_remove_ip_rule(rule_dict)
else:
return
commodel.firewall.reload()
public.ExecShell("systemctl reload BT-FirewallServices")
print("-* IP rule data migration completed...")
# 9.5.0-9.6.0
def upgrade_countrys(commodel):
country_list = public.M('firewall_country').select()
if len(country_list) == 0:
return
from safeModelV2.firewallModel import main as firewallModel
firewallmodel = firewallModel()
print("-* Area rule data being migrated...")
for country in country_list:
ports = country['ports']
brief = country['brief']
types = country['types']
print("Rules for relocation areas:{}".format(brief))
if __isUfw or not __isFirewalld:
if not ports:
public.ExecShell('iptables -D INPUT -m set --match-set ' + brief + ' src -j ' + types.upper())
else:
public.ExecShell(
'iptables -D INPUT -m set --match-set ' + brief + ' src -p tcp --destination-port ' + ports + ' -j ' + types.upper()
)
else:
if not ports:
o, e = public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set {} src -j {}".format(
brief, types.upper()))
if e != '':
public.ExecShell(
'firewall-cmd --permanent --remove-rich-rule=\'rule source ipset="{}" {}\''.format(brief,
types.upper()))
else:
o, e = public.ExecShell(
'firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set {} src -p tcp --dport {} -j {}'.format(
brief, ports, types.upper()))
if e != '':
public.ExecShell(
'firewall-cmd --permanent --remove-rich-rule=\'rule source ipset="' + brief + '" port port="' + ports + '" protocol=tcp ' + types.upper() + '\'')
commodel.firewall.reload()
for country in country_list:
ports = country['ports']
brief = country['brief']
types = country['types']
print("Rules for relocation areas:{}".format(brief))
o, e = public.ExecShell("ipset destroy " + brief)
if e != '':
public.ExecShell("firewall-cmd --permanent --delete-ipset=" + brief)
tmp_file = "/tmp/firewall_{}.txt".format(brief)
if os.path.exists(tmp_file): # bt 9.5.0之后
command = '''grep -q "in_bt_country" {filename} || awk '{{print "add in_bt_country_" $2, $3}}' {filename} > {filename}.tmp && mv {filename}.tmp {filename}'''.format(
filename=tmp_file
)
public.ExecShell(command)
_ipset = "in_bt_country_" + brief
public.ExecShell(
'ipset create {} hash:net maxelem 1000000; ipset restore -f {}'.format(_ipset, tmp_file)
)
if ports:
public.ExecShell(
'iptables -I IN_BT_Country -m set --match-set {} src -p tcp --destination-port {} -j {}'.format(
_ipset,
ports,
types.upper())
)
else:
public.ExecShell(
'iptables -I IN_BT_Country -m set --match-set {} src -j {}'.format(_ipset, types.upper())
)
public.ExecShell("systemctl reload BT-FirewallServices")
else: # bt 9.5.0之前
public.M("firewall_country").where("id=?", (country['id'],)).delete()
get_tmp = public.dict_obj()
get_tmp.country = country['country']
get_tmp.types = country['types']
get_tmp.ports = country['ports']
get_tmp.choose = "all"
get_tmp.is_update = True
res = firewallmodel.create_countrys(get_tmp)
if res['status'] is False:
print(f"[ {brief} ] Area rule migrated fail : {res['message']}")
print("-* Area rule data migration completed...")
# 9.5.0-9.6.0
def upgrade_malicious_ip():
pl_path = "/www/server/panel/config/firewalld_malicious_ip.pl"
if not os.path.exists(pl_path):
return
if not check_ipset_exist("malicious_ipset"):
return
print("-* Migration of malicious IP blocking data in progress...")
# 移除旧规则
if __isUfw or not __isFirewalld:
public.ExecShell('rm -rf /var/log/FIREWALL-ACCESS-LOG*')
public.ExecShell('rm -rf /etc/rsyslog.d/firewall-access-log.conf')
public.ExecShell('rm -rf /etc/logrotate.d/firewall-access-log')
public.ExecShell(
'iptables -D INPUT -m conntrack --ctstate NEW -j LOG --log-prefix "FIREWALL-ACCESS: " --log-level 4')
public.ExecShell('iptables -D INPUT -j IP-DAILY-LOG')
public.ExecShell('iptables -D IP-DAILY-LOG -m recent --name DAILY_IPS --rcheck --seconds 86400 -j RETURN')
public.ExecShell(
'iptables -D IP-DAILY-LOG -m recent --name DAILY_IPS --set -j LOG --log-prefix "DAILY-IP: " --log-level 4')
public.ExecShell('iptables -D IP-DAILY-LOG -j RETURN')
public.ExecShell('iptables -X IP-DAILY-LOG')
public.ExecShell("iptables -D INPUT -m set --match-set malicious_ipset src -j DROP")
else:
public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 1 -m conntrack --ctstate NEW -j LOG --log-prefix 'FIREWALL-ACCESS: ' --log-level 4")
public.ExecShell("firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 2 -j IP-DAILY-LOG")
public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 0 -m recent --name DAILY_IPS --rcheck --seconds 86400 -j RETURN")
public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 1 -m recent --name DAILY_IPS --set -j LOG --log-prefix 'DAILY-IP: ' --log-level 4")
public.ExecShell("firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 2 -j RETURN")
public.ExecShell("firewall-cmd --permanent --direct --remove-chain ipv4 filter IP-DAILY-LOG")
public.ExecShell(
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set malicious_ipset src -j DROP")
commodel.firewall.reload()
public.ExecShell("ipset destroy malicious_ipset")
read = public.readFile(pl_path)
if read.strip() == "open":
tmp_file = "/tmp/firewall_malicious_ip.txt"
command = '''grep -q "in_bt" {filename} || awk '{{print "add in_bt_" $2, $3 ,"timeout", 86400}}' {filename} > {filename}.tmp && mv {filename}.tmp {filename}'''.format(
filename=tmp_file)
public.ExecShell(command)
public.ExecShell("sh /www/server/panel/script/open_malicious_ip.sh")
public.ExecShell("ipset restore -f {}".format(tmp_file))
public.ExecShell("systemctl reload BT-FirewallServices")
print("-* Malicious IP blocking data migration completed...")
def upgrade_port_forward(commodel):
"""
升级端口转发规则
"""
port_forward_list = commodel.firewall.list_port_forward()
print("-* Migrating port forwarding rule data...")
for port_forward in port_forward_list:
print("Migrating port forwarding rules:{}".format(port_forward))
info = {
"Family": "ipv4",
"Protocol": port_forward["Protocol"],
"S_Address": port_forward['S_Address'],
"S_Port": port_forward['S_Port'],
"T_Address": port_forward['T_Address'],
"T_Port": port_forward['T_Port'],
}
commodel.firewall.port_forward(info, "remove")
commodel.iptables.port_forward(info, "add")
public.ExecShell("systemctl reload BT-FirewallServices")
commodel.firewall.reload()
print("-* Port forwarding rule data migration complete...")
if __name__ == '__main__':
try:
from firewallModelV2.comModel import main as comModel
import time
commodel = comModel()
upgrade_iprule(commodel)
upgrade_countrys(commodel)
upgrade_malicious_ip()
upgrade_port_forward(commodel)
print("yakpanel: FireWall Migrate Service Finish...")
except Exception as e:
import traceback
print("-" * 50)
print(traceback.format_exc())
print("-" * 50)
print(f"Error: FireWall Migrate Error: {e}")