mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-11-02 11:26:28 +01:00
Add firewall rule management features and enhance repository setup
- Implemented functionality to edit existing firewall rules, including validation and error handling. - Added endpoints for exporting and importing firewall rules in JSON format, allowing users to manage rules more efficiently. - Enhanced the user interface with modals for editing rules and buttons for exporting/importing rules. - Updated the `cyberpanel.sh` script to support AlmaLinux 10 and improved LiteSpeed GPG key import with fallback options. - Refactored repository setup to accommodate different OS versions, ensuring compatibility with CentOS and AlmaLinux.
This commit is contained in:
@@ -270,6 +270,11 @@ setup_epel_repo() {
|
|||||||
yum install -y https://cyberpanel.sh/dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
|
yum install -y https://cyberpanel.sh/dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
|
||||||
Check_Return "yum repo" "no_exit"
|
Check_Return "yum repo" "no_exit"
|
||||||
;;
|
;;
|
||||||
|
"10")
|
||||||
|
# AlmaLinux 10 EPEL support
|
||||||
|
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
|
||||||
|
Check_Return "yum repo" "no_exit"
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,11 +314,12 @@ gpgcheck=1
|
|||||||
EOF
|
EOF
|
||||||
elif [[ "$Server_OS_Version" = "10" ]] && uname -m | grep -q 'x86_64'; then
|
elif [[ "$Server_OS_Version" = "10" ]] && uname -m | grep -q 'x86_64'; then
|
||||||
cat <<EOF >/etc/yum.repos.d/MariaDB.repo
|
cat <<EOF >/etc/yum.repos.d/MariaDB.repo
|
||||||
# MariaDB 10.11 CentOS repository list - created 2021-08-06 02:01 UTC
|
# MariaDB 10.11 RHEL10 repository list - AlmaLinux 10 compatible
|
||||||
# http://downloads.mariadb.org/mariadb/repositories/
|
# http://downloads.mariadb.org/mariadb/repositories/
|
||||||
[mariadb]
|
[mariadb]
|
||||||
name = MariaDB
|
name = MariaDB
|
||||||
baseurl = http://yum.mariadb.org/10.11/rhel9-amd64/
|
baseurl = http://yum.mariadb.org/10.11/rhel10-amd64/
|
||||||
|
module_hotfixes=1
|
||||||
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
|
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
|
||||||
enabled=1
|
enabled=1
|
||||||
gpgcheck=1
|
gpgcheck=1
|
||||||
@@ -1117,8 +1123,14 @@ log_function_start "Pre_Install_Setup_Repository"
|
|||||||
log_info "Setting up package repositories for $Server_OS $Server_OS_Version"
|
log_info "Setting up package repositories for $Server_OS $Server_OS_Version"
|
||||||
if [[ $Server_OS = "CentOS" ]] ; then
|
if [[ $Server_OS = "CentOS" ]] ; then
|
||||||
log_debug "Importing LiteSpeed GPG key"
|
log_debug "Importing LiteSpeed GPG key"
|
||||||
rpm --import https://cyberpanel.sh/rpms.litespeedtech.com/centos/RPM-GPG-KEY-litespeed
|
# Import LiteSpeed GPG key with fallback
|
||||||
#import the LiteSpeed GPG key
|
rpm --import https://cyberpanel.sh/rpms.litespeedtech.com/centos/RPM-GPG-KEY-litespeed || {
|
||||||
|
warning "Primary GPG key import failed, trying alternative source"
|
||||||
|
rpm --import https://rpms.litespeedtech.com/centos/RPM-GPG-KEY-litespeed || {
|
||||||
|
error "Failed to import LiteSpeed GPG key from all sources"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
yum clean all
|
yum clean all
|
||||||
yum autoremove -y epel-release
|
yum autoremove -y epel-release
|
||||||
@@ -1151,7 +1163,12 @@ if [[ $Server_OS = "CentOS" ]] ; then
|
|||||||
dnf config-manager --set-enabled crb
|
dnf config-manager --set-enabled crb
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Install appropriate remi-release based on version
|
||||||
|
if [[ "$Server_OS_Version" = "9" ]]; then
|
||||||
yum install -y https://rpms.remirepo.net/enterprise/remi-release-9.rpm
|
yum install -y https://rpms.remirepo.net/enterprise/remi-release-9.rpm
|
||||||
|
elif [[ "$Server_OS_Version" = "10" ]]; then
|
||||||
|
yum install -y https://rpms.remirepo.net/enterprise/remi-release-10.rpm
|
||||||
|
fi
|
||||||
Check_Return "yum repo" "no_exit"
|
Check_Return "yum repo" "no_exit"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1341,8 +1358,22 @@ if [[ "$Server_OS" = "CentOS" ]] || [[ "$Server_OS" = "openEuler" ]] ; then
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
dnf install -y libnsl zip wget strace net-tools curl which bc telnet htop libevent-devel gcc libattr-devel xz-devel MariaDB-server MariaDB-client MariaDB-devel curl-devel git platform-python-devel tar socat python3 zip unzip bind-utils gpgme-devel openssl-devel
|
dnf install -y libnsl zip wget strace net-tools curl which bc telnet htop libevent-devel gcc libattr-devel xz-devel MariaDB-server MariaDB-client MariaDB-devel curl-devel git platform-python-devel tar socat python3 zip unzip bind-utils gpgme-devel openssl-devel boost-devel boost-program-options
|
||||||
Check_Return
|
Check_Return
|
||||||
|
|
||||||
|
# Fix boost library compatibility for galera-4 on AlmaLinux 10
|
||||||
|
if [[ "$Server_OS_Version" = "10" ]]; then
|
||||||
|
# Create symlink for boost libraries if needed
|
||||||
|
if [ ! -f /usr/lib64/libboost_program_options.so.1.75.0 ]; then
|
||||||
|
BOOST_VERSION=$(find /usr/lib64 -name "libboost_program_options.so.*" | head -1 | sed 's/.*libboost_program_options\.so\.//')
|
||||||
|
if [ -n "$BOOST_VERSION" ]; then
|
||||||
|
ln -sf /usr/lib64/libboost_program_options.so.$BOOST_VERSION /usr/lib64/libboost_program_options.so.1.75.0
|
||||||
|
log_info "Created boost library symlink for galera-4 compatibility: $BOOST_VERSION -> 1.75.0"
|
||||||
|
else
|
||||||
|
warning "Could not find boost libraries, galera-4 may not work properly"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
elif [[ "$Server_OS_Version" = "20" ]] || [[ "$Server_OS_Version" = "22" ]] || [[ "$Server_OS_Version" = "24" ]] ; then
|
elif [[ "$Server_OS_Version" = "20" ]] || [[ "$Server_OS_Version" = "22" ]] || [[ "$Server_OS_Version" = "24" ]] ; then
|
||||||
dnf install -y libnsl zip wget strace net-tools curl which bc telnet htop libevent-devel gcc libattr-devel xz-devel mariadb-devel curl-devel git python3-devel tar socat python3 zip unzip bind-utils gpgme-devel
|
dnf install -y libnsl zip wget strace net-tools curl which bc telnet htop libevent-devel gcc libattr-devel xz-devel mariadb-devel curl-devel git python3-devel tar socat python3 zip unzip bind-utils gpgme-devel
|
||||||
Check_Return
|
Check_Return
|
||||||
|
|||||||
121
firewall/EXPORT_IMPORT_FIREWALL_RULES.md
Normal file
121
firewall/EXPORT_IMPORT_FIREWALL_RULES.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# Firewall Rules Export/Import Feature
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This feature allows CyberPanel administrators to export and import firewall rules between servers, making it easy to replicate security configurations across multiple servers.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Export Functionality
|
||||||
|
- Exports all custom firewall rules to a JSON file
|
||||||
|
- Excludes default CyberPanel rules (CyberPanel Admin, SSHCustom) to prevent conflicts
|
||||||
|
- Includes metadata such as export timestamp and rule count
|
||||||
|
- Downloads file directly to the user's browser
|
||||||
|
|
||||||
|
### Import Functionality
|
||||||
|
- Imports firewall rules from a previously exported JSON file
|
||||||
|
- Validates file format before processing
|
||||||
|
- Skips duplicate rules (same name, protocol, port, and IP address)
|
||||||
|
- Excludes default CyberPanel rules from import
|
||||||
|
- Provides detailed import summary (imported, skipped, error counts)
|
||||||
|
- Shows specific error messages for failed imports
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Exporting Rules
|
||||||
|
1. Navigate to the Firewall section in CyberPanel
|
||||||
|
2. Click the "Export Rules" button in the Firewall Rules panel header
|
||||||
|
3. The system will generate and download a JSON file containing your custom rules
|
||||||
|
|
||||||
|
### Importing Rules
|
||||||
|
1. Navigate to the Firewall section in CyberPanel
|
||||||
|
2. Click the "Import Rules" button in the Firewall Rules panel header
|
||||||
|
3. Select a previously exported JSON file
|
||||||
|
4. The system will process the import and show a summary of results
|
||||||
|
|
||||||
|
## File Format
|
||||||
|
|
||||||
|
The exported JSON file has the following structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"exported_at": "2024-01-15 14:30:25",
|
||||||
|
"total_rules": 5,
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "Custom Web Server",
|
||||||
|
"proto": "tcp",
|
||||||
|
"port": "8080",
|
||||||
|
"ipAddress": "0.0.0.0/0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Database Access",
|
||||||
|
"proto": "tcp",
|
||||||
|
"port": "3306",
|
||||||
|
"ipAddress": "192.168.1.0/24"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Only administrators can export/import firewall rules
|
||||||
|
- Default CyberPanel rules are excluded to prevent system conflicts
|
||||||
|
- Import process validates file format and rule data
|
||||||
|
- Failed imports are logged for troubleshooting
|
||||||
|
- Duplicate rules are automatically skipped
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The system provides comprehensive error handling:
|
||||||
|
- Invalid file format detection
|
||||||
|
- Missing required fields validation
|
||||||
|
- Individual rule import error tracking
|
||||||
|
- Detailed error messages for troubleshooting
|
||||||
|
- Import summary with counts of successful, skipped, and failed imports
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### Backend Components
|
||||||
|
- `exportFirewallRules()` method in `FirewallManager`
|
||||||
|
- `importFirewallRules()` method in `FirewallManager`
|
||||||
|
- New URL patterns for export/import endpoints
|
||||||
|
- File upload handling for import functionality
|
||||||
|
|
||||||
|
### Frontend Components
|
||||||
|
- Export/Import buttons in firewall UI
|
||||||
|
- File download handling for exports
|
||||||
|
- File upload dialog for imports
|
||||||
|
- Progress indicators and error messaging
|
||||||
|
- Import summary display
|
||||||
|
|
||||||
|
### Database Integration
|
||||||
|
- Uses existing `FirewallRules` model
|
||||||
|
- Maintains referential integrity
|
||||||
|
- Preserves rule relationships and constraints
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Time Efficiency**: Significantly reduces time to replicate firewall rules across servers
|
||||||
|
2. **Error Reduction**: Minimizes human error in manual rule creation
|
||||||
|
3. **Consistency**: Ensures identical security policies across multiple servers
|
||||||
|
4. **Backup**: Provides a way to backup and restore firewall configurations
|
||||||
|
5. **Migration**: Simplifies server migration and setup processes
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
- Compatible with CyberPanel's existing firewall system
|
||||||
|
- Works with both TCP and UDP protocols
|
||||||
|
- Supports all IP address formats (single IPs, CIDR ranges)
|
||||||
|
- Maintains compatibility with existing firewall utilities
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential future improvements could include:
|
||||||
|
- Rule conflict detection and resolution
|
||||||
|
- Selective rule import (choose specific rules)
|
||||||
|
- Rule templates and presets
|
||||||
|
- Bulk rule management
|
||||||
|
- Integration with configuration management tools
|
||||||
@@ -168,6 +168,79 @@ class FirewallManager:
|
|||||||
final_json = json.dumps(final_dic)
|
final_json = json.dumps(final_dic)
|
||||||
return HttpResponse(final_json)
|
return HttpResponse(final_json)
|
||||||
|
|
||||||
|
def editRule(self, userID = None, data = None):
|
||||||
|
"""
|
||||||
|
Edit an existing firewall rule
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
currentACL = ACLManager.loadedACL(userID)
|
||||||
|
|
||||||
|
if currentACL['admin'] == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return ACLManager.loadErrorJson('edit_status', 0)
|
||||||
|
|
||||||
|
ruleID = data['id']
|
||||||
|
newRuleName = data['ruleName']
|
||||||
|
newRuleProtocol = data['ruleProtocol']
|
||||||
|
newRulePort = data['rulePort']
|
||||||
|
newRuleIP = data['ruleIP']
|
||||||
|
|
||||||
|
# Get the existing rule
|
||||||
|
try:
|
||||||
|
existingRule = FirewallRules.objects.get(id=ruleID)
|
||||||
|
except FirewallRules.DoesNotExist:
|
||||||
|
final_dic = {'status': 0, 'edit_status': 0, 'error_message': 'Rule not found'}
|
||||||
|
final_json = json.dumps(final_dic)
|
||||||
|
return HttpResponse(final_json)
|
||||||
|
|
||||||
|
# Store old values for system firewall update
|
||||||
|
oldProtocol = existingRule.proto
|
||||||
|
oldPort = existingRule.port
|
||||||
|
oldIP = existingRule.ipAddress
|
||||||
|
|
||||||
|
# Check if any values actually changed
|
||||||
|
if (existingRule.name == newRuleName and
|
||||||
|
existingRule.proto == newRuleProtocol and
|
||||||
|
existingRule.port == newRulePort and
|
||||||
|
existingRule.ipAddress == newRuleIP):
|
||||||
|
final_dic = {'status': 1, 'edit_status': 1, 'error_message': "No changes detected"}
|
||||||
|
final_json = json.dumps(final_dic)
|
||||||
|
return HttpResponse(final_json)
|
||||||
|
|
||||||
|
# Check if another rule with the same name already exists (excluding current rule)
|
||||||
|
if existingRule.name != newRuleName:
|
||||||
|
duplicateRule = FirewallRules.objects.filter(name=newRuleName).exclude(id=ruleID).first()
|
||||||
|
if duplicateRule:
|
||||||
|
final_dic = {'status': 0, 'edit_status': 0, 'error_message': f'A rule with name "{newRuleName}" already exists'}
|
||||||
|
final_json = json.dumps(final_dic)
|
||||||
|
return HttpResponse(final_json)
|
||||||
|
|
||||||
|
# Update the rule in the system firewall
|
||||||
|
# First remove the old rule
|
||||||
|
FirewallUtilities.deleteRule(oldProtocol, oldPort, oldIP)
|
||||||
|
|
||||||
|
# Then add the new rule
|
||||||
|
FirewallUtilities.addRule(newRuleProtocol, newRulePort, newRuleIP)
|
||||||
|
|
||||||
|
# Update the database record
|
||||||
|
existingRule.name = newRuleName
|
||||||
|
existingRule.proto = newRuleProtocol
|
||||||
|
existingRule.port = newRulePort
|
||||||
|
existingRule.ipAddress = newRuleIP
|
||||||
|
existingRule.save()
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f"Firewall rule edited successfully. ID: {ruleID}, Name: {newRuleName}")
|
||||||
|
|
||||||
|
final_dic = {'status': 1, 'edit_status': 1, 'error_message': "None"}
|
||||||
|
final_json = json.dumps(final_dic)
|
||||||
|
return HttpResponse(final_json)
|
||||||
|
|
||||||
|
except BaseException as msg:
|
||||||
|
final_dic = {'status': 0, 'edit_status': 0, 'error_message': str(msg)}
|
||||||
|
final_json = json.dumps(final_dic)
|
||||||
|
return HttpResponse(final_json)
|
||||||
|
|
||||||
def reloadFirewall(self, userID = None, data = None):
|
def reloadFirewall(self, userID = None, data = None):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
@@ -1755,6 +1828,164 @@ class FirewallManager:
|
|||||||
final_json = json.dumps(final_dic)
|
final_json = json.dumps(final_dic)
|
||||||
return HttpResponse(final_json)
|
return HttpResponse(final_json)
|
||||||
|
|
||||||
|
def exportFirewallRules(self, userID = None):
|
||||||
|
"""
|
||||||
|
Export all custom firewall rules to a JSON file, excluding default CyberPanel rules
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
currentACL = ACLManager.loadedACL(userID)
|
||||||
|
|
||||||
|
if currentACL['admin'] == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return ACLManager.loadErrorJson('exportStatus', 0)
|
||||||
|
|
||||||
|
# Get all firewall rules
|
||||||
|
rules = FirewallRules.objects.all()
|
||||||
|
|
||||||
|
# Default CyberPanel rules to exclude
|
||||||
|
default_rules = ['CyberPanel Admin', 'SSHCustom']
|
||||||
|
|
||||||
|
# Filter out default rules
|
||||||
|
custom_rules = []
|
||||||
|
for rule in rules:
|
||||||
|
if rule.name not in default_rules:
|
||||||
|
custom_rules.append({
|
||||||
|
'name': rule.name,
|
||||||
|
'proto': rule.proto,
|
||||||
|
'port': rule.port,
|
||||||
|
'ipAddress': rule.ipAddress
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create export data with metadata
|
||||||
|
export_data = {
|
||||||
|
'version': '1.0',
|
||||||
|
'exported_at': time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'total_rules': len(custom_rules),
|
||||||
|
'rules': custom_rules
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create JSON response with file download
|
||||||
|
json_content = json.dumps(export_data, indent=2)
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f"Firewall rules exported successfully. Total rules: {len(custom_rules)}")
|
||||||
|
|
||||||
|
# Return file as download
|
||||||
|
response = HttpResponse(json_content, content_type='application/json')
|
||||||
|
response['Content-Disposition'] = f'attachment; filename="firewall_rules_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 importFirewallRules(self, userID = None, data = None):
|
||||||
|
"""
|
||||||
|
Import firewall rules 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 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 'rules' not in import_data:
|
||||||
|
final_dic = {'importStatus': 0, 'error_message': 'Invalid import file format. Missing rules array.'}
|
||||||
|
final_json = json.dumps(final_dic)
|
||||||
|
return HttpResponse(final_json)
|
||||||
|
|
||||||
|
imported_count = 0
|
||||||
|
skipped_count = 0
|
||||||
|
error_count = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# Default CyberPanel rules to exclude from import
|
||||||
|
default_rules = ['CyberPanel Admin', 'SSHCustom']
|
||||||
|
|
||||||
|
for rule_data in import_data['rules']:
|
||||||
|
try:
|
||||||
|
# Skip default rules
|
||||||
|
if rule_data.get('name', '') in default_rules:
|
||||||
|
skipped_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if rule already exists
|
||||||
|
existing_rule = FirewallRules.objects.filter(
|
||||||
|
name=rule_data['name'],
|
||||||
|
proto=rule_data['proto'],
|
||||||
|
port=rule_data['port'],
|
||||||
|
ipAddress=rule_data['ipAddress']
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing_rule:
|
||||||
|
skipped_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add the rule to the system firewall
|
||||||
|
FirewallUtilities.addRule(
|
||||||
|
rule_data['proto'],
|
||||||
|
rule_data['port'],
|
||||||
|
rule_data['ipAddress']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the rule to the database
|
||||||
|
new_rule = FirewallRules(
|
||||||
|
name=rule_data['name'],
|
||||||
|
proto=rule_data['proto'],
|
||||||
|
port=rule_data['port'],
|
||||||
|
ipAddress=rule_data['ipAddress']
|
||||||
|
)
|
||||||
|
new_rule.save()
|
||||||
|
|
||||||
|
imported_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_count += 1
|
||||||
|
errors.append(f"Rule '{rule_data.get('name', 'Unknown')}': {str(e)}")
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f"Error importing rule {rule_data.get('name', 'Unknown')}: {str(e)}")
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f"Firewall rules 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ app.controller('firewallController', function ($scope, $http) {
|
|||||||
$scope.couldNotConnect = true;
|
$scope.couldNotConnect = true;
|
||||||
$scope.rulesDetails = false;
|
$scope.rulesDetails = false;
|
||||||
|
|
||||||
|
// Edit modal variables
|
||||||
|
$scope.showEditModal = false;
|
||||||
|
$scope.editingRule = {};
|
||||||
|
|
||||||
firewallStatus();
|
firewallStatus();
|
||||||
|
|
||||||
populateCurrentRecords();
|
populateCurrentRecords();
|
||||||
@@ -503,6 +507,249 @@ app.controller('firewallController', function ($scope, $http) {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Export/Import Functions
|
||||||
|
$scope.exportRules = function () {
|
||||||
|
$scope.rulesLoading = false;
|
||||||
|
$scope.actionFailed = true;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
|
||||||
|
url = "/firewall/exportFirewallRules";
|
||||||
|
|
||||||
|
var data = {};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(exportSuccess, exportError);
|
||||||
|
|
||||||
|
function exportSuccess(response) {
|
||||||
|
$scope.rulesLoading = true;
|
||||||
|
|
||||||
|
// Check if response is JSON (error) or file download
|
||||||
|
if (typeof response.data === 'string' && response.data.includes('{')) {
|
||||||
|
try {
|
||||||
|
var errorData = JSON.parse(response.data);
|
||||||
|
if (errorData.exportStatus === 0) {
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = errorData.error_message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// If not JSON, assume it's the file content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, it's a successful file download
|
||||||
|
$scope.actionFailed = true;
|
||||||
|
$scope.actionSuccess = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportError(response) {
|
||||||
|
$scope.rulesLoading = true;
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.importRules = function () {
|
||||||
|
// Create file input element
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.accept = '.json';
|
||||||
|
input.style.display = 'none';
|
||||||
|
|
||||||
|
input.onchange = function(event) {
|
||||||
|
var file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
try {
|
||||||
|
var importData = JSON.parse(e.target.result);
|
||||||
|
|
||||||
|
// Validate file format
|
||||||
|
if (!importData.rules || !Array.isArray(importData.rules)) {
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = "Invalid import file format. Please select a valid firewall rules export file.";
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload file to server
|
||||||
|
uploadImportFile(file);
|
||||||
|
} catch (error) {
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = "Invalid JSON file. Please select a valid firewall rules export file.";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.click();
|
||||||
|
document.body.removeChild(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
function uploadImportFile(file) {
|
||||||
|
$scope.rulesLoading = false;
|
||||||
|
$scope.actionFailed = true;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append('import_file', file);
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken'),
|
||||||
|
'Content-Type': undefined
|
||||||
|
},
|
||||||
|
transformRequest: angular.identity
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post("/firewall/importFirewallRules", formData, config).then(importSuccess, importError);
|
||||||
|
|
||||||
|
function importSuccess(response) {
|
||||||
|
$scope.rulesLoading = true;
|
||||||
|
|
||||||
|
if (response.data.importStatus === 1) {
|
||||||
|
$scope.actionFailed = true;
|
||||||
|
$scope.actionSuccess = false;
|
||||||
|
|
||||||
|
// Refresh rules list
|
||||||
|
populateCurrentRecords();
|
||||||
|
|
||||||
|
// Show import summary
|
||||||
|
var summary = `Import completed successfully!\n` +
|
||||||
|
`Imported: ${response.data.imported_count} rules\n` +
|
||||||
|
`Skipped: ${response.data.skipped_count} rules\n` +
|
||||||
|
`Errors: ${response.data.error_count} rules`;
|
||||||
|
|
||||||
|
if (response.data.errors && response.data.errors.length > 0) {
|
||||||
|
summary += `\n\nErrors:\n${response.data.errors.join('\n')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(summary);
|
||||||
|
} else {
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function importError(response) {
|
||||||
|
$scope.rulesLoading = true;
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit Rule Functions
|
||||||
|
$scope.editRule = function(rule) {
|
||||||
|
$scope.editingRule = {
|
||||||
|
id: rule.id,
|
||||||
|
name: rule.name,
|
||||||
|
proto: rule.proto,
|
||||||
|
port: rule.port,
|
||||||
|
ipAddress: rule.ipAddress
|
||||||
|
};
|
||||||
|
$scope.showEditModal = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.closeEditModal = function() {
|
||||||
|
$scope.showEditModal = false;
|
||||||
|
$scope.editingRule = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.saveEditedRule = function() {
|
||||||
|
// Basic validation
|
||||||
|
if (!$scope.editingRule.name || !$scope.editingRule.port || !$scope.editingRule.ipAddress) {
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = "Please fill in all required fields.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.rulesLoading = false;
|
||||||
|
$scope.actionFailed = true;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
|
||||||
|
url = "/firewall/editRule";
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
id: $scope.editingRule.id,
|
||||||
|
ruleName: $scope.editingRule.name,
|
||||||
|
ruleProtocol: $scope.editingRule.proto,
|
||||||
|
rulePort: $scope.editingRule.port,
|
||||||
|
ruleIP: $scope.editingRule.ipAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(editSuccess, editError);
|
||||||
|
|
||||||
|
function editSuccess(response) {
|
||||||
|
$scope.rulesLoading = true;
|
||||||
|
|
||||||
|
if (response.data.edit_status === 1) {
|
||||||
|
// Close modal and refresh rules
|
||||||
|
$scope.closeEditModal();
|
||||||
|
populateCurrentRecords();
|
||||||
|
|
||||||
|
$scope.actionFailed = true;
|
||||||
|
$scope.actionSuccess = false;
|
||||||
|
} else {
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function editError(response) {
|
||||||
|
$scope.rulesLoading = true;
|
||||||
|
$scope.actionFailed = false;
|
||||||
|
$scope.actionSuccess = true;
|
||||||
|
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close modal when clicking outside or pressing Escape
|
||||||
|
$scope.$on('$locationChangeStart', function() {
|
||||||
|
$scope.closeEditModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard support for modal
|
||||||
|
$(document).on('keydown', function(e) {
|
||||||
|
if ($scope.showEditModal && e.keyCode === 27) { // Escape key
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.closeEditModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus management for modal
|
||||||
|
$scope.$watch('showEditModal', function(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#editRuleModal input[name="ruleName"]').focus();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -421,6 +421,205 @@
|
|||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
background: var(--bg-info-light, #dbeafe);
|
||||||
|
color: var(--info-color, #3b82f6);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-weight: 700;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit:hover {
|
||||||
|
background: var(--info-color, #3b82f6);
|
||||||
|
color: var(--text-light, white);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Export/Import Buttons */
|
||||||
|
.export-import-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-export, .btn-import {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
color: var(--text-light, white);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-export:hover, .btn-import:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-export:disabled, .btn-import:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit Modal Styles */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--bg-secondary, white);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
animation: slideInUp 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
border-bottom: 1px solid var(--border-color, #e8e9ff);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: linear-gradient(135deg, var(--firewall-gradient-start, #ef4444) 0%, var(--firewall-gradient-end, #dc2626) 100%);
|
||||||
|
color: var(--text-light, white);
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: none;
|
||||||
|
color: var(--text-light, white);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-rule-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
border-top: 1px solid var(--border-color, #e8e9ff);
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
background: var(--bg-tertiary, #f8f9ff);
|
||||||
|
border-radius: 0 0 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel, .btn-save {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 120px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background: var(--bg-muted, #f1f5f9);
|
||||||
|
color: var(--text-secondary, #475569);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background: var(--bg-muted-hover, #e2e8f0);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background: var(--firewall-accent, #ef4444);
|
||||||
|
color: var(--text-light, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
background: var(--firewall-accent-dark, #dc2626);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px var(--firewall-shadow, rgba(239, 68, 68, 0.3));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 4rem 2rem;
|
padding: 4rem 2rem;
|
||||||
@@ -600,7 +799,25 @@
|
|||||||
</div>
|
</div>
|
||||||
{% trans "Firewall Rules" %}
|
{% trans "Firewall Rules" %}
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||||
<div ng-show="rulesLoading" class="loading-spinner"></div>
|
<div ng-show="rulesLoading" class="loading-spinner"></div>
|
||||||
|
<div class="export-import-buttons">
|
||||||
|
<button type="button"
|
||||||
|
ng-click="exportRules()"
|
||||||
|
class="btn-export"
|
||||||
|
ng-disabled="rulesLoading">
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
{% trans "Export Rules" %}
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
ng-click="importRules()"
|
||||||
|
class="btn-import"
|
||||||
|
ng-disabled="rulesLoading">
|
||||||
|
<i class="fas fa-upload"></i>
|
||||||
|
{% trans "Import Rules" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Rule Section -->
|
<!-- Add Rule Section -->
|
||||||
@@ -680,12 +897,20 @@
|
|||||||
<span class="port-number">{$ rule.port $}</span>
|
<span class="port-number">{$ rule.port $}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div style="display: flex; gap: 0.5rem; align-items: center;">
|
||||||
|
<button type="button"
|
||||||
|
ng-click="editRule(rule)"
|
||||||
|
class="btn-edit"
|
||||||
|
title="{% trans 'Edit Rule' %}">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
ng-click="deleteRule(rule.id, rule.proto, rule.port, rule.ipAddress)"
|
ng-click="deleteRule(rule.id, rule.proto, rule.port, rule.ipAddress)"
|
||||||
class="btn-delete"
|
class="btn-delete"
|
||||||
title="{% trans 'Delete Rule' %}">
|
title="{% trans 'Delete Rule' %}">
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -717,6 +942,75 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Rule Modal -->
|
||||||
|
<div id="editRuleModal" class="modal" ng-show="showEditModal" ng-click="closeEditModal()">
|
||||||
|
<div class="modal-content" ng-click="$event.stopPropagation()">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
{% trans "Edit Firewall Rule" %}
|
||||||
|
</h3>
|
||||||
|
<button type="button" class="modal-close" ng-click="closeEditModal()">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form class="edit-rule-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{% trans "Rule Name" %}</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="editingRule.name"
|
||||||
|
name="ruleName"
|
||||||
|
placeholder="{% trans 'e.g., Allow SSH' %}"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{% trans "Protocol" %}</label>
|
||||||
|
<select ng-model="editingRule.proto" class="form-control select-control">
|
||||||
|
<option value="tcp">TCP</option>
|
||||||
|
<option value="udp">UDP</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{% trans "IP Address" %}</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="editingRule.ipAddress"
|
||||||
|
placeholder="{% trans '0.0.0.0/0 for all IPs' %}"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">{% trans "Port" %}</label>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
ng-model="editingRule.port"
|
||||||
|
placeholder="{% trans 'e.g., 80' %}"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button"
|
||||||
|
class="btn-cancel"
|
||||||
|
ng-click="closeEditModal()">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn-save"
|
||||||
|
ng-click="saveEditedRule()"
|
||||||
|
ng-disabled="rulesLoading">
|
||||||
|
<i class="fas fa-save"></i>
|
||||||
|
{% trans "Save Changes" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -32,6 +32,13 @@ urlpatterns = [
|
|||||||
path('modSecRulesPacks', views.modSecRulesPacks, name='modSecRulesPacks'),
|
path('modSecRulesPacks', views.modSecRulesPacks, name='modSecRulesPacks'),
|
||||||
path('getOWASPAndComodoStatus', views.getOWASPAndComodoStatus, name='getOWASPAndComodoStatus'),
|
path('getOWASPAndComodoStatus', views.getOWASPAndComodoStatus, name='getOWASPAndComodoStatus'),
|
||||||
path('installModSecRulesPack', views.installModSecRulesPack, name='installModSecRulesPack'),
|
path('installModSecRulesPack', views.installModSecRulesPack, name='installModSecRulesPack'),
|
||||||
|
|
||||||
|
# Firewall Export/Import
|
||||||
|
path('exportFirewallRules', views.exportFirewallRules, name='exportFirewallRules'),
|
||||||
|
path('importFirewallRules', views.importFirewallRules, name='importFirewallRules'),
|
||||||
|
|
||||||
|
# Firewall Rule Edit
|
||||||
|
path('editRule', views.editRule, name='editRule'),
|
||||||
path('getRulesFiles', views.getRulesFiles, name='getRulesFiles'),
|
path('getRulesFiles', views.getRulesFiles, name='getRulesFiles'),
|
||||||
path('enableDisableRuleFile', views.enableDisableRuleFile, name='enableDisableRuleFile'),
|
path('enableDisableRuleFile', views.enableDisableRuleFile, name='enableDisableRuleFile'),
|
||||||
|
|
||||||
|
|||||||
@@ -648,3 +648,36 @@ def saveLitespeed_conf(request):
|
|||||||
return fm.saveLitespeed_conf(userID, json.loads(request.body))
|
return fm.saveLitespeed_conf(userID, json.loads(request.body))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return redirect(loadLoginPage)
|
return redirect(loadLoginPage)
|
||||||
|
|
||||||
|
|
||||||
|
def exportFirewallRules(request):
|
||||||
|
try:
|
||||||
|
userID = request.session['userID']
|
||||||
|
fm = FirewallManager()
|
||||||
|
return fm.exportFirewallRules(userID)
|
||||||
|
except KeyError:
|
||||||
|
return redirect(loadLoginPage)
|
||||||
|
|
||||||
|
|
||||||
|
def importFirewallRules(request):
|
||||||
|
try:
|
||||||
|
userID = request.session['userID']
|
||||||
|
fm = FirewallManager(request)
|
||||||
|
|
||||||
|
# Handle file upload
|
||||||
|
if request.method == 'POST' and 'import_file' in request.FILES:
|
||||||
|
return fm.importFirewallRules(userID, None)
|
||||||
|
else:
|
||||||
|
# Handle JSON data
|
||||||
|
return fm.importFirewallRules(userID, json.loads(request.body))
|
||||||
|
except KeyError:
|
||||||
|
return redirect(loadLoginPage)
|
||||||
|
|
||||||
|
|
||||||
|
def editRule(request):
|
||||||
|
try:
|
||||||
|
userID = request.session['userID']
|
||||||
|
fm = FirewallManager()
|
||||||
|
return fm.editRule(userID, json.loads(request.body))
|
||||||
|
except KeyError:
|
||||||
|
return redirect(loadLoginPage)
|
||||||
20
key.pem
20
key.pem
@@ -1,20 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDNDCCAhwCCQDEgz2Vkmv5NDANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJV
|
|
||||||
UzEPMA0GA1UECAwGRGVuaWFsMRQwEgYDVQQHDAtTcHJpbmdmaWVsZDEMMAoGA1UE
|
|
||||||
CgwDRGlzMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wHhcNMjIwODI2MTEwNzEy
|
|
||||||
WhcNMzIwODIzMTEwNzEyWjBcMQswCQYDVQQGEwJVUzEPMA0GA1UECAwGRGVuaWFs
|
|
||||||
MRQwEgYDVQQHDAtTcHJpbmdmaWVsZDEMMAoGA1UECgwDRGlzMRgwFgYDVQQDDA93
|
|
||||||
d3cuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCq
|
|
||||||
BesL5DNNansmBYyn0DSqJyBOwGEIH/Ur4KFbKICrhy376gec2UyGG5lurgQa8Nz6
|
|
||||||
Gt7Z1B9LbLVE3bXS1f82bJHyPFUP8WmQuC+/3ZMRPFiG7/4n//0QwMptkDvGb5E1
|
|
||||||
0NXfJjuGHNfpVaHAs83v9mvUKc7oOjxwa+lbkhobD8HKByzAi/fpD90WQS9JRUJX
|
|
||||||
8lGquw+k5flL31AkqCyZOJw22VEoIpoF7RSh0xZvpsLze6G1thY5R27YWChcOR0E
|
|
||||||
m9/TUZ9/oCaP3WBvCldjr5OT6eZxJJzlt1jYndQKUyO5OtaJuNd4MkCNz9u9wwjE
|
|
||||||
CfZF7VQj8FABR9tqcVEfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGCNMmGXem6k
|
|
||||||
iqvJG2eFvuSdIh+x7x8EnXpRi2lW0ZqLYZfZuiMQL1fedhVoix+rzo9/Qhj6BTnt
|
|
||||||
5cC5oexhj/s76gqehNatMC0HAbIcK+MpvmwNoA/7U/bQAlbNxR3aLKSV0/B5YlTP
|
|
||||||
9yUoMtBqGEiesqAVAD26jOG5Ch1fHHElPtp3rE6qxGsAL0Eu+2Gezq3OqW2ejlvY
|
|
||||||
hZFpB/ZEynmYDUjT02+2J+3bAhfGaeUXC75YsbyfRAdc0OHa9/r7RhK+tgzmUhJt
|
|
||||||
sKUDW2OIwOTumUOSDgh1ayeTBddRAcMyIoGFeHF9degfJFiSo4vQrmaqFr9/bUFp
|
|
||||||
pn+y1/A3gNY=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
Reference in New Issue
Block a user