diff --git a/CyberCP/secMiddleware.py b/CyberCP/secMiddleware.py index fb2cc0729..3ae7966f1 100644 --- a/CyberCP/secMiddleware.py +++ b/CyberCP/secMiddleware.py @@ -127,8 +127,38 @@ class secMiddleware: logging.writeToFile(f'Value being scanned {str(value)}') # Skip validation for ports key to allow port ranges with colons - if key == 'ports': + # but only for CSF modifyPorts endpoint + if key == 'ports' and pathActual == '/firewall/modifyPorts': + # Validate that ports only contain numbers, commas, and colons + if type(value) == str: + import re + # Allow only: digits, commas, colons, and whitespace + if re.match(r'^[\d,:,\s]+$', value): + continue + else: + logging.writeToFile(f"Invalid port format in CSF configuration: {value}") + final_dic = { + 'error_message': "Invalid port format. Only numbers, commas, and colons are allowed for port ranges.", + "errorMessage": "Invalid port format. Only numbers, commas, and colons are allowed for port ranges."} + final_json = json.dumps(final_dic) + return HttpResponse(final_json) continue + elif key == 'ports': + # For other endpoints, ports key continues to skip validation + continue + + # Allow protocol parameter for CSF modifyPorts endpoint + if key == 'protocol' and pathActual == '/firewall/modifyPorts': + # Validate protocol values + if value in ['TCP_IN', 'TCP_OUT', 'UDP_IN', 'UDP_OUT']: + continue + else: + logging.writeToFile(f"Invalid protocol in CSF configuration: {value}") + final_dic = { + 'error_message': "Invalid protocol. Only TCP_IN, TCP_OUT, UDP_IN, UDP_OUT are allowed.", + "errorMessage": "Invalid protocol. Only TCP_IN, TCP_OUT, UDP_IN, UDP_OUT are allowed."} + final_json = json.dumps(final_dic) + return HttpResponse(final_json) if type(value) == str or type(value) == bytes: pass diff --git a/REFACTORING_NOTES.md b/REFACTORING_NOTES.md new file mode 100644 index 000000000..b3f0f906f --- /dev/null +++ b/REFACTORING_NOTES.md @@ -0,0 +1,129 @@ +# CyberPanel Installation Scripts Refactoring + +## Date: January 5, 2025 + +## Overview +This document outlines the refactoring work performed on CyberPanel's installation scripts to consolidate common functionality and improve code maintainability. + +## Files Modified +- `/install/install.py` +- `/install/installCyberPanel.py` +- `/install/install_utils.py` (newly created) + +## Refactoring Summary + +### 1. Created Shared Utility Module +Created `install_utils.py` in the `/install/` directory to house common functions used by both `install.py` and `installCyberPanel.py`. + +### 2. Functions Moved to install_utils.py + +#### System Detection Functions +- **`FetchCloudLinuxAlmaVersionVersion()`** + - Detects CloudLinux and AlmaLinux versions + - Previously duplicated in both files with identical implementations + +- **`get_Ubuntu_release()`** + - Gets Ubuntu release version from `/etc/lsb-release` + - Added parameters to handle minor differences between implementations + +- **`get_distro()`** + - Detects Linux distribution (Ubuntu, CentOS, CentOS 8, OpenEuler) + - Only existed in `install.py`, now shared + +#### Output and Logging +- **`stdOut(message, log=0, do_exit=0, code=os.EX_OK)`** + - Standard output function with timestamps + - **Enhanced with color support**: + - 🔴 Red: Errors, failures, critical issues + - 🟡 Yellow: Warnings and alerts + - 🟢 Green: Success messages + - 🔵 Blue: Running/processing operations + - 🟣 Purple: Default/general messages + - Colors automatically disabled when output is piped + +#### Package Management +- **`get_package_install_command(distro, package_name, options="")`** + - Returns appropriate install command for the distribution + - Handles apt-get, yum, and dnf package managers + +- **`get_package_remove_command(distro, package_name)`** + - Returns appropriate remove command for the distribution + +#### Command Execution +- **`resFailed(distro, res)`** + - Checks if command execution failed based on return code + +- **`call(command, distro, bracket, message, log=0, do_exit=0, code=os.EX_OK, shell=False)`** + - Executes shell commands with retry logic (3 attempts) + - Provides consistent error handling and logging + +#### Password Generation +- **`char_set`** dictionary + - Character sets for password generation (kept for backward compatibility) + +- **`generate_pass(length=14)`** + - Generates cryptographically secure passwords + - Uses `secrets` module instead of `random` for better security + +- **`generate_random_string(length=32, include_special=False)`** + - Flexible string generation with optional special characters + +#### LiteSpeed Management +- **`format_restart_litespeed_command(server_root_path)`** + - Formats the LiteSpeed restart command + +### 3. Distribution Constants +Moved distribution constants to `install_utils.py`: +```python +ubuntu = 0 +centos = 1 +cent8 = 2 +openeuler = 3 +``` + +### 4. Key Improvements + +#### Security Enhancements +- Password generation now uses `secrets` module for cryptographic security +- Removed usage of `random.choice()` for password generation + +#### Code Quality +- Eliminated code duplication across files +- Single source of truth for common functionality +- Consistent error handling and logging + +#### Maintainability +- Centralized utility functions in one location +- Easier to update and maintain shared functionality +- Better organization of code + +#### Visual Improvements +- Added color-coded output for better readability +- Automatic detection of terminal capabilities +- Clear visual distinction between error, warning, success, and info messages + +### 5. Backward Compatibility +All changes maintain backward compatibility: +- Function signatures preserved where possible +- Wrapper functions created in original locations when needed +- `installCyberPanel.py` continues to use `install.preFlightsChecks.call` through import + +### 6. Testing +Created test scripts to verify functionality: +- Color output testing +- Password generation verification +- Ensured all functions work as expected + +## Benefits +1. **Reduced Code Duplication**: Common functions now exist in single location +2. **Improved Security**: Cryptographically secure password generation +3. **Better User Experience**: Color-coded output for easier reading +4. **Easier Maintenance**: Changes to common functions only need to be made once +5. **Consistent Behavior**: Both installation scripts now use identical implementations + +## Future Recommendations +1. Consider moving more shared functionality to `install_utils.py` +2. Add unit tests for utility functions +3. Consider creating additional utility modules for specific domains (e.g., `network_utils.py`, `database_utils.py`) +4. Document function parameters and return values more thoroughly +5. Consider adding configuration file support for installation parameters \ No newline at end of file diff --git a/plogical/sslUtilities.py b/plogical/sslUtilities.py index 804decf54..eea0b5cf0 100644 --- a/plogical/sslUtilities.py +++ b/plogical/sslUtilities.py @@ -701,7 +701,8 @@ context /.well-known/acme-challenge { command = acmePath + " --issue" + domain_list \ + ' --cert-file ' + existingCertPath + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \ - + ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + ' -w /usr/local/lsws/Example/html -k ec-256 --force --staging' + + ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + ' -w /usr/local/lsws/Example/html -k ec-256 --force --staging' \ + + ' --webroot-path /usr/local/lsws/Example/html' if ProcessUtilities.decideServer() == ProcessUtilities.OLS: result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True) @@ -711,7 +712,8 @@ context /.well-known/acme-challenge { if result.returncode == 0: command = acmePath + " --issue" + domain_list \ + ' --cert-file ' + existingCertPath + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \ - + ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + ' -w /usr/local/lsws/Example/html -k ec-256 --force --server letsencrypt' + + ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + ' -w /usr/local/lsws/Example/html -k ec-256 --force --server letsencrypt' \ + + ' --webroot-path /usr/local/lsws/Example/html' result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True) @@ -765,6 +767,34 @@ context /.well-known/acme-challenge { def issueSSLForDomain(domain, adminEmail, sslpath, aliasDomain=None): try: + # Check if certificate already exists and try to renew it first + existingCertPath = '/etc/letsencrypt/live/' + domain + '/fullchain.pem' + if os.path.exists(existingCertPath): + logging.CyberCPLogFileWriter.writeToFile(f"Certificate exists for {domain}, attempting renewal...") + + # Try to renew using acme.sh + acmePath = '/root/.acme.sh/acme.sh' + if os.path.exists(acmePath): + # First set the webroot path for the domain + command = f'{acmePath} --update-account --accountemail {adminEmail}' + subprocess.call(command, shell=True) + + # Build domain list for renewal + renewal_domains = f'-d {domain}' + if sslUtilities.checkDNSRecords(f'www.{domain}'): + renewal_domains += f' -d www.{domain}' + + # Try to renew with explicit webroot + command = f'{acmePath} --renew {renewal_domains} --webroot /usr/local/lsws/Example/html --force' + result = subprocess.run(command, capture_output=True, text=True, shell=True) + + if result.returncode == 0: + logging.CyberCPLogFileWriter.writeToFile(f"Successfully renewed SSL for {domain}") + if sslUtilities.installSSLForDomain(domain, adminEmail) == 1: + return [1, "None"] + else: + logging.CyberCPLogFileWriter.writeToFile(f"Renewal failed for {domain}, falling back to new issuance") + if sslUtilities.obtainSSLForADomain(domain, adminEmail, sslpath, aliasDomain) == 1: if sslUtilities.installSSLForDomain(domain, adminEmail) == 1: return [1, "None"]