mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
Fixed known bugs Panel Feature Modification Points to note after updating the panel: 1. The default port of the panel is changed to 7800 (Port 8888 has been flooded) 2. Panel entry error will prompt 404 3. After the panel is bound to the domain name, if the domain access is not used,it will return 401 4. After the panel is set to authorize IP access, other IP access panels will return 401 5. The website welcome page is changed to the nginx welcome page 6. Change the stop page to Nginx 404 page
1008 lines
44 KiB
Python
1008 lines
44 KiB
Python
#:coding:utf-8
|
||
# The MIT License (MIT)
|
||
#
|
||
# Copyright (c) 2017 Komu Wairagu
|
||
#
|
||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
# of this software and associated documentation files (the "Software"), to deal
|
||
# in the Software without restriction, including without limitation the rights
|
||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
# copies of the Software, and to permit persons to whom the Software is
|
||
# furnished to do so, subject to the following conditions:
|
||
#
|
||
# The above copyright notice and this permission notice shall be included in all
|
||
# copies or substantial portions of the Software.
|
||
#
|
||
# THE SOFTWARE IS PROVIDED "https://github.com/komuw/sewer", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
# SOFTWARE.
|
||
|
||
import sys, os
|
||
import time
|
||
import copy
|
||
import json
|
||
import base64
|
||
import hashlib
|
||
import binascii
|
||
import urllib
|
||
|
||
if sys.version_info[0] == 2: # python2
|
||
import urlparse
|
||
import urllib2
|
||
import cryptography.hazmat
|
||
import cryptography.hazmat.backends
|
||
import cryptography.hazmat.primitives.serialization
|
||
else: # python3
|
||
from urllib.parse import urlparse
|
||
import cryptography
|
||
import platform
|
||
import hmac
|
||
try:
|
||
import requests
|
||
except:
|
||
os.system('pip install requests')
|
||
import requests
|
||
try:
|
||
import OpenSSL
|
||
except:
|
||
os.system('pip install pyopenssl')
|
||
import OpenSSL
|
||
import random
|
||
import datetime
|
||
import logging
|
||
from hashlib import sha1
|
||
|
||
os.chdir("/www/server/panel")
|
||
if not 'class/' in sys.path:
|
||
sys.path.insert(0,'class/')
|
||
import public
|
||
|
||
|
||
class ACMEclient(object):
|
||
def __init__(
|
||
self,
|
||
dns_class,
|
||
domain_alt_names=[],
|
||
Manual=0, # 手动验证 0 非手动,1 获取手动要添加的txt记录值,2 验证添加后的txt解析记录
|
||
contact_email=None,
|
||
account_key=None,
|
||
certificate_key=None,
|
||
bits=2048,
|
||
digest="sha256",
|
||
ACME_REQUEST_TIMEOUT=7, # 请求超时时间/s
|
||
ACME_AUTH_STATUS_WAIT_PERIOD=8, # 认证等待时间/s
|
||
ACME_AUTH_STATUS_MAX_CHECKS=3, # 验证最大重试次数
|
||
ACME_DIRECTORY_URL="https://acme-v02.api.letsencrypt.org/directory",
|
||
LOG_LEVEL="INFO",
|
||
):
|
||
self.Manual = Manual
|
||
self.domain_name = domain_alt_names[0]
|
||
self.dns_class = dns_class
|
||
if not domain_alt_names:
|
||
domain_alt_names = []
|
||
self.domain_alt_names = domain_alt_names
|
||
self.domain_alt_names = list(set(self.domain_alt_names))
|
||
self.contact_email = contact_email
|
||
self.bits = bits
|
||
self.digest = digest
|
||
self.ACME_REQUEST_TIMEOUT = ACME_REQUEST_TIMEOUT
|
||
self.ACME_AUTH_STATUS_WAIT_PERIOD = ACME_AUTH_STATUS_WAIT_PERIOD
|
||
self.ACME_AUTH_STATUS_MAX_CHECKS = ACME_AUTH_STATUS_MAX_CHECKS
|
||
self.ACME_DIRECTORY_URL = ACME_DIRECTORY_URL
|
||
self.LOG_LEVEL = LOG_LEVEL.upper()
|
||
|
||
self.logger = logging.getLogger()
|
||
handler = logging.StreamHandler()
|
||
formatter = logging.Formatter("%(message)s")
|
||
handler.setFormatter(formatter)
|
||
if not self.logger.handlers:
|
||
self.logger.addHandler(handler)
|
||
self.logger.setLevel(self.LOG_LEVEL)
|
||
|
||
try:
|
||
self.all_domain_names = copy.copy(self.domain_alt_names)
|
||
# self.all_domain_names.insert(0, self.domain_name)
|
||
self.domain_alt_names = list(set(self.domain_alt_names))
|
||
|
||
self.User_Agent = self.get_user_agent()
|
||
acme_endpoints = self.get_acme_endpoints().json()
|
||
self.ACME_GET_NONCE_URL = acme_endpoints["newNonce"]
|
||
self.ACME_TOS_URL = acme_endpoints["meta"]["termsOfService"]
|
||
self.ACME_KEY_CHANGE_URL = acme_endpoints["keyChange"]
|
||
self.ACME_NEW_ACCOUNT_URL = acme_endpoints["newAccount"]
|
||
self.ACME_NEW_ORDER_URL = acme_endpoints["newOrder"]
|
||
self.ACME_REVOKE_CERT_URL = acme_endpoints["revokeCert"]
|
||
|
||
# 唯一帐户标识符
|
||
# https://tools.ietf.org/html/draft-ietf-acme-acme#section-6.2
|
||
self.kid = None
|
||
|
||
self.certificate_key = certificate_key or self.create_certificate_key()
|
||
self.csr = self.create_csr()
|
||
|
||
if not account_key:
|
||
self.account_key = self.create_account_key()
|
||
self.PRIOR_REGISTERED = False
|
||
else:
|
||
self.account_key = account_key
|
||
self.PRIOR_REGISTERED = True
|
||
|
||
print(
|
||
"初始化成功, domain_names={}, acme_server={}".format(
|
||
self.all_domain_names,
|
||
self.ACME_DIRECTORY_URL,
|
||
)
|
||
)
|
||
except Exception as e:
|
||
self.logger.error("{0}. error={1}".format(public.GetMsg("ACME_ERR2"),str(e)))
|
||
sys.exit(json.dumps({"data": public.GetMsg("ACME_ERR1")}))
|
||
|
||
@staticmethod
|
||
def log_response(response):
|
||
try:
|
||
log_body = response.json()
|
||
except ValueError:
|
||
log_body = response.content[:30]
|
||
return log_body
|
||
|
||
@staticmethod
|
||
def get_user_agent():
|
||
return "{system}: {machine} ".format(
|
||
system=platform.system(),
|
||
machine=platform.machine(),
|
||
)
|
||
|
||
def get_acme_endpoints(self):
|
||
print("Get _acme_ endpoint")
|
||
headers = {"User-Agent": self.User_Agent}
|
||
i = 0
|
||
while i < 3:
|
||
try:
|
||
get_acme_endpoints = requests.get(
|
||
self.ACME_DIRECTORY_URL, timeout=self.ACME_REQUEST_TIMEOUT, headers=headers
|
||
)
|
||
except Exception:
|
||
i += 1
|
||
else:
|
||
break
|
||
else:
|
||
sys.exit(json.dumps({"data": public.GetMsg("ACME_ERR3")}))
|
||
|
||
if get_acme_endpoints.status_code not in [200, 201]:
|
||
raise ValueError(
|
||
"{acme}: status_code={status_code} response={response}".format(
|
||
status_code=get_acme_endpoints.status_code,
|
||
response=self.log_response(get_acme_endpoints),
|
||
)
|
||
)
|
||
return get_acme_endpoints
|
||
|
||
def create_certificate_key(self):
|
||
print("Create certificate_key")
|
||
return self.create_key().decode()
|
||
|
||
def create_account_key(self):
|
||
self.logger.debug(public.GetMsg("CREATE_ACCOUNT_KEY"))
|
||
return self.create_key().decode()
|
||
|
||
def create_key(self, key_type=OpenSSL.crypto.TYPE_RSA):
|
||
key = OpenSSL.crypto.PKey()
|
||
key.generate_key(key_type, self.bits)
|
||
private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
|
||
return private_key
|
||
|
||
def create_csr(self):
|
||
print("Create _csr")
|
||
X509Req = OpenSSL.crypto.X509Req()
|
||
X509Req.get_subject().CN = self.domain_name
|
||
|
||
if self.domain_alt_names:
|
||
SAN = "DNS:{0}, ".format(self.domain_name).encode("utf8") + ", ".join(
|
||
"DNS:" + i for i in self.domain_alt_names
|
||
).encode("utf8")
|
||
else:
|
||
SAN = "DNS:{0}".format(self.domain_name).encode("utf8")
|
||
|
||
X509Req.add_extensions(
|
||
[
|
||
OpenSSL.crypto.X509Extension(
|
||
"subjectAltName".encode("utf8"), critical=False, value=SAN
|
||
)
|
||
]
|
||
)
|
||
pk = OpenSSL.crypto.load_privatekey(
|
||
OpenSSL.crypto.FILETYPE_PEM, self.certificate_key.encode()
|
||
)
|
||
X509Req.set_pubkey(pk)
|
||
X509Req.set_version(2)
|
||
X509Req.sign(pk, self.digest)
|
||
return OpenSSL.crypto.dump_certificate_request(OpenSSL.crypto.FILETYPE_ASN1, X509Req)
|
||
|
||
def acme_register(self):
|
||
print("acme registration")
|
||
if self.PRIOR_REGISTERED:
|
||
payload = {"onlyReturnExisting": True}
|
||
elif self.contact_email:
|
||
payload = {
|
||
"termsOfServiceAgreed": True,
|
||
"contact": ["mailto:{0}".format(self.contact_email)],
|
||
}
|
||
else:
|
||
payload = {"termsOfServiceAgreed": True}
|
||
url = self.ACME_NEW_ACCOUNT_URL
|
||
acme_register_response = self.make_signed_acme_request(url=url, payload=payload)
|
||
if acme_register_response.status_code not in [201, 200, 409]:
|
||
public.WriteFile(os.path.join(ssl_home_path, "apply_for_cert_issuance_response"), acme_register_response.text, mode="w")
|
||
raise ValueError(
|
||
"{ssl_register}: status_code={status_code} response={response}".format(
|
||
status_code=acme_register_response.status_code,
|
||
response=self.log_response(acme_register_response),
|
||
)
|
||
)
|
||
kid = acme_register_response.headers["Location"]
|
||
setattr(self, "kid", kid)
|
||
print("acme_register_success")
|
||
return acme_register_response
|
||
|
||
def apply_for_cert_issuance(self):
|
||
print("Apply for a certificate")
|
||
identifiers = []
|
||
for domain_name in self.all_domain_names:
|
||
identifiers.append({"type": "model", "value": domain_name})
|
||
payload = {"identifiers": identifiers}
|
||
url = self.ACME_NEW_ORDER_URL
|
||
apply_for_cert_issuance_response = self.make_signed_acme_request(url=url, payload=payload)
|
||
if apply_for_cert_issuance_response.status_code != 201:
|
||
public.WriteFile(os.path.join(ssl_home_path, "apply_for_cert_issuance_response"), apply_for_cert_issuance_response.text, mode="w")
|
||
raise ValueError(
|
||
"{ssl_accept_err}: status_code={status_code} response={response}".format(
|
||
status_code=apply_for_cert_issuance_response.status_code,
|
||
response=self.log_response(apply_for_cert_issuance_response),
|
||
)
|
||
)
|
||
apply_for_cert_issuance_response_json = apply_for_cert_issuance_response.json()
|
||
finalize_url = apply_for_cert_issuance_response_json["finalize"]
|
||
authorizations = apply_for_cert_issuance_response_json["authorizations"]
|
||
print("Successful application for certificate")
|
||
return authorizations, finalize_url
|
||
|
||
def get_identifier_authorization(self, url):
|
||
print("Get identifier authorization")
|
||
headers = {"User-Agent": self.User_Agent}
|
||
i = 0
|
||
while i < 3:
|
||
try:
|
||
get_identifier_authorization_response = requests.get(
|
||
url, timeout=self.ACME_REQUEST_TIMEOUT, headers=headers
|
||
)
|
||
except Exception:
|
||
i += 1
|
||
else:
|
||
break
|
||
else:
|
||
sys.exit(json.dumps({"data": public.GetMsg("ACME_ERR3")}))
|
||
|
||
if get_identifier_authorization_response.status_code not in [200, 201]:
|
||
raise ValueError(
|
||
"{ssl_accept_err1}: status_code={status_code} response={response}".format(
|
||
status_code=get_identifier_authorization_response.status_code,
|
||
response=self.log_response(get_identifier_authorization_response),
|
||
)
|
||
)
|
||
res = get_identifier_authorization_response.json()
|
||
domain = res["identifier"]["value"]
|
||
wildcard = res.get("wildcard")
|
||
if wildcard:
|
||
domain = "*." + domain
|
||
for i in res["challenges"]:
|
||
if i["type"] == "model-01":
|
||
dns_challenge = i
|
||
dns_token = dns_challenge["token"]
|
||
dns_challenge_url = dns_challenge["url"]
|
||
identifier_auth = {
|
||
"domain": domain,
|
||
"url": url,
|
||
"wildcard": wildcard,
|
||
"dns_token": dns_token,
|
||
"dns_challenge_url": dns_challenge_url,
|
||
}
|
||
print(
|
||
"Get identifier authorization successfully. identifier_auth={0}".format(identifier_auth)
|
||
)
|
||
|
||
return identifier_auth
|
||
|
||
def get_keyauthorization(self, dns_token):
|
||
print("Get key authorization")
|
||
acme_header_jwk_json = json.dumps(
|
||
self.get_acme_header("GET_THUMBPRINT")["jwk"], sort_keys=True, separators=(",", ":")
|
||
)
|
||
acme_thumbprint = self.calculate_safe_base64(
|
||
hashlib.sha256(acme_header_jwk_json.encode("utf8")).digest()
|
||
)
|
||
acme_keyauthorization = "{0}.{1}".format(dns_token, acme_thumbprint)
|
||
base64_of_acme_keyauthorization = self.calculate_safe_base64(
|
||
hashlib.sha256(acme_keyauthorization.encode("utf8")).digest()
|
||
)
|
||
return acme_keyauthorization, base64_of_acme_keyauthorization
|
||
|
||
def check_authorization_status(self, authorization_url, desired_status=None, dns_names_to_delete=[]):
|
||
"""
|
||
检查授权的状态,验证dns有没有添加txt解析记录
|
||
"""
|
||
print("Check authorization status")
|
||
time.sleep(self.ACME_AUTH_STATUS_WAIT_PERIOD) # 等待
|
||
desired_status = desired_status or ["pending", "valid"]
|
||
number_of_checks = 0
|
||
while True:
|
||
headers = {"User-Agent": self.User_Agent}
|
||
i = 0
|
||
while i < 3:
|
||
try:
|
||
check_authorization_status_response = requests.get(
|
||
authorization_url, timeout=self.ACME_REQUEST_TIMEOUT, headers=headers
|
||
)
|
||
except Exception:
|
||
i += 1
|
||
else:
|
||
break
|
||
else:
|
||
sys.exit(json.dumps({"data": public.GetMsg("ACME_ERR3")}))
|
||
|
||
authorization_status = check_authorization_status_response.json()["status"]
|
||
number_of_checks = number_of_checks + 1
|
||
if number_of_checks == self.ACME_AUTH_STATUS_MAX_CHECKS:
|
||
msg = public.GetMsg("SSL_CHECK_TIPS",(number_of_checks,self.ACME_AUTH_STATUS_MAX_CHECKS,self.ACME_AUTH_STATUS_WAIT_PERIOD))
|
||
print (msg)
|
||
for i in dns_names_to_delete: # 验证失败后也删除添加的dns
|
||
self.dns_class.delete_dns_record(i["dns_name"], i["domain_dns_value"])
|
||
sys.exit(json.dumps({"status": False, "data": public.GetMsg("CHECK_TXT_ERR5"), "msg": msg, }))
|
||
if authorization_status in desired_status:
|
||
break
|
||
else:
|
||
print("Failed to verify model txt wait {} seconds to re-verify model, returned information:".format(self.ACME_AUTH_STATUS_WAIT_PERIOD))
|
||
print(check_authorization_status_response.json())
|
||
public.WriteFile(os.path.join(ssl_home_path, "check_authorization_status_response"), check_authorization_status_response.text, mode="w")
|
||
# 等待
|
||
time.sleep(self.ACME_AUTH_STATUS_WAIT_PERIOD)
|
||
print("End of checking authorization status")
|
||
return check_authorization_status_response
|
||
|
||
def respond_to_challenge(self, acme_keyauthorization, dns_challenge_url):
|
||
print("Response challenge")
|
||
payload = {"keyAuthorization": acme_keyauthorization}
|
||
respond_to_challenge_response = self.make_signed_acme_request(dns_challenge_url, payload)
|
||
print("Response challenge_ success")
|
||
return respond_to_challenge_response
|
||
|
||
def send_csr(self, finalize_url):
|
||
self.logger.info("send_csr")
|
||
payload = {"csr": self.calculate_safe_base64(self.csr)}
|
||
send_csr_response = self.make_signed_acme_request(url=finalize_url, payload=payload)
|
||
|
||
if send_csr_response.status_code not in [200, 201]:
|
||
raise ValueError(
|
||
"{send_csr_err}: status_code={status_code} response={response}".format(
|
||
status_code=send_csr_response.status_code,
|
||
response=self.log_response(send_csr_response),
|
||
)
|
||
)
|
||
send_csr_response_json = send_csr_response.json()
|
||
certificate_url = send_csr_response_json["certificate"]
|
||
print("send_csr_success")
|
||
return certificate_url
|
||
|
||
def download_certificate(self, certificate_url):
|
||
print("Download the certificate")
|
||
download_certificate_response = self.make_signed_acme_request(
|
||
certificate_url, payload="DOWNLOAD_Z_CERTIFICATE"
|
||
)
|
||
if download_certificate_response.status_code not in [200, 201]:
|
||
raise ValueError(
|
||
"{get_cert_err}: status_code={status_code} response={response}".format(
|
||
status_code=download_certificate_response.status_code,
|
||
response=self.log_response(download_certificate_response),
|
||
)
|
||
)
|
||
pem_certificate = download_certificate_response.content.decode("utf-8")
|
||
print("Download certificate successful")
|
||
return pem_certificate
|
||
|
||
def sign_message(self, message):
|
||
self.logger.debug("sign_message")
|
||
pk = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, self.account_key.encode())
|
||
return OpenSSL.crypto.sign(pk, message.encode("utf8"), self.digest)
|
||
|
||
def get_nonce(self):
|
||
"""
|
||
https://tools.ietf.org/html/draft-ietf-acme-acme#section-6.4
|
||
对ACME服务器的每个请求都必须包含一个新的未使用的nonce 。
|
||
"""
|
||
print("get_nonce")
|
||
headers = {"User-Agent": self.User_Agent}
|
||
i = 0
|
||
while i < 3:
|
||
try:
|
||
response = requests.get(
|
||
self.ACME_GET_NONCE_URL, timeout=self.ACME_REQUEST_TIMEOUT, headers=headers
|
||
)
|
||
except Exception:
|
||
i += 1
|
||
else:
|
||
break
|
||
else:
|
||
sys.exit(json.dumps({"data": public.GetMsg("ACME_ERR3")}))
|
||
|
||
nonce = response.headers["Replay-Nonce"]
|
||
return nonce
|
||
|
||
@staticmethod
|
||
def stringfy_items(payload):
|
||
if isinstance(payload, str):
|
||
return payload
|
||
for k, v in payload.items():
|
||
if isinstance(k, bytes):
|
||
k = k.decode("utf-8")
|
||
if isinstance(v, bytes):
|
||
v = v.decode("utf-8")
|
||
payload[k] = v
|
||
return payload
|
||
|
||
@staticmethod
|
||
def calculate_safe_base64(un_encoded_data):
|
||
if sys.version_info[0] == 3:
|
||
if isinstance(un_encoded_data, str):
|
||
un_encoded_data = un_encoded_data.encode("utf8")
|
||
r = base64.urlsafe_b64encode(un_encoded_data).rstrip(b"=")
|
||
return r.decode("utf8")
|
||
|
||
def get_acme_header(self, url):
|
||
print("get_acme_header")
|
||
header = {"alg": "RS256", "nonce": self.get_nonce(), "url": url}
|
||
if url in [self.ACME_NEW_ACCOUNT_URL, self.ACME_REVOKE_CERT_URL, "GET_THUMBPRINT"]:
|
||
private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||
self.account_key.encode(),
|
||
password=None,
|
||
backend=cryptography.hazmat.backends.default_backend(),
|
||
)
|
||
public_key_public_numbers = private_key.public_key().public_numbers()
|
||
# private key public exponent in hex format
|
||
exponent = "{0:x}".format(public_key_public_numbers.e)
|
||
exponent = "0{0}".format(exponent) if len(exponent) % 2 else exponent
|
||
# private key modulus in hex format
|
||
modulus = "{0:x}".format(public_key_public_numbers.n)
|
||
jwk = {
|
||
"kty": "RSA",
|
||
"e": self.calculate_safe_base64(binascii.unhexlify(exponent)),
|
||
"n": self.calculate_safe_base64(binascii.unhexlify(modulus)),
|
||
}
|
||
header["jwk"] = jwk
|
||
else:
|
||
header["kid"] = self.kid
|
||
return header
|
||
|
||
def make_signed_acme_request(self, url, payload):
|
||
print("Sign acme request")
|
||
headers = {"User-Agent": self.User_Agent}
|
||
payload = self.stringfy_items(payload)
|
||
if payload in ["GET_Z_CHALLENGE", "DOWNLOAD_Z_CERTIFICATE"]:
|
||
i = 0
|
||
while i < 3:
|
||
try:
|
||
response = requests.get(url, timeout=self.ACME_REQUEST_TIMEOUT, headers=headers)
|
||
except Exception:
|
||
i += 1
|
||
else:
|
||
break
|
||
else:
|
||
sys.exit(json.dumps({"data": public.GetMsg("ACME_ERR3")}))
|
||
|
||
else:
|
||
payload64 = self.calculate_safe_base64(json.dumps(payload))
|
||
protected = self.get_acme_header(url)
|
||
protected64 = self.calculate_safe_base64(json.dumps(protected))
|
||
signature = self.sign_message(message="{0}.{1}".format(protected64, payload64)) # bytes
|
||
signature64 = self.calculate_safe_base64(signature) # str
|
||
data = json.dumps(
|
||
{"protected": protected64, "payload": payload64, "signature": signature64}
|
||
)
|
||
headers.update({"Content-Type": "application/jose+json"})
|
||
i = 0
|
||
while i < 3:
|
||
try:
|
||
response = requests.post(
|
||
url, data=data.encode("utf8"), timeout=self.ACME_REQUEST_TIMEOUT, headers=headers
|
||
)
|
||
except Exception:
|
||
i += 1
|
||
else:
|
||
break
|
||
else:
|
||
sys.exit(json.dumps({"data": public.GetMsg("ACME_ERR3")}))
|
||
|
||
return response
|
||
|
||
def get_certificate(self):
|
||
self.logger.debug("get_certificate")
|
||
domain_dns_value = "placeholder"
|
||
dns_names_to_delete = []
|
||
# try:
|
||
self.acme_register()
|
||
authorizations, finalize_url = self.apply_for_cert_issuance()
|
||
|
||
responders = []
|
||
domain_txt_dns_value = []
|
||
for url in authorizations:
|
||
identifier_auth = self.get_identifier_authorization(url)
|
||
authorization_url = identifier_auth["url"]
|
||
dns_name = identifier_auth["domain"]
|
||
dns_token = identifier_auth["dns_token"]
|
||
dns_challenge_url = identifier_auth["dns_challenge_url"]
|
||
|
||
acme_keyauthorization, domain_dns_value = self.get_keyauthorization(dns_token)
|
||
_, _, acme_txt = extract_zone(dns_name)
|
||
domain_txt_dns_value.append({"acme_txt": acme_txt,"dns_name":dns_name, "acme_txt_value": domain_dns_value})
|
||
self.dns_class.create_dns_record(dns_name, domain_dns_value)
|
||
dns_names_to_delete.append(
|
||
{"dns_name": dns_name, "domain_dns_value": domain_dns_value}
|
||
)
|
||
responders.append(
|
||
{
|
||
"authorization_url": authorization_url,
|
||
"acme_keyauthorization": acme_keyauthorization,
|
||
"dns_challenge_url": dns_challenge_url,
|
||
}
|
||
)
|
||
|
||
# 1
|
||
if self.Manual == 1:
|
||
|
||
print("Please add txt parsing")
|
||
print(domain_txt_dns_value)
|
||
public.WriteFile(os.path.join(ssl_home_path, "domain_txt_dns_value.json"), json.dumps(domain_txt_dns_value), mode="w")
|
||
public.WriteFile(os.path.join(ssl_home_path, "Confirmation_verification"), "", mode="w")
|
||
num = 0
|
||
while True:
|
||
if os.path.isfile(os.path.join(ssl_home_path, "Confirmation_verification")):
|
||
Confirmation_verification = public.ReadFile(os.path.join(ssl_home_path, "Confirmation_verification"))
|
||
if Confirmation_verification.strip() == "ok":
|
||
break
|
||
time.sleep(5)
|
||
num += 1
|
||
if num > 90:
|
||
timeout_info = json.dumps({"data": public.GetMsg("CHECK_TXT_ERR6"), "status": False})
|
||
public.WriteFile(os.path.join(ssl_home_path, "timeout_info"), timeout_info, mode="w")
|
||
sys.exit(timeout_info) # 5分钟后失效
|
||
for i in responders:
|
||
auth_status_response = self.check_authorization_status(i["authorization_url"])
|
||
if auth_status_response.json()["status"] == "pending":
|
||
self.respond_to_challenge(i["acme_keyauthorization"], i["dns_challenge_url"])
|
||
|
||
for i in responders:
|
||
self.check_authorization_status(i["authorization_url"], ["valid"], dns_names_to_delete)
|
||
certificate_url = self.send_csr(finalize_url)
|
||
certificate = self.download_certificate(certificate_url)
|
||
# except Exception as e:
|
||
# sys.exit("错误:无法颁发证书. error={0}".format(str(e)))
|
||
# finally:
|
||
# for i in dns_names_to_delete:
|
||
# self.dns_class.delete_dns_record(i["dns_name"], i["domain_dns_value"])
|
||
for i in dns_names_to_delete:
|
||
self.dns_class.delete_dns_record(i["dns_name"], i["domain_dns_value"])
|
||
return certificate
|
||
|
||
def cert(self):
|
||
return self.get_certificate()
|
||
|
||
def renew(self):
|
||
""" 续签证书。 续订实际上是获得新证书。 https://letsencrypt.org/docs/rate-limits/ """
|
||
return self.cert()
|
||
|
||
|
||
def extract_zone(domain_name):
|
||
domain_name = domain_name.lstrip("*.")
|
||
if domain_name.count(".") > 1:
|
||
zone, middle, last = str(domain_name).rsplit(".", 2)
|
||
root = ".".join([middle, last])
|
||
acme_txt = "_acme-challenge.%s" % zone
|
||
else:
|
||
zone = ""
|
||
root = domain_name
|
||
acme_txt = "_acme-challenge"
|
||
return root, zone, acme_txt
|
||
|
||
|
||
class DNSPodDns(object):
|
||
dns_provider_name = "dnspod"
|
||
|
||
def __init__(self, DNSPOD_ID, DNSPOD_API_KEY, DNSPOD_API_BASE_URL="https://dnsapi.cn/"):
|
||
self.DNSPOD_ID = DNSPOD_ID
|
||
self.DNSPOD_API_KEY = DNSPOD_API_KEY
|
||
self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL
|
||
self.HTTP_TIMEOUT = 65 # seconds
|
||
self.DNSPOD_LOGIN = "{0},{1}".format(self.DNSPOD_ID, self.DNSPOD_API_KEY)
|
||
|
||
if DNSPOD_API_BASE_URL[-1] != "/":
|
||
self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL + "/"
|
||
else:
|
||
self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL
|
||
|
||
def create_dns_record(self, domain_name, domain_dns_value):
|
||
print("create_dns_record {} {}".format(domain_name, domain_dns_value))
|
||
# if we have been given a wildcard name, strip wildcard
|
||
domain_name = domain_name.lstrip("*.")
|
||
subd = ""
|
||
if domain_name.count(".") != 1: # not top level domain
|
||
pos = domain_name.rfind(".", 0, domain_name.rfind("."))
|
||
subd = domain_name[:pos]
|
||
domain_name = domain_name[pos + 1:]
|
||
if subd != "":
|
||
subd = "." + subd
|
||
if sys.version_info[0] == 2:
|
||
url = urlparse.urljoin(self.DNSPOD_API_BASE_URL, "Record.Create")
|
||
else:
|
||
url = urllib.parse.urljoin(self.DNSPOD_API_BASE_URL, "Record.Create")
|
||
body = {
|
||
"record_type": "TXT",
|
||
"domain": domain_name,
|
||
"sub_domain": "_acme-challenge" + subd,
|
||
"value": domain_dns_value,
|
||
"record_line_id": "0",
|
||
"format": "json",
|
||
"login_token": self.DNSPOD_LOGIN,
|
||
}
|
||
create_dnspod_dns_record_response = requests.post(
|
||
url, data=body, timeout=self.HTTP_TIMEOUT
|
||
).json()
|
||
if create_dnspod_dns_record_response["status"]["code"] != "1":
|
||
if create_dnspod_dns_record_response["status"]["code"] == "13" or create_dnspod_dns_record_response["status"]["code"] == "7":
|
||
sys.exit(json.dumps({"data": public.GetMsg("DNSPORD_ERR"), "msg": create_dnspod_dns_record_response}))
|
||
elif create_dnspod_dns_record_response["status"]["code"] == "10004" or create_dnspod_dns_record_response["status"]["code"] == "10002":
|
||
sys.exit(json.dumps({"data": public.GetMsg("DNSPORD_ERR1"), "msg": create_dnspod_dns_record_response}))
|
||
else:
|
||
sys.exit(json.dumps({"data": create_dnspod_dns_record_response["status"]['message'], "msg": create_dnspod_dns_record_response}))
|
||
print("create_dns_record_end")
|
||
|
||
def delete_dns_record(self, domain_name, domain_dns_value):
|
||
print("delete_dns_record", domain_name)
|
||
domain_name = domain_name.lstrip("*.")
|
||
subd = ""
|
||
if domain_name.count(".") != 1: # not top level domain
|
||
pos = domain_name.rfind(".", 0, domain_name.rfind("."))
|
||
subd = domain_name[:pos]
|
||
domain_name = domain_name[pos + 1:]
|
||
if subd != "":
|
||
subd = "." + subd
|
||
if sys.version_info[0] == 2:
|
||
url = urlparse.urljoin(self.DNSPOD_API_BASE_URL, "Record.List")
|
||
else:
|
||
url = urllib.parse.urljoin(self.DNSPOD_API_BASE_URL, "Record.List")
|
||
# pos = domain_name.rfind(".",0, domain_name.rfind("."))
|
||
subdomain = "_acme-challenge." + subd
|
||
rootdomain = domain_name
|
||
body = {
|
||
"login_token": self.DNSPOD_LOGIN,
|
||
"format": "json",
|
||
"domain": rootdomain,
|
||
"subdomain": subdomain,
|
||
"record_type": "TXT",
|
||
}
|
||
list_dns_response = requests.post(url, data=body, timeout=self.HTTP_TIMEOUT).json()
|
||
if list_dns_response["status"]["code"] != "1":
|
||
print("list_dns_record_response. status_code={0}. message={1}".format(list_dns_response["status"]["code"], list_dns_response["status"]["message"]))
|
||
return
|
||
for i in range(0, len(list_dns_response["records"])):
|
||
rid = list_dns_response["records"][i]["id"]
|
||
if sys.version_info[0] == 2:
|
||
urlr = urlparse.urljoin(self.DNSPOD_API_BASE_URL, "Record.Remove")
|
||
else:
|
||
urlr = urllib.parse.urljoin(self.DNSPOD_API_BASE_URL, "Record.Remove")
|
||
bodyr = {
|
||
"login_token": self.DNSPOD_LOGIN,
|
||
"format": "json",
|
||
"domain": rootdomain,
|
||
"record_id": rid,
|
||
}
|
||
delete_dns_record_response = requests.post(
|
||
urlr, data=bodyr, timeout=self.HTTP_TIMEOUT
|
||
).json()
|
||
if delete_dns_record_response["status"]["code"] != "1":
|
||
print("delete_dns_record_response. status_code={0}. message={1}".format(delete_dns_record_response["status"]["code"], delete_dns_record_response["status"]["message"], ))
|
||
print("delete_dns_record_success")
|
||
|
||
|
||
class AliyunDns(object):
|
||
def __init__(self, key, secret, ):
|
||
self.key = str(key).strip()
|
||
self.secret = str(secret).strip()
|
||
self.url = "http://alidns.aliyuncs.com"
|
||
|
||
def sign(self, accessKeySecret, parameters): # '''签名方法
|
||
def percent_encode(encodeStr):
|
||
encodeStr = str(encodeStr)
|
||
if sys.version_info[0] == 3:
|
||
res = urllib.parse.quote(encodeStr, '')
|
||
else:
|
||
res = urllib2.quote(encodeStr, '')
|
||
res = res.replace('+', '%20')
|
||
res = res.replace('*', '%2A')
|
||
res = res.replace('%7E', '~')
|
||
return res
|
||
|
||
sortedParameters = sorted(parameters.items(), key=lambda parameters: parameters[0])
|
||
canonicalizedQueryString = ''
|
||
for (k, v) in sortedParameters:
|
||
canonicalizedQueryString += '&' + percent_encode(k) + '=' + percent_encode(v)
|
||
stringToSign = 'GET&%2F&' + percent_encode(canonicalizedQueryString[1:])
|
||
if sys.version_info[0] == 2:
|
||
h = hmac.new(accessKeySecret + "&", stringToSign, sha1)
|
||
else:
|
||
h = hmac.new(bytes(accessKeySecret + "&", encoding="utf8"), stringToSign.encode('utf8'), sha1)
|
||
signature = base64.encodestring(h.digest()).strip()
|
||
return signature
|
||
|
||
def create_dns_record(self, domain_name, domain_dns_value):
|
||
root, _, acme_txt = extract_zone(domain_name)
|
||
print("create_dns_record start: ", acme_txt, domain_dns_value)
|
||
randomint = random.randint(11111111111111, 99999999999999)
|
||
now = datetime.datetime.utcnow()
|
||
otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||
paramsdata = {
|
||
"Action": "AddDomainRecord", "Format": "json", "Version": "2015-01-09", "SignatureMethod": "HMAC-SHA1", "Timestamp": otherStyleTime,
|
||
"SignatureVersion": "1.0", "SignatureNonce": str(randomint), "AccessKeyId": self.key,
|
||
"DomainName": root,
|
||
"RR": acme_txt,
|
||
"Type": "TXT",
|
||
"Value": domain_dns_value,
|
||
}
|
||
Signature = self.sign(self.secret, paramsdata)
|
||
paramsdata['Signature'] = Signature
|
||
req = requests.get(url=self.url, params=paramsdata)
|
||
if req.status_code != 200:
|
||
if req.json()['Code'] == 'IncorrectDomainUser' or req.json()['Code'] == 'InvalidDomainName.NoExist':
|
||
sys.exit(json.dumps({"data": public.GetMsg("ALICLOUD_ERR"), "msg": req.json()}))
|
||
elif req.json()['Code'] == 'InvalidAccessKeyId.NotFound' or req.json()['Code'] == 'SignatureDoesNotMatch':
|
||
sys.exit(json.dumps({"data": public.GetMsg("API_SK_ERR"), "msg": req.json()}))
|
||
else:
|
||
sys.exit(json.dumps({"data": req.json()['Message'], "msg": req.json()}))
|
||
print("create_dns_record end")
|
||
|
||
def query_recored_items(self, host, zone=None, tipe=None, page=1, psize=200):
|
||
randomint = random.randint(11111111111111, 99999999999999)
|
||
now = datetime.datetime.utcnow()
|
||
otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||
paramsdata = {
|
||
"Action": "DescribeDomainRecords", "Format": "json", "Version": "2015-01-09", "SignatureMethod": "HMAC-SHA1", "Timestamp": otherStyleTime,
|
||
"SignatureVersion": "1.0", "SignatureNonce": str(randomint), "AccessKeyId": self.key,
|
||
"DomainName": host,
|
||
}
|
||
if zone:
|
||
paramsdata['RRKeyWord'] = zone
|
||
if tipe:
|
||
paramsdata['TypeKeyWord'] = tipe
|
||
Signature = self.sign(self.secret, paramsdata)
|
||
paramsdata['Signature'] = Signature
|
||
req = requests.get(url=self.url, params=paramsdata)
|
||
return req.json()
|
||
|
||
def query_recored_id(self, root, zone, tipe="TXT"):
|
||
record_id = None
|
||
recoreds = self.query_recored_items(root, zone, tipe=tipe)
|
||
recored_list = recoreds.get("DomainRecords", {}).get("Record", [])
|
||
recored_item_list = [i for i in recored_list if i["RR"] == zone]
|
||
if len(recored_item_list):
|
||
record_id = recored_item_list[0]["RecordId"]
|
||
return record_id
|
||
|
||
def delete_dns_record(self, domain_name, domain_dns_value):
|
||
root, _, acme_txt = extract_zone(domain_name)
|
||
print("delete_dns_record start: ", acme_txt, domain_dns_value)
|
||
record_id = self.query_recored_id(root, acme_txt)
|
||
if not record_id:
|
||
msg = public.GetMsg("CANT_FIND_RECORDID"), domain_name
|
||
print(msg)
|
||
return
|
||
print("start to delete model record, id: ", record_id)
|
||
randomint = random.randint(11111111111111, 99999999999999)
|
||
now = datetime.datetime.utcnow()
|
||
otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||
paramsdata = {
|
||
"Action": "DeleteDomainRecord", "Format": "json", "Version": "2015-01-09", "SignatureMethod": "HMAC-SHA1", "Timestamp": otherStyleTime,
|
||
"SignatureVersion": "1.0", "SignatureNonce": str(randomint), "AccessKeyId": self.key,
|
||
"RecordId": record_id,
|
||
}
|
||
Signature = self.sign(self.secret, paramsdata)
|
||
paramsdata['Signature'] = Signature
|
||
req = requests.get(url=self.url, params=paramsdata)
|
||
if req.status_code != 200:
|
||
sys.exit(json.dumps({"data": public.GetMsg("DEL_RES_FAIL"), "msg": req.json()}))
|
||
print("delete_dns_record end: ", acme_txt)
|
||
|
||
|
||
class CloudxnsDns(object):
|
||
def __init__(self, key, secret, ):
|
||
self.key = key
|
||
self.secret = secret
|
||
self.APIREQUESTDATE = time.ctime()
|
||
|
||
def get_headers(self, url, parameter=''):
|
||
APIREQUESTDATE = self.APIREQUESTDATE
|
||
APIHMAC = public.Md5(self.key + url + parameter + APIREQUESTDATE + self.secret)
|
||
headers = {
|
||
"API-KEY": self.key,
|
||
"API-REQUEST-DATE": APIREQUESTDATE,
|
||
"API-HMAC": APIHMAC,
|
||
"API-FORMAT": "json"
|
||
}
|
||
return headers
|
||
|
||
def get_domain_list(self):
|
||
url = "https://www.cloudxns.net/api2/domain"
|
||
headers = self.get_headers(url)
|
||
req = requests.get(url=url, headers=headers)
|
||
req = req.json()
|
||
if req['code'] != 1:
|
||
sys.exit(json.dumps({"data": public.GetMsg("SK_ERR"), "status": False, "msg": req}))
|
||
return req
|
||
|
||
def get_domain_id(self, domain_name):
|
||
req = self.get_domain_list()
|
||
for i in req["data"]:
|
||
if domain_name.strip() == i['domain'][:-1]:
|
||
return i['id']
|
||
# print (i['domain'][:-1], "======", domain_name.strip())
|
||
sys.exit(json.dumps({"data": public.GetMsg("CLOUDXNS_ERR"), "status": False, "msg": req}))
|
||
|
||
def create_dns_record(self, domain_name, domain_dns_value):
|
||
root, _, acme_txt = extract_zone(domain_name)
|
||
print("create_dns_record,", acme_txt, domain_dns_value)
|
||
url = "https://www.cloudxns.net/api2/record"
|
||
data = {
|
||
"domain_id": int(self.get_domain_id(root)),
|
||
"host": acme_txt,
|
||
"value": domain_dns_value,
|
||
"type": "TXT",
|
||
"line_id": 1,
|
||
}
|
||
parameter = json.dumps(data)
|
||
headers = self.get_headers(url, parameter)
|
||
req = requests.post(url=url, headers=headers, data=parameter)
|
||
req = req.json()
|
||
if req['code'] != 1:
|
||
sys.exit(json.dumps({"data": public.GetMsg("SK_ERR"), "status": False, "msg": req}))
|
||
|
||
print("create_dns_record_end")
|
||
|
||
def delete_dns_record(self, domain_name, domain_dns_value):
|
||
root, _, acme_txt = extract_zone(domain_name)
|
||
print("delete_dns_record start: ", acme_txt, domain_dns_value)
|
||
url = "https://www.cloudxns.net/api2/record/{}/{}".format(self.get_record_id(root), self.get_domain_id(root))
|
||
headers = self.get_headers(url, )
|
||
req = requests.delete(url=url, headers=headers, )
|
||
req = req.json()
|
||
print("delete_dns_record_success")
|
||
|
||
def get_record_id(self, domain_name):
|
||
url = "http://www.cloudxns.net/api2/record/{}?host_id=0&offset=0&row_num=2000".format(self.get_domain_id(domain_name))
|
||
headers = self.get_headers(url, )
|
||
req = requests.get(url=url, headers=headers, )
|
||
req = req.json()
|
||
for i in req['data']:
|
||
if i['type'] == "TXT":
|
||
return i['record_id']
|
||
|
||
|
||
class Dns_com(object):
|
||
|
||
def create_dns_record(self, domain_name, domain_dns_value):
|
||
root, _, acme_txt = extract_zone(domain_name)
|
||
print("create_dns_record,", acme_txt, domain_dns_value)
|
||
result = public.ExecShell('''{} /www/server/panel/plugin/model/dns_main.py add_txt {} {}'''.format(public.get_python_bin(),acme_txt + '.' + root, domain_dns_value))
|
||
if result[0].strip() == "False":
|
||
sys.exit(json.dumps({"data": public.GetMsg("BT_DNSRES_ERR")}))
|
||
print("create_dns_record_end")
|
||
|
||
def delete_dns_record(self, domain_name, domain_dns_value):
|
||
root, _, acme_txt = extract_zone(domain_name)
|
||
print("delete_dns_record start: ", acme_txt, domain_dns_value)
|
||
public.ExecShell('''{} /www/server/panel/plugin/model/dns_main.py remove_txt {} {}'''.format(public.get_python_bin() ,acme_txt + '.' + root, domain_dns_value))
|
||
print("delete_dns_record_success")
|
||
|
||
|
||
class Dns_Manual(object):
|
||
def create_dns_record(self, domain_name, domain_dns_value):
|
||
pass
|
||
|
||
def delete_dns_record(self, domain_name, domain_dns_value):
|
||
pass
|
||
|
||
|
||
if __name__ == "__main__":#dns调用验证脚本
|
||
if len(sys.argv) == 1:
|
||
print ('''dnspod使用方法: python xxxx.py '{"dnsapi":"dns_dp","key":"ID","secret":"Token","domain_alt_names":"a.com,b.com","contact_email":"290070744@qq.com","dnssleep":10,"path":"/xxx/xxx"}' ''')
|
||
print ('''阿里dns使用方法: python xxxx.py '{"dnsapi":"dns_ali","key":"AccessKey","secret":"SecretKey","domain_alt_names":"a.com,b.com","contact_email":"290070744@qq.com","dnssleep":10,"path":"/xxx/xxx"}' ''')
|
||
sys.exit(json.dumps({"data": public.GetMsg("ADD_ARGS_TO_SHELL_ERR")}))
|
||
print(sys.argv[1])
|
||
data = json.loads(sys.argv[1])
|
||
# data = {"domain_alt_names": "a1.jiangwenhui.xyz", "dnssleep": "10", "dnsapi": "dns_dp", "secret": "xxxxxxxxxx", "contact_email": "", "key": "xxxxx","path":"/xxx/xxx"}
|
||
Manual = 0
|
||
dnsapi = data['dnsapi']
|
||
key = data['key']
|
||
secret = data['secret']
|
||
if dnsapi == "dns_dp": # 腾讯DnsPod
|
||
dns_class = DNSPodDns(DNSPOD_ID=key, DNSPOD_API_KEY=secret)
|
||
elif dnsapi == "dns_ali": # 阿里云DNS
|
||
dns_class = AliyunDns(key=key, secret=secret)
|
||
elif dnsapi == "dns_cx": # CloudXns
|
||
dns_class = CloudxnsDns(key=key, secret=secret)
|
||
elif dnsapi == "dns_bt": # model.com
|
||
dns_class = Dns_com()
|
||
elif dnsapi == "model": # 手动的
|
||
dns_class = Dns_Manual()
|
||
Manual = 1
|
||
domain_alt_names = data['domain_alt_names'].split(",")
|
||
ACME_AUTH_STATUS_WAIT_PERIOD = int(data['dnssleep']) # 等待时间/秒
|
||
contact_email = data['contact_email']
|
||
sitename = data['path'].split("/")[-1]
|
||
|
||
ssl_home_path = os.path.join("/www/server/panel/vhost/cert/", sitename)
|
||
public.ExecShell("mkdir -p {}".format(ssl_home_path))
|
||
if os.path.isfile(os.path.join(ssl_home_path, "account_key.key")): # 如果存在account_key就是续签
|
||
with open(os.path.join(ssl_home_path, "account_key.key"), 'r') as account_key_file:
|
||
account_key = account_key_file.read()
|
||
else:
|
||
account_key = None
|
||
client = ACMEclient(
|
||
account_key=account_key,
|
||
Manual=Manual, # 手动验证 0 非手动 ,1 获取手动要添加的txt记录值
|
||
contact_email=contact_email,
|
||
dns_class=dns_class,
|
||
domain_alt_names=domain_alt_names,
|
||
ACME_AUTH_STATUS_WAIT_PERIOD=ACME_AUTH_STATUS_WAIT_PERIOD, # 验证域名解析授权状态的等待时间/秒
|
||
ACME_AUTH_STATUS_MAX_CHECKS=2, # 检查授权状态的最大次数
|
||
# ACME_DIRECTORY_URL="https://acme-staging-v02.api.letsencrypt.org/directory", # 测试api地址 https://acme-staging-v02.api.letsencrypt.org/directory
|
||
ACME_DIRECTORY_URL="https://acme-v02.api.letsencrypt.org/directory", # 正式ali地址 https://acme-v02.api.letsencrypt.org/directory
|
||
ACME_REQUEST_TIMEOUT=30, # ACME请求超时/s
|
||
)
|
||
|
||
certificate = client.cert()
|
||
certificate_key = client.certificate_key
|
||
account_key = client.account_key
|
||
|
||
print("证书certificate:{}".format(certificate))
|
||
print("证书certificate_key:{}".format(certificate_key))
|
||
print("your letsencrypt.org account key is:{}".format(account_key))
|
||
|
||
home_key = os.path.join(ssl_home_path, "privkey.pem")
|
||
home_csr = os.path.join(ssl_home_path, "fullchain.pem")
|
||
with open(home_csr, 'w') as certificate_file:
|
||
certificate_file.write(certificate)
|
||
with open(home_key, 'w') as certificate_key_file:
|
||
certificate_key_file.write(certificate_key)
|
||
if not os.path.isfile(os.path.join(ssl_home_path, "account_key.key")):
|
||
with open(os.path.join(ssl_home_path, "account_key.key"), 'w') as account_key_file:
|
||
account_key_file.write(account_key)
|
||
|
||
key_install_path = data['path']
|
||
ssl_certificate = os.path.join(key_install_path, "fullchain.pem")
|
||
ssl_certificate_key = os.path.join(key_install_path, "privkey.pem")
|
||
key_install_path = key_install_path.replace("*.", '')
|
||
ssl_certificate = ssl_certificate.replace("*.", '')
|
||
ssl_certificate_key = ssl_certificate_key.replace("*.", '')
|
||
public.ExecShell("mkdir -p {}".format(key_install_path))
|
||
public.ExecShell('''/bin/cp {} {}'''.format(home_csr, ssl_certificate))
|
||
public.ExecShell('''/bin/cp {} {}'''.format(home_key, ssl_certificate_key))
|
||
print("ssl_success")
|
||
# 重载Web服务配置
|
||
if os.path.exists('/www/server/nginx/sbin/nginx'):
|
||
result = public.ExecShell('/etc/init.d/nginx reload')
|
||
if result[1].find('nginx.pid') != -1:
|
||
public.ExecShell('pkill -9 nginx && sleep 1');
|
||
public.ExecShell('/etc/init.d/nginx start');
|
||
else:
|
||
result = public.ExecShell('/etc/init.d/httpd reload')
|