usmannasir
2025-08-08 00:56:41 +05:00
parent 8fcf18279b
commit 651324b464
7 changed files with 495 additions and 299 deletions

9
.idea/workspace.xml generated
View File

@@ -6,6 +6,12 @@
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="5251c5c9-f2a1-41f2-bc76-10b517091df1" name="Changes" comment=""> <list default="true" id="5251c5c9-f2a1-41f2-bc76-10b517091df1" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/loginSystem/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/loginSystem/views.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/plogical/adminPass.py" beforeDir="false" afterPath="$PROJECT_DIR$/plogical/adminPass.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/plogical/customACME.py" beforeDir="false" afterPath="$PROJECT_DIR$/plogical/customACME.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/plogical/sslUtilities.py" beforeDir="false" afterPath="$PROJECT_DIR$/plogical/sslUtilities.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/plogical/sslv2.py" beforeDir="false" afterPath="$PROJECT_DIR$/plogical/sslv2.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/websiteFunctions/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/websiteFunctions/views.py" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -48,7 +54,7 @@
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager": "true", "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager": "true",
"RunOnceActivity.git.unshallow": "true", "RunOnceActivity.git.unshallow": "true",
"SHELLCHECK.PATH": "/Users/cyberpersons/Library/Application Support/JetBrains/PyCharm2025.1/plugins/Shell Script/shellcheck", "SHELLCHECK.PATH": "/Users/cyberpersons/Library/Application Support/JetBrains/PyCharm2025.1/plugins/Shell Script/shellcheck",
"git-widget-placeholder": "stable", "git-widget-placeholder": "v2.4.3",
"last_opened_file_path": "/Users/cyberpersons/cyberpanel", "last_opened_file_path": "/Users/cyberpersons/cyberpanel",
"node.js.detected.package.eslint": "true", "node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true", "node.js.detected.package.tslint": "true",
@@ -117,6 +123,7 @@
<workItem from="1754429757112" duration="3503000" /> <workItem from="1754429757112" duration="3503000" />
<workItem from="1754433799097" duration="517000" /> <workItem from="1754433799097" duration="517000" />
<workItem from="1754448353513" duration="2970000" /> <workItem from="1754448353513" duration="2970000" />
<workItem from="1754511414251" duration="12135000" />
</task> </task>
<servers /> <servers />
</component> </component>

View File

@@ -222,7 +222,7 @@ def loadLoginPage(request):
token = hashPassword.generateToken('admin', '1234567') token = hashPassword.generateToken('admin', '1234567')
email = 'example@example.org' email = 'admin@cyberpanel.net'
admin = Administrator(userName="admin", password=password, type=1, email=email, admin = Administrator(userName="admin", password=password, type=1, email=email,
firstName="Cyber", lastName="Panel", acl=acl, token=token) firstName="Cyber", lastName="Panel", acl=acl, token=token)
admin.save() admin.save()

View File

@@ -47,7 +47,7 @@ def main():
acl = ACL.objects.get(name='admin') acl = ACL.objects.get(name='admin')
token = hashPassword.generateToken('admin', adminPass) token = hashPassword.generateToken('admin', adminPass)
email = 'example@example.org' email = 'admin@cyberpanel.net'
admin = Administrator(userName="admin", password=hashPassword.hash_password(adminPass), type=1, email=email, admin = Administrator(userName="admin", password=hashPassword.hash_password(adminPass), type=1, email=email,
firstName="Cyber", lastName="Panel", acl=acl, token=token) firstName="Cyber", lastName="Panel", acl=acl, token=token)
admin.save() admin.save()

View File

@@ -17,10 +17,12 @@ from plogical import CyberCPLogFileWriter as logging
from plogical.processUtilities import ProcessUtilities from plogical.processUtilities import ProcessUtilities
import socket import socket
class CustomACME: class CustomACME:
def __init__(self, domain, admin_email, staging=False, provider='letsencrypt'): def __init__(self, domain, admin_email, staging=False, provider='letsencrypt'):
"""Initialize CustomACME""" """Initialize CustomACME"""
logging.CyberCPLogFileWriter.writeToFile(f'Initializing CustomACME for domain: {domain}, email: {admin_email}, staging: {staging}, provider: {provider}') logging.CyberCPLogFileWriter.writeToFile(
f'Initializing CustomACME for domain: {domain}, email: {admin_email}, staging: {staging}, provider: {provider}')
self.domain = domain self.domain = domain
self.admin_email = admin_email self.admin_email = admin_email
self.staging = staging self.staging = staging
@@ -55,7 +57,8 @@ class CustomACME:
self.cert_path = f'/etc/letsencrypt/live/{domain}' self.cert_path = f'/etc/letsencrypt/live/{domain}'
self.challenge_path = '/usr/local/lsws/Example/html/.well-known/acme-challenge' self.challenge_path = '/usr/local/lsws/Example/html/.well-known/acme-challenge'
self.account_key_path = f'/etc/letsencrypt/accounts/{domain}.key' self.account_key_path = f'/etc/letsencrypt/accounts/{domain}.key'
logging.CyberCPLogFileWriter.writeToFile(f'Certificate path: {self.cert_path}, Challenge path: {self.challenge_path}') logging.CyberCPLogFileWriter.writeToFile(
f'Certificate path: {self.cert_path}, Challenge path: {self.challenge_path}')
# Create accounts directory if it doesn't exist # Create accounts directory if it doesn't exist
os.makedirs('/etc/letsencrypt/accounts', exist_ok=True) os.makedirs('/etc/letsencrypt/accounts', exist_ok=True)
@@ -82,7 +85,8 @@ class CustomACME:
logging.CyberCPLogFileWriter.writeToFile(f'Fetching ACME directory from {self.acme_directory}') logging.CyberCPLogFileWriter.writeToFile(f'Fetching ACME directory from {self.acme_directory}')
response = requests.get(self.acme_directory) response = requests.get(self.acme_directory)
self.directory = response.json() self.directory = response.json()
logging.CyberCPLogFileWriter.writeToFile(f'Successfully fetched ACME directory: {json.dumps(self.directory)}') logging.CyberCPLogFileWriter.writeToFile(
f'Successfully fetched ACME directory: {json.dumps(self.directory)}')
return True return True
except Exception as e: except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error getting directory: {str(e)}') logging.CyberCPLogFileWriter.writeToFile(f'Error getting directory: {str(e)}')
@@ -197,13 +201,10 @@ class CustomACME:
logging.CyberCPLogFileWriter.writeToFile('Creating final JWS...') logging.CyberCPLogFileWriter.writeToFile('Creating final JWS...')
jws = { jws = {
"protected": protected_b64, "protected": protected_b64,
"payload": payload_b64, # Always include payload field, even if empty
"signature": signature_b64 "signature": signature_b64
} }
# Only add payload if it exists
if payload is not None:
jws["payload"] = payload_b64
# Ensure the JWS is properly formatted # Ensure the JWS is properly formatted
jws_str = json.dumps(jws, separators=(',', ':')) jws_str = json.dumps(jws, separators=(',', ':'))
logging.CyberCPLogFileWriter.writeToFile(f'Final JWS: {jws_str}') logging.CyberCPLogFileWriter.writeToFile(f'Final JWS: {jws_str}')
@@ -259,14 +260,17 @@ class CustomACME:
} }
# Check if External Account Binding is required (for ZeroSSL) # Check if External Account Binding is required (for ZeroSSL)
if self.provider == 'zerossl' and 'meta' in self.directory and 'externalAccountRequired' in self.directory['meta']: if self.provider == 'zerossl' and 'meta' in self.directory and 'externalAccountRequired' in self.directory[
'meta']:
if self.directory['meta']['externalAccountRequired']: if self.directory['meta']['externalAccountRequired']:
logging.CyberCPLogFileWriter.writeToFile('ZeroSSL requires External Account Binding, getting EAB credentials...') logging.CyberCPLogFileWriter.writeToFile(
'ZeroSSL requires External Account Binding, getting EAB credentials...')
# Get EAB credentials from ZeroSSL # Get EAB credentials from ZeroSSL
eab_kid, eab_hmac_key = self._get_zerossl_eab_credentials() eab_kid, eab_hmac_key = self._get_zerossl_eab_credentials()
if not eab_kid or not eab_hmac_key: if not eab_kid or not eab_hmac_key:
logging.CyberCPLogFileWriter.writeToFile('Failed to get ZeroSSL EAB credentials, falling back to Let\'s Encrypt') logging.CyberCPLogFileWriter.writeToFile(
'Failed to get ZeroSSL EAB credentials, falling back to Let\'s Encrypt')
# Fallback to Let's Encrypt # Fallback to Let's Encrypt
self.provider = 'letsencrypt' self.provider = 'letsencrypt'
self.acme_directory = "https://acme-v02.api.letsencrypt.org/directory" self.acme_directory = "https://acme-v02.api.letsencrypt.org/directory"
@@ -294,12 +298,14 @@ class CustomACME:
if response.status_code == 201: if response.status_code == 201:
self.account_url = response.headers['Location'] self.account_url = response.headers['Location']
logging.CyberCPLogFileWriter.writeToFile(f'Successfully created account. Account URL: {self.account_url}') logging.CyberCPLogFileWriter.writeToFile(
f'Successfully created account. Account URL: {self.account_url}')
# Save the account key for future use # Save the account key for future use
self._save_account_key() self._save_account_key()
return True return True
elif response.status_code == 429: elif response.status_code == 429:
logging.CyberCPLogFileWriter.writeToFile('Rate limit hit for account creation. Using staging environment...') logging.CyberCPLogFileWriter.writeToFile(
'Rate limit hit for account creation. Using staging environment...')
self.staging = True self.staging = True
self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory" self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
# Get new directory and nonce for staging # Get new directory and nonce for staging
@@ -645,17 +651,20 @@ class CustomACME:
order_status = response.json().get('status') order_status = response.json().get('status')
if order_status == 'valid': if order_status == 'valid':
self.certificate_url = response.json().get('certificate') self.certificate_url = response.json().get('certificate')
logging.CyberCPLogFileWriter.writeToFile(f'Successfully finalized order. Certificate URL: {self.certificate_url}') logging.CyberCPLogFileWriter.writeToFile(
f'Successfully finalized order. Certificate URL: {self.certificate_url}')
return True return True
elif order_status == 'invalid': elif order_status == 'invalid':
logging.CyberCPLogFileWriter.writeToFile('Order validation failed') logging.CyberCPLogFileWriter.writeToFile('Order validation failed')
return False return False
elif order_status == 'processing': elif order_status == 'processing':
logging.CyberCPLogFileWriter.writeToFile(f'Order still processing, attempt {attempt + 1}/{max_attempts}') logging.CyberCPLogFileWriter.writeToFile(
f'Order still processing, attempt {attempt + 1}/{max_attempts}')
time.sleep(delay) time.sleep(delay)
continue continue
logging.CyberCPLogFileWriter.writeToFile(f'Order status check failed, attempt {attempt + 1}/{max_attempts}') logging.CyberCPLogFileWriter.writeToFile(
f'Order status check failed, attempt {attempt + 1}/{max_attempts}')
time.sleep(delay) time.sleep(delay)
logging.CyberCPLogFileWriter.writeToFile('Order processing timed out') logging.CyberCPLogFileWriter.writeToFile('Order processing timed out')
@@ -671,15 +680,30 @@ class CustomACME:
logging.CyberCPLogFileWriter.writeToFile('Downloading certificate...') logging.CyberCPLogFileWriter.writeToFile('Downloading certificate...')
logging.CyberCPLogFileWriter.writeToFile(f'Certificate URL: {self.certificate_url}') logging.CyberCPLogFileWriter.writeToFile(f'Certificate URL: {self.certificate_url}')
# For certificate downloads, we can use a simple GET request # Get a fresh nonce for the request
response = requests.get(self.certificate_url) if not self._get_nonce():
logging.CyberCPLogFileWriter.writeToFile('Failed to get nonce for certificate download')
return None
# Use POST-as-GET for certificate download (ACME v2 requirement)
jws = self._create_jws(None, self.certificate_url)
if not jws:
logging.CyberCPLogFileWriter.writeToFile('Failed to create JWS for certificate download')
return None
headers = {
'Content-Type': 'application/jose+json'
}
response = requests.post(self.certificate_url, data=jws, headers=headers)
logging.CyberCPLogFileWriter.writeToFile(f'Certificate download response status: {response.status_code}') logging.CyberCPLogFileWriter.writeToFile(f'Certificate download response status: {response.status_code}')
logging.CyberCPLogFileWriter.writeToFile(f'Certificate download response headers: {response.headers}') logging.CyberCPLogFileWriter.writeToFile(f'Certificate download response headers: {response.headers}')
logging.CyberCPLogFileWriter.writeToFile(f'Certificate download response content: {response.text}')
if response.status_code == 200: if response.status_code == 200:
logging.CyberCPLogFileWriter.writeToFile('Successfully downloaded certificate') logging.CyberCPLogFileWriter.writeToFile('Successfully downloaded certificate')
return response.content # The response should be the PEM-encoded certificate chain
return response.text.encode('utf-8') if isinstance(response.text, str) else response.content
else:
logging.CyberCPLogFileWriter.writeToFile(f'Certificate download failed: {response.text}')
return None return None
except Exception as e: except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error downloading certificate: {str(e)}') logging.CyberCPLogFileWriter.writeToFile(f'Error downloading certificate: {str(e)}')
@@ -715,7 +739,8 @@ class CustomACME:
logging.CyberCPLogFileWriter.writeToFile('Challenge validation failed') logging.CyberCPLogFileWriter.writeToFile('Challenge validation failed')
return False return False
logging.CyberCPLogFileWriter.writeToFile(f'Challenge still pending, attempt {attempt + 1}/{max_attempts}') logging.CyberCPLogFileWriter.writeToFile(
f'Challenge still pending, attempt {attempt + 1}/{max_attempts}')
time.sleep(delay) time.sleep(delay)
logging.CyberCPLogFileWriter.writeToFile('Challenge validation timed out') logging.CyberCPLogFileWriter.writeToFile('Challenge validation timed out')
@@ -745,9 +770,9 @@ class CustomACME:
# List of public DNS servers to check against # List of public DNS servers to check against
dns_servers = [ dns_servers = [
'8.8.8.8', # Google DNS '8.8.8.8', # Google DNS
'1.1.1.1', # Cloudflare DNS '1.1.1.1', # Cloudflare DNS
'208.67.222.222' # OpenDNS '208.67.222.222' # OpenDNS
] ]
# Function to check DNS record with specific DNS server # Function to check DNS record with specific DNS server
@@ -876,11 +901,13 @@ class CustomACME:
logging.CyberCPLogFileWriter.writeToFile('Order validation failed') logging.CyberCPLogFileWriter.writeToFile('Order validation failed')
return False return False
elif order_status == 'processing': elif order_status == 'processing':
logging.CyberCPLogFileWriter.writeToFile(f'Order still processing, attempt {attempt + 1}/{max_attempts}') logging.CyberCPLogFileWriter.writeToFile(
f'Order still processing, attempt {attempt + 1}/{max_attempts}')
time.sleep(delay) time.sleep(delay)
continue continue
logging.CyberCPLogFileWriter.writeToFile(f'Order status check failed, attempt {attempt + 1}/{max_attempts}') logging.CyberCPLogFileWriter.writeToFile(
f'Order status check failed, attempt {attempt + 1}/{max_attempts}')
time.sleep(delay) time.sleep(delay)
logging.CyberCPLogFileWriter.writeToFile('Order processing timed out') logging.CyberCPLogFileWriter.writeToFile('Order processing timed out')
@@ -892,7 +919,8 @@ class CustomACME:
def issue_certificate(self, domains, use_dns=False): def issue_certificate(self, domains, use_dns=False):
"""Main method to issue certificate""" """Main method to issue certificate"""
try: try:
logging.CyberCPLogFileWriter.writeToFile(f'Starting certificate issuance for domains: {domains}, use_dns: {use_dns}') logging.CyberCPLogFileWriter.writeToFile(
f'Starting certificate issuance for domains: {domains}, use_dns: {use_dns}')
# Try to load existing account key first # Try to load existing account key first
if self._load_account_key(): if self._load_account_key():

View File

@@ -6,6 +6,7 @@ import shlex
import subprocess import subprocess
import socket import socket
from plogical.processUtilities import ProcessUtilities from plogical.processUtilities import ProcessUtilities
try: try:
from websiteFunctions.models import ChildDomains, Websites from websiteFunctions.models import ChildDomains, Websites
except: except:
@@ -14,7 +15,6 @@ from plogical.acl import ACLManager
class sslUtilities: class sslUtilities:
Server_root = "/usr/local/lsws" Server_root = "/usr/local/lsws"
redisConf = '/usr/local/lsws/conf/dvhost_redis.conf' redisConf = '/usr/local/lsws/conf/dvhost_redis.conf'
@@ -108,7 +108,8 @@ class sslUtilities:
result = subprocess.run(command, shell=True, capture_output=True, text=True) result = subprocess.run(command, shell=True, capture_output=True, text=True)
except TypeError: except TypeError:
# Fallback for Python < 3.7 # Fallback for Python < 3.7
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
# If there's any output, the domain has A records # If there's any output, the domain has A records
if result.stdout.strip(): if result.stdout.strip():
@@ -120,7 +121,8 @@ class sslUtilities:
result = subprocess.run(command, shell=True, capture_output=True, text=True) result = subprocess.run(command, shell=True, capture_output=True, text=True)
except TypeError: except TypeError:
# Fallback for Python < 3.7 # Fallback for Python < 3.7
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
if result.stdout.strip(): if result.stdout.strip():
return True return True
@@ -164,7 +166,6 @@ class sslUtilities:
except BaseException as msg: except BaseException as msg:
return 0, str(msg) return 0, str(msg)
@staticmethod @staticmethod
def CheckIfSSLNeedsToBeIssued(virtualHostName): def CheckIfSSLNeedsToBeIssued(virtualHostName):
#### if website already have an SSL, better not issue again - need to check for wild-card #### if website already have an SSL, better not issue again - need to check for wild-card
@@ -177,7 +178,6 @@ class sslUtilities:
if os.path.exists(ProcessUtilities.debugPath): if os.path.exists(ProcessUtilities.debugPath):
logging.CyberCPLogFileWriter.writeToFile(f'SSL provider for {virtualHostName} is {SSLProvider}.') logging.CyberCPLogFileWriter.writeToFile(f'SSL provider for {virtualHostName} is {SSLProvider}.')
#### totally seprate check to see if both non-www and www are covered #### totally seprate check to see if both non-www and www are covered
if SSLProvider == "(STAGING) Let's Encrypt": if SSLProvider == "(STAGING) Let's Encrypt":
@@ -189,7 +189,8 @@ class sslUtilities:
if len(domains) > 1: if len(domains) > 1:
### need further checks here to see if ssl is valid for less then 15 days etc ### need further checks here to see if ssl is valid for less then 15 days etc
logging.CyberCPLogFileWriter.writeToFile( logging.CyberCPLogFileWriter.writeToFile(
'[CheckIfSSLNeedsToBeIssued] SSL exists for %s and both versions are covered, just need to ensure if SSL is valid for less then 15 days.' % (virtualHostName), 0) '[CheckIfSSLNeedsToBeIssued] SSL exists for %s and both versions are covered, just need to ensure if SSL is valid for less then 15 days.' % (
virtualHostName), 0)
pass pass
else: else:
return sslUtilities.ISSUE_SSL return sslUtilities.ISSUE_SSL
@@ -202,7 +203,7 @@ class sslUtilities:
now = datetime.now() now = datetime.now()
diff = finalDate - now diff = finalDate - now
if int(diff.days) >= 15 and SSLProvider!='Denial': if int(diff.days) >= 15 and SSLProvider != 'Denial':
logging.CyberCPLogFileWriter.writeToFile( logging.CyberCPLogFileWriter.writeToFile(
'[CheckIfSSLNeedsToBeIssued] SSL exists for %s and is not ready to fetch new SSL., skipping..' % ( '[CheckIfSSLNeedsToBeIssued] SSL exists for %s and is not ready to fetch new SSL., skipping..' % (
virtualHostName), 0) virtualHostName), 0)
@@ -261,7 +262,6 @@ class sslUtilities:
return str(msg) return str(msg)
return 0 return 0
@staticmethod @staticmethod
def checkSSLIPv6Listener(): def checkSSLIPv6Listener():
try: try:
@@ -271,7 +271,8 @@ class sslUtilities:
return 1 return 1
except BaseException as msg: except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [IO Error with main config file [checkSSLIPv6Listener]]") logging.CyberCPLogFileWriter.writeToFile(
str(msg) + " [IO Error with main config file [checkSSLIPv6Listener]]")
return str(msg) return str(msg)
return 0 return 0
@@ -350,7 +351,8 @@ class sslUtilities:
# If conflicts found, log them and return # If conflicts found, log them and return
if conflicts: if conflicts:
conflict_message = 'Configuration conflicts found: ' + '; '.join(conflicts) conflict_message = 'Configuration conflicts found: ' + '; '.join(conflicts)
logging.CyberCPLogFileWriter.writeToFile(f'Configuration conflicts for {virtualHostName}: {conflict_message}') logging.CyberCPLogFileWriter.writeToFile(
f'Configuration conflicts for {virtualHostName}: {conflict_message}')
return 0, conflict_message return 0, conflict_message
# Create challenge directory if it doesn't exist # Create challenge directory if it doesn't exist
@@ -397,7 +399,8 @@ context /.well-known/acme-challenge {
for line in lines: for line in lines:
f.write(line) f.write(line)
if line.find('DocumentRoot /home/') > -1 and check == 0: if line.find('DocumentRoot /home/') > -1 and check == 0:
f.write(' Alias /.well-known/acme-challenge /usr/local/lsws/Example/html/.well-known/acme-challenge\n') f.write(
' Alias /.well-known/acme-challenge /usr/local/lsws/Example/html/.well-known/acme-challenge\n')
check = 1 check = 1
except IOError as e: except IOError as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error writing Apache configuration: {str(e)}') logging.CyberCPLogFileWriter.writeToFile(f'Error writing Apache configuration: {str(e)}')
@@ -407,7 +410,8 @@ context /.well-known/acme-challenge {
try: try:
from plogical import installUtilities from plogical import installUtilities
installUtilities.installUtilities.reStartLiteSpeed() installUtilities.installUtilities.reStartLiteSpeed()
logging.CyberCPLogFileWriter.writeToFile(f'Successfully configured ACME challenge for {virtualHostName}') logging.CyberCPLogFileWriter.writeToFile(
f'Successfully configured ACME challenge for {virtualHostName}')
return 1, 'Successfully configured ACME challenge' return 1, 'Successfully configured ACME challenge'
except Exception as e: except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error restarting LiteSpeed: {str(e)}') logging.CyberCPLogFileWriter.writeToFile(f'Error restarting LiteSpeed: {str(e)}')
@@ -418,7 +422,7 @@ context /.well-known/acme-challenge {
return 0, f'Unexpected error: {str(e)}' return 0, f'Unexpected error: {str(e)}'
@staticmethod @staticmethod
def installSSLForDomain(virtualHostName, adminEmail='example@example.org'): def installSSLForDomain(virtualHostName, adminEmail='domain@cyberpanel.net'):
try: try:
website = Websites.objects.get(domain=virtualHostName) website = Websites.objects.get(domain=virtualHostName)
@@ -674,6 +678,14 @@ context /.well-known/acme-challenge {
import json import json
import socket import socket
# Replace example.org emails with domain-specific email
if adminEmail and ('example.org' in adminEmail or 'example.com' in adminEmail):
import re
# Remove special characters and create domain-based email
clean_domain = re.sub(r'[^a-zA-Z0-9]', '', virtualHostName)
adminEmail = f'{clean_domain}@cyberpanel.net'
logging.CyberCPLogFileWriter.writeToFile(f'Replacing invalid email with {adminEmail}')
Status = 1 Status = 1
if sslUtilities.CheckIfSSLNeedsToBeIssued(virtualHostName) == sslUtilities.ISSUE_SSL: if sslUtilities.CheckIfSSLNeedsToBeIssued(virtualHostName) == sslUtilities.ISSUE_SSL:
@@ -700,18 +712,22 @@ context /.well-known/acme-challenge {
# Check if www subdomain has DNS records before adding it (skip for hostnames) # Check if www subdomain has DNS records before adding it (skip for hostnames)
if not isHostname and sslUtilities.checkDNSRecords(f'www.{virtualHostName}'): if not isHostname and sslUtilities.checkDNSRecords(f'www.{virtualHostName}'):
domains.append(f'www.{virtualHostName}') domains.append(f'www.{virtualHostName}')
logging.CyberCPLogFileWriter.writeToFile(f"www.{virtualHostName} has DNS records, including in SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{virtualHostName} has DNS records, including in SSL request")
elif not isHostname: elif not isHostname:
logging.CyberCPLogFileWriter.writeToFile(f"www.{virtualHostName} has no DNS records, excluding from SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{virtualHostName} has no DNS records, excluding from SSL request")
if aliasDomain: if aliasDomain:
domains.append(aliasDomain) domains.append(aliasDomain)
# Check if www.aliasDomain has DNS records # Check if www.aliasDomain has DNS records
if sslUtilities.checkDNSRecords(f'www.{aliasDomain}'): if sslUtilities.checkDNSRecords(f'www.{aliasDomain}'):
domains.append(f'www.{aliasDomain}') domains.append(f'www.{aliasDomain}')
logging.CyberCPLogFileWriter.writeToFile(f"www.{aliasDomain} has DNS records, including in SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{aliasDomain} has DNS records, including in SSL request")
else: else:
logging.CyberCPLogFileWriter.writeToFile(f"www.{aliasDomain} has no DNS records, excluding from SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{aliasDomain} has no DNS records, excluding from SSL request")
# Check if Cloudflare is used # Check if Cloudflare is used
use_dns = False use_dns = False
@@ -750,18 +766,22 @@ context /.well-known/acme-challenge {
# Check if www subdomain has DNS records before adding it (skip for hostnames) # Check if www subdomain has DNS records before adding it (skip for hostnames)
if not isHostname and sslUtilities.checkDNSRecords(f'www.{virtualHostName}'): if not isHostname and sslUtilities.checkDNSRecords(f'www.{virtualHostName}'):
domains.append(f'www.{virtualHostName}') domains.append(f'www.{virtualHostName}')
logging.CyberCPLogFileWriter.writeToFile(f"www.{virtualHostName} has DNS records, including in SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{virtualHostName} has DNS records, including in SSL request")
elif not isHostname: elif not isHostname:
logging.CyberCPLogFileWriter.writeToFile(f"www.{virtualHostName} has no DNS records, excluding from SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{virtualHostName} has no DNS records, excluding from SSL request")
if aliasDomain: if aliasDomain:
domains.append(aliasDomain) domains.append(aliasDomain)
# Check if www.aliasDomain has DNS records # Check if www.aliasDomain has DNS records
if sslUtilities.checkDNSRecords(f'www.{aliasDomain}'): if sslUtilities.checkDNSRecords(f'www.{aliasDomain}'):
domains.append(f'www.{aliasDomain}') domains.append(f'www.{aliasDomain}')
logging.CyberCPLogFileWriter.writeToFile(f"www.{aliasDomain} has DNS records, including in SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{aliasDomain} has DNS records, including in SSL request")
else: else:
logging.CyberCPLogFileWriter.writeToFile(f"www.{aliasDomain} has no DNS records, excluding from SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{aliasDomain} has no DNS records, excluding from SSL request")
acme = CustomACME(virtualHostName, adminEmail, staging=False, provider='zerossl') acme = CustomACME(virtualHostName, adminEmail, staging=False, provider='zerossl')
if acme.issue_certificate(domains, use_dns=use_dns): if acme.issue_certificate(domains, use_dns=use_dns):
@@ -794,9 +814,11 @@ context /.well-known/acme-challenge {
# Check if www subdomain has DNS records (skip for hostnames) # Check if www subdomain has DNS records (skip for hostnames)
if not isHostname and sslUtilities.checkDNSRecords(f'www.{virtualHostName}'): if not isHostname and sslUtilities.checkDNSRecords(f'www.{virtualHostName}'):
domain_list += " -d www." + virtualHostName domain_list += " -d www." + virtualHostName
logging.CyberCPLogFileWriter.writeToFile(f"www.{virtualHostName} has DNS records, including in acme.sh SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{virtualHostName} has DNS records, including in acme.sh SSL request")
elif not isHostname: elif not isHostname:
logging.CyberCPLogFileWriter.writeToFile(f"www.{virtualHostName} has no DNS records, excluding from acme.sh SSL request") logging.CyberCPLogFileWriter.writeToFile(
f"www.{virtualHostName} has no DNS records, excluding from acme.sh SSL request")
command = acmePath + " --issue" + domain_list \ command = acmePath + " --issue" + domain_list \
+ ' --cert-file ' + existingCertPath + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \ + ' --cert-file ' + existingCertPath + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \
@@ -807,7 +829,8 @@ context /.well-known/acme-challenge {
result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True) result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True)
except TypeError: except TypeError:
# Fallback for Python < 3.7 # Fallback for Python < 3.7
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=True)
if result.returncode == 0: if result.returncode == 0:
command = acmePath + " --issue" + domain_list \ command = acmePath + " --issue" + domain_list \
@@ -819,7 +842,8 @@ context /.well-known/acme-challenge {
result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True) result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True)
except TypeError: except TypeError:
# Fallback for Python < 3.7 # Fallback for Python < 3.7
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=True)
if result.returncode == 0: if result.returncode == 0:
logging.CyberCPLogFileWriter.writeToFile( logging.CyberCPLogFileWriter.writeToFile(
@@ -860,7 +884,8 @@ context /.well-known/acme-challenge {
result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True) result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True)
except TypeError: except TypeError:
# Fallback for Python < 3.7 # Fallback for Python < 3.7
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=True)
if result.returncode == 0: if result.returncode == 0:
return 1 return 1
@@ -910,7 +935,8 @@ def issueSSLForDomain(domain, adminEmail, sslpath, aliasDomain=None, isHostname=
# For expired certificates, use --issue --force instead of --renew # For expired certificates, use --issue --force instead of --renew
if is_expired: if is_expired:
logging.CyberCPLogFileWriter.writeToFile(f"Certificate is expired, using --issue --force for {domain}") logging.CyberCPLogFileWriter.writeToFile(
f"Certificate is expired, using --issue --force for {domain}")
command = f'{acmePath} --issue {renewal_domains} --webroot /usr/local/lsws/Example/html --force' command = f'{acmePath} --issue {renewal_domains} --webroot /usr/local/lsws/Example/html --force'
else: else:
# Try to renew with explicit webroot # Try to renew with explicit webroot
@@ -920,7 +946,8 @@ def issueSSLForDomain(domain, adminEmail, sslpath, aliasDomain=None, isHostname=
result = subprocess.run(command, capture_output=True, text=True, shell=True) result = subprocess.run(command, capture_output=True, text=True, shell=True)
except TypeError: except TypeError:
# Fallback for Python < 3.7 # Fallback for Python < 3.7
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=True)
if result.returncode == 0: if result.returncode == 0:
logging.CyberCPLogFileWriter.writeToFile(f"Successfully renewed SSL for {domain}") logging.CyberCPLogFileWriter.writeToFile(f"Successfully renewed SSL for {domain}")
@@ -948,13 +975,16 @@ def issueSSLForDomain(domain, adminEmail, sslpath, aliasDomain=None, isHostname=
if os.path.exists(pathToStoreSSLFullChain): if os.path.exists(pathToStoreSSLFullChain):
import OpenSSL import OpenSSL
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, open(pathToStoreSSLFullChain, 'r').read()) x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
open(pathToStoreSSLFullChain, 'r').read())
SSLProvider = x509.get_issuer().get_components()[1][1].decode('utf-8') SSLProvider = x509.get_issuer().get_components()[1][1].decode('utf-8')
if SSLProvider != 'Denial': if SSLProvider != 'Denial':
if sslUtilities.installSSLForDomain(domain) == 1: if sslUtilities.installSSLForDomain(domain) == 1:
logging.CyberCPLogFileWriter.writeToFile("We are not able to get new SSL for " + domain + ". But there is an existing SSL, it might only be for the main domain (excluding www).") logging.CyberCPLogFileWriter.writeToFile(
return [1, "We are not able to get new SSL for " + domain + ". But there is an existing SSL, it might only be for the main domain (excluding www)." + " [issueSSLForDomain]"] "We are not able to get new SSL for " + domain + ". But there is an existing SSL, it might only be for the main domain (excluding www).")
return [1,
"We are not able to get new SSL for " + domain + ". But there is an existing SSL, it might only be for the main domain (excluding www)." + " [issueSSLForDomain]"]
command = 'openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=' + domain + '" -keyout ' + pathToStoreSSLPrivKey + ' -out ' + pathToStoreSSLFullChain command = 'openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=' + domain + '" -keyout ' + pathToStoreSSLPrivKey + ' -out ' + pathToStoreSSLFullChain
cmd = shlex.split(command) cmd = shlex.split(command)

View File

@@ -10,6 +10,7 @@ import socket
from plogical.acl import ACLManager from plogical.acl import ACLManager
from plogical.processUtilities import ProcessUtilities from plogical.processUtilities import ProcessUtilities
try: try:
from websiteFunctions.models import ChildDomains, Websites from websiteFunctions.models import ChildDomains, Websites
except: except:
@@ -17,7 +18,6 @@ except:
class sslUtilities: class sslUtilities:
Server_root = "/usr/local/lsws" Server_root = "/usr/local/lsws"
redisConf = '/usr/local/lsws/conf/dvhost_redis.conf' redisConf = '/usr/local/lsws/conf/dvhost_redis.conf'
@@ -57,7 +57,6 @@ class sslUtilities:
return str(msg) return str(msg)
return 0 return 0
@staticmethod @staticmethod
def checkSSLIPv6Listener(): def checkSSLIPv6Listener():
try: try:
@@ -67,7 +66,8 @@ class sslUtilities:
return 1 return 1
except BaseException as msg: except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [IO Error with main config file [checkSSLIPv6Listener]]") logging.CyberCPLogFileWriter.writeToFile(
str(msg) + " [IO Error with main config file [checkSSLIPv6Listener]]")
return str(msg) return str(msg)
return 0 return 0
@@ -84,7 +84,7 @@ class sslUtilities:
return [0, "347 " + str(msg) + " [issueSSLForDomain]"] return [0, "347 " + str(msg) + " [issueSSLForDomain]"]
@staticmethod @staticmethod
def installSSLForDomain(virtualHostName, adminEmail='example@example.org'): def installSSLForDomain(virtualHostName, adminEmail='domain@cyberpanel.net'):
try: try:
website = Websites.objects.get(domain=virtualHostName) website = Websites.objects.get(domain=virtualHostName)
@@ -332,7 +332,6 @@ class sslUtilities:
ProcessUtilities.executioner(command) ProcessUtilities.executioner(command)
return 1 return 1
@staticmethod @staticmethod
def FindIfDomainInCloudflare(virtualHostName): def FindIfDomainInCloudflare(virtualHostName):
try: try:
@@ -403,6 +402,14 @@ class sslUtilities:
@staticmethod @staticmethod
def obtainSSLForADomain(virtualHostName, adminEmail, sslpath, aliasDomain=None): def obtainSSLForADomain(virtualHostName, adminEmail, sslpath, aliasDomain=None):
# Replace example.org emails with domain-specific email
if adminEmail and ('example.org' in adminEmail or 'example.com' in adminEmail):
import re
# Remove special characters and create domain-based email
clean_domain = re.sub(r'[^a-zA-Z0-9]', '', virtualHostName)
adminEmail = f'{clean_domain}@cyberpanel.net'
logging.CyberCPLogFileWriter.writeToFile(f'Replacing invalid email with {adminEmail}')
sender_email = 'root@%s' % (socket.gethostname()) sender_email = 'root@%s' % (socket.gethostname())
CF_Check = 0 CF_Check = 0
@@ -419,7 +426,6 @@ class sslUtilities:
if SSLProvider != 'Denial': if SSLProvider != 'Denial':
return 1, 'This domain already have a valid SSL.' return 1, 'This domain already have a valid SSL.'
CF_Check, message = sslUtilities.FindIfDomainInCloudflare(virtualHostName) CF_Check, message = sslUtilities.FindIfDomainInCloudflare(virtualHostName)
DNS_TO_USE = '' DNS_TO_USE = ''
@@ -456,8 +462,8 @@ class sslUtilities:
command = acmePath + f" --issue -d {virtualHostName} -d *.{virtualHostName}" \ command = acmePath + f" --issue -d {virtualHostName} -d *.{virtualHostName}" \
+ ' --cert-file ' + existingCertPath + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \ + ' --cert-file ' + existingCertPath + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \
+ ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + f' --dns {DNS_TO_USE} -k ec-256 --force --server letsencrypt --dnssleep 20' + ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + f' --dns {DNS_TO_USE} -k ec-256 --force --server letsencrypt --dnssleep 20'
#ResultText = open(logging.CyberCPLogFileWriter.fileName, 'r').read() # ResultText = open(logging.CyberCPLogFileWriter.fileName, 'r').read()
#CurrentMessage = "Trying to obtain SSL for: " + virtualHostName + " and: www." + virtualHostName # CurrentMessage = "Trying to obtain SSL for: " + virtualHostName + " and: www." + virtualHostName
# logging.CyberCPLogFileWriter.writeToFile(CurrentMessage, 0) # logging.CyberCPLogFileWriter.writeToFile(CurrentMessage, 0)
logging.CyberCPLogFileWriter.writeToFile(command, 0) logging.CyberCPLogFileWriter.writeToFile(command, 0)
@@ -480,7 +486,7 @@ class sslUtilities:
+ '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \ + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \
+ ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + f' --dns {DNS_TO_USE} -k ec-256 --force --server letsencrypt --dnssleep 20' + ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + f' --dns {DNS_TO_USE} -k ec-256 --force --server letsencrypt --dnssleep 20'
#ResultText = open(logging.CyberCPLogFileWriter.fileName, 'r').read() # ResultText = open(logging.CyberCPLogFileWriter.fileName, 'r').read()
CurrentMessage = '%s\nTrying to obtain SSL for: %s' % (finalText, virtualHostName) CurrentMessage = '%s\nTrying to obtain SSL for: %s' % (finalText, virtualHostName)
finalText = '%s\nTrying to obtain SSL for: %s' % (finalText, virtualHostName) finalText = '%s\nTrying to obtain SSL for: %s' % (finalText, virtualHostName)
@@ -494,8 +500,10 @@ class sslUtilities:
'SSL Notification for %s.' % (virtualHostName)) 'SSL Notification for %s.' % (virtualHostName))
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
logging.CyberCPLogFileWriter.writeToFile('Failed to obtain SSL, issuing self-signed SSL for: ' + virtualHostName, 0) logging.CyberCPLogFileWriter.writeToFile(
logging.CyberCPLogFileWriter.SendEmail(sender_email, adminEmail, 'Failed to obtain SSL, issuing self-signed SSL for: ' + virtualHostName, 'Failed to obtain SSL, issuing self-signed SSL for: ' + virtualHostName, 0)
logging.CyberCPLogFileWriter.SendEmail(sender_email, adminEmail,
'Failed to obtain SSL, issuing self-signed SSL for: ' + virtualHostName,
'SSL Notification for %s.' % (virtualHostName)) 'SSL Notification for %s.' % (virtualHostName))
return 0, output return 0, output
else: else:
@@ -510,7 +518,7 @@ class sslUtilities:
"Trying to obtain SSL for: " + virtualHostName + ", www." + virtualHostName + ", " + aliasDomain + " and www." + aliasDomain + ",") "Trying to obtain SSL for: " + virtualHostName + ", www." + virtualHostName + ", " + aliasDomain + " and www." + aliasDomain + ",")
command = acmePath + " --issue -d " + virtualHostName + " -d www." + virtualHostName \ command = acmePath + " --issue -d " + virtualHostName + " -d www." + virtualHostName \
+ ' -d ' + aliasDomain + ' -d www.' + aliasDomain\ + ' -d ' + aliasDomain + ' -d www.' + aliasDomain \
+ ' --cert-file ' + existingCertPath + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \ + ' --cert-file ' + existingCertPath + '/cert.pem' + ' --key-file ' + existingCertPath + '/privkey.pem' \
+ ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + f' --dns {DNS_TO_USE} -k ec-256 --force --server letsencrypt --dnssleep 20' + ' --fullchain-file ' + existingCertPath + '/fullchain.pem' + f' --dns {DNS_TO_USE} -k ec-256 --force --server letsencrypt --dnssleep 20'

File diff suppressed because it is too large Load Diff