mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-10-26 07:46:35 +01:00
This commit is contained in:
14
.idea/workspace.xml
generated
14
.idea/workspace.xml
generated
@@ -4,7 +4,15 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<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$/baseTemplate/templates/baseTemplate/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/baseTemplate/templates/baseTemplate/index.html" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/manageSSL/static/manageSSL/manageSSL.js" beforeDir="false" afterPath="$PROJECT_DIR$/manageSSL/static/manageSSL/manageSSL.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/manageSSL/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/manageSSL/views.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/plogical/renew.py" beforeDir="false" afterPath="$PROJECT_DIR$/plogical/renew.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/virtualHostUtilities.py" beforeDir="false" afterPath="$PROJECT_DIR$/plogical/virtualHostUtilities.py" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
@@ -112,7 +120,9 @@
|
||||
<workItem from="1753690170133" duration="11704000" />
|
||||
<workItem from="1753711196398" duration="51975000" />
|
||||
<workItem from="1754042060203" duration="66331000" />
|
||||
<workItem from="1754429757112" duration="2192000" />
|
||||
<workItem from="1754429757112" duration="3503000" />
|
||||
<workItem from="1754433799097" duration="517000" />
|
||||
<workItem from="1754448353513" duration="1462000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% with CP_VERSION="2.4.3" %}
|
||||
{% with CP_VERSION="2.4.3.1" %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="CyberCP">
|
||||
<head>
|
||||
|
||||
@@ -78,6 +78,13 @@ app.controller('sslIssueCtrl', function ($scope, $http) {
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.sslDomain = $scope.virtualHost;
|
||||
$scope.fetchSSLDetails(); // Refresh SSL details after issuing
|
||||
|
||||
// Show success notification
|
||||
new PNotify({
|
||||
title: 'Success',
|
||||
text: 'SSL certificate successfully issued/renewed for ' + $scope.virtualHost,
|
||||
type: 'success'
|
||||
});
|
||||
} else {
|
||||
$scope.sslIssueCtrl = true;
|
||||
$scope.manageSSLLoading = true;
|
||||
@@ -85,7 +92,46 @@ app.controller('sslIssueCtrl', function ($scope, $http) {
|
||||
$scope.canNotIssue = false;
|
||||
$scope.sslIssued = true;
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.errorMessage = response.data.error_message;
|
||||
|
||||
// Enhanced error handling
|
||||
$scope.errorMessage = response.data.error_message || 'SSL issuance failed';
|
||||
$scope.technicalDetails = response.data.technicalDetails || '';
|
||||
$scope.sslLogs = response.data.sslLogs || '';
|
||||
|
||||
// Show detailed error notification
|
||||
var errorText = response.data.error_message || 'Unknown error occurred';
|
||||
if (response.data.technicalDetails) {
|
||||
console.error('SSL Technical Details:', response.data.technicalDetails);
|
||||
}
|
||||
if (response.data.sslLogs) {
|
||||
console.error('SSL Logs:', response.data.sslLogs);
|
||||
}
|
||||
|
||||
new PNotify({
|
||||
title: 'SSL Issuance Failed',
|
||||
text: errorText,
|
||||
type: 'error',
|
||||
delay: 10000, // Show for 10 seconds
|
||||
buttons: {
|
||||
closer: true,
|
||||
sticker: true
|
||||
}
|
||||
});
|
||||
|
||||
// Check for specific error types and provide helpful suggestions
|
||||
if (errorText.toLowerCase().includes('rate limit')) {
|
||||
$scope.errorSuggestion = 'You have hit the Let\'s Encrypt rate limit. Please wait before retrying or use a different domain.';
|
||||
} else if (errorText.toLowerCase().includes('dns')) {
|
||||
$scope.errorSuggestion = 'Please ensure your domain DNS is properly configured and pointing to this server.';
|
||||
} else if (errorText.toLowerCase().includes('connection') || errorText.toLowerCase().includes('timeout')) {
|
||||
$scope.errorSuggestion = 'Check your firewall settings and ensure port 80 is accessible from the internet.';
|
||||
} else if (errorText.toLowerCase().includes('authorization') || errorText.toLowerCase().includes('unauthorized')) {
|
||||
$scope.errorSuggestion = 'Domain validation failed. Verify that this server is accessible via the domain name.';
|
||||
} else if (errorText.toLowerCase().includes('caa')) {
|
||||
$scope.errorSuggestion = 'CAA DNS records are preventing SSL issuance. Update your DNS CAA records to allow Let\'s Encrypt.';
|
||||
} else if (errorText.toLowerCase().includes('challenge')) {
|
||||
$scope.errorSuggestion = 'The ACME challenge failed. Ensure the .well-known/acme-challenge path is accessible.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +142,13 @@ app.controller('sslIssueCtrl', function ($scope, $http) {
|
||||
$scope.canNotIssue = true;
|
||||
$scope.sslIssued = true;
|
||||
$scope.couldNotConnect = false;
|
||||
|
||||
// Show connection error
|
||||
new PNotify({
|
||||
title: 'Connection Error',
|
||||
text: 'Could not connect to the server. Please check your connection and try again.',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -103,14 +103,46 @@ def v2IssueSSL(request):
|
||||
|
||||
website.ssl = 1
|
||||
website.save()
|
||||
|
||||
# Extract detailed logs from output
|
||||
logs = output.split("1,", 1)[1] if "1," in output else output
|
||||
|
||||
data_ret = {'status': 1, "SSL": 1,
|
||||
'error_message': "None", 'sslLogs': output}
|
||||
'error_message': "None", 'sslLogs': logs, 'fullOutput': output}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
else:
|
||||
# Parse error details from output
|
||||
error_message = output
|
||||
detailed_error = "SSL issuance failed"
|
||||
|
||||
# Check for common ACME errors
|
||||
if "Rate limit" in output or "rate limit" in output:
|
||||
detailed_error = "Let's Encrypt rate limit exceeded. Please wait before retrying."
|
||||
elif "DNS problem" in output or "NXDOMAIN" in output:
|
||||
detailed_error = "DNS validation failed. Please ensure your domain points to this server."
|
||||
elif "Connection refused" in output or "Connection timeout" in output:
|
||||
detailed_error = "Could not connect to ACME server. Check your firewall settings."
|
||||
elif "Unauthorized" in output or "authorization" in output:
|
||||
detailed_error = "Domain authorization failed. Verify domain ownership and DNS settings."
|
||||
elif "CAA record" in output:
|
||||
detailed_error = "CAA record prevents issuance. Check your DNS CAA records."
|
||||
elif "Challenge failed" in output or "challenge failed" in output:
|
||||
detailed_error = "ACME challenge failed. Ensure port 80 is accessible and .well-known path is not blocked."
|
||||
elif "Invalid response" in output:
|
||||
detailed_error = "Invalid response from ACME challenge. Check your web server configuration."
|
||||
else:
|
||||
# Try to extract the actual error message
|
||||
if "0," in output:
|
||||
error_parts = output.split("0,", 1)
|
||||
if len(error_parts) > 1:
|
||||
detailed_error = error_parts[1].strip()
|
||||
|
||||
data_ret = {'status': 0, "SSL": 0,
|
||||
'error_message': output, 'sslLogs': output}
|
||||
'error_message': detailed_error,
|
||||
'sslLogs': output,
|
||||
'fullOutput': output,
|
||||
'technicalDetails': error_message}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
except BaseException as msg:
|
||||
|
||||
@@ -41,14 +41,30 @@ class Renew:
|
||||
ssl_provider = x509.get_issuer().get_components()[1][1].decode('utf-8')
|
||||
logging.writeToFile(f'Provider: {ssl_provider}, Days until expiration: {diff.days}', 0)
|
||||
|
||||
if diff.days >= 15 and ssl_provider != 'Denial':
|
||||
# Check if certificate is expired or needs renewal
|
||||
needs_renewal = diff.days < 15 # This handles both negative (expired) and soon-to-expire certs
|
||||
|
||||
if not needs_renewal and ssl_provider != 'Denial':
|
||||
logging.writeToFile(f'SSL exists for {domain} and is not ready to renew, skipping..', 0)
|
||||
return
|
||||
|
||||
if ssl_provider == 'Denial' or ssl_provider == "Let's Encrypt":
|
||||
logging.writeToFile(f'SSL exists for {domain} and ready to renew..', 0)
|
||||
logging.writeToFile(f'Renewing SSL for {domain}..', 0)
|
||||
virtualHostUtilities.issueSSL(domain, path, admin_email)
|
||||
# Handle expired certificates (negative days) with higher priority
|
||||
if diff.days < 0:
|
||||
logging.writeToFile(f'SSL for {domain} is EXPIRED ({abs(diff.days)} days ago). Forcing renewal..', 0)
|
||||
logging.writeToFile(f'Attempting SSL renewal for expired certificate: {domain}..', 0)
|
||||
result = virtualHostUtilities.issueSSL(domain, path, admin_email)
|
||||
if result[0] == 0:
|
||||
logging.writeToFile(f'SSL renewal FAILED for {domain}: {result[1]}', 1)
|
||||
else:
|
||||
logging.writeToFile(f'SSL renewal SUCCESSFUL for {domain}', 0)
|
||||
elif ssl_provider == 'Denial' or ssl_provider == "Let's Encrypt":
|
||||
logging.writeToFile(f'SSL exists for {domain} and ready to renew (expires in {diff.days} days)..', 0)
|
||||
logging.writeToFile(f'Attempting SSL renewal for {domain}..', 0)
|
||||
result = virtualHostUtilities.issueSSL(domain, path, admin_email)
|
||||
if result[0] == 0:
|
||||
logging.writeToFile(f'SSL renewal FAILED for {domain}: {result[1]}', 1)
|
||||
else:
|
||||
logging.writeToFile(f'SSL renewal SUCCESSFUL for {domain}', 0)
|
||||
elif ssl_provider != "Let's Encrypt":
|
||||
logging.writeToFile(f'Custom SSL exists for {domain} and ready to renew..', 1)
|
||||
|
||||
|
||||
@@ -18,6 +18,86 @@ class sslUtilities:
|
||||
Server_root = "/usr/local/lsws"
|
||||
redisConf = '/usr/local/lsws/conf/dvhost_redis.conf'
|
||||
|
||||
@staticmethod
|
||||
def parseACMEError(error_output):
|
||||
"""Parse ACME error output to extract meaningful error messages"""
|
||||
if not error_output:
|
||||
return "Unknown error occurred"
|
||||
|
||||
error_output = str(error_output)
|
||||
|
||||
# Common ACME/Let's Encrypt errors
|
||||
error_patterns = {
|
||||
r"rateLimited": "Rate limit exceeded. Too many certificates issued for this domain. Please wait before retrying.",
|
||||
r"urn:ietf:params:acme:error:rateLimited": "Rate limit exceeded. Please wait before retrying.",
|
||||
r"too many certificates": "Rate limit: Too many certificates issued recently.",
|
||||
r"DNS problem: NXDOMAIN": "DNS Error: Domain does not exist or DNS not propagated.",
|
||||
r"DNS problem": "DNS validation failed. Ensure domain points to this server.",
|
||||
r"Connection refused": "Cannot connect to ACME server. Check firewall/network settings.",
|
||||
r"Connection timeout": "Connection to ACME server timed out. Check network connectivity.",
|
||||
r"Timeout during connect": "Connection timeout. The ACME server may be unreachable.",
|
||||
r"unauthorized": "Authorization failed. Domain validation unsuccessful.",
|
||||
r"urn:ietf:params:acme:error:unauthorized": "Domain authorization failed. Verify domain ownership.",
|
||||
r"Invalid response from": "Invalid response from domain during validation.",
|
||||
r"404": "Challenge file not found. Check web server configuration.",
|
||||
r"403": "Access forbidden. Check file permissions and .htaccess rules.",
|
||||
r"CAA record": "CAA record prevents certificate issuance. Update DNS CAA records.",
|
||||
r"urn:ietf:params:acme:error:caa": "CAA record forbids issuance. Check DNS CAA settings.",
|
||||
r"Challenge failed": "ACME challenge failed. Ensure port 80 is accessible.",
|
||||
r"No valid IP addresses": "No valid IP addresses found for domain.",
|
||||
r"Could not connect to": "Cannot connect to domain for validation.",
|
||||
r"conflictingRequest": "A conflicting request exists. Previous request may still be processing.",
|
||||
r"urn:ietf:params:acme:error:malformed": "Malformed request. Check domain format.",
|
||||
r"urn:ietf:params:acme:error:serverInternal": "ACME server internal error. Try again later.",
|
||||
r"urn:ietf:params:acme:error:orderNotReady": "Order not ready. Domain validation incomplete.",
|
||||
r"badNonce": "Bad nonce error. This is usually temporary, please retry.",
|
||||
r"JWS has an invalid anti-replay nonce": "Invalid nonce. Please retry the request.",
|
||||
r"Account registration error": "Account registration failed. Check email address.",
|
||||
r"Error creating new account": "Cannot create ACME account. Check email validity.",
|
||||
r"Verify error": "Certificate verification failed.",
|
||||
r"Fetching http://": "HTTP validation failed. Ensure port 80 is open.",
|
||||
r"Fetching https://": "HTTPS validation issue detected.",
|
||||
r"Invalid email address": "Invalid email address provided for registration.",
|
||||
r"blacklisted": "Domain is blacklisted by the certificate authority.",
|
||||
r"PolicyForbids": "Certificate authority policy forbids issuance for this domain."
|
||||
}
|
||||
|
||||
# Check each pattern
|
||||
import re
|
||||
for pattern, message in error_patterns.items():
|
||||
if re.search(pattern, error_output, re.IGNORECASE):
|
||||
# Try to extract additional context
|
||||
lines = error_output.split('\n')
|
||||
for line in lines:
|
||||
if 'Detail:' in line:
|
||||
message += f" Detail: {line.split('Detail:')[1].strip()}"
|
||||
break
|
||||
return message
|
||||
|
||||
# Try to extract specific error details from acme.sh output
|
||||
if "[" in error_output and "]" in error_output:
|
||||
# Extract content between brackets which often contains the error
|
||||
import re
|
||||
bracket_content = re.findall(r'\[([^\]]+)\]', error_output)
|
||||
if bracket_content:
|
||||
# Get the last bracketed content as it's usually the error
|
||||
potential_error = bracket_content[-1]
|
||||
if len(potential_error) > 10: # Make sure it's meaningful
|
||||
return f"SSL issuance failed: {potential_error}"
|
||||
|
||||
# Look for lines starting with "Error:" or containing "error:"
|
||||
lines = error_output.split('\n')
|
||||
for line in lines:
|
||||
if line.strip().startswith('Error:') or 'error:' in line.lower():
|
||||
return line.strip()
|
||||
|
||||
# If we can't parse a specific error, return a portion of the output
|
||||
if len(error_output) > 200:
|
||||
# Get the last 200 characters which likely contain the error
|
||||
return f"SSL issuance failed: ...{error_output[-200:]}"
|
||||
|
||||
return f"SSL issuance failed: {error_output}"
|
||||
|
||||
@staticmethod
|
||||
def checkDNSRecords(domain):
|
||||
"""Check if domain has valid DNS records using external DNS query"""
|
||||
@@ -648,8 +728,19 @@ context /.well-known/acme-challenge {
|
||||
f"Successfully obtained SSL using Let's Encrypt for: {virtualHostName}")
|
||||
return 1
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
# Try to extract more detailed error information
|
||||
if hasattr(e, '__dict__'):
|
||||
error_details = str(e.__dict__)
|
||||
else:
|
||||
error_details = error_msg
|
||||
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f"Let's Encrypt failed: {str(e)}. Trying ZeroSSL...")
|
||||
f"Let's Encrypt failed for {virtualHostName}: {error_msg}"
|
||||
)
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f"Detailed error: {error_details}. Trying ZeroSSL..."
|
||||
)
|
||||
|
||||
# Try ZeroSSL if Let's Encrypt fails
|
||||
try:
|
||||
@@ -787,6 +878,22 @@ def issueSSLForDomain(domain, adminEmail, sslpath, aliasDomain=None, isHostname=
|
||||
# Check if certificate already exists and try to renew it first
|
||||
existingCertPath = '/etc/letsencrypt/live/' + domain + '/fullchain.pem'
|
||||
if os.path.exists(existingCertPath):
|
||||
# Check if certificate is expired
|
||||
is_expired = False
|
||||
try:
|
||||
import OpenSSL
|
||||
from datetime import datetime
|
||||
with open(existingCertPath, 'r') as cert_file:
|
||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_file.read())
|
||||
expire_data = x509.get_notAfter().decode('ascii')
|
||||
final_date = datetime.strptime(expire_data, '%Y%m%d%H%M%SZ')
|
||||
now = datetime.now()
|
||||
diff = final_date - now
|
||||
is_expired = diff.days < 0
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Certificate for {domain} expires in {diff.days} days")
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Could not check certificate expiry: {str(e)}")
|
||||
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Certificate exists for {domain}, attempting renewal...")
|
||||
|
||||
# Try to renew using acme.sh
|
||||
@@ -801,8 +908,14 @@ def issueSSLForDomain(domain, adminEmail, sslpath, aliasDomain=None, isHostname=
|
||||
if not isHostname and 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'
|
||||
# For expired certificates, use --issue --force instead of --renew
|
||||
if is_expired:
|
||||
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'
|
||||
else:
|
||||
# Try to renew with explicit webroot
|
||||
command = f'{acmePath} --renew {renewal_domains} --webroot /usr/local/lsws/Example/html --force'
|
||||
|
||||
try:
|
||||
result = subprocess.run(command, capture_output=True, text=True, shell=True)
|
||||
except TypeError:
|
||||
@@ -812,9 +925,13 @@ def issueSSLForDomain(domain, adminEmail, sslpath, aliasDomain=None, isHostname=
|
||||
if result.returncode == 0:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Successfully renewed SSL for {domain}")
|
||||
if sslUtilities.installSSLForDomain(domain, adminEmail) == 1:
|
||||
return [1, "None"]
|
||||
return [1, "SSL successfully renewed"]
|
||||
else:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Renewal failed for {domain}, falling back to new issuance")
|
||||
# Parse ACME error details
|
||||
error_output = result.stderr if hasattr(result, 'stderr') and result.stderr else result.stdout
|
||||
error_details = sslUtilities.parseACMEError(error_output)
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Renewal failed for {domain}. Error: {error_details}")
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Full error output: {error_output}")
|
||||
|
||||
if sslUtilities.obtainSSLForADomain(domain, adminEmail, sslpath, aliasDomain, isHostname) == 1:
|
||||
if sslUtilities.installSSLForDomain(domain, adminEmail) == 1:
|
||||
|
||||
@@ -749,9 +749,16 @@ local_name %s {
|
||||
retValues = sslUtilities.issueSSLForDomain(virtualHost, adminEmail, path)
|
||||
|
||||
if retValues[0] == 0:
|
||||
print("0," + str(retValues[1]))
|
||||
logging.CyberCPLogFileWriter.writeToFile(str(retValues[1]))
|
||||
return 0, str(retValues[1])
|
||||
# Enhanced error reporting
|
||||
error_msg = str(retValues[1])
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"SSL issuance failed for {virtualHost}: {error_msg}")
|
||||
|
||||
# Parse and format the error message for better readability
|
||||
from plogical.sslUtilities import sslUtilities as sslUtil
|
||||
parsed_error = sslUtil.parseACMEError(error_msg)
|
||||
|
||||
print("0," + parsed_error)
|
||||
return 0, parsed_error
|
||||
|
||||
installUtilities.installUtilities.reStartLiteSpeed()
|
||||
|
||||
@@ -762,10 +769,12 @@ local_name %s {
|
||||
ProcessUtilities.executioner(command)
|
||||
|
||||
print("1,None")
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"SSL successfully issued for {virtualHost}")
|
||||
return 1, None
|
||||
|
||||
except BaseException as msg:
|
||||
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [issueSSL]")
|
||||
error_detail = f"Exception in issueSSL for {virtualHost}: {str(msg)}"
|
||||
logging.CyberCPLogFileWriter.writeToFile(error_detail + " [issueSSL]")
|
||||
print("0," + str(msg))
|
||||
return 0, str(msg)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user