Files
yakpanel-core/class/sewer_Usage.py

1008 lines
44 KiB
Python
Raw Normal View History

2026-04-07 02:04:22 +05:30
#: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('btpip install requests')
import requests
try:
import OpenSSL
except:
os.system('btpip 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": "dns", "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"] == "dns-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 dns 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/dns/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/dns/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": # dns.com
dns_class = Dns_com()
elif dnsapi == "dns": # 手动的
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')