Refactor configuration modification methods for improved safety and validation

- Introduced a `safeModifyHttpdConfig` method in `installUtilities` to handle modifications to the OpenLiteSpeed configuration file with backup, validation, and rollback capabilities.
- Updated various modules (`modSec.py`, `sslUtilities.py`, `tuning.py`, `vhost.py`, etc.) to utilize the new safe modification method, enhancing reliability and preventing configuration corruption.
- Improved error handling and logging throughout the configuration modification processes to ensure better traceability and debugging.
This commit is contained in:
Master3395 2025-12-31 23:13:53 +01:00
parent b5d81eb68a
commit 18b1bad51f
8 changed files with 369 additions and 172 deletions

View File

@ -177,10 +177,18 @@ class PHPManager:
php_versions = []
for entry in lsphp_lines:
# Find substring starting with 'php' and extract the version part
version = entry.split('php')[1]
# Format version as PHP X.Y
formatted_version = f"PHP {version[0]}.{version[1]}"
php_versions.append(formatted_version)
try:
if 'php' not in entry:
continue
parts = entry.split('php')
if len(parts) < 2 or len(parts[1]) < 2:
continue
version = parts[1]
# Format version as PHP X.Y
formatted_version = f"PHP {version[0]}.{version[1]}"
php_versions.append(formatted_version)
except (IndexError, ValueError):
continue
else:
lsphp_lines = [line for line in result.split('\n')]

View File

@ -6,6 +6,7 @@ import pexpect
import os
import shlex
from plogical.processUtilities import ProcessUtilities
from datetime import datetime
class installUtilities:
@ -219,26 +220,147 @@ class installUtilities:
return 0
return 1
@staticmethod
def safeModifyHttpdConfig(config_modifier, description="config modification"):
"""
Safely modify httpd_config.conf with backup, validation, and rollback on failure.
Prevents corrupted configs that cause OpenLiteSpeed to fail binding ports 80/443.
Args:
config_modifier: A function that takes file content (list of lines) and returns modified content
description: Description of the modification for logging
Returns:
tuple: (success: bool, error_message: str or None)
Reference: https://github.com/usmannasir/cyberpanel/issues/1609
"""
config_file = "/usr/local/lsws/conf/httpd_config.conf"
if not os.path.exists(config_file):
error_msg = f"Config file not found: {config_file}"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
return False, error_msg
# Create backup with timestamp
try:
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
backup_file = f"{config_file}.backup-{timestamp}"
shutil.copy2(config_file, backup_file)
logging.writeToFile(f"[safeModifyHttpdConfig] Created backup: {backup_file} for {description}")
except Exception as e:
error_msg = f"Failed to create backup: {str(e)}"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
return False, error_msg
# Read current config
try:
with open(config_file, 'r') as f:
original_content = f.readlines()
except Exception as e:
error_msg = f"Failed to read config file: {str(e)}"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
return False, error_msg
# Modify config using callback
try:
modified_content = config_modifier(original_content)
if not isinstance(modified_content, list):
error_msg = "Config modifier must return a list of lines"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
return False, error_msg
except Exception as e:
error_msg = f"Config modifier function failed: {str(e)}"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
return False, error_msg
# Write modified config
try:
with open(config_file, 'w') as f:
f.writelines(modified_content)
except Exception as e:
error_msg = f"Failed to write modified config: {str(e)}"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
# Restore backup
try:
shutil.copy2(backup_file, config_file)
logging.writeToFile(f"[safeModifyHttpdConfig] Restored backup due to write failure")
except:
pass
return False, error_msg
# Validate config using openlitespeed -t
try:
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
validate_cmd = ['/usr/local/lsws/bin/openlitespeed', '-t', '-f', config_file]
else:
# For LiteSpeed Enterprise, use lswsctrl
validate_cmd = ['/usr/local/lsws/bin/lswsctrl', '-t', '-f', config_file]
result = subprocess.run(validate_cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
error_msg = f"Config validation failed: {result.stderr}"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
# Restore backup
try:
shutil.copy2(backup_file, config_file)
logging.writeToFile(f"[safeModifyHttpdConfig] Restored backup due to validation failure")
except Exception as restore_error:
logging.writeToFile(f"[safeModifyHttpdConfig] CRITICAL: Failed to restore backup: {str(restore_error)}")
return False, error_msg
except subprocess.TimeoutExpired:
error_msg = "Config validation timed out"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
# Restore backup
try:
shutil.copy2(backup_file, config_file)
logging.writeToFile(f"[safeModifyHttpdConfig] Restored backup due to validation timeout")
except:
pass
return False, error_msg
except Exception as e:
error_msg = f"Config validation error: {str(e)}"
logging.writeToFile(f"[safeModifyHttpdConfig] {error_msg}")
# Restore backup
try:
shutil.copy2(backup_file, config_file)
logging.writeToFile(f"[safeModifyHttpdConfig] Restored backup due to validation error")
except:
pass
return False, error_msg
logging.writeToFile(f"[safeModifyHttpdConfig] Successfully modified and validated config: {description}")
return True, None
@staticmethod
def changePortTo80():
try:
data = open("/usr/local/lsws/conf/httpd_config.conf").readlines()
writeDataToFile = open("/usr/local/lsws/conf/httpd_config.conf", 'w')
for items in data:
if (items.find("*:8088") > -1):
writeDataToFile.writelines(items.replace("*:8088","*:80"))
else:
writeDataToFile.writelines(items)
writeDataToFile.close()
except IOError as msg:
def modify_config(lines):
modified = []
for line in lines:
if "*:8088" in line:
modified.append(line.replace("*:8088", "*:80"))
else:
modified.append(line)
return modified
success, error = installUtilities.safeModifyHttpdConfig(
modify_config,
"Change port from 8088 to 80"
)
if not success:
error_msg = error if error else "Unknown error"
logging.writeToFile(f"[changePortTo80] Failed: {error_msg}")
return 0
return installUtilities.reStartLiteSpeed()
except Exception as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [changePortTo80]")
return 0
return installUtilities.reStartLiteSpeed()
@staticmethod
def installAllPHPVersion():

View File

@ -234,36 +234,49 @@ modsecurity_rules_file /usr/local/lsws/conf/modsec/rules.conf
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
confFile = os.path.join(virtualHostUtilities.Server_root, "conf/httpd_config.conf")
confData = open(confFile).readlines()
conf = open(confFile, 'w')
for items in confData:
if items.find('modsecurity ') > -1:
conf.writelines(data[0])
continue
elif items.find('SecAuditEngine ') > -1:
conf.writelines(data[1])
continue
elif items.find('SecRuleEngine ') > -1:
conf.writelines(data[2])
continue
elif items.find('SecDebugLogLevel') > -1:
conf.writelines(data[3])
continue
elif items.find('SecAuditLogRelevantStatus ') > -1:
conf.writelines(data[5])
continue
elif items.find('SecAuditLogParts ') > -1:
conf.writelines(data[4])
continue
elif items.find('SecAuditLogType ') > -1:
conf.writelines(data[6])
continue
else:
conf.writelines(items)
conf.close()
def modify_config(lines):
"""Update ModSecurity configuration parameters"""
modified = []
for line in lines:
if line.find('modsecurity ') > -1:
modified.append(data[0])
continue
elif line.find('SecAuditEngine ') > -1:
modified.append(data[1])
continue
elif line.find('SecRuleEngine ') > -1:
modified.append(data[2])
continue
elif line.find('SecDebugLogLevel') > -1:
modified.append(data[3])
continue
elif line.find('SecAuditLogRelevantStatus ') > -1:
modified.append(data[5])
continue
elif line.find('SecAuditLogParts ') > -1:
modified.append(data[4])
continue
elif line.find('SecAuditLogType ') > -1:
modified.append(data[6])
continue
else:
modified.append(line)
return modified
# Use safe modification with backup and validation
success, error = installUtilities.installUtilities.safeModifyHttpdConfig(
modify_config,
"Update ModSecurity configuration parameters"
)
if not success:
error_msg = error if error else "Unknown error"
logging.writeToFile(f"[saveModSecConfigs] Failed: {error_msg}")
print(f"0,{error_msg}")
return
installUtilities.reStartLiteSpeed()

View File

@ -518,22 +518,36 @@ context /.well-known/acme-challenge {
else:
if sslUtilities.checkIfSSLMap(virtualHostName) == 0:
data = open("/usr/local/lsws/conf/httpd_config.conf").readlines()
writeDataToFile = open("/usr/local/lsws/conf/httpd_config.conf", 'w')
sslCheck = 0
for items in data:
if items.find("listener") > -1 and items.find("SSL") > -1:
sslCheck = 1
if (sslCheck == 1):
writeDataToFile.writelines(items)
writeDataToFile.writelines(map)
sslCheck = 0
else:
writeDataToFile.writelines(items)
writeDataToFile.close()
from plogical import installUtilities
def modify_config(lines):
"""Add SSL map entry to existing SSL listener"""
modified = []
sslCheck = 0
for line in lines:
if line.find("listener") > -1 and line.find("SSL") > -1:
sslCheck = 1
if (sslCheck == 1):
modified.append(line)
modified.append(map)
sslCheck = 0
else:
modified.append(line)
return modified
# Use safe modification with backup and validation
success, error = installUtilities.installUtilities.safeModifyHttpdConfig(
modify_config,
f"Add SSL map entry for {virtualHostName}"
)
if not success:
error_msg = error if error else "Unknown error"
logging.writeToFile(f"[sslUtilities] Failed to add SSL map entry: {error_msg}")
raise BaseException(f"Failed to add SSL map entry: {error_msg}")
###################### Write per host Configs for SSL ###################

View File

@ -81,49 +81,52 @@ class tuning:
def saveTuningDetails(maxConnections,maxSSLConnections,connectionTimeOut,keepAliveTimeOut,cacheSizeInMemory,gzipCompression):
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
try:
datas = open("/usr/local/lsws/conf/httpd_config.conf").readlines()
writeDataToFile = open("/usr/local/lsws/conf/httpd_config.conf","w")
if gzipCompression == "Enable":
gzip = 1
else:
gzip = 0
for items in datas:
if items.find("maxConnections") > -1:
data = " maxConnections "+str(maxConnections)+"\n"
writeDataToFile.writelines(data)
continue
elif items.find("maxSSLConnections") > -1:
data = " maxSSLConnections "+str(maxSSLConnections) + "\n"
writeDataToFile.writelines(data)
continue
elif items.find("connTimeout") > -1:
data =" connTimeout "+str(connectionTimeOut)+"\n"
writeDataToFile.writelines(data)
continue
elif items.find("keepAliveTimeout") > -1:
data = " keepAliveTimeout " + str(keepAliveTimeOut) + "\n"
writeDataToFile.writelines(data)
continue
elif items.find("totalInMemCacheSize") > -1:
data = " totalInMemCacheSize " + str(cacheSizeInMemory) + "\n"
writeDataToFile.writelines(data)
continue
elif items.find("enableGzipCompress") > -1:
data = " enableGzipCompress " + str(gzip) + "\n"
writeDataToFile.writelines(data)
continue
from plogical import installUtilities
def modify_config(lines):
"""Modify tuning parameters in config"""
modified = []
if gzipCompression == "Enable":
gzip = 1
else:
writeDataToFile.writelines(items)
writeDataToFile.close()
gzip = 0
for line in lines:
if line.find("maxConnections") > -1:
modified.append(" maxConnections "+str(maxConnections)+"\n")
continue
elif line.find("maxSSLConnections") > -1:
modified.append(" maxSSLConnections "+str(maxSSLConnections) + "\n")
continue
elif line.find("connTimeout") > -1:
modified.append(" connTimeout "+str(connectionTimeOut)+"\n")
continue
elif line.find("keepAliveTimeout") > -1:
modified.append(" keepAliveTimeout " + str(keepAliveTimeOut) + "\n")
continue
elif line.find("totalInMemCacheSize") > -1:
modified.append(" totalInMemCacheSize " + str(cacheSizeInMemory) + "\n")
continue
elif line.find("enableGzipCompress") > -1:
modified.append(" enableGzipCompress " + str(gzip) + "\n")
continue
else:
modified.append(line)
return modified
# Use safe modification with backup and validation
success, error = installUtilities.installUtilities.safeModifyHttpdConfig(
modify_config,
"Update tuning parameters (maxConnections, maxSSLConnections, etc.)"
)
if not success:
error_msg = error if error else "Unknown error"
logging.writeToFile(f"[saveTuningDetails] Failed: {error_msg}")
print(f"0,{error_msg}")
return
print("1,None")
except BaseException as msg:

View File

@ -10,6 +10,7 @@ import re
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
from plogical.errorSanitizer import ErrorSanitizer
from plogical.installUtilities import installUtilities
import shlex
import subprocess
import shutil
@ -3481,6 +3482,7 @@ class Migration(migrations.Migration):
critical_files = [
'/usr/local/CyberCP/CyberCP/settings.py',
'/usr/local/CyberCP/.git/config', # Git configuration
'/usr/local/lsws/conf/httpd_config.conf', # OpenLiteSpeed config - critical for preventing port binding failures
]
# Also backup any custom configurations
@ -6397,15 +6399,15 @@ extprocessor proxyApacheBackendSSL {
lsws_config = "/usr/local/lsws/conf/httpd_config.conf"
if os.path.exists(lsws_config):
with open(lsws_config, 'r') as f:
lsws_content = f.read()
modified = False
# Check for apachebackend extprocessor
if 'extprocessor apachebackend' not in lsws_content:
# Add apachebackend configuration
backend_config = '''
def modify_apache_backends(lines):
"""Modify config to add Apache proxy backends if missing"""
content = ''.join(lines)
modified_lines = lines[:]
# Check for apachebackend extprocessor
if 'extprocessor apachebackend' not in content:
# Add apachebackend configuration
backend_config = '''
extprocessor apachebackend {
type proxy
address 127.0.0.1:8082
@ -6415,14 +6417,13 @@ extprocessor apachebackend {
respBuffer 0
}
'''
lsws_content += backend_config
modified = True
print("Added apachebackend extprocessor configuration")
# Check for proxyApacheBackendSSL extprocessor
if 'extprocessor proxyApacheBackendSSL' not in lsws_content:
# Add proxyApacheBackendSSL configuration
ssl_backend_config = '''
modified_lines.append(backend_config)
print("Added apachebackend extprocessor configuration")
# Check for proxyApacheBackendSSL extprocessor
if 'extprocessor proxyApacheBackendSSL' not in content:
# Add proxyApacheBackendSSL configuration
ssl_backend_config = '''
extprocessor proxyApacheBackendSSL {
type proxy
address https://127.0.0.1:8083
@ -6432,14 +6433,22 @@ extprocessor proxyApacheBackendSSL {
respBuffer 0
}
'''
lsws_content += ssl_backend_config
modified = True
print("Added proxyApacheBackendSSL extprocessor configuration")
modified_lines.append(ssl_backend_config)
print("Added proxyApacheBackendSSL extprocessor configuration")
return modified_lines
if modified:
with open(lsws_config, 'w') as f:
f.write(lsws_content)
# Use safe modification with backup and validation
success, error = installUtilities.safeModifyHttpdConfig(
modify_apache_backends,
"Add Apache proxy backend configurations"
)
if success:
print("Updated OpenLiteSpeed configuration with Apache proxy backends")
else:
print(f"WARNING: Failed to update OpenLiteSpeed configuration: {error}")
Upgrade.stdOut(f"Failed to update OpenLiteSpeed config: {error}", 0)
# Fix 3: Create/Update .htaccess files ONLY for domains actually using Apache
print("Creating/Updating .htaccess files for Apache domains...")

View File

@ -322,21 +322,31 @@ class vhost:
@staticmethod
def createNONSSLMapEntry(virtualHostName):
try:
data = open("/usr/local/lsws/conf/httpd_config.conf").readlines()
writeDataToFile = open("/usr/local/lsws/conf/httpd_config.conf", 'w')
map = " map " + virtualHostName + " " + virtualHostName + "\n"
mapchecker = 1
for items in data:
if (mapchecker == 1 and (items.find("listener") > -1 and items.find("Default") > -1)):
writeDataToFile.writelines(items)
writeDataToFile.writelines(map)
mapchecker = 0
else:
writeDataToFile.writelines(items)
def modify_config(lines):
map_entry = " map " + virtualHostName + " " + virtualHostName + "\n"
modified = []
mapchecker = 1
for line in lines:
if (mapchecker == 1 and (line.find("listener") > -1 and line.find("Default") > -1)):
modified.append(line)
modified.append(map_entry)
mapchecker = 0
else:
modified.append(line)
return modified
success, error = installUtilities.installUtilities.safeModifyHttpdConfig(
modify_config,
f"Add NON-SSL map entry for {virtualHostName}"
)
if not success:
error_msg = error if error else "Unknown error"
logging.writeToFile(f"[createNONSSLMapEntry] Failed: {error_msg}")
return 0
return 1
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg))
@ -581,35 +591,47 @@ class vhost:
if os.path.exists(confPath):
shutil.rmtree(confPath)
data = open("/usr/local/lsws/conf/httpd_config.conf").readlines()
def modify_config(lines):
"""Remove virtual host entries from config"""
modified = []
check = 1
sslCheck = 1
writeDataToFile = open("/usr/local/lsws/conf/httpd_config.conf", 'w')
check = 1
sslCheck = 1
for items in data:
if numberOfSites == 1:
if (items.find(' ' + virtualHostName) > -1 and items.find(" map " + virtualHostName) > -1):
continue
if (items.find(' ' + virtualHostName) > -1 and (items.find("virtualHost") > -1 or items.find("virtualhost") > -1)):
check = 0
if items.find("listener") > -1 and items.find("SSL") > -1:
sslCheck = 0
if (check == 1 and sslCheck == 1):
writeDataToFile.writelines(items)
if (items.find("}") > -1 and (check == 0 or sslCheck == 0)):
check = 1
sslCheck = 1
else:
if (items.find(' ' + virtualHostName) > -1 and items.find(" map " + virtualHostName) > -1):
continue
if (items.find(' ' + virtualHostName) > -1 and (items.find("virtualHost") > -1 or items.find("virtualhost") > -1)):
check = 0
if (check == 1):
writeDataToFile.writelines(items)
if (items.find("}") > -1 and check == 0):
check = 1
for line in lines:
if numberOfSites == 1:
if (line.find(' ' + virtualHostName) > -1 and line.find(" map " + virtualHostName) > -1):
continue
if (line.find(' ' + virtualHostName) > -1 and (line.find("virtualHost") > -1 or line.find("virtualhost") > -1)):
check = 0
if line.find("listener") > -1 and line.find("SSL") > -1:
sslCheck = 0
if (check == 1 and sslCheck == 1):
modified.append(line)
if (line.find("}") > -1 and (check == 0 or sslCheck == 0)):
check = 1
sslCheck = 1
else:
if (line.find(' ' + virtualHostName) > -1 and line.find(" map " + virtualHostName) > -1):
continue
if (line.find(' ' + virtualHostName) > -1 and (line.find("virtualHost") > -1 or line.find("virtualhost") > -1)):
check = 0
if (check == 1):
modified.append(line)
if (line.find("}") > -1 and check == 0):
check = 1
return modified
# Use safe modification with backup and validation
success, error = installUtilities.installUtilities.safeModifyHttpdConfig(
modify_config,
f"Remove virtual host {virtualHostName} from config"
)
if not success:
error_msg = error if error else "Unknown error"
logging.writeToFile(f"[deleteCoreConf] Failed to remove vhost config: {error_msg}")
raise BaseException(f"Failed to remove vhost config: {error_msg}")
## Delete Apache Conf

View File

@ -5737,6 +5737,12 @@ StrictHostKeyChecking no
if os.path.exists(finalConfPath):
phpPath = ApacheVhost.whichPHPExists(self.domain)
if phpPath is None:
# If PHP path is not found, return error response
data_ret = {'status': 0, 'saveStatus': 0, 'error_message': 'PHP configuration file not found for this domain'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
command = 'sudo cat ' + phpPath
phpConf = ProcessUtilities.outputExecutioner(command).splitlines()
pmMaxChildren = phpConf[8].split(' ')[2]