339 lines
12 KiB
Python
339 lines
12 KiB
Python
|
|
import json
|
|||
|
|
|
|||
|
|
from sslModel.base import sslBase
|
|||
|
|
|
|||
|
|
import public
|
|||
|
|
|
|||
|
|
import copy
|
|||
|
|
import sys
|
|||
|
|
import hashlib
|
|||
|
|
import hmac
|
|||
|
|
import binascii
|
|||
|
|
from datetime import datetime
|
|||
|
|
import requests
|
|||
|
|
|
|||
|
|
|
|||
|
|
class main(sslBase):
|
|||
|
|
dns_provider_name = "huaweicloud"
|
|||
|
|
_type = 0
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
super().__init__()
|
|||
|
|
self.region = "cn-south-1"
|
|||
|
|
self.BasicDateFormat = "%Y%m%dT%H%M%SZ"
|
|||
|
|
self.Algorithm = "SDK-HMAC-SHA256"
|
|||
|
|
self.HeaderXDate = "X-Sdk-Date"
|
|||
|
|
self.HeaderHost = "host"
|
|||
|
|
self.HeaderAuthorization = "Authorization"
|
|||
|
|
self.HeaderContentSha256 = "x-sdk-content-sha256"
|
|||
|
|
self.url = "https://dns.cn-north-1.myhuaweicloud.com"
|
|||
|
|
|
|||
|
|
def __init_data(self, data):
|
|||
|
|
self.ak = data["AccessKey"]
|
|||
|
|
self.sk = data["SecretKey"]
|
|||
|
|
self.project_id = data["project_id"]
|
|||
|
|
|
|||
|
|
def sign_to_response(self, dns_id, method="", url="", headers=None, body=""):
|
|||
|
|
url = self.url + url
|
|||
|
|
self.__init_data(self.get_dns_data(None)[dns_id])
|
|||
|
|
if sys.version_info.major < 3:
|
|||
|
|
body = body
|
|||
|
|
from urllib import quote, unquote
|
|||
|
|
|
|||
|
|
def hmacsha256(keyByte, message):
|
|||
|
|
return hmac.new(keyByte, message, digestmod=hashlib.sha256).digest()
|
|||
|
|
|
|||
|
|
# Create a "String to Sign".
|
|||
|
|
def StringToSign(canonicalRequest, t):
|
|||
|
|
bytes = HexEncodeSHA256Hash(canonicalRequest)
|
|||
|
|
return "%s\n%s\n%s" % (self.Algorithm, datetime.strftime(t, self.BasicDateFormat), bytes)
|
|||
|
|
|
|||
|
|
else:
|
|||
|
|
body = body.encode("utf-8")
|
|||
|
|
from urllib.parse import quote, unquote
|
|||
|
|
|
|||
|
|
def hmacsha256(keyByte, message):
|
|||
|
|
return hmac.new(keyByte.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256).digest()
|
|||
|
|
|
|||
|
|
# Create a "String to Sign".
|
|||
|
|
def StringToSign(canonicalRequest, t):
|
|||
|
|
bytes = HexEncodeSHA256Hash(canonicalRequest.encode('utf-8'))
|
|||
|
|
return "%s\n%s\n%s" % (self.Algorithm, datetime.strftime(t, self.BasicDateFormat), bytes)
|
|||
|
|
|
|||
|
|
def HexEncodeSHA256Hash(data):
|
|||
|
|
sha256 = hashlib.sha256()
|
|||
|
|
sha256.update(data)
|
|||
|
|
return sha256.hexdigest()
|
|||
|
|
|
|||
|
|
def findHeader(headers, header):
|
|||
|
|
for k in headers:
|
|||
|
|
if k.lower() == header.lower():
|
|||
|
|
return headers[k]
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def CanonicalQueryString(query):
|
|||
|
|
keys = []
|
|||
|
|
for key in query:
|
|||
|
|
keys.append(key)
|
|||
|
|
keys.sort()
|
|||
|
|
a = []
|
|||
|
|
for key in keys:
|
|||
|
|
k = quote(key, safe='~')
|
|||
|
|
value = query[key]
|
|||
|
|
if type(value) is list:
|
|||
|
|
value.sort()
|
|||
|
|
for v in value:
|
|||
|
|
kv = k + "=" + quote(str(v), safe='~')
|
|||
|
|
a.append(kv)
|
|||
|
|
else:
|
|||
|
|
kv = k + "=" + quote(str(value), safe='~')
|
|||
|
|
a.append(kv)
|
|||
|
|
return '&'.join(a)
|
|||
|
|
|
|||
|
|
def SignStringToSign(stringToSign, signingKey):
|
|||
|
|
hm = hmacsha256(signingKey, stringToSign)
|
|||
|
|
return binascii.hexlify(hm).decode()
|
|||
|
|
|
|||
|
|
def AuthHeaderValue(signature, AppKey, signedHeaders):
|
|||
|
|
return "%s Access=%s, SignedHeaders=%s, Signature=%s" % (
|
|||
|
|
self.Algorithm, AppKey, ";".join(signedHeaders), signature)
|
|||
|
|
|
|||
|
|
spl = url.split("://", 1)
|
|||
|
|
scheme = 'http'
|
|||
|
|
if len(spl) > 1:
|
|||
|
|
scheme = spl[0]
|
|||
|
|
url = spl[1]
|
|||
|
|
query = {}
|
|||
|
|
spl = url.split('?', 1)
|
|||
|
|
url = spl[0]
|
|||
|
|
if len(spl) > 1:
|
|||
|
|
for kv in spl[1].split("&"):
|
|||
|
|
spl = kv.split("=", 1)
|
|||
|
|
key = spl[0]
|
|||
|
|
value = ""
|
|||
|
|
if len(spl) > 1:
|
|||
|
|
value = spl[1]
|
|||
|
|
if key != '':
|
|||
|
|
key = unquote(key)
|
|||
|
|
value = unquote(value)
|
|||
|
|
if key in query:
|
|||
|
|
query[key].append(value)
|
|||
|
|
else:
|
|||
|
|
query[key] = [value]
|
|||
|
|
spl = url.split('/', 1)
|
|||
|
|
host = spl[0]
|
|||
|
|
if len(spl) > 1:
|
|||
|
|
url = '/' + spl[1]
|
|||
|
|
else:
|
|||
|
|
url = '/'
|
|||
|
|
if headers is None:
|
|||
|
|
headers = {"content-type": "application/json"}
|
|||
|
|
else:
|
|||
|
|
headers = copy.deepcopy(headers)
|
|||
|
|
|
|||
|
|
headerTime = findHeader(headers, self.HeaderXDate)
|
|||
|
|
|
|||
|
|
if headerTime is None:
|
|||
|
|
t = datetime.utcnow()
|
|||
|
|
headers[self.HeaderXDate] = datetime.strftime(t, self.BasicDateFormat)
|
|||
|
|
else:
|
|||
|
|
t = datetime.strptime(headerTime, self.BasicDateFormat)
|
|||
|
|
|
|||
|
|
haveHost = False
|
|||
|
|
for key in headers:
|
|||
|
|
if key.lower() == 'host':
|
|||
|
|
haveHost = True
|
|||
|
|
break
|
|||
|
|
if not haveHost:
|
|||
|
|
headers["host"] = host
|
|||
|
|
signedHeaders = []
|
|||
|
|
for key in headers:
|
|||
|
|
signedHeaders.append(key.lower())
|
|||
|
|
signedHeaders.sort()
|
|||
|
|
a = []
|
|||
|
|
__headers = {}
|
|||
|
|
for key in headers:
|
|||
|
|
keyEncoded = key.lower()
|
|||
|
|
value = headers[key]
|
|||
|
|
valueEncoded = value.strip()
|
|||
|
|
__headers[keyEncoded] = valueEncoded
|
|||
|
|
if sys.version_info.major == 3:
|
|||
|
|
headers[key] = valueEncoded.encode("utf-8").decode('iso-8859-1')
|
|||
|
|
for key in signedHeaders:
|
|||
|
|
a.append(key + ":" + __headers[key])
|
|||
|
|
canonicalHeaders = '\n'.join(a) + "\n"
|
|||
|
|
|
|||
|
|
hexencode = findHeader(headers, self.HeaderContentSha256)
|
|||
|
|
if hexencode is None:
|
|||
|
|
hexencode = HexEncodeSHA256Hash(body)
|
|||
|
|
pattens = unquote(url).split('/')
|
|||
|
|
CanonicalURI = []
|
|||
|
|
for v in pattens:
|
|||
|
|
CanonicalURI.append(quote(v, safe="~"))
|
|||
|
|
CanonicalURL = "/".join(CanonicalURI)
|
|||
|
|
if CanonicalURL[-1] != '/':
|
|||
|
|
CanonicalURL = CanonicalURL + "/" # always end with /
|
|||
|
|
|
|||
|
|
canonicalRequest = "%s\n%s\n%s\n%s\n%s\n%s" % (method.upper(), CanonicalURL, CanonicalQueryString(query),
|
|||
|
|
canonicalHeaders, ";".join(signedHeaders), hexencode)
|
|||
|
|
|
|||
|
|
stringToSign = StringToSign(canonicalRequest, t)
|
|||
|
|
signature = SignStringToSign(stringToSign, self.sk)
|
|||
|
|
authValue = AuthHeaderValue(signature, self.ak, signedHeaders)
|
|||
|
|
headers[self.HeaderAuthorization] = authValue
|
|||
|
|
headers["content-length"] = str(len(body))
|
|||
|
|
queryString = CanonicalQueryString(query)
|
|||
|
|
if queryString != "":
|
|||
|
|
url = url + "?" + queryString
|
|||
|
|
|
|||
|
|
res = requests.request(method, scheme + "://" + host + url, headers=headers, data=body)
|
|||
|
|
return res
|
|||
|
|
|
|||
|
|
def create_dns_record(self, get):
|
|||
|
|
domain_name = get.domain_name
|
|||
|
|
domain_dns_value = get.domain_dns_value
|
|||
|
|
record_type = 'TXT'
|
|||
|
|
if 'record_type' in get:
|
|||
|
|
record_type = get.record_type
|
|||
|
|
if record_type == 'TXT':
|
|||
|
|
domain_dns_value = "\"{}\"".format(domain_dns_value)
|
|||
|
|
|
|||
|
|
root_domain, sub_domain, _ = self.extract_zone(domain_name)
|
|||
|
|
if sub_domain == "@":
|
|||
|
|
domain_name = root_domain
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
zone_dic = self.get_zoneid_dict(get.dns_id)
|
|||
|
|
zone_id = zone_dic.get(root_domain)
|
|||
|
|
body = json.dumps({
|
|||
|
|
"name": domain_name,
|
|||
|
|
"type": record_type,
|
|||
|
|
"records": [domain_dns_value],
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
res = self.sign_to_response(get.dns_id, "POST", "/v2/zones/{}/recordsets".format(zone_id), body=body)
|
|||
|
|
if res.status_code != 202:
|
|||
|
|
return public.returnMsg(False, self.get_error(res.text))
|
|||
|
|
|
|||
|
|
return public.returnMsg(True, '添加成功')
|
|||
|
|
except Exception as e:
|
|||
|
|
return public.returnMsg(False, self.get_error(str(e)))
|
|||
|
|
|
|||
|
|
def delete_dns_record(self, get):
|
|||
|
|
domain_name = get.domain_name
|
|||
|
|
RecordId = get.RecordId
|
|||
|
|
root_domain, _, sub_domain = self.extract_zone(domain_name)
|
|||
|
|
|
|||
|
|
zone_dic = self.get_zoneid_dict(get.dns_id)
|
|||
|
|
zone_id = zone_dic[root_domain]
|
|||
|
|
try:
|
|||
|
|
res = self.sign_to_response(get.dns_id, "DELETE", "/v2/zones/{}/recordsets/{}".format(zone_id, RecordId))
|
|||
|
|
if res.status_code != 202:
|
|||
|
|
return public.returnMsg(False, self.get_error(res.text))
|
|||
|
|
return public.returnMsg(True, '删除成功')
|
|||
|
|
except Exception as e:
|
|||
|
|
return public.returnMsg(False, self.get_error(str(e)))
|
|||
|
|
|
|||
|
|
def get_zoneid_dict(self, dns_id):
|
|||
|
|
"""
|
|||
|
|
获取所有域名对应id
|
|||
|
|
"""
|
|||
|
|
res = self.sign_to_response(dns_id, "GET", "/v2/zones")
|
|||
|
|
if res.status_code != 200:
|
|||
|
|
return {}
|
|||
|
|
response = res.json()
|
|||
|
|
data = {i["name"][:-1]: i["id"] for i in response["zones"]}
|
|||
|
|
|
|||
|
|
return data
|
|||
|
|
|
|||
|
|
def get_dns_record(self, get):
|
|||
|
|
domain_name = get.domain_name
|
|||
|
|
root_domain, _, sub_domain = self.extract_zone(domain_name)
|
|||
|
|
data = {}
|
|||
|
|
try:
|
|||
|
|
zone_dic = self.get_zoneid_dict(get.dns_id)
|
|||
|
|
limit = 100
|
|||
|
|
offset = 0
|
|||
|
|
zone_id = zone_dic[root_domain]
|
|||
|
|
res = self.sign_to_response(get.dns_id, "GET",
|
|||
|
|
"/v2/zones/{}/recordsets?limit={}&offset={}".format(zone_id, limit, offset))
|
|||
|
|
if res.status_code != 200:
|
|||
|
|
return {}
|
|||
|
|
response = res.json()
|
|||
|
|
|
|||
|
|
data["list"] = [
|
|||
|
|
{
|
|||
|
|
"RecordId": i["id"],
|
|||
|
|
"name": i["name"][:-1],
|
|||
|
|
"value": '\r\n'.join(i["records"]) if i["type"] != "TXT" else '\r\n'.join([j.replace('"', '') for j in i["records"]]),
|
|||
|
|
"line": "默认",
|
|||
|
|
"ttl": i["ttl"],
|
|||
|
|
"type": i["type"],
|
|||
|
|
"status": "启用"if i["status"] == "ACTIVE" else "暂停" if i["status"] == "DISABLE" else i["status"],
|
|||
|
|
"mx": "",
|
|||
|
|
"updated_on": i["update_at"],
|
|||
|
|
"remark": i.get("description") or "",
|
|||
|
|
}
|
|||
|
|
for i in response["recordsets"]
|
|||
|
|
]
|
|||
|
|
data["info"] = {
|
|||
|
|
'record_total': response['metadata']['total_count']
|
|||
|
|
}
|
|||
|
|
except Exception as e:
|
|||
|
|
pass
|
|||
|
|
self.set_record_data({root_domain: data})
|
|||
|
|
return data
|
|||
|
|
|
|||
|
|
def update_dns_record(self, get):
|
|||
|
|
domain_name = get.domain_name
|
|||
|
|
RecordId = get.RecordId
|
|||
|
|
record_type = get.record_type
|
|||
|
|
domain_dns_value = get.domain_dns_value
|
|||
|
|
root_domain, _, sub_domain = self.extract_zone(domain_name)
|
|||
|
|
|
|||
|
|
if sub_domain == "@":
|
|||
|
|
domain_name = root_domain
|
|||
|
|
if record_type == 'TXT':
|
|||
|
|
domain_dns_value = "\"{}\"".format(domain_dns_value)
|
|||
|
|
|
|||
|
|
zone_dic = self.get_zoneid_dict(get.dns_id)
|
|||
|
|
zone_id = zone_dic[root_domain]
|
|||
|
|
try:
|
|||
|
|
body = json.dumps({
|
|||
|
|
"name": domain_name,
|
|||
|
|
"type": record_type,
|
|||
|
|
"records": [domain_dns_value],
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
res = self.sign_to_response(get.dns_id, "PUT", "/v2/zones/{}/recordsets/{}".format(zone_id, RecordId), body=body)
|
|||
|
|
if res.status_code != 202:
|
|||
|
|
return public.returnMsg(False, self.get_error(res.text))
|
|||
|
|
|
|||
|
|
return public.returnMsg(True, '修改成功')
|
|||
|
|
except Exception as e:
|
|||
|
|
return public.returnMsg(False, self.get_error(str(e)))
|
|||
|
|
|
|||
|
|
def get_error(self, error):
|
|||
|
|
if "DNS.0317" in error:
|
|||
|
|
return "此记录为系统默认域名记录值,不能删除。"
|
|||
|
|
elif "DNS.0318" in error:
|
|||
|
|
return "此记录为系统默认域名记录值,不能更新。"
|
|||
|
|
elif "DNS.0321" in error:
|
|||
|
|
return "子域名级别超过限制。"
|
|||
|
|
elif "DNS.0324" in error:
|
|||
|
|
return "系统默认解析记录不能操作。"
|
|||
|
|
elif "DNS.0308" in error:
|
|||
|
|
return "记录集类型非法。"
|
|||
|
|
elif "DNS.0307" in error:
|
|||
|
|
return "记录集的值非法。"
|
|||
|
|
elif "DNS.0302" in error:
|
|||
|
|
return "这个华为云账户下面不存在这个域名,请检查dns接口配置后重试"
|
|||
|
|
elif "APIGW.0301" in error:
|
|||
|
|
return "dns账号信息有误"
|
|||
|
|
elif "DNS.0312" in error:
|
|||
|
|
return "存在同名的主机记录"
|
|||
|
|
elif "DNS.0313" in error:
|
|||
|
|
return "不存在这条记录"
|
|||
|
|
else:
|
|||
|
|
return error
|