Merge pull request #1663 from master3395/v2.5.5-dev

V2.5.5 dev Fixing Firewall rules and Banned IP's page.
This commit is contained in:
Master3395
2026-01-28 23:25:42 +01:00
committed by GitHub
7 changed files with 2897 additions and 82 deletions

View File

@@ -1543,6 +1543,9 @@ fi
# Test if CyberPanel is accessible
echo -e "\n🔍 Testing CyberPanel accessibility..."
# Check if lscpd service is running
if systemctl is-active --quiet lscpd 2>/dev/null; then
echo "╔═════════════════════════════════════════════════════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ 🌐 ACCESS YOUR CYBERPANEL: ║"

View File

@@ -11,6 +11,7 @@ sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
django.setup()
import json
import tempfile
from plogical.acl import ACLManager
import plogical.CyberCPLogFileWriter as logging
from plogical.virtualHostUtilities import virtualHostUtilities
@@ -139,6 +140,107 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def modifyRule(self, userID=None, data=None):
"""
Modify an existing firewall rule
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('modify_status', 0)
ruleID = data.get('id')
newRuleName = data.get('ruleName', '').strip()
newRuleProtocol = data.get('ruleProtocol', '').strip()
newRulePort = data.get('rulePort', '').strip()
newRuleIP = data.get('ruleIP', '').strip()
# Validate inputs
if not newRuleName:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Rule name is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRuleProtocol or newRuleProtocol not in ['tcp', 'udp']:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Valid protocol (tcp/udp) is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRulePort:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Port is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRuleIP:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'IP address is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Get existing rule
try:
existingRule = FirewallRules.objects.get(id=ruleID)
except FirewallRules.DoesNotExist:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Rule not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Check if anything changed
changed = False
if (existingRule.name != newRuleName or
existingRule.proto != newRuleProtocol or
existingRule.port != newRulePort or
existingRule.ipAddress != newRuleIP):
changed = True
if changed:
# Check if new rule already exists (different ID)
existingDuplicate = FirewallRules.objects.filter(
name=newRuleName,
proto=newRuleProtocol,
port=newRulePort,
ipAddress=newRuleIP
).exclude(id=ruleID).first()
if existingDuplicate:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'A rule with these settings already exists'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Delete old firewall rule
try:
FirewallUtilities.deleteRule(existingRule.proto, existingRule.port, existingRule.ipAddress)
logging.CyberCPLogFileWriter.writeToFile(f'Removed old firewall rule: {existingRule.proto}/{existingRule.port}/{existingRule.ipAddress}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not remove old firewall rule: {str(e)}')
# Add new firewall rule
try:
FirewallUtilities.addRule(newRuleProtocol, newRulePort, newRuleIP)
logging.CyberCPLogFileWriter.writeToFile(f'Added new firewall rule: {newRuleProtocol}/{newRulePort}/{newRuleIP}')
except Exception as e:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': f'Failed to add firewall rule: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Update database record
existingRule.name = newRuleName
existingRule.proto = newRuleProtocol
existingRule.port = newRulePort
existingRule.ipAddress = newRuleIP
existingRule.save()
final_dic = {'status': 1, 'modify_status': 1, 'error_message': "None"}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteRule(self, userID = None, data = None):
try:
@@ -1829,24 +1931,72 @@ class FirewallManager:
except:
banned_ips = []
# Filter out expired bans
# Filter out expired bans and format data consistently
current_time = time.time()
active_banned_ips = []
for banned_ip in banned_ips:
if banned_ip.get('expires') == 'Never' or banned_ip.get('expires', 0) > current_time:
banned_ip['active'] = True
if banned_ip.get('expires') != 'Never':
banned_ip['expires'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip['expires']))
else:
banned_ip['expires'] = 'Never'
banned_ip['banned_on'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('banned_on', current_time)))
else:
banned_ip['active'] = False
banned_ip['expires'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('expires', current_time)))
banned_ip['banned_on'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('banned_on', current_time)))
# Get original values
expires_val = banned_ip.get('expires')
banned_on_val = banned_ip.get('banned_on', current_time)
active_banned_ips.append(banned_ip)
# Convert banned_on to timestamp if it's a string
if isinstance(banned_on_val, str):
try:
# Try parsing formatted date string
banned_on_val = time.mktime(time.strptime(banned_on_val, '%Y-%m-%d %H:%M:%S'))
except:
banned_on_val = current_time
elif not isinstance(banned_on_val, (int, float)):
banned_on_val = current_time
# Check if expired
is_expired = False
if expires_val == 'Never' or expires_val is None:
expires_timestamp = None
expires_display = 'Never'
elif isinstance(expires_val, str):
if expires_val == 'Never':
expires_timestamp = None
expires_display = 'Never'
else:
try:
expires_timestamp = time.mktime(time.strptime(expires_val, '%Y-%m-%d %H:%M:%S'))
expires_display = expires_val
except:
expires_timestamp = None
expires_display = 'Never'
else:
expires_timestamp = expires_val
if expires_val > current_time:
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_val))
else:
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_val))
is_expired = True
# Determine if active
if expires_timestamp is None:
is_active = True
else:
is_active = expires_timestamp > current_time and not is_expired
# Format banned_on for display
banned_on_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_on_val))
# Create formatted entry
formatted_ip = {
'id': banned_ip.get('id'),
'ip': banned_ip.get('ip'),
'reason': banned_ip.get('reason', ''),
'duration': banned_ip.get('duration', 'never'),
'banned_on': banned_on_display, # String for display
'banned_on_timestamp': banned_on_val * 1000, # Milliseconds for AngularJS date filter
'expires': expires_display, # String for display
'expires_timestamp': expires_timestamp * 1000 if expires_timestamp else None, # Milliseconds for AngularJS
'active': is_active
}
active_banned_ips.append(formatted_ip)
final_dic = {'status': 1, 'bannedIPs': active_banned_ips}
final_json = json.dumps(final_dic)
@@ -1927,12 +2077,23 @@ class FirewallManager:
}
banned_ips.append(new_banned_ip)
# Ensure directory exists
os.makedirs(os.path.dirname(banned_ips_file), exist_ok=True)
# Save to file
with open(banned_ips_file, 'w') as f:
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Apply firewall rule to block the IP
try:
@@ -1992,9 +2153,23 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Save updated banned IPs
with open(banned_ips_file, 'w') as f:
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Remove iptables rule
try:
@@ -2019,6 +2194,153 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def modifyBannedIP(self, userID=None, data=None):
"""
Modify a banned IP address (update reason and/or expiration)
"""
try:
admin = Administrator.objects.get(pk=userID)
if admin.acl.adminStatus != 1:
return ACLManager.loadError()
banned_ip_id = data.get('id')
new_ip = data.get('ip', '').strip()
reason = data.get('reason', '').strip()
duration = data.get('duration', 'never')
if not new_ip:
final_dic = {'status': 0, 'error_message': 'IP address is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Validate IP address format
import ipaddress
try:
ipaddress.ip_address(new_ip.split('/')[0]) # Handle CIDR notation
except ValueError:
final_dic = {'status': 0, 'error_message': 'Invalid IP address format'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not reason:
final_dic = {'status': 0, 'error_message': 'Reason is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Load existing banned IPs
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
banned_ips = []
if os.path.exists(banned_ips_file):
try:
with open(banned_ips_file, 'r') as f:
banned_ips = json.load(f)
except:
banned_ips = []
# Find and update the banned IP
old_ip = None
found = False
for banned_ip in banned_ips:
if banned_ip.get('id') == banned_ip_id:
found = True
old_ip = banned_ip['ip']
# Check if new IP is already banned (and not the same record)
ip_changed = (new_ip != old_ip)
if ip_changed:
for other_banned_ip in banned_ips:
if other_banned_ip.get('id') != banned_ip_id and other_banned_ip.get('ip') == new_ip and other_banned_ip.get('active', True):
final_dic = {'status': 0, 'error_message': f'IP address {new_ip} is already banned'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Update IP address if changed
if ip_changed:
# Remove old iptables rule
try:
if '/' in old_ip:
subprocess.run(['iptables', '-D', 'INPUT', '-s', old_ip, '-j', 'DROP'], check=False)
else:
subprocess.run(['iptables', '-D', 'INPUT', '-s', old_ip, '-j', 'DROP'], check=False)
logging.CyberCPLogFileWriter.writeToFile(f'Removed iptables rule for old IP {old_ip}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not remove old iptables rule for {old_ip}: {str(e)}')
# Add new iptables rule
try:
if '/' in new_ip:
subprocess.run(['iptables', '-A', 'INPUT', '-s', new_ip, '-j', 'DROP'], check=True)
else:
subprocess.run(['iptables', '-A', 'INPUT', '-s', new_ip, '-j', 'DROP'], check=True)
logging.CyberCPLogFileWriter.writeToFile(f'Added iptables rule for new IP {new_ip}')
except subprocess.CalledProcessError as e:
final_dic = {'status': 0, 'error_message': f'Failed to add firewall rule for IP {new_ip}: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
banned_ip['ip'] = new_ip
# Update reason
banned_ip['reason'] = reason
# Update expiration if duration changed
if duration == 'never' or duration == 'permanent':
banned_ip['expires'] = 'Never'
banned_ip['duration'] = 'never'
else:
# Calculate new expiration time
current_time = time.time()
duration_map = {
'1h': 3600,
'6h': 21600,
'12h': 43200,
'24h': 86400,
'48h': 172800,
'7d': 604800,
'30d': 2592000
}
duration_seconds = duration_map.get(duration, 86400)
banned_ip['expires'] = current_time + duration_seconds
banned_ip['duration'] = duration
break
if not found:
final_dic = {'status': 0, 'error_message': 'Banned IP record not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
ip_display = new_ip if new_ip != old_ip else old_ip
change_msg = f'IP changed from {old_ip} to {new_ip}' if new_ip != old_ip else f'IP unchanged ({old_ip})'
logging.CyberCPLogFileWriter.writeToFile(f'Modified banned IP record: {change_msg}, reason={reason}, duration={duration}')
final_dic = {'status': 1, 'message': f'Banned IP has been modified successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteBannedIP(self, userID=None, data=None):
"""
Permanently delete a banned IP record
@@ -2054,9 +2376,36 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Save updated banned IPs
with open(banned_ips_file, 'w') as f:
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(updated_banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Remove iptables rule to unban the IP
try:
# Remove iptables rule to unblock the IP
if '/' in ip_to_delete:
# CIDR notation
subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_delete, '-j', 'DROP'], check=False)
else:
# Single IP
subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_delete, '-j', 'DROP'], check=False)
logging.CyberCPLogFileWriter.writeToFile(f'Deleted and unbanned IP {ip_to_delete}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to remove iptables rule for {ip_to_delete}: {str(e)}')
logging.CyberCPLogFileWriter.writeToFile(f'Deleted banned IP record for {ip_to_delete}')
@@ -2227,6 +2576,253 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def exportBannedIPs(self, userID=None):
"""
Export all banned IPs to a JSON file
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('exportStatus', 0)
# Load banned IPs from file
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
banned_ips = []
if os.path.exists(banned_ips_file):
try:
with open(banned_ips_file, 'r') as f:
banned_ips = json.load(f)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error reading banned IPs file: {str(e)}")
banned_ips = []
# Filter out expired bans for export (optional - you might want to include all)
current_time = time.time()
active_banned_ips = []
for banned_ip in banned_ips:
expires_val = banned_ip.get('expires')
expires_timestamp = banned_ip.get('expires_timestamp')
# Include if never expires or not yet expired
if expires_val == 'Never' or expires_timestamp is None:
active_banned_ips.append(banned_ip)
elif isinstance(expires_timestamp, (int, float)) and expires_timestamp > current_time:
active_banned_ips.append(banned_ip)
# Create export data with metadata
export_data = {
'version': '1.0',
'exported_at': time.strftime('%Y-%m-%d %H:%M:%S'),
'total_banned_ips': len(active_banned_ips),
'banned_ips': active_banned_ips
}
# Create JSON response with file download
json_content = json.dumps(export_data, indent=2)
logging.CyberCPLogFileWriter.writeToFile(f"Banned IPs exported successfully. Total IPs: {len(active_banned_ips)}")
# Return file as download
response = HttpResponse(json_content, content_type='application/json')
response['Content-Disposition'] = f'attachment; filename="banned_ips_export_{int(time.time())}.json"'
return response
except BaseException as msg:
final_dic = {'exportStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def importBannedIPs(self, userID=None, data=None):
"""
Import banned IPs from a JSON file
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('importStatus', 0)
# Handle file upload
if hasattr(self.request, 'FILES') and 'import_file' in self.request.FILES:
import_file = self.request.FILES['import_file']
# Read file content
import_data = json.loads(import_file.read().decode('utf-8'))
else:
# Fallback to file path method
import_file_path = data.get('import_file_path', '') if data else ''
if not import_file_path or not os.path.exists(import_file_path):
final_dic = {'importStatus': 0, 'error_message': 'Import file not found or invalid path'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Read and parse the import file
with open(import_file_path, 'r') as f:
import_data = json.load(f)
# Validate the import data structure
if 'banned_ips' not in import_data:
final_dic = {'importStatus': 0, 'error_message': 'Invalid import file format. Missing banned_ips array.'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
imported_count = 0
skipped_count = 0
error_count = 0
errors = []
# Load existing banned IPs
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
existing_banned_ips = []
if os.path.exists(banned_ips_file):
try:
with open(banned_ips_file, 'r') as f:
existing_banned_ips = json.load(f)
except:
existing_banned_ips = []
# Create a set of existing IPs for quick lookup
existing_ips = {banned_ip.get('ip') for banned_ip in existing_banned_ips}
# Import IP address validation
import ipaddress
for banned_ip_data in import_data['banned_ips']:
try:
ip_address = banned_ip_data.get('ip', '').strip()
reason = banned_ip_data.get('reason', '').strip()
# Validate IP address
if not ip_address:
error_count += 1
errors.append(f"Invalid entry: Missing IP address")
continue
try:
ipaddress.ip_address(ip_address.split('/')[0]) # Handle CIDR notation
except ValueError:
error_count += 1
errors.append(f"IP '{ip_address}': Invalid IP address format")
continue
# Check if IP already exists
if ip_address in existing_ips:
skipped_count += 1
continue
# Validate reason
if not reason:
reason = 'Imported from backup'
# Get duration or calculate from expires
duration = banned_ip_data.get('duration', 'never')
expires = banned_ip_data.get('expires', 'Never')
expires_timestamp = banned_ip_data.get('expires_timestamp')
# Calculate expiration if needed
if expires == 'Never' or expires_timestamp is None:
expires_timestamp = None
expires_display = 'Never'
duration = 'never'
elif expires_timestamp and isinstance(expires_timestamp, (int, float)):
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_timestamp))
else:
expires_display = expires if isinstance(expires, str) else 'Never'
expires_timestamp = None
# Create banned IP entry
banned_on = banned_ip_data.get('banned_on', time.time())
if isinstance(banned_on, str):
try:
banned_on = time.mktime(time.strptime(banned_on, '%Y-%m-%d %H:%M:%S'))
except:
banned_on = time.time()
new_banned_ip = {
'id': banned_ip_data.get('id', randint(100000, 999999)),
'ip': ip_address,
'reason': reason,
'banned_on': banned_on,
'banned_on_timestamp': banned_on if isinstance(banned_on, (int, float)) else time.time(),
'expires': expires_display,
'expires_timestamp': expires_timestamp,
'duration': duration,
'active': True
}
# Add iptables rule
try:
if '/' in ip_address:
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip_address, '-j', 'DROP'], check=True)
else:
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip_address, '-j', 'DROP'], check=True)
logging.CyberCPLogFileWriter.writeToFile(f'Added iptables rule for imported IP {ip_address}')
except subprocess.CalledProcessError as e:
error_count += 1
errors.append(f"IP '{ip_address}': Failed to add firewall rule - {str(e)}")
continue
# Add to existing list
existing_banned_ips.append(new_banned_ip)
existing_ips.add(ip_address)
imported_count += 1
except Exception as e:
error_count += 1
errors.append(f"IP '{banned_ip_data.get('ip', 'Unknown')}': {str(e)}")
logging.CyberCPLogFileWriter.writeToFile(f"Error importing banned IP {banned_ip_data.get('ip', 'Unknown')}: {str(e)}")
# Save updated banned IPs
if imported_count > 0 or error_count > 0:
try:
# Create temp file
temp_file = f'/tmp/banned_ips_{randint(100000, 999999)}.json'
with open(temp_file, 'w') as f:
json.dump(existing_banned_ips, f, indent=2)
# Ensure directory exists
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command)
# Move temp file to final location
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command)
# Set permissions
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error saving imported banned IPs: {str(e)}")
final_dic = {'importStatus': 0, 'error_message': f'Failed to save imported IPs: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
logging.CyberCPLogFileWriter.writeToFile(f"Banned IPs import completed. Imported: {imported_count}, Skipped: {skipped_count}, Errors: {error_count}")
final_dic = {
'importStatus': 1,
'error_message': "None",
'imported_count': imported_count,
'skipped_count': skipped_count,
'error_count': error_count,
'errors': errors
}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'importStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
overflow: visible; /* Allow modal to show above container */
}
.page-header {
@@ -611,7 +612,63 @@
border-radius: 16px;
box-shadow: 0 4px 12px var(--shadow-medium, rgba(0,0,0,0.15));
border: 1px solid var(--border-color, #e8e9ff);
overflow: hidden;
overflow: visible; /* Changed from hidden to allow modal to show */
position: relative;
z-index: 1;
}
/* Modal Styles - Ensure it's above everything */
#modifyBannedIPModal, #modifyRuleModal {
z-index: 99999 !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
}
#modifyBannedIPModal.show, #modifyRuleModal.show {
display: flex !important;
opacity: 1 !important;
visibility: visible !important;
align-items: center !important;
justify-content: center !important;
}
#modifyBannedIPModal.fade.show, #modifyRuleModal.fade.show {
opacity: 1 !important;
}
#modifyBannedIPModal .modal-dialog, #modifyRuleModal .modal-dialog {
z-index: 100000 !important;
position: relative !important;
margin: 1.75rem auto !important;
max-width: 500px !important;
width: 90% !important;
}
.modal-backdrop {
z-index: 99998 !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
background-color: rgba(0, 0, 0, 0.5) !important;
}
/* Ensure modal is always on top */
body.modal-open #modifyBannedIPModal {
display: flex !important;
}
/* Make sure modal is not hidden by parent containers */
.modern-container #modifyBannedIPModal,
.banned-ips-panel #modifyBannedIPModal,
.modern-container #modifyRuleModal,
.rules-panel #modifyRuleModal {
position: fixed !important;
z-index: 99999 !important;
}
.add-banned-section {
@@ -770,18 +827,114 @@
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
}
.btn-modify {
background: #2196f3 !important;
color: var(--bg-secondary, white) !important;
border: none !important;
padding: 0.75rem 1.5rem !important;
border-radius: 6px !important;
font-weight: 600 !important;
cursor: pointer !important;
transition: all 0.3s ease;
display: flex !important;
align-items: center !important;
gap: 0.5rem !important;
font-size: 0.95rem !important;
visibility: visible !important;
opacity: 1 !important;
min-width: 100px !important;
min-height: 40px !important;
position: relative !important;
z-index: 10 !important;
}
.btn-modify:hover {
background: #1976d2;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.3);
}
.export-import-buttons {
display: flex;
gap: 0.75rem;
align-items: center;
}
.btn-export, .btn-import {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
font-weight: 500;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
border: none;
}
.btn-export {
background: #10b981;
color: white;
}
.btn-export:hover:not(:disabled) {
background: #059669;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
}
.btn-import {
background: #3b82f6;
color: white;
}
.btn-import:hover:not(:disabled) {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.btn-export:disabled, .btn-import:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Ensure actions column and buttons are always visible */
.banned-table td.actions {
display: flex !important;
visibility: visible !important;
}
.banned-table td.actions {
display: flex !important;
gap: 0.5rem !important;
align-items: center !important;
}
.banned-table td.actions .btn-modify {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
width: auto !important;
height: auto !important;
}
.btn-delete {
background: var(--danger-color, #ef4444);
color: var(--bg-secondary, white);
border: none;
padding: 0.5rem;
padding: 0.75rem 1.5rem !important;
border-radius: 6px;
font-weight: 600 !important;
cursor: pointer;
transition: all 0.3s ease;
width: 32px;
height: 32px;
display: flex;
display: flex !important;
align-items: center;
gap: 0.5rem !important;
font-size: 0.95rem !important;
min-width: 100px !important;
min-height: 40px !important;
justify-content: center;
}
@@ -1009,7 +1162,14 @@
<td>
<span class="port-number">{$ rule.port $}</span>
</td>
<td>
<td class="actions" style="display: flex !important; gap: 0.5rem !important; align-items: center !important;">
<button type="button"
ng-click="handleModifyRuleClick(rule, $event)"
class="btn-modify"
title="{% trans 'Modify Rule' %}">
<i class="fas fa-edit"></i>
{% trans "Modify" %}
</button>
<button type="button"
ng-click="deleteRule(rule.id, rule.proto, rule.port, rule.ipAddress)"
class="btn-delete"
@@ -1057,7 +1217,25 @@
</div>
{% trans "Banned IP Addresses" %}
</div>
<div ng-show="bannedIPsLoading" class="loading-spinner"></div>
<div style="display: flex; align-items: center; gap: 1rem;">
<div ng-show="bannedIPsLoading" class="loading-spinner"></div>
<div class="export-import-buttons">
<button type="button"
ng-click="exportBannedIPs()"
class="btn-export"
ng-disabled="bannedIPsLoading">
<i class="fas fa-download"></i>
{% trans "Export Banned IPs" %}
</button>
<button type="button"
ng-click="importBannedIPs()"
class="btn-import"
ng-disabled="bannedIPsLoading">
<i class="fas fa-upload"></i>
{% trans "Import Banned IPs" %}
</button>
</div>
</div>
</div>
<!-- Add Banned IP Section -->
@@ -1115,22 +1293,23 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="bannedIP in bannedIPs">
<tr ng-repeat="bannedIP in bannedIPs" style="position: relative; z-index: 1;">
<td class="ip-address">
<i class="fas fa-globe"></i>
{$ bannedIP.ip $}
</td>
<td class="reason">
<span class="reason-text">{$ bannedIP.reason $}</span>
<span class="reason-text ng-binding">{$ bannedIP.reason $}</span>
</td>
<td class="banned-date">
<td class="banned-date ng-binding">
<i class="fas fa-calendar"></i>
{$ bannedIP.banned_on | date:'MMM dd, yyyy HH:mm' $}
<span ng-if="bannedIP.banned_on_timestamp">{$ bannedIP.banned_on_timestamp | date:'MMM dd, yyyy HH:mm' $}</span>
<span ng-if="!bannedIP.banned_on_timestamp">{$ bannedIP.banned_on $}</span>
</td>
<td class="expires-date">
<i class="fas fa-clock"></i>
<span ng-if="bannedIP.expires === 'Never'">{% trans "Never" %}</span>
<span ng-if="bannedIP.expires !== 'Never'">{$ bannedIP.expires | date:'MMM dd, yyyy HH:mm' $}</span>
<span ng-if="bannedIP.expires === 'Never' || !bannedIP.expires_timestamp">{% trans "Never" %}</span>
<span ng-if="bannedIP.expires !== 'Never' && bannedIP.expires_timestamp">{$ bannedIP.expires_timestamp | date:'MMM dd, yyyy HH:mm' $}</span>
</td>
<td class="status">
<span ng-class="{'status-active': bannedIP.active, 'status-expired': !bannedIP.active}"
@@ -1139,20 +1318,24 @@
{$ bannedIP.active ? 'Active' : 'Expired' $}
</span>
</td>
<td class="actions">
<td class="actions" style="display: flex !important; gap: 0.5rem !important; align-items: center !important; position: relative !important; z-index: 100 !important;">
<button type="button"
ng-click="removeBannedIP(bannedIP.id, bannedIP.ip)"
class="btn-unban"
title="{% trans 'Unban IP' %}"
ng-if="bannedIP.active">
<i class="fas fa-unlock"></i>
{% trans "Unban" %}
ng-click="handleModifyButtonClick(bannedIP, $event)"
class="btn-modify"
style="display: flex !important; visibility: visible !important; opacity: 1 !important; cursor: pointer !important; pointer-events: auto !important; z-index: 1000 !important; position: relative !important; background: #2196f3 !important; color: white !important; padding: 0.75rem 1.5rem !important; border-radius: 6px !important; border: none !important; font-weight: 600 !important; min-width: 100px !important; min-height: 40px !important;"
data-ip="{$ bannedIP.ip $}"
data-id="{$ bannedIP.id $}"
title="{% trans 'Modify Ban' %}">
<i class="fas fa-edit"></i>
{% trans "Modify" %}
</button>
<button type="button"
ng-click="deleteBannedIP(bannedIP.id, bannedIP.ip)"
class="btn-delete"
title="{% trans 'Delete Record' %}">
style="padding: 0.75rem 1.5rem !important; font-size: 0.95rem !important; min-width: 100px !important; min-height: 40px !important; font-weight: 600 !important;"
title="{% trans 'Delete Record and Unban IP' %}">
<i class="fas fa-trash"></i>
{% trans "Delete" %}
</button>
</td>
</tr>
@@ -1185,6 +1368,141 @@
</div>
</div>
</div>
</div>
<!-- Modify Firewall Rule Modal -->
<div class="modal fade" id="modifyRuleModal" tabindex="-1" role="dialog" aria-labelledby="modifyRuleModalLabel" style="display: none;">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modifyRuleModalLabel">
<i class="fas fa-edit"></i> {% trans "Modify Firewall Rule" %}
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-click="closeModifyRuleModal()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="modifyRuleForm">
<input type="hidden" id="modifyRuleId" name="rule_id">
<div class="form-group">
<label for="modifyRuleName">
<i class="fas fa-tag"></i> {% trans "Rule Name" %}
</label>
<input type="text" class="form-control" id="modifyRuleName" name="rule_name" placeholder="{% trans 'e.g., Allow SSH' %}">
<small class="form-text text-muted">{% trans "Enter a descriptive name for this rule" %}</small>
</div>
<div class="form-group">
<label for="modifyRuleProtocol">
<i class="fas fa-network-wired"></i> {% trans "Protocol" %}
</label>
<select class="form-control" id="modifyRuleProtocol" name="protocol">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
<small class="form-text text-muted">{% trans "Select the network protocol" %}</small>
</div>
<div class="form-group">
<label for="modifyRuleIP">
<i class="fas fa-globe"></i> {% trans "IP Address" %}
</label>
<input type="text" class="form-control" id="modifyRuleIP" name="ip_address" placeholder="{% trans '0.0.0.0/0 for all IPs' %}">
<small class="form-text text-muted">{% trans "Enter IP address or CIDR notation (e.g., 192.168.1.0/24)" %}</small>
</div>
<div class="form-group">
<label for="modifyRulePort">
<i class="fas fa-plug"></i> {% trans "Port" %}
</label>
<input type="text" class="form-control" id="modifyRulePort" name="port" placeholder="{% trans 'e.g., 80 or 8000-8010' %}">
<small class="form-text text-muted">{% trans "Enter port number or range" %}</small>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>{% trans "Note:" %}</strong> {% trans "Modifying a rule will update the firewall configuration. The old rule will be removed and replaced with the new settings." %}
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" ng-click="closeModifyRuleModal()">
<i class="fas fa-times"></i> {% trans "Cancel" %}
</button>
<button type="button" class="btn btn-primary" ng-click="modifyRule()">
<i class="fas fa-save"></i> {% trans "Save Changes" %}
</button>
</div>
</div>
</div>
</div>
<!-- Modify Banned IP Modal - Inside controller for AngularJS scope access -->
<div class="modal fade" id="modifyBannedIPModal" tabindex="-1" role="dialog" aria-labelledby="modifyBannedIPModalLabel" style="display: none;">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modifyBannedIPModalLabel">
<i class="fas fa-edit"></i> {% trans "Modify Banned IP" %}
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-click="closeModifyModal()" onclick="if(window.closeModifyModalGlobal) { window.closeModifyModalGlobal(); } else { var modal = document.getElementById('modifyBannedIPModal'); if(modal) { modal.classList.remove('show'); modal.removeAttribute('aria-hidden'); modal.setAttribute('aria-hidden', 'true'); modal.style.display = 'none'; document.body.classList.remove('modal-open'); document.body.style.overflow = ''; var backdrops = document.querySelectorAll('.modal-backdrop'); for(var i=0; i<backdrops.length; i++) backdrops[i].remove(); } } return false;">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="modifyBannedIPForm">
<input type="hidden" id="modifyBannedIPId" name="ban_id">
<div class="form-group">
<label for="modifyBannedIPAddress">
<i class="fas fa-globe"></i> {% trans "IP Address" %}
</label>
<input type="text" class="form-control" id="modifyBannedIPAddress" name="ip_address" placeholder="{% trans 'Enter IP address' %}">
<small class="form-text text-muted">{% trans "Enter the IP address to ban" %}</small>
</div>
<div class="form-group">
<label for="modifyBannedIPReason">
<i class="fas fa-comment-alt"></i> {% trans "Reason" %}
</label>
<input type="text" class="form-control" id="modifyBannedIPReason" name="reason" placeholder="{% trans 'Enter ban reason' %}">
<small class="form-text text-muted">{% trans "Reason for banning this IP address" %}</small>
</div>
<div class="form-group">
<label for="modifyBannedIPDuration">
<i class="fas fa-clock"></i> {% trans "Duration" %}
</label>
<select class="form-control" id="modifyBannedIPDuration" name="duration">
<option value="1h">{% trans "1 Hour" %}</option>
<option value="6h">{% trans "6 Hours" %}</option>
<option value="12h">{% trans "12 Hours" %}</option>
<option value="24h">{% trans "24 Hours" %}</option>
<option value="48h">{% trans "48 Hours" %}</option>
<option value="7d">{% trans "7 Days" %}</option>
<option value="30d">{% trans "30 Days" %}</option>
<option value="never" selected>{% trans "Never (Permanent)" %}</option>
</select>
<small class="form-text text-muted">{% trans "How long should this IP remain banned?" %}</small>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>{% trans "Note:" %}</strong> {% trans "Modifying a ban will update the IP address, reason, and expiration time. Changing the IP address will update the firewall rule." %}
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" ng-click="closeModifyModal()" onclick="if(window.closeModifyModalGlobal) { window.closeModifyModalGlobal(); } else { var modal = document.getElementById('modifyBannedIPModal'); if(modal) { modal.classList.remove('show'); modal.removeAttribute('aria-hidden'); modal.setAttribute('aria-hidden', 'true'); modal.style.display = 'none'; document.body.classList.remove('modal-open'); document.body.style.overflow = ''; var backdrops = document.querySelectorAll('.modal-backdrop'); for(var i=0; i<backdrops.length; i++) backdrops[i].remove(); } } return false;">
<i class="fas fa-times"></i> {% trans "Cancel" %}
</button>
<button type="button" class="btn btn-primary" ng-click="modifyBannedIP()" onclick="if(window.modifyBannedIPGlobal) { window.modifyBannedIPGlobal(); } else { alert('Error: Cannot save changes. Please refresh the page.'); } return false;">
<i class="fas fa-save"></i> {% trans "Save Changes" %}
</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -6,6 +6,7 @@ urlpatterns = [
path('', views.firewallHome, name='firewallHome'),
path('getCurrentRules', views.getCurrentRules, name='getCurrentRules'),
path('addRule', views.addRule, name='addRule'),
path('modifyRule', views.modifyRule, name='modifyRule'),
path('deleteRule', views.deleteRule, name='deleteRule'),
path('reloadFirewall', views.reloadFirewall, name='reloadFirewall'),
@@ -36,8 +37,11 @@ urlpatterns = [
# Banned IPs
path('getBannedIPs', views.getBannedIPs, name='getBannedIPs'),
path('addBannedIP', views.addBannedIP, name='addBannedIP'),
path('modifyBannedIP', views.modifyBannedIP, name='modifyBannedIP'),
path('removeBannedIP', views.removeBannedIP, name='removeBannedIP'),
path('deleteBannedIP', views.deleteBannedIP, name='deleteBannedIP'),
path('exportBannedIPs', views.exportBannedIPs, name='exportBannedIPs'),
path('importBannedIPs', views.importBannedIPs, name='importBannedIPs'),
path('getRulesFiles', views.getRulesFiles, name='getRulesFiles'),
path('enableDisableRuleFile', views.enableDisableRuleFile, name='enableDisableRuleFile'),

View File

@@ -66,6 +66,15 @@ def addRule(request):
return redirect(loadLoginPage)
def modifyRule(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.modifyRule(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def deleteRule(request):
try:
userID = request.session['userID']
@@ -666,6 +675,14 @@ def addBannedIP(request):
except KeyError:
return redirect(loadLoginPage)
def modifyBannedIP(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.modifyBannedIP(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def removeBannedIP(request):
try:
userID = request.session['userID']
@@ -703,5 +720,30 @@ def importFirewallRules(request):
else:
# Handle JSON data
return fm.importFirewallRules(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def exportBannedIPs(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.exportBannedIPs(userID)
except KeyError:
return redirect(loadLoginPage)
def importBannedIPs(request):
try:
userID = request.session['userID']
fm = FirewallManager()
fm.request = request # Set request for file upload handling
# Handle file upload
if request.method == 'POST' and 'import_file' in request.FILES:
return fm.importBannedIPs(userID, None)
else:
# Handle JSON data
return fm.importBannedIPs(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)

View File

@@ -15,6 +15,6 @@ fi
echo "Upgrading CyberPanel from branch: $BRANCH_NAME"
rm -f /usr/local/cyberpanel_upgrade.sh
wget -O /usr/local/cyberpanel_upgrade.sh https://raw.githubusercontent.com/usmannasir/cyberpanel/$BRANCH_NAME/cyberpanel_upgrade.sh 2>/dev/null
wget -O /usr/local/cyberpanel_upgrade.sh https://raw.githubusercontent.com/master3395/cyberpanel/$BRANCH_NAME/cyberpanel_upgrade.sh 2>/dev/null
chmod 700 /usr/local/cyberpanel_upgrade.sh
/usr/local/cyberpanel_upgrade.sh $@