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

Add export and import functionality for firewall rules
This commit is contained in:
Master3395
2025-09-25 02:40:17 +02:00
committed by GitHub
7 changed files with 700 additions and 4 deletions

View File

@@ -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)

View File

@@ -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.";
}
}
});

View File

@@ -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 @@
</div>
{% trans "Firewall Rules" %}
</div>
<div ng-show="rulesLoading" class="loading-spinner"></div>
<div style="display: flex; align-items: center; gap: 1rem;">
<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>
<!-- Add Rule Section -->

View File

@@ -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'),
]

View File

@@ -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)

View 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

View File

@@ -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.