├── domain.ini ├── au.sh ├── python-version ├── godaddydns.py ├── txydns.py ├── alydns.py └── hwydns.py ├── README.md └── php-version ├── godaddydns.php ├── alydns.php └── txydns.php /domain.ini: -------------------------------------------------------------------------------- 1 | net 2 | com 3 | com.cn 4 | cn 5 | org 6 | co.jp 7 | com.tw 8 | gov 9 | net.cn 10 | io 11 | top 12 | me 13 | int 14 | edu 15 | link 16 | uk 17 | hk 18 | shop 19 | -------------------------------------------------------------------------------- /au.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #ywdblog@gmail.com 欢迎关注我的书《深入浅出HTTPS:从原理到实战》 4 | 5 | ###### 根据自己的情况修改 Begin ############## 6 | 7 | #PHP 命令行路径,如果有需要可以修改 8 | phpcmd="/usr/bin/php" 9 | #Python 命令行路径,如果有需要可以修改 10 | pythoncmd="/usr/bin/python" 11 | 12 | #填写阿里云的AccessKey ID及AccessKey Secret 13 | #如何申请见https://help.aliyun.com/knowledge_detail/38738.html 14 | ALY_KEY="" 15 | ALY_TOKEN="" 16 | 17 | #填写腾讯云的SecretId及SecretKey 18 | #如何申请见https://console.cloud.tencent.com/cam/capi 19 | TXY_KEY="" 20 | TXY_TOKEN="" 21 | 22 | #填写华为云的 Access Key Id 及 Secret Access Key 23 | #如何申请见https://support.huaweicloud.com/devg-apisign/api-sign-provide.html 24 | HWY_KEY="" 25 | HWY_TOKEN="" 26 | 27 | #GoDaddy的SecretId及SecretKey 28 | #如何申请见https://developer.godaddy.com/getstarted 29 | GODADDY_KEY="" 30 | GODADDY_TOKEN="" 31 | 32 | ################ END ############## 33 | 34 | PATH=$(cd `dirname $0`; pwd) 35 | 36 | # 命令行参数 37 | # 第一个参数:使用什么语言环境 38 | # 第二个参数:使用那个 DNS 的 API 39 | # 第三个参数:add or clean 40 | plang=$1 #python or php 41 | pdns=$2 #aly, txy, hwy, godaddy 42 | paction=$3 #add or clean 43 | 44 | #内部变量 45 | cmd="" 46 | key="" 47 | token="" 48 | 49 | if [[ "$paction" != "clean" ]]; then 50 | paction="add" 51 | fi 52 | 53 | case $plang in 54 | "php") 55 | 56 | cmd=$phpcmd 57 | if [[ "$pdns" == "aly" ]]; then 58 | dnsapi=$PATH"/php-version/alydns.php" 59 | key=$ALY_KEY 60 | token=$ALY_TOKEN 61 | elif [[ "$pdns" == "txy" ]]; then 62 | dnsapi="$PATH/php-version/txydns.php" 63 | key=$TXY_KEY 64 | token=$TXY_TOKEN 65 | elif [[ "$pdns" == "hwy" ]]; then 66 | # TODO 67 | dnsapi="" 68 | key=$HWY_KEY 69 | token=$HWY_TOKEN 70 | exit 71 | elif [[ "$pdns" == "godaddy" ]] ;then 72 | dnsapi="$PATH/php-version/godaddydns.php" 73 | key=$GODADDY_KEY 74 | token=$GODADDY_TOKEN 75 | else 76 | echo "Not support this dns services" 77 | exit 78 | fi 79 | ;; 80 | 81 | "python") 82 | 83 | cmd=$pythoncmd 84 | if [[ "$pdns" == "aly" ]]; then 85 | dnsapi=$PATH"/python-version/alydns.py" 86 | key=$ALY_KEY 87 | token=$ALY_TOKEN 88 | elif [[ "$pdns" == "txy" ]] ;then 89 | dnsapi=$PATH"/python-version/txydns.py" 90 | key=$TXY_KEY 91 | token=$TXY_TOKEN 92 | elif [[ "$pdns" == "txy" ]]; then 93 | dnsapi=$PATH"/python-version/txydns.py" 94 | key=$TXY_KEY 95 | token=$TXY_TOKEN 96 | elif [[ "$pdns" == "hwy" ]]; then 97 | dnsapi="$PATH/python-version/hwydns.py" 98 | key=$HWY_KEY 99 | token=$HWY_TOKEN 100 | elif [[ "$pdns" == "godaddy" ]] ;then 101 | dnsapi=$PATH"/python-version/godaddydns.py" 102 | key=$GODADDY_KEY 103 | token=$GODADDY_TOKEN 104 | else 105 | echo "Not support this dns services" 106 | exit 107 | fi 108 | ;; 109 | 110 | esac 111 | 112 | $cmd $dnsapi $paction $CERTBOT_DOMAIN "_acme-challenge" $CERTBOT_VALIDATION $key $token >>"/var/log/certd.log" 113 | 114 | if [[ "$paction" == "add" ]]; then 115 | # DNS TXT 记录刷新时间 116 | /bin/sleep 20 117 | fi 118 | 119 | -------------------------------------------------------------------------------- /python-version/godaddydns.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | import json 4 | import sys 5 | import os 6 | 7 | class GodaddyDns: 8 | def __init__(self, access_key_id, access_key_secret, domain_name): 9 | self.access_key_id = access_key_id 10 | self.access_key_secret = access_key_secret 11 | self.domain_name = domain_name 12 | 13 | @staticmethod 14 | def getDomain(domain): 15 | domain_parts = domain.split('.') 16 | if len(domain_parts) > 2: 17 | dirpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 18 | domainfile = dirpath + "/domain.ini" 19 | domainarr = [] 20 | with open(domainfile) as f: 21 | for line in f: 22 | val = line.strip() 23 | domainarr.append(val) 24 | 25 | rootdomain = '.'.join(domain_parts[-(2 if domain_parts[-1] in domainarr else 3): ]) 26 | selfdomain = domain.split(rootdomain)[0] 27 | return (selfdomain[0:len(selfdomain)-1], rootdomain) 28 | return ("", domain) 29 | 30 | def curl(self, url, data, method): 31 | if sys.version_info[0] < 3: 32 | print ("s") 33 | import urllib2 34 | from urllib2 import URLError, HTTPError 35 | httpdata = json.dumps(data).encode('utf-8') 36 | req = urllib2.Request(url=url, data=httpdata) 37 | req.get_method = lambda: method 38 | req.add_header('accept', 'application/json') 39 | req.add_header('Content-Type', 'application/json') 40 | key = "sso-key " + self.access_key_id + ':' + self.access_key_secret 41 | req.add_header('authorization', key) 42 | try: 43 | with urllib2.urlopen(req) as res: 44 | code = res.getcode() 45 | print (res.info()) 46 | resinfo = res.read().decode('utf-8') 47 | result = True 48 | if code != 200: 49 | result = False 50 | return (result, resinfo) 51 | except AttributeError as e: 52 | #python2 处理 PATCH HTTP 方法的一个Bug,不影响结果 53 | return (True,'') 54 | except (HTTPError, URLError) as e: 55 | return (False, str(e)) 56 | 57 | else : 58 | import urllib.request 59 | from urllib.error import URLError, HTTPError 60 | 61 | httpdata = json.dumps(data).encode('utf-8') 62 | 63 | req = urllib.request.Request(url=url, data=httpdata, method=method) 64 | req.add_header('accept', 'application/json') 65 | req.add_header('Content-Type', 'application/json') 66 | key = "sso-key " + self.access_key_id + ':' + self.access_key_secret 67 | 68 | req.add_header('authorization', key) 69 | try: 70 | with urllib.request.urlopen(req) as res: 71 | code = res.getcode() 72 | # print (res.info()) 73 | resinfo = res.read().decode('utf-8') 74 | result = True 75 | if code != 200: 76 | result = False 77 | return (result, resinfo) 78 | except (HTTPError, URLError) as e: 79 | return (False, str(e)) 80 | 81 | def CreateDNSRecord(self, name, value, recordType='TXT'): 82 | url = "https://api.godaddy.com/v1/domains/" + \ 83 | self.domain_name + "/records" 84 | data = [{"data": value, "name": name, "ttl": 3600, "type": recordType}] 85 | return self.curl(url, data, "PATCH") 86 | 87 | def GetDNSRecord(self, name, recordType='TXT'): 88 | url = "https://api.godaddy.com/v1/domains/" + \ 89 | self.domain_name + "/records/" + recordType + "/" + name 90 | return self.curl(url, {}, "GET") 91 | 92 | def DeleteDNSRecord(self, name, recordType='TXT'): 93 | ''' 94 | Godaddy DNS 没有提供删除DSN记录的API 95 | ''' 96 | return True 97 | 98 | file_name, cmd, certbot_domain, acme_challenge, certbot_validation, ACCESS_KEY_ID, ACCESS_KEY_SECRET = sys.argv 99 | 100 | certbot_domain = GodaddyDns.getDomain(certbot_domain) 101 | if certbot_domain[0] == "": 102 | selfdomain = acme_challenge 103 | else: 104 | selfdomain = acme_challenge + "." + certbot_domain[0] 105 | 106 | domain = GodaddyDns(ACCESS_KEY_ID, ACCESS_KEY_SECRET, certbot_domain[1]) 107 | # print (domain.GetDNSRecord(selfdomain)) 108 | 109 | if cmd == "add": 110 | print(domain.CreateDNSRecord(selfdomain, certbot_validation)) 111 | -------------------------------------------------------------------------------- /python-version/txydns.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | import hmac 4 | import time 5 | import random 6 | import hashlib 7 | import json 8 | import urllib 9 | import base64 10 | import os 11 | 12 | pv = "python2" 13 | if sys.version_info[0] < 3: 14 | from urllib import quote 15 | from urllib import urlencode 16 | else: 17 | from urllib.parse import quote 18 | from urllib.parse import urlencode 19 | from urllib import request 20 | pv = "python3" 21 | 22 | class Client(object): 23 | def __init__(self, secret_id, secret_key, host, uri, **params): 24 | self.secret_id = secret_id 25 | self.secret_key = secret_key 26 | self.host = host 27 | self.uri = uri 28 | self.params = params 29 | 30 | def public_params(self): 31 | params = { 32 | 'Nonce': random.randint(1, 9999), 33 | 'SecretId': self.secret_id, 34 | 'SignatureMethod': 'HmacSHA1', 35 | 'Timestamp': int(time.time()), 36 | } 37 | params.update(self.params) 38 | 39 | return params 40 | 41 | def sign(self, params, method='GET'): 42 | params = params.copy() 43 | params.update(self.public_params()) 44 | p = {} 45 | for k in params: 46 | if method == 'POST' and str(params[k])[0:1] == '@': 47 | continue 48 | p[k.replace('_', '.')] = params[k] 49 | ps = '&'.join('%s=%s' % (k, p[k]) for k in sorted(p)) 50 | 51 | msg = '%s%s%s?%s' % (method.upper(), self.host, self.uri, ps) 52 | 53 | if pv == "python2": 54 | h = hmac.new(self.secret_key, msg, digestmod=hashlib.sha1) 55 | signature = base64.encodestring(h.digest()).strip() 56 | else: 57 | h = hmac.new(self.secret_key.encode('utf-8'), 58 | msg.encode('utf-8'), digestmod=hashlib.sha1) 59 | signature = base64.encodebytes(h.digest()).strip() 60 | 61 | ''' 62 | hashed = hmac.new(self.secret_key, msg, hashlib.sha1) 63 | base64 = binascii.b2a_base64(hashed.digest())[:-1] 64 | ''' 65 | params['Signature'] = signature 66 | return params 67 | 68 | def send(self, params, method='GET'): 69 | params = self.sign(params, method) 70 | req_host = 'https://{}{}'.format(self.host, self.uri) 71 | url = req_host + "?" + urlencode(params) 72 | 73 | if pv == "python2": 74 | f = urllib.urlopen(url) 75 | result = f.read().decode('utf-8') 76 | return json.loads(result) 77 | else: 78 | req = request.Request(url) 79 | with request.urlopen(req) as f: 80 | result = f.read().decode('utf-8') 81 | return json.loads(result) 82 | 83 | class Cns: 84 | def __init__(self, secret_id, secret_key): 85 | host, uri = 'cns.api.qcloud.com', '/v2/index.php' 86 | self.client = Client(secret_id, secret_key, host, uri) 87 | 88 | def list(self, domain, subDomain): 89 | body = { 90 | 'Action': 'RecordList', 91 | 'domain': domain, 92 | 'subDomain': subDomain 93 | } 94 | 95 | return self.client.send(body) 96 | 97 | @staticmethod 98 | def getDomain(domain): 99 | domain_parts = domain.split('.') 100 | 101 | if len(domain_parts) > 2: 102 | dirpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 103 | domainfile = dirpath + "/domain.ini" 104 | domainarr = [] 105 | with open(domainfile) as f: 106 | for line in f: 107 | val = line.strip() 108 | domainarr.append(val) 109 | 110 | rootdomain = '.'.join(domain_parts[-(2 if domain_parts[-1] in domainarr else 3): ]) 111 | selfdomain = domain.split(rootdomain)[0] 112 | return (selfdomain[0:len(selfdomain)-1], rootdomain) 113 | return ("", domain) 114 | 115 | def create(self, domain, name, _type, value): 116 | body = { 117 | 'Action': 'RecordCreate', 118 | 'domain': domain, 119 | 'subDomain': name, 120 | 'recordType': _type, 121 | 'recordLine': '默认', 122 | 'value': value 123 | } 124 | return self.client.send(body) 125 | 126 | def delete(self, domain, _id): 127 | body = { 128 | 'Action': 'RecordDelete', 129 | 'domain': domain, 130 | 'recordId': _id 131 | } 132 | 133 | return self.client.send(body) 134 | 135 | 136 | if __name__ == '__main__': 137 | # Create your secret_id and secret_key at https://console.cloud.tencent.com/cam/capi 138 | 139 | _, option, domain, name, value, secret_id, secret_key = sys.argv # pylint: disable=all 140 | 141 | domain = Cns.getDomain(domain) 142 | if domain[0] == "": 143 | selfdomain = name 144 | else: 145 | selfdomain = name + "." + domain[0] 146 | 147 | cns = Cns(secret_id, secret_key) 148 | if option == 'add': 149 | result = (cns.create(domain[1], selfdomain, 'TXT', value)) 150 | elif option == 'clean': 151 | for record in cns.list(domain[1], selfdomain)['data']['records']: 152 | #print (record['name'],record['id'] ) 153 | result = (cns.delete(domain[1], record['id'])) 154 | #print (result["message"]) 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu) [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) 2 | 3 | ### 功能 4 | 5 | 使用 certbot 工具,为不能自动给 letencrypt 通配符证书自动续期(renew)而烦恼吗?这个工具能够帮忙! 6 | 7 | 不管是申请还是续期,只要是通配符证书,只能采用 dns-01 的方式校验申请者的域名,也就是说 certbot 操作者必须手动添加 DNS TXT 记录。 8 | 9 | 如果你编写一个 Cron (比如 1 1 */1 * * root certbot-auto renew),自动 renew 通配符证书,此时 Cron 无法自动添加 TXT 记录,这样 renew 操作就会失败,如何解决? 10 | 11 | certbot 提供了一个 hook,可以编写一个 Shell 脚本,让脚本调用 DNS 服务商的 API 接口,动态添加 TXT 记录,这样就无需人工干预了。 12 | 13 | 在 certbot 官方提供的插件和 hook 例子中,都没有针对国内 DNS 服务器的样例,所以我编写了这样一个工具,目前支持**阿里云 DNS**、**腾讯云 DNS**、**华为云 NDS**、**GoDaddy**(certbot 官方没有对应的插件)。 14 | 15 | **近期合并了几个PR,没有测试,有问题反馈给我,谢谢!** 16 | 17 | ### 自动申请通配符证书 18 | 19 | 1:下载 20 | 21 | ``` 22 | $ git clone https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au 23 | 24 | $ cd certbot-letencrypt-wildcardcertificates-alydns-au 25 | 26 | $ chmod 0777 au.sh 27 | ``` 28 | 29 | 2:配置 30 | 31 | (1)domain.ini 32 | 33 | 如果domain.ini文件没有你的根域名,请自行添加。 34 | 35 | (2)DNS API 密钥: 36 | 37 | 这个 API 密钥什么意思呢?由于需要通过 API 操作阿里云 DNS, 腾讯云 DNS 的记录,所以需要去域名服务商哪儿获取 API 密钥,然后配置在 au.sh 文件中: 38 | 39 | - ALY_KEY 和 ALY_TOKEN:阿里云 [API key 和 Secrec 官方申请文档](https://help.aliyun.com/knowledge_detail/38738.html)。 40 | - TXY_KEY 和 TXY_TOKEN:腾讯云 [API 密钥官方申请文档](https://console.cloud.tencent.com/cam/capi)。 41 | - HWY_KEY 和 HWY_TOKEN: 华为云 [API 密钥官方申请文档](https://support.huaweicloud.com/devg-apisign/api-sign-provide.html) 42 | - GODADDY_KEY 和 GODADDY_TOKEN:GoDaddy [API 密钥官方申请文档](https://developer.godaddy.com/getstarted)。 43 | 44 | (3)选择运行环境 45 | 46 | 目前该工具支持五种运行环境和场景,通过 hook 文件和参数来调用: 47 | 48 | - PHP(>4以上版本均可) 49 | - au.sh php aly add/clean:PHP操作阿里云DNS,增加/清空DNS。 50 | - au.sh php txy add/clean:PHP操作腾讯云DNS,增加/清空DNS。 51 | - au.sh php godaddy add/clean:PHP操作GoDaddy DNS,增加/清空DNS。 52 | - Python(支持2.7和3.7,无需任何第三方库) 53 | - au.sh python aly add/clean:Python操作阿里云DNS,增加/清空DNS。 54 | - au.sh python txy add/clean:Python操作腾讯云DNS,增加/清空DNS。 55 | - au.sh python hwy add/clean:Python操作华为云DNS,增加/清空DNS。 56 | - au.sh python godaddy add/clean:Python操作GoDaddy DNS,增加/清空DNS。 57 | 58 | 根据自己服务器环境和域名服务商选择任意一个 hook shell(包含相应参数),具体使用见下面。 59 | 60 | 3:申请证书 61 | 62 | 测试是否有错误: 63 | 64 | ``` 65 | $ ./certbot-auto certonly -d *.example.com --manual --preferred-challenges dns --dry-run --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean" 66 | ``` 67 | 68 | **Debug:** 操作 DNS API 可能会遇到一系列问题,比如 API token 权限不足,遇到相关问题,可以查看 /var/log/certd.log。 69 | 70 | **重要解释:** --manual-auth-hook 和 --manual-cleanup-hook 有三个参数: 71 | 72 | - 第一个代表你要选择那种语言(php/python) 73 | - 第二个参数代表你的DNS厂商(aly/txy) 74 | - 第三个参数是固定的(--manual-auth-hook中用add,--manual-clean-hook中用clean) 75 | 76 | 比如你要选择Python环境,可以将 --manual-auth-hook 输入修改为 "/脚本目录/au.sh python aly add",--manual-cleanup-hook 输入修改为 "/脚本目录/au.sh python aly clean" 77 | 78 | 确认无误后,实际运行(去除 --dry-run 参数): 79 | 80 | ``` 81 | # 实际申请 82 | $ ./certbot-auto certonly -d *.example.com --manual --preferred-challenges dns --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean" 83 | ``` 84 | 85 | 参数解释(可以不用关心): 86 | 87 | - certonly:表示采用验证模式,只会获取证书,不会为web服务器配置证书 88 | - --manual:表示插件 89 | - --preferred-challenges dns:表示采用DNS验证申请者合法性(是不是域名的管理者) 90 | - --dry-run:在实际申请/更新证书前进行测试,强烈推荐 91 | - -d:表示需要为那个域名申请证书,可以有多个。 92 | - --manual-auth-hook:在执行命令的时候调用一个 hook 文件 93 | - --manual-cleanup-hook:清除 DNS 添加的记录 94 | 95 | 如果你想为多个域名申请通配符证书(合并在一张证书中,也叫做 **SAN 通配符证书**),直接输入多个 -d 参数即可,比如: 96 | 97 | ``` 98 | $ ./certbot-auto certonly -d *.example.com -d *.example.org -d www.example.cn --manual --preferred-challenges dns --dry-run --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean" 99 | ``` 100 | 101 | ### 续期证书 102 | 103 | 1:对机器上所有证书 renew 104 | 105 | ``` 106 | $ ./certbot-auto renew --manual --preferred-challenges dns --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean" 107 | ``` 108 | 109 | 2:对某一张证书进行续期 110 | 111 | 先看看机器上有多少证书: 112 | 113 | ``` 114 | $ ./certbot-auto certificates 115 | ``` 116 | 117 | 可以看到很多证书,如图: 118 | 119 | ![管理证书](https://notes.newyingyong.cn/static/image/2018/2018-07-17-certbot-managercert.png) 120 | 121 | 记住证书名,比如 simplehttps.com,然后运行下列命令 renew: 122 | 123 | ``` 124 | $ ./certbot-auto renew --cert-name simplehttps.com --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean" 125 | ``` 126 | 127 | ### 加入 crontab 128 | 129 | 编辑文件 /etc/crontab : 130 | 131 | ``` 132 | #证书有效期<30天才会renew,所以crontab可以配置为1天或1周 133 | 1 1 */1 * * root certbot-auto renew --manual --preferred-challenges dns --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean" 134 | ``` 135 | 136 | 如果是certbot 机器和运行web服务(比如 nginx,apache)的机器是同一台,那么成功renew证书后,可以启动对应的web 服务器,运行下列crontab : 137 | 138 | ``` 139 | # 注意只有成功renew证书,才会重新启动nginx 140 | 1 1 */1 * * root certbot-auto renew --manual --preferred-challenges dns --deploy-hook "service nginx restart" --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean" 141 | ``` 142 | 143 | 144 | **注意:只有单机建议这样运行,如果要将证书同步到多台web服务器,需要有别的方案,目前在开发中,主要目的就是同步证书到集群服务器上** 145 | 146 | ### 贡献 147 | 148 | - 阿里云 python 版 @Duke-Wu 149 | - 腾讯云 python 版 @akgnah 150 | - 华为云 python 版 @jinhucheung 151 | - GoDaddy PHP 版 wlx_1990 (2019-01-11) 152 | 153 | ### 其他 154 | 155 | - 可以关注公众号(虞大胆的叽叽喳喳,yudadanwx),了解更多密码学&HTTPS协议知识。 156 | - 我写了一本书[《深入浅出HTTPS:从原理到实战》](https://mp.weixin.qq.com/s/80oQhzmP9BTimoReo1oMeQ)了解更多关于HTTPS方面的知识。**如果你觉得本书还可以,希望能在豆瓣做个点评,以便让更多人了解,非常感谢。豆瓣评论地址:[https://book.douban.com/subject/30250772/](https://book.douban.com/subject/30250772/)** 157 | 158 | 公众号二维码: 159 | 160 | ![公众号:虞大胆的叽叽喳喳,yudadanwx](https://notes.newyingyong.cn/static/image/wxgzh/qrcode_258.jpg) 161 | 162 | 《深入浅出HTTPS:从原理到实战》二维码: 163 | 164 | ![深入浅出HTTPS:从原理到实战](https://notes.newyingyong.cn/static/image/httpsbook/httpsbook-small-jd.jpg) 165 | -------------------------------------------------------------------------------- /php-version/godaddydns.php: -------------------------------------------------------------------------------- 1 | getDomains(); 30 | $data_obj = json_decode($data['result']); 31 | $code = $data['httpCode']; 32 | test :php godaddydns.php add yudadan.com v k 33 | */ 34 | 35 | $obj = new GodaddyDns($argv[5], $argv[6], $domainarray[1]); 36 | 37 | switch ($argv[1]) { 38 | case "clean": 39 | //api 不包含该操作 40 | break; 41 | 42 | case "add": 43 | //$data = $obj->GetDNSRecord($domainarray[1], $selfdomain); 44 | //$data_obj = json_decode($data['result']); 45 | //$count = count($data_obj); 46 | //if ($count > 0) { 47 | 48 | // $data = $obj->UpdateDNSRecord($domainarray[1], $selfdomain, $argv[4]); 49 | //} else { 50 | $data = $obj->CreateDNSRecord($domainarray[1], $selfdomain, $argv[4]); 51 | //} 52 | if ($data["httpCode"] != 200) { 53 | $message = json_decode($data["result"], true); 54 | echo "域名处理失败-".$message["message"]; 55 | exit; 56 | } 57 | break; 58 | } 59 | 60 | echo "域名 API 调用结束\n"; 61 | 62 | // $r = $obj->UpdateDNSRecord($domain, $selfdomain, $argv[3], $type); 63 | 64 | class GodaddyDns 65 | { 66 | private $accessKeyId = null; 67 | private $accessSecrec = null; 68 | private $DomainName = null; 69 | private $Host = ""; 70 | private $Path = ""; 71 | 72 | public function __construct($accessKeyId, $accessSecrec, $domain = "") 73 | { 74 | $this->accessKeyId = $accessKeyId; 75 | $this->accessSecrec = $accessSecrec; 76 | $this->DomainName = $domain; 77 | } 78 | /* 79 | 根据域名返回主机名和二级域名 80 | */ 81 | 82 | public static function getDomain($domain) 83 | { 84 | 85 | //常见根域名 【https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains】 86 | // 【http://www.seobythesea.com/2006/01/googles-most-popular-and-least-popular-top-level-domains/】 87 | 88 | global $domainfile; 89 | $tmp = file($domainfile); 90 | $arr = array(); 91 | foreach ($tmp as $k=>$v) { 92 | $v = trim($v); 93 | if ($v!="") 94 | $arr[]= "." . $v; 95 | } 96 | 97 | //二级域名 98 | $seconddomain = ""; 99 | //子域名 100 | $selfdomain = ""; 101 | //根域名 102 | $rootdomain = ""; 103 | foreach ($arr as $k => $v) { 104 | $pos = stripos($domain, $v); 105 | if ($pos) { 106 | $rootdomain = substr($domain, $pos); 107 | $s = explode(".", substr($domain, 0, $pos)); 108 | $seconddomain = $s[count($s) - 1].$rootdomain; 109 | for ($i = 0; $i < count($s) - 1; $i++) 110 | $selfdomain .= $s[$i] . "."; 111 | $selfdomain = substr($selfdomain,0,strlen($selfdomain)-1); 112 | break; 113 | } 114 | } 115 | //echo $seconddomain ;exit; 116 | if ($rootdomain == "") { 117 | $seconddomain = $domain; 118 | $selfdomain = ""; 119 | } 120 | return array($selfdomain, $seconddomain); 121 | } 122 | 123 | public function error($code, $str) 124 | { 125 | echo "操作错误:".$code.":".$str; 126 | exit; 127 | } 128 | 129 | private function curl($url, $header = '', $data = '', $method = 'get') 130 | { 131 | $ch = curl_init(); 132 | curl_setopt($ch, CURLOPT_URL, $url); 133 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 134 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); //设置请求方式 135 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 136 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); //设置提交的字符串 137 | $result = curl_exec($ch); 138 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 139 | curl_close($ch); 140 | return array( 141 | 'result' => $result, 142 | 'httpCode' => $httpCode 143 | ); 144 | } 145 | 146 | private function out($msg) 147 | { 148 | return json_decode($msg, true); 149 | } 150 | 151 | public function getDomains() 152 | { 153 | 154 | $url = "https://api.godaddy.com/v1/domains"; 155 | $header = ['accept: application/json', 'authorization:sso-key '.$this->accessKeyId.':'.$this->accessSecrec]; 156 | return $this->curl($url, $header); 157 | } 158 | 159 | public function delRecords($domain) 160 | { 161 | 162 | $url = "https://api.godaddy.com/v1/domains/$domain"; 163 | $header = ['accept: application/json', 'Content-Type: application/json', 164 | 'authorization:sso-key '.$this->accessKeyId.':'.$this->accessSecrec]; 165 | 166 | return $this->curl($url, $header, '', 'delete'); 167 | } 168 | 169 | public function GetDNSRecord($domain, $record, $recordType = 'TXT') 170 | { 171 | $url = "https://api.godaddy.com/v1/domains/$domain/records/$recordType/$record"; 172 | $header = ['accept: application/json', 'authorization:sso-key '.$this->accessKeyId.':'.$this->accessSecrec]; 173 | return $this->curl($url, $header); 174 | } 175 | 176 | public function UpdateDNSRecord($domain, $name, $value, $recordType = 'TXT') 177 | { 178 | $url = "https://api.godaddy.com/v1/domains/$domain/records/$recordType/$name"; 179 | $header = ['accept: application/json', 'Content-Type: application/json', 180 | 'authorization:sso-key '.$this->accessKeyId.':'.$this->accessSecrec]; 181 | $data = array( 182 | array( 183 | 'data' => $value, 184 | 'name' => $name, 185 | 'ttl' => 3600, 186 | 'type' => $recordType) 187 | ); 188 | return $this->curl($url, $header, json_encode($data), 'put'); 189 | } 190 | 191 | public function CreateDNSRecord($domain, $name, $value, $recordType = 'TXT') 192 | { 193 | $url = "https://api.godaddy.com/v1/domains/$domain/records"; 194 | $header = ['accept: application/json', 'Content-Type: application/json', 195 | 'authorization:sso-key '.$this->accessKeyId.':'.$this->accessSecrec]; 196 | $data = array( 197 | array( 198 | 'data' => $value, 199 | 'name' => $name, 200 | 'ttl' => 3600, 201 | 'type' => $recordType) 202 | ); 203 | return $this->curl($url, $header, json_encode($data), 'PATCH'); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /php-version/alydns.php: -------------------------------------------------------------------------------- 1 | DescribeDomainRecords(); 11 | if ($data["httpcode"]!=200) { 12 | echo "aly dns 域名获取失败-" . $data["Code"] . ":" . $data["Message"]; 13 | } 14 | //显示所有 15 | //$data = $obj->DescribeDomainRecords(); 16 | 17 | //增加解析 18 | //$data= $obj->AddDomainRecord("TXT", "test", "test"); 19 | 20 | //修改解析 21 | //$data = $obj->UpdateDomainRecord("3965724468724736","TXT", "test", "test2"); 22 | 23 | //删除解析 24 | //$data = $obj->DescribeDomainRecords(); 25 | //$data = $data["DomainRecords"]["Record"]; 26 | //if (is_array($data)) { 27 | //foreach ($data as $v) { 28 | //if ($v["RR"] == "test") { 29 | //$obj->DeleteDomainRecord($v["RecordId"]); 30 | //} 31 | //} 32 | //} 33 | */ 34 | 35 | 36 | /* 37 | example: 38 | 39 | php alydns.php add "simplehttps.com" "dnsv" "dnsk" APPKEY APPTOKEN 40 | */ 41 | 42 | ########## 配合 cerbot 运行 43 | # 第一个参数是 action,代表 (add/clean) 44 | # 第二个参数是域名 45 | # 第三个参数是主机名(第三个参数+第二个参数组合起来就是要添加的 TXT 记录) 46 | # 第四个参数是 TXT 记录值 47 | # 第五个参数是 APPKEY 48 | # 第六个参数是 APPTOKEN 49 | 50 | echo "域名 API 调用开始\n"; 51 | 52 | print_r($argv); 53 | if (count($argv) < 7) { 54 | echo "参数有误\n"; 55 | exit; 56 | } 57 | echo $argv[1]."-".$argv[2]."-".$argv[3]."-".$argv[4]."-".$argv[5]."-".$argv[6]."\n"; 58 | 59 | $domainarray = AliDns::getDomain($argv[2]); 60 | $selfdomain = ($domainarray[0] == "") ? $argv[3] : $argv[3].".".$domainarray[0]; 61 | 62 | $obj = new AliDns($argv[5], $argv[6], $domainarray[1]); 63 | 64 | switch ($argv[1]) { 65 | case "clean": 66 | $data = $obj->DescribeDomainRecords(); 67 | $data = $data["DomainRecords"]["Record"]; 68 | if (is_array($data)) { 69 | foreach ($data as $v) { 70 | if ($v["RR"] == $selfdomain) { 71 | $data = $obj->DeleteDomainRecord($v["RecordId"]); 72 | if ($data["httpcode"] != 200) { 73 | echo "aly dns 域名删除失败-".$data["Code"].":".$data["Message"]; 74 | exit; 75 | } 76 | } 77 | } 78 | } 79 | break; 80 | 81 | case "add": 82 | $data = $obj->AddDomainRecord("TXT", $selfdomain, $argv[4]); 83 | 84 | if ($data["httpcode"] != 200) { 85 | echo "aly dns 域名增加失败-".$data["Code"].":".$data["Message"]; 86 | exit; 87 | } 88 | break; 89 | } 90 | 91 | echo "域名 API 调用结束\n"; 92 | 93 | ############ Class 定义 94 | 95 | class AliDns 96 | { 97 | private $accessKeyId = null; 98 | private $accessSecrec = null; 99 | private $DomainName = null; 100 | 101 | public function __construct($accessKeyId, $accessSecrec, $domain) 102 | { 103 | $this->accessKeyId = $accessKeyId; 104 | $this->accessSecrec = $accessSecrec; 105 | $this->DomainName = $domain; 106 | } 107 | /* 108 | 根据域名返回主机名和二级域名 109 | */ 110 | 111 | public static function getDomain($domain) 112 | { 113 | 114 | //https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains 115 | //常见根域名 116 | global $domainfile; 117 | $tmp = file($domainfile); 118 | $arr = array(); 119 | foreach ($tmp as $k=>$v) { 120 | $v = trim($v); 121 | if ($v!="") 122 | $arr[]= "." . $v; 123 | } 124 | 125 | //二级域名 126 | $seconddomain = ""; 127 | //子域名 128 | $selfdomain = ""; 129 | //根域名 130 | $rootdomain = ""; 131 | foreach ($arr as $k => $v) { 132 | $pos = stripos($domain, $v); 133 | if ($pos) { 134 | $rootdomain = substr($domain, $pos); 135 | $s = explode(".", substr($domain, 0, $pos)); 136 | $seconddomain = $s[count($s) - 1].$rootdomain; 137 | for ($i = 0; $i < count($s) - 1; $i++) 138 | $selfdomain .= $s[$i] . "."; 139 | $selfdomain = substr($selfdomain,0,strlen($selfdomain)-1); 140 | break; 141 | } 142 | } 143 | //echo $seconddomain ;exit; 144 | if ($rootdomain == "") { 145 | $seconddomain = $domain; 146 | $selfdomain = ""; 147 | } 148 | return array($selfdomain, $seconddomain); 149 | } 150 | 151 | public function DescribeDomainRecords() 152 | { 153 | $requestParams = array( 154 | "Action" => "DescribeDomainRecords" 155 | ); 156 | $val = $this->send($requestParams); 157 | 158 | return $this->out($val); 159 | } 160 | 161 | public function UpdateDomainRecord($id, $type, $rr, $value) 162 | { 163 | $requestParams = array( 164 | "Action" => "UpdateDomainRecord", 165 | "RecordId" => $id, 166 | "RR" => $rr, 167 | "Type" => $type, 168 | "Value" => $value, 169 | ); 170 | $val = $this->send($requestParams); 171 | return $this->out($val); 172 | } 173 | 174 | public function DeleteDomainRecord($id) 175 | { 176 | $requestParams = array( 177 | "Action" => "DeleteDomainRecord", 178 | "RecordId" => $id, 179 | ); 180 | $val = $this->send($requestParams); 181 | return $this->out($val); 182 | } 183 | 184 | public function AddDomainRecord($type, $rr, $value) 185 | { 186 | 187 | $requestParams = array( 188 | "Action" => "AddDomainRecord", 189 | "RR" => $rr, 190 | "Type" => $type, 191 | "Value" => $value, 192 | ); 193 | $val = $this->send($requestParams); 194 | return $this->out($val); 195 | } 196 | 197 | private function send($requestParams) 198 | { 199 | $publicParams = array( 200 | "DomainName" => $this->DomainName, 201 | "Format" => "JSON", 202 | "Version" => "2015-01-09", 203 | "AccessKeyId" => $this->accessKeyId, 204 | "Timestamp" => date("Y-m-d\TH:i:s\Z"), 205 | "SignatureMethod" => "HMAC-SHA1", 206 | "SignatureVersion" => "1.0", 207 | "SignatureNonce" => substr(md5(rand(1, 99999999)), rand(1, 9), 14), 208 | ); 209 | 210 | $params = array_merge($publicParams, $requestParams); 211 | $params['Signature'] = $this->sign($params, $this->accessSecrec); 212 | $uri = http_build_query($params); 213 | $url = 'http://alidns.aliyuncs.com/?'.$uri; 214 | return $this->curl($url); 215 | } 216 | 217 | private function sign($params, $accessSecrec, $method = "GET") 218 | { 219 | ksort($params); 220 | $stringToSign = strtoupper($method).'&'.$this->percentEncode('/').'&'; 221 | 222 | $tmp = ""; 223 | foreach ($params as $key => $val) { 224 | $tmp .= '&'.$this->percentEncode($key).'='.$this->percentEncode($val); 225 | } 226 | $tmp = trim($tmp, '&'); 227 | $stringToSign = $stringToSign.$this->percentEncode($tmp); 228 | 229 | $key = $accessSecrec.'&'; 230 | $hmac = hash_hmac("sha1", $stringToSign, $key, true); 231 | 232 | return base64_encode($hmac); 233 | } 234 | 235 | private function percentEncode($value = null) 236 | { 237 | $en = urlencode($value); 238 | $en = str_replace("+", "%20", $en); 239 | $en = str_replace("*", "%2A", $en); 240 | $en = str_replace("%7E", "~", $en); 241 | return $en; 242 | } 243 | 244 | private function curl($url) 245 | { 246 | $ch = curl_init(); 247 | curl_setopt($ch, CURLOPT_URL, $url); 248 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 249 | //curl_setopt($ch, CURLOPT_HEADER, 1); 250 | //curl_setopt($ch, CURLINFO_HEADER_OUT, true); 251 | $result = curl_exec($ch); 252 | $info = curl_getinfo($ch); 253 | 254 | curl_close($ch); 255 | return array($info["http_code"], $result); 256 | } 257 | 258 | private function out($arr) 259 | { 260 | $t = json_decode($arr[1], true); 261 | $t["httpcode"] = $arr[0]; 262 | return $t; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /php-version/txydns.php: -------------------------------------------------------------------------------- 1 | DomainList(); 12 | if ($data["code"]!=0) { 13 | echo $data["message"] . "\n"; 14 | } 15 | //可以增加同名的二条 16 | $data = $obj->RecordCreate("www3","TXT",rand(10,1000)); 17 | $data = $obj->RecordCreate("www3","TXT",rand(10,1000)); 18 | $data = $obj->RecordCreate("www3.www3","TXT",rand(10,1000)); 19 | 20 | if ($data["code"]!=0) { 21 | echo $data["message"] . "\n"; 22 | } 23 | 24 | //查看一个主机的所有txt 记录 25 | $data = $obj->RecordList("www3.www3","TXT"); 26 | 27 | $data = $obj->RecordList("www3","TXT"); 28 | $records = $data["data"]["records"]; 29 | foreach ($records as $k=>$v) { 30 | //根据ID修改记录 31 | $data = $obj->RecordModify("www3", "TXT", rand(1000,2000), $v["id"]); 32 | //根据ID删除记录 33 | $obj->RecordDelete($v["id"]); 34 | } 35 | */ 36 | 37 | ###### 代码运行 38 | //php txydns.php add "www.yudadan.com" "k1" "v1" AKIDwlPr7DUpLgpZBb4tlT0MWUHtIVXOJwxm mMkxzoTxOirrfJlFYfbS7g7792jEi5GG 39 | # 第一个参数是 action,代表 (add/clean) 40 | # 第二个参数是域名 41 | # 第三个参数是主机名(第三个参数+第二个参数组合起来就是要添加的 TXT 记录) 42 | # 第四个参数是 TXT 记录值 43 | # 第五个参数是 APPKEY 44 | # 第六个参数是 APPTOKEN 45 | 46 | echo "域名 API 调用开始\n"; 47 | 48 | 49 | if (count($argv) < 7) { 50 | echo "参数有误\n"; 51 | exit; 52 | } 53 | 54 | echo $argv[1] . "-" . $argv[2] . "-" . $argv[3] . "-" . $argv[4] . "-" . $argv[5] . "-" . $argv[6] . "\n"; 55 | 56 | $domainarray = TxyDns::getDomain($argv[2]); 57 | $selfdomain = ($domainarray[0] == "") ? $argv[3] : $argv[3] . "." . $domainarray[0]; 58 | $obj = new TxyDns($argv[5], $argv[6], $domainarray[1]); 59 | 60 | switch ($argv[1]) { 61 | case "clean": 62 | $data = $obj->RecordList($selfdomain, "TXT"); 63 | if ($data["code"] != 0) { 64 | echo "txy dns 记录获取失败-" . $data["message"] . "\n"; 65 | exit; 66 | } 67 | $records = $data["data"]["records"]; 68 | foreach ($records as $k => $v) { 69 | 70 | $data = $obj->RecordDelete($v["id"]); 71 | 72 | if ($data["code"] != 0) { 73 | echo "txy dns 记录删除失败-" . $data["message"] . "\n"; 74 | exit; 75 | } 76 | } 77 | 78 | break; 79 | 80 | case "add": 81 | $data = $obj->RecordCreate($selfdomain, "TXT", $argv[4]); 82 | if ($data["code"] != 0) { 83 | echo "txy dns 记录添加失败-" . $data["message"] . "\n"; 84 | exit; 85 | } 86 | break; 87 | } 88 | 89 | echo "域名 API 调用成功结束\n"; 90 | 91 | ####### 基于腾讯云 DNS API 实现的 PHP 类,参考 https://cloud.tencent.com/document/product/302/4032 92 | 93 | class TxyDns { 94 | 95 | private $accessKeyId = null; 96 | private $accessSecrec = null; 97 | private $DomainName = null; 98 | private $Host = "cns.api.qcloud.com"; 99 | private $Path = "/v2/index.php"; 100 | 101 | public function __construct($accessKeyId, $accessSecrec, $domain = "") { 102 | $this->accessKeyId = $accessKeyId; 103 | $this->accessSecrec = $accessSecrec; 104 | $this->DomainName = $domain; 105 | } 106 | 107 | /* 108 | 根据域名返回主机名和二级域名 109 | */ 110 | 111 | public static function getDomain($domain) { 112 | 113 | //常见根域名 【https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains】 114 | // 【http://www.seobythesea.com/2006/01/googles-most-popular-and-least-popular-top-level-domains/】 115 | global $domainfile; 116 | $tmp = file($domainfile); 117 | $arr = array(); 118 | foreach ($tmp as $k=>$v) { 119 | $v = trim($v); 120 | if ($v!="") 121 | $arr[]= "." . $v; 122 | } 123 | 124 | //二级域名 125 | $seconddomain = ""; 126 | //子域名 127 | $selfdomain = ""; 128 | //根域名 129 | $rootdomain = ""; 130 | foreach ($arr as $k => $v) { 131 | $pos = stripos($domain, $v); 132 | if ($pos) { 133 | $rootdomain = substr($domain, $pos); 134 | $s = explode(".", substr($domain, 0, $pos)); 135 | $seconddomain = $s[count($s) - 1] . $rootdomain; 136 | for ($i = 0; $i < count($s) - 1; $i++) 137 | $selfdomain .= $s[$i] . "."; 138 | $selfdomain = substr($selfdomain,0,strlen($selfdomain)-1); 139 | break; 140 | } 141 | } 142 | //echo $seconddomain ;exit; 143 | if ($rootdomain == "") { 144 | $seconddomain = $domain; 145 | $selfdomain = ""; 146 | } 147 | return array($selfdomain, $seconddomain); 148 | } 149 | 150 | public function error($code, $str) { 151 | echo "操作错误:" . $code . ":" . $str; 152 | exit; 153 | } 154 | 155 | public function RecordDelete($recordId) { 156 | $param["domain"] = $this->DomainName; 157 | $param["recordId"] = $recordId; 158 | 159 | $data = $this->send("RecordDelete", "GET", $param); 160 | return ($this->out($data)); 161 | } 162 | 163 | public function RecordList($subDomain, $recordType = "") { 164 | 165 | if ($recordType != "") 166 | $param["recordType"] = $recordType; 167 | $param["subDomain"] = $subDomain; 168 | $param["domain"] = $this->DomainName; 169 | 170 | $data = $this->send("RecordList", "GET", $param); 171 | return ($this->out($data)); 172 | } 173 | 174 | public function RecordModify($subDomain, $recordType = "TXT", $value, $recordId) { 175 | $param["recordType"] = $recordType; 176 | $param["subDomain"] = $subDomain; 177 | $param["recordId"] = $recordId; 178 | $param["domain"] = $this->DomainName; 179 | $param["recordLine"] = "默认"; 180 | $param["value"] = $value; 181 | 182 | $data = $this->send("RecordModify", "GET", $param); 183 | return ($this->out($data)); 184 | } 185 | 186 | public function RecordCreate($subDomain, $recordType = "TXT", $value) { 187 | $param["recordType"] = $recordType; 188 | $param["subDomain"] = $subDomain; 189 | $param["domain"] = $this->DomainName; 190 | $param["recordLine"] = "默认"; 191 | $param["value"] = $value; 192 | 193 | $data = $this->send("RecordCreate", "GET", $param); 194 | return ($this->out($data)); 195 | } 196 | 197 | public function DomainList() { 198 | 199 | $data = $this->send("DomainList", "GET", array()); 200 | return ($this->out($data)); 201 | } 202 | 203 | private function send($action, $reqMethod, $requestParams) { 204 | 205 | $params = $this->formatRequestData($action, $requestParams, $reqMethod); 206 | 207 | $uri = http_build_query($params); 208 | $url = "https://" . $this->Host . "" . $this->Path . "?" . $uri; 209 | return $this->curl($url); 210 | } 211 | 212 | private function formatRequestData($action, $request, $reqMethod) { 213 | $param = $request; 214 | $param["Action"] = ucfirst($action); 215 | //$param["RequestClient"] = $this->sdkVersion; 216 | $param["Nonce"] = rand(); 217 | $param["Timestamp"] = time(); 218 | //$param["Version"] = $this->apiVersion; 219 | 220 | $param["SecretId"] = $this->accessKeyId; 221 | 222 | $signStr = $this->formatSignString($this->Host, $this->Path, $param, $reqMethod); 223 | $param["Signature"] = $this->sign($signStr); 224 | return $param; 225 | } 226 | 227 | //签名 228 | private function formatSignString($host, $path, $param, $requestMethod) { 229 | $tmpParam = array(); 230 | ksort($param); 231 | foreach ($param as $key => $value) { 232 | array_push($tmpParam, str_replace("_", ".", $key) . "=" . $value); 233 | } 234 | $strParam = join("&", $tmpParam); 235 | $signStr = strtoupper($requestMethod) . $host . $path . "?" . $strParam; 236 | return $signStr; 237 | } 238 | 239 | private function sign($signStr) { 240 | 241 | $signature = base64_encode(hash_hmac("sha1", $signStr, $this->accessSecrec, true)); 242 | return $signature; 243 | } 244 | 245 | private function curl($url) { 246 | $ch = curl_init(); 247 | curl_setopt($ch, CURLOPT_URL, $url); 248 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 249 | $result = curl_exec($ch); 250 | curl_close($ch); 251 | return $result; 252 | } 253 | 254 | private function out($msg) { 255 | return json_decode($msg, true); 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /python-version/alydns.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | import base64 4 | import urllib 5 | import hmac 6 | import datetime 7 | import random 8 | import string 9 | import json 10 | import sys 11 | import os 12 | 13 | pv = "python2" 14 | #python2 15 | if sys.version_info[0] < 3: 16 | from urllib import quote 17 | from urllib import urlencode 18 | import hashlib 19 | else: 20 | from urllib.parse import quote 21 | from urllib.parse import urlencode 22 | from urllib import request 23 | pv = "python3" 24 | 25 | 26 | class AliDns: 27 | def __init__(self, access_key_id, access_key_secret, domain_name): 28 | self.access_key_id = access_key_id 29 | self.access_key_secret = access_key_secret 30 | self.domain_name = domain_name 31 | 32 | @staticmethod 33 | def getDomain(domain): 34 | domain_parts = domain.split('.') 35 | 36 | 37 | if len(domain_parts) > 2: 38 | dirpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 39 | domainfile = dirpath + "/domain.ini" 40 | domainarr = [] 41 | with open(domainfile) as f: 42 | for line in f: 43 | val = line.strip() 44 | domainarr.append(val) 45 | 46 | #rootdomain = '.'.join(domain_parts[-(2 if domain_parts[-1] in {"co.jp", "com.tw", "net", "com", "com.cn", "org", "cn", "gov", "net.cn", "io", "top", "me", "int", "edu", "link"} else 3):]) 47 | rootdomain = '.'.join(domain_parts[-(2 if domain_parts[-1] in 48 | domainarr else 3):]) 49 | selfdomain = domain.split(rootdomain)[0] 50 | return (selfdomain[0:len(selfdomain)-1], rootdomain) 51 | return ("", domain) 52 | 53 | @staticmethod 54 | def generate_random_str(length=14): 55 | """ 56 | 生成一个指定长度(默认14位)的随机数值,其中 57 | string.digits = "0123456789' 58 | """ 59 | str_list = [random.choice(string.digits) for i in range(length)] 60 | random_str = ''.join(str_list) 61 | return random_str 62 | 63 | @staticmethod 64 | def percent_encode(str): 65 | res = quote(str.encode('utf-8'), '') 66 | res = res.replace('+', '%20') 67 | res = res.replace('*', '%2A') 68 | res = res.replace('%7E', '~') 69 | return res 70 | 71 | @staticmethod 72 | def utc_time(): 73 | """ 74 | 请求的时间戳。日期格式按照ISO8601标准表示, 75 | 并需要使用UTC时间。格式为YYYY-MM-DDThh:mm:ssZ 76 | 例如,2015-01-09T12:00:00Z(为UTC时间2015年1月9日12点0分0秒) 77 | :return: 78 | """ 79 | #utc_tz = pytz.timezone('UTC') 80 | #time = datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') 81 | #time = datetime.datetime.now(tz=utc_tz).strftime('%Y-%m-%dT%H:%M:%SZ') 82 | time = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') 83 | return time 84 | 85 | @staticmethod 86 | def sign_string(url_param): 87 | percent_encode = AliDns.percent_encode 88 | sorted_url_param = sorted(url_param.items(), key=lambda x: x[0]) 89 | can_string = '' 90 | for k, v in sorted_url_param: 91 | can_string += '&' + percent_encode(k) + '=' + percent_encode(v) 92 | string_to_sign = 'GET' + '&' + '%2F' + \ 93 | '&' + percent_encode(can_string[1:]) 94 | return string_to_sign 95 | 96 | @staticmethod 97 | def access_url(url): 98 | if pv == "python2": 99 | f = urllib.urlopen(url) 100 | result = f.read().decode('utf-8') 101 | #print(result) 102 | return json.loads(result) 103 | else: 104 | req = request.Request(url) 105 | with request.urlopen(req) as f: 106 | result = f.read().decode('utf-8') 107 | #print(result) 108 | return json.loads(result) 109 | 110 | def visit_url(self, action_param): 111 | common_param = { 112 | 'Format': 'json', 113 | 'Version': '2015-01-09', 114 | 'AccessKeyId': self.access_key_id, 115 | 'SignatureMethod': 'HMAC-SHA1', 116 | 'Timestamp': AliDns.utc_time(), 117 | 'SignatureVersion': '1.0', 118 | 'SignatureNonce': AliDns.generate_random_str(), 119 | 'DomainName': self.domain_name, 120 | } 121 | url_param = dict(common_param, **action_param) 122 | string_to_sign = AliDns.sign_string(url_param) 123 | 124 | hash_bytes = self.access_key_secret + "&" 125 | if pv == "python2": 126 | h = hmac.new(hash_bytes, string_to_sign, digestmod=hashlib.sha1) 127 | else: 128 | # Return a new hmac object 129 | # key is a bytes or bytearray object giving the secret key 130 | # Parameter msg can be of any type supported by hashlib 131 | # Parameter digestmod can be the name of a hash algorithm.(字符串) 132 | h = hmac.new(hash_bytes.encode('utf-8'), 133 | string_to_sign.encode('utf-8'), digestmod='SHA1') 134 | 135 | if pv == "python2": 136 | signature = base64.encodestring(h.digest()).strip() 137 | else: 138 | # digest() 返回摘要,= HMAC(key, msg, digest).digest() 139 | # encodestring Deprecated since version 3.1 140 | # encodebytes() Encode the bytes-like object s,which can contain arbitrary binary data, and return bytes containing the base64-encoded data 141 | signature = base64.encodebytes(h.digest()).strip() 142 | 143 | url_param.setdefault('Signature', signature) 144 | url = 'https://alidns.aliyuncs.com/?' + urlencode(url_param) 145 | #print(url) 146 | return AliDns.access_url(url) 147 | 148 | # 显示所有 149 | def describe_domain_records(self): 150 | """ 151 | 最多只能查询此域名的 500条解析记录 152 | PageNumber 当前页数,起始值为1,默认为1 153 | PageSize 分页查询时设置的每页行数,最大值500,默认为20 154 | :return: 155 | """ 156 | action_param = dict( 157 | Action='DescribeDomainRecords', 158 | PageNumber='1', 159 | PageSize='500', 160 | ) 161 | result = self.visit_url(action_param) 162 | return result 163 | 164 | # 增加解析 165 | def add_domain_record(self, type, rr, value): 166 | action_param = dict( 167 | Action='AddDomainRecord', 168 | RR=rr, 169 | Type=type, 170 | Value=value, 171 | ) 172 | result = self.visit_url(action_param) 173 | return result 174 | 175 | # 修改解析 176 | def update_domain_record(self, id, type, rr, value): 177 | action_param = dict( 178 | Action="UpdateDomainRecord", 179 | RecordId=id, 180 | RR=rr, 181 | Type=type, 182 | Value=value, 183 | ) 184 | result = self.visit_url(action_param) 185 | return result 186 | 187 | # 删除解析 188 | def delete_domain_record(self, id): 189 | action_param = dict( 190 | Action="DeleteDomainRecord", 191 | RecordId=id, 192 | ) 193 | result = self.visit_url(action_param) 194 | return result 195 | 196 | 197 | if __name__ == '__main__': 198 | #filename,ACCESS_KEY_ID, ACCESS_KEY_SECRET = sys.argv 199 | #domain = AliDns(ACCESS_KEY_ID, ACCESS_KEY_SECRET, 'simplehttps.com') 200 | #domain.describe_domain_records() 201 | #增加记录 202 | #print(domain.add_domain_record("TXT", "test", "test")) 203 | 204 | # 修改解析 205 | #domain.update_domain_record('4011918010876928', 'TXT', 'test2', 'text2') 206 | # 删除解析记录 207 | # data = domain.describe_domain_records() 208 | # record_list = data["DomainRecords"]["Record"] 209 | # for item in record_list: 210 | # if 'test' in item['RR']: 211 | # domain.delete_domain_record(item['RecordId']) 212 | 213 | # 第一个参数是 action,代表 (add/clean) 214 | # 第二个参数是域名 215 | # 第三个参数是主机名(第三个参数+第二个参数组合起来就是要添加的 TXT 记录) 216 | # 第四个参数是 TXT 记录值 217 | # 第五个参数是 APPKEY 218 | # 第六个参数是 APPTOKEN 219 | #sys.exit(0) 220 | 221 | print("域名 API 调用开始") 222 | print("-".join(sys.argv)) 223 | file_name, cmd, certbot_domain, acme_challenge, certbot_validation, ACCESS_KEY_ID, ACCESS_KEY_SECRET = sys.argv 224 | 225 | certbot_domain = AliDns.getDomain(certbot_domain) 226 | #print (certbot_domain) 227 | if certbot_domain[0] == "": 228 | selfdomain = acme_challenge 229 | else: 230 | selfdomain = acme_challenge + "." + certbot_domain[0] 231 | 232 | domain = AliDns(ACCESS_KEY_ID, ACCESS_KEY_SECRET, certbot_domain[1]) 233 | 234 | if cmd == "add": 235 | result = (domain.add_domain_record( 236 | "TXT", selfdomain, certbot_validation)) 237 | if "Code" in result: 238 | print("aly dns 域名增加失败-" + 239 | str(result["Code"]) + ":" + str(result["Message"])) 240 | sys.exit(0) 241 | elif cmd == "clean": 242 | data = domain.describe_domain_records() 243 | if "Code" in data: 244 | print("aly dns 域名删除失败-" + 245 | str(data["Code"]) + ":" + str(data["Message"])) 246 | sys.exit(0) 247 | record_list = data["DomainRecords"]["Record"] 248 | if record_list: 249 | for item in record_list: 250 | if (item['RR'] == selfdomain): 251 | domain.delete_domain_record(item['RecordId']) 252 | 253 | print("域名 API 调用结束") 254 | -------------------------------------------------------------------------------- /python-version/hwydns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import time 7 | import urllib 8 | import hashlib 9 | import hmac 10 | import binascii 11 | import json 12 | 13 | if sys.version_info < (3, 0): 14 | import urllib2 15 | import urllib 16 | import urlparse 17 | else: 18 | import urllib.request as urllib2 19 | import urllib.parse as urllib 20 | 21 | class HwyDns: 22 | __endpoint = 'dns.myhuaweicloud.com' 23 | 24 | def __init__(self, access_key_id, secret_access_key): 25 | self.access_key_id = access_key_id 26 | self.secret_access_key = secret_access_key 27 | 28 | @staticmethod 29 | def getDomain(domain): 30 | domain_parts = domain.split('.') 31 | 32 | if len(domain_parts) > 2: 33 | dirpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 34 | domainfile = dirpath + '/domain.ini' 35 | domainarr = [] 36 | with open(domainfile) as f: 37 | for line in f: 38 | val = line.strip() 39 | domainarr.append(val) 40 | 41 | index = -3 if '.'.join(domain_parts[-2:]).lower() in domainarr else -2 42 | return ('.'.join(domain_parts[:index]), '.'.join(domain_parts[index:])) 43 | return ('', domain) 44 | 45 | # @example hwydns.add_domain_record("example.com", "_acme-challenge", "123456", "TXT") 46 | def add_domain_record(self, domain, rr, value, _type = 'TXT'): 47 | zone_id = self.get_domain_zone_id(domain) 48 | 49 | if not zone_id: 50 | return 51 | 52 | self.__request('POST', '/v2/zones/%s/recordsets' % (zone_id), { 53 | 'name' : '%s.%s.' % (rr, domain), 54 | 'type' : _type, 55 | 'records' : [ "\"%s\"" % (value) ] 56 | }) 57 | 58 | # @example hwydns.delete_domain_record("example.com", "_acme-challenge", "TXT") 59 | def delete_domain_record(self, domain, rr, _type = 'TXT'): 60 | zone_id = self.get_domain_zone_id(domain) 61 | recordset_id = self.get_domain_recordset_id(domain, rr, _type) 62 | 63 | if not (zone_id and recordset_id): 64 | return 65 | 66 | self.__request('DELETE', '/v2/zones/%s/recordsets/%s' % (zone_id, recordset_id)) 67 | 68 | # @example hwydns.get_domain_record("example.com", "_acme-challenge", "TXT") 69 | def get_domain_record(self, domain, rr, _type = 'TXT'): 70 | try: 71 | full_domain = '.'.join([rr, domain]) 72 | response = self.__request('GET', '/v2/recordsets?type=%s&name=%s' % (_type, full_domain)) 73 | content = json.loads(response) 74 | return list(filter(lambda record: record['name'][:-1] == full_domain and record['type'] == _type, content['recordsets']))[0] 75 | except Exception as e: 76 | print('hwydns#get_domain_record raise: ' + str(e)) 77 | return None 78 | 79 | # @example hwydns.get_domain("example.com") 80 | def get_domain(self, domain): 81 | try: 82 | response = self.__request('GET', '/v2/zones?type=public&name=%s' % (domain)) 83 | content = json.loads(response) 84 | return list(filter(lambda item: item['name'][:-1] == domain, content['zones']))[0] 85 | except Exception as e: 86 | print('hwydns#get_domain raise: ' + str(e)) 87 | return None 88 | 89 | def get_domain_recordset_id(self, domain, rr, _type = 'TXT'): 90 | try: 91 | record = self.get_domain_record(domain, rr, _type) 92 | return record['id'] if record else None 93 | except Exception as e: 94 | print('hwydns#get_domain_recordset_id raise: ' + str(e)) 95 | return None 96 | 97 | def get_domain_zone_id(self, domain): 98 | try: 99 | record = self.get_domain(domain) 100 | return record['id'] if record else None 101 | except Exception as e: 102 | print('hwydns#get_domain_zone_id raise: ' + str(e)) 103 | return None 104 | 105 | def __request(self, method, path, payload={}): 106 | url = 'https://%s%s?%s' % (self.__endpoint, self.__parse_path(path)[:-1], self.__parse_query_string(path)) 107 | data = json.dumps(payload).encode('utf8') 108 | sdk_date = self.__build_sdk_date() 109 | 110 | print('Request URL: ' + url) 111 | print('Request Data: ' + str(data)) 112 | 113 | request = urllib2.Request(url=url, data=data) 114 | request.get_method = lambda: method 115 | request.add_header('Content-Type', 'application/json') 116 | request.add_header('Host', self.__endpoint) 117 | request.add_header('X-sdk-date', sdk_date) 118 | request.add_header('Authorization', self.__build_authorization(request)) 119 | print('Request headers: ' + str(request.headers)) 120 | 121 | try: 122 | f = urllib2.urlopen(request, timeout=45) 123 | response = f.read().decode('utf-8') 124 | print(response) 125 | return response 126 | except urllib2.HTTPError as e: 127 | print('hwydns#__request raise urllib2.HTTPError: ' + str(e)) 128 | raise SystemExit(e) 129 | 130 | def __build_sdk_date(self): 131 | return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime()) 132 | 133 | def __build_authorization(self, request): 134 | algorithm = 'SDK-HMAC-SHA256' 135 | canonical_request = self.__build_canonical_request(request) 136 | canonical_request_hexencode = self.__hexencode_sha256_hash(canonical_request.encode('utf-8')) 137 | string2sign = "%s\n%s\n%s" % (algorithm, request.get_header('X-sdk-date'), canonical_request_hexencode) 138 | sign = self.__build_sign(string2sign) 139 | 140 | return "%s Access=%s, SignedHeaders=%s, Signature=%s" % (algorithm, self.access_key_id, self.__parse_header_keys(request.headers), sign) 141 | 142 | def __build_canonical_request(self, request): 143 | return "%(method)s\n%(path)s\n%(query_string)s\n%(headers)s\n%(header_keys)s\n%(data_hexencode)s" % { 144 | 'method': request.get_method().upper(), 145 | 'path': self.__parse_path(request.get_full_url()), 146 | 'query_string': self.__parse_query_string(request.get_full_url()), 147 | 'headers': self.__parse_headers(request.headers), 148 | 'header_keys': self.__parse_header_keys(request.headers), 149 | 'data_hexencode': self.__hexencode_sha256_hash(request.data) 150 | } 151 | 152 | def __parse_path(self, url): 153 | if sys.version_info < (3,0): 154 | path = urlparse.urlsplit(url).path 155 | else: 156 | path = urllib.urlsplit(url).path 157 | 158 | path = path if path else '/' 159 | pattens = urllib.unquote(path).split('/') 160 | 161 | tmp_paths = [] 162 | for v in pattens: 163 | tmp_paths.append(self.__urlencode(v)) 164 | urlpath = '/'.join(tmp_paths) 165 | if urlpath[-1] != '/': 166 | urlpath = urlpath + '/' 167 | return urlpath 168 | 169 | def __parse_query_string(self, url): 170 | if sys.version_info < (3,0): 171 | query = urlparse.parse_qs(urlparse.urlsplit(url).query) 172 | else: 173 | query = urllib.parse_qs(urllib.urlsplit(url).query) 174 | 175 | sorted_query = sorted(query.items(), key=lambda item: item[0]) 176 | sorted_query_string = '' 177 | for (k, v) in sorted_query: 178 | if type(v) is list: 179 | v.sort() 180 | for item in v: 181 | sorted_query_string += '&' + self.__urlencode(k) + '=' + self.__urlencode(item) 182 | else: 183 | sorted_query_string += '&' + self.__urlencode(k) + '=' + self.__urlencode(v) 184 | 185 | return sorted_query_string[1:] 186 | 187 | def __parse_headers(self, headers): 188 | format_headers = dict(((k.lower(), v.strip())) for (k, v) in headers.items()) 189 | 190 | header_string = '' 191 | for (k, v) in sorted(format_headers.items(), key=lambda item: item[0]): 192 | header_string += "%s:%s\n" % (k, v) 193 | return header_string 194 | 195 | def __parse_header_keys(self, headers): 196 | return ';'.join(sorted(map(lambda key: key.lower(), headers.keys()))) 197 | 198 | def __build_sign(self, string2sign): 199 | if sys.version_info < (3,0): 200 | hm = hmac.new(self.secret_access_key, string2sign, digestmod=hashlib.sha256).digest() 201 | else: 202 | hm = hmac.new(self.secret_access_key.encode('utf-8'), string2sign.encode('utf-8'), digestmod=hashlib.sha256).digest() 203 | return binascii.hexlify(hm).decode() 204 | 205 | def __urlencode(self, string): 206 | return urllib.quote(str(string), safe='~') 207 | 208 | def __hexencode_sha256_hash(self, data): 209 | sha256 = hashlib.sha256() 210 | sha256.update(data) 211 | return sha256.hexdigest() 212 | 213 | if __name__ == '__main__': 214 | print('开始调用华为云 DNS API') 215 | print('-'.join(sys.argv)) 216 | 217 | _, action, certbot_domain, acme_challenge, certbot_validation, api_key, api_secret = sys.argv 218 | 219 | subdomain, main_domain = HwyDns.getDomain(certbot_domain) 220 | if subdomain: 221 | subdomain = acme_challenge + '.' + subdomain 222 | else: 223 | subdomain = acme_challenge 224 | 225 | hwydns = HwyDns(api_key, api_secret) 226 | 227 | if 'add' == action: 228 | hwydns.add_domain_record(main_domain, subdomain, certbot_validation) 229 | elif 'clean' == action: 230 | hwydns.delete_domain_record(main_domain, subdomain) 231 | 232 | print('结束调用华为云 DNS API') --------------------------------------------------------------------------------