From 3a78551762564c2daca443cb3c487925d294aaeb Mon Sep 17 00:00:00 2001 From: Master3395 Date: Thu, 25 Sep 2025 02:39:35 +0200 Subject: [PATCH] Add export and import functionality for firewall rules - Implemented `exportFirewallRules` method in `FirewallManager` to export custom firewall rules to a JSON file, excluding default rules. - Added `importFirewallRules` method to handle the import of firewall rules from a JSON file, with validation and error handling. - Updated `urls.py` to include routes for exporting and importing firewall rules. - Created corresponding view functions in `views.py` to manage requests for exporting and importing rules. - Enhanced the frontend with buttons for exporting and importing rules, along with appropriate loading and error handling in `firewall.js`. - Updated the HTML template to include export/import buttons and loading indicators for better user experience. --- firewall/firewallManager.py | 160 +++++++++++++++++- firewall/static/firewall/firewall.js | 147 ++++++++++++++++ firewall/templates/firewall/firewall.html | 55 +++++- firewall/urls.py | 4 + firewall/views.py | 24 +++ guides/EXPORT_IMPORT_FIREWALL_RULES.md | 121 ++++++++++++++ guides/SECURITY_INSTALLATION.md | 193 ++++++++++++++++++++++ 7 files changed, 700 insertions(+), 4 deletions(-) create mode 100644 guides/EXPORT_IMPORT_FIREWALL_RULES.md create mode 100644 guides/SECURITY_INSTALLATION.md diff --git a/firewall/firewallManager.py b/firewall/firewallManager.py index 0427ac38d..d3127b586 100644 --- a/firewall/firewallManager.py +++ b/firewall/firewallManager.py @@ -2050,9 +2050,163 @@ class FirewallManager: final_json = json.dumps(final_dic) 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) diff --git a/firewall/static/firewall/firewall.js b/firewall/static/firewall/firewall.js index b2f9b2326..495b88ec0 100644 --- a/firewall/static/firewall/firewall.js +++ b/firewall/static/firewall/firewall.js @@ -2549,4 +2549,151 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window) }); }; + // Export/Import Firewall Rules 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."; + } + } + }); \ No newline at end of file diff --git a/firewall/templates/firewall/firewall.html b/firewall/templates/firewall/firewall.html index 2081c94ed..711043857 100644 --- a/firewall/templates/firewall/firewall.html +++ b/firewall/templates/firewall/firewall.html @@ -445,6 +445,41 @@ color: var(--text-muted, #64748b); } + /* 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; + } + .alert { padding: 1rem 1.5rem; border-radius: 8px; @@ -877,7 +912,25 @@ {% trans "Firewall Rules" %} -
+
+
+
+ + +
+
diff --git a/firewall/urls.py b/firewall/urls.py index b866bbbe7..eee5da43d 100644 --- a/firewall/urls.py +++ b/firewall/urls.py @@ -63,4 +63,8 @@ urlpatterns = [ path('litespeed_ent_conf', views.litespeed_ent_conf, name='litespeed_ent_conf'), path('fetchlitespeed_conf', views.fetchlitespeed_conf, name='fetchlitespeed_conf'), path('saveLitespeed_conf', views.saveLitespeed_conf, name='saveLitespeed_conf'), + + # Firewall Export/Import + path('exportFirewallRules', views.exportFirewallRules, name='exportFirewallRules'), + path('importFirewallRules', views.importFirewallRules, name='importFirewallRules'), ] diff --git a/firewall/views.py b/firewall/views.py index 609af0adf..15f894bd2 100644 --- a/firewall/views.py +++ b/firewall/views.py @@ -679,5 +679,29 @@ def deleteBannedIP(request): userID = request.session['userID'] fm = FirewallManager() return fm.deleteBannedIP(userID, json.loads(request.body)) + except KeyError: + 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) \ No newline at end of file diff --git a/guides/EXPORT_IMPORT_FIREWALL_RULES.md b/guides/EXPORT_IMPORT_FIREWALL_RULES.md new file mode 100644 index 000000000..9fb19b983 --- /dev/null +++ b/guides/EXPORT_IMPORT_FIREWALL_RULES.md @@ -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 diff --git a/guides/SECURITY_INSTALLATION.md b/guides/SECURITY_INSTALLATION.md new file mode 100644 index 000000000..60cd302c3 --- /dev/null +++ b/guides/SECURITY_INSTALLATION.md @@ -0,0 +1,193 @@ +# CyberPanel Secure Installation Guide + +## Overview + +This document describes the secure installation process for CyberPanel that eliminates hardcoded passwords and implements environment-based configuration. + +## Security Improvements + +### ✅ **Fixed Security Vulnerabilities** + +1. **Hardcoded Database Passwords** - Now generated securely during installation +2. **Hardcoded Django Secret Key** - Now generated using cryptographically secure random generation +3. **Environment Variables** - All sensitive configuration moved to `.env` file +4. **File Permissions** - `.env` file set to 600 (owner read/write only) + +### 🔐 **Security Features** + +- **Cryptographically Secure Passwords**: Uses Python's `secrets` module for password generation +- **Environment-based Configuration**: Sensitive data stored in `.env` file, not in code +- **Secure File Permissions**: Environment files protected with 600 permissions +- **Credential Backup**: Automatic backup of credentials for recovery +- **Fallback Security**: Maintains backward compatibility with fallback method + +## Installation Process + +### 1. **Automatic Secure Installation** + +The installation script now automatically: + +1. Generates secure random passwords for: + - MySQL root user + - CyberPanel database user + - Django secret key + +2. Creates `.env` file with secure configuration: + ```bash + # Generated during installation + SECRET_KEY=your_64_character_secure_key + DB_PASSWORD=your_24_character_secure_password + ROOT_DB_PASSWORD=your_24_character_secure_password + ``` + +3. Creates `.env.backup` file for credential recovery +4. Sets secure file permissions (600) on all environment files + +### 2. **Manual Installation** (if needed) + +If you need to manually generate environment configuration: + +```bash +cd /usr/local/CyberCP +python install/env_generator.py /usr/local/CyberCP +``` + +## File Structure + +``` +/usr/local/CyberCP/ +├── .env # Main environment configuration (600 permissions) +├── .env.backup # Credential backup (600 permissions) +├── .env.template # Template for manual configuration +├── .gitignore # Prevents .env files from being committed +└── CyberCP/ + └── settings.py # Updated to use environment variables +``` + +## Security Best Practices + +### ✅ **Do's** + +- Keep `.env` and `.env.backup` files secure +- Record credentials from `.env.backup` and delete the file after installation +- Use strong, unique passwords for production deployments +- Regularly rotate database passwords +- Monitor access to environment files + +### ❌ **Don'ts** + +- Never commit `.env` files to version control +- Don't share `.env` files via insecure channels +- Don't use default passwords in production +- Don't leave `.env.backup` files on the system after recording credentials + +## Recovery + +### **Lost Credentials** + +If you lose your database credentials: + +1. Check if `.env.backup` file exists: + ```bash + sudo cat /usr/local/CyberCP/.env.backup + ``` + +2. If backup doesn't exist, you'll need to reset MySQL passwords using MySQL recovery procedures + +### **Regenerate Environment** + +To regenerate environment configuration: + +```bash +cd /usr/local/CyberCP +sudo python install/env_generator.py /usr/local/CyberCP +``` + +## Configuration Options + +### **Environment Variables** + +| Variable | Description | Default | +|----------|-------------|---------| +| `SECRET_KEY` | Django secret key | Generated (64 chars) | +| `DB_PASSWORD` | CyberPanel DB password | Generated (24 chars) | +| `ROOT_DB_PASSWORD` | MySQL root password | Generated (24 chars) | +| `DEBUG` | Debug mode | False | +| `ALLOWED_HOSTS` | Allowed hosts | localhost,127.0.0.1,hostname | + +### **Custom Configuration** + +To use custom passwords during installation: + +```bash +python install/env_generator.py /usr/local/CyberCP "your_root_password" "your_db_password" +``` + +## Troubleshooting + +### **Installation Fails** + +If the new secure installation fails: + +1. Check installation logs for error messages +2. The system will automatically fallback to the original installation method +3. Verify Python dependencies are installed: + ```bash + pip install python-dotenv + ``` + +### **Environment Loading Issues** + +If Django can't load environment variables: + +1. Ensure `.env` file exists and has correct permissions: + ```bash + ls -la /usr/local/CyberCP/.env + # Should show: -rw------- 1 root root + ``` + +2. Install python-dotenv if missing: + ```bash + pip install python-dotenv + ``` + +## Migration from Old Installation + +### **Existing Installations** + +For existing CyberPanel installations with hardcoded passwords: + +1. **Backup current configuration**: + ```bash + cp /usr/local/CyberCP/CyberCP/settings.py /usr/local/CyberCP/CyberCP/settings.py.backup + ``` + +2. **Generate new environment configuration**: + ```bash + cd /usr/local/CyberCP + python install/env_generator.py /usr/local/CyberCP + ``` + +3. **Update settings.py** (already done in new installations): + - The settings.py file now supports environment variables + - It will fallback to hardcoded values if .env is not available + +4. **Test the configuration**: + ```bash + cd /usr/local/CyberCP + python manage.py check + ``` + +## Support + +For issues with the secure installation: + +1. Check the installation logs +2. Verify file permissions +3. Ensure all dependencies are installed +4. Review the fallback installation method if needed + +--- + +**Security Notice**: This installation method significantly improves security by eliminating hardcoded credentials. Always ensure proper file permissions and secure handling of environment files. +