mirror of
https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au.git
synced 2025-12-25 20:32:47 +00:00
feat: add python version of huaweicloud dns api
This commit is contained in:
parent
c96b4bc5c0
commit
379ba69430
41
README.md
41
README.md
|
|
@ -7,10 +7,10 @@
|
||||||
不管是申请还是续期,只要是通配符证书,只能采用 dns-01 的方式校验申请者的域名,也就是说 certbot 操作者必须手动添加 DNS TXT 记录。
|
不管是申请还是续期,只要是通配符证书,只能采用 dns-01 的方式校验申请者的域名,也就是说 certbot 操作者必须手动添加 DNS TXT 记录。
|
||||||
|
|
||||||
如果你编写一个 Cron (比如 1 1 */1 * * root certbot-auto renew),自动 renew 通配符证书,此时 Cron 无法自动添加 TXT 记录,这样 renew 操作就会失败,如何解决?
|
如果你编写一个 Cron (比如 1 1 */1 * * root certbot-auto renew),自动 renew 通配符证书,此时 Cron 无法自动添加 TXT 记录,这样 renew 操作就会失败,如何解决?
|
||||||
|
|
||||||
certbot 提供了一个 hook,可以编写一个 Shell 脚本,让脚本调用 DNS 服务商的 API 接口,动态添加 TXT 记录,这样就无需人工干预了。
|
certbot 提供了一个 hook,可以编写一个 Shell 脚本,让脚本调用 DNS 服务商的 API 接口,动态添加 TXT 记录,这样就无需人工干预了。
|
||||||
|
|
||||||
在 certbot 官方提供的插件和 hook 例子中,都没有针对国内 DNS 服务器的样例,所以我编写了这样一个工具,目前支持阿里云 DNS、腾讯云 DNS、GoDaddy(certbot 官方没有对应的插件)。
|
在 certbot 官方提供的插件和 hook 例子中,都没有针对国内 DNS 服务器的样例,所以我编写了这样一个工具,目前支持阿里云 DNS、腾讯云 DNS、GoDaddy(certbot 官方没有对应的插件)。
|
||||||
|
|
||||||
### 自动申请通配符证书
|
### 自动申请通配符证书
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ $ git clone https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-a
|
||||||
|
|
||||||
$ cd certbot-letencrypt-wildcardcertificates-alydns-au
|
$ cd certbot-letencrypt-wildcardcertificates-alydns-au
|
||||||
|
|
||||||
$ chmod 0777 au.sh
|
$ chmod 0777 au.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
2:配置
|
2:配置
|
||||||
|
|
@ -32,10 +32,11 @@ $ chmod 0777 au.sh
|
||||||
|
|
||||||
(2)DNS API 密钥:
|
(2)DNS API 密钥:
|
||||||
|
|
||||||
这个 API 密钥什么意思呢?由于需要通过 API 操作阿里云 DNS 或腾讯云 DNS 的记录,所以需要去域名服务商哪儿获取 API 密钥,然后配置在 au.sh 文件中:
|
这个 API 密钥什么意思呢?由于需要通过 API 操作阿里云 DNS, 腾讯云 DNS 的记录,所以需要去域名服务商哪儿获取 API 密钥,然后配置在 au.sh 文件中:
|
||||||
|
|
||||||
- ALY_KEY 和 ALY_TOKEN:阿里云 [API key 和 Secrec 官方申请文档](https://help.aliyun.com/knowledge_detail/38738.html)。
|
- ALY_KEY 和 ALY_TOKEN:阿里云 [API key 和 Secrec 官方申请文档](https://help.aliyun.com/knowledge_detail/38738.html)。
|
||||||
- TXY_KEY 和 TXY_TOKEN:腾讯云 [API 密钥官方申请文档](https://console.cloud.tencent.com/cam/capi)。
|
- TXY_KEY 和 TXY_TOKEN:腾讯云 [API 密钥官方申请文档](https://console.cloud.tencent.com/cam/capi)。
|
||||||
|
- HWY_KEY 和 HWY_TOKEN: 华为云 [API 密钥官方申请文档](https://support.huaweicloud.com/devg-apisign/api-sign-provide.html)
|
||||||
- GODADDY_KEY 和 GODADDY_TOKEN:GoDaddy [API 密钥官方申请文档](https://developer.godaddy.com/getstarted)。
|
- GODADDY_KEY 和 GODADDY_TOKEN:GoDaddy [API 密钥官方申请文档](https://developer.godaddy.com/getstarted)。
|
||||||
|
|
||||||
(3)选择运行环境
|
(3)选择运行环境
|
||||||
|
|
@ -49,16 +50,17 @@ $ chmod 0777 au.sh
|
||||||
- Python(支持2.7和3.7,无需任何第三方库)
|
- Python(支持2.7和3.7,无需任何第三方库)
|
||||||
- au.sh python aly add/clean:Python操作阿里云DNS,增加/清空DNS。
|
- au.sh python aly add/clean:Python操作阿里云DNS,增加/清空DNS。
|
||||||
- au.sh python txy add/clean:Python操作腾讯云DNS,增加/清空DNS。
|
- au.sh python txy add/clean:Python操作腾讯云DNS,增加/清空DNS。
|
||||||
- au.sh python godaddy add/clean:Python操作GoDaddy DNS,增加/清空DNS。
|
- au.sh python hwy add/clean:Python操作华为云DNS,增加/清空DNS。
|
||||||
|
- au.sh python godaddy add/clean:Python操作GoDaddy DNS,增加/清空DNS。
|
||||||
|
|
||||||
根据自己服务器环境和域名服务商选择任意一个 hook shell(包含相应参数),具体使用见下面。
|
根据自己服务器环境和域名服务商选择任意一个 hook shell(包含相应参数),具体使用见下面。
|
||||||
|
|
||||||
3:申请证书
|
3:申请证书
|
||||||
|
|
||||||
测试是否有错误:
|
测试是否有错误:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./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"
|
$ ./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"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Debug:** 操作 DNS API 可能会遇到一系列问题,比如 API token 权限不足,遇到相关问题,可以查看 /var/log/certd.log。
|
**Debug:** 操作 DNS API 可能会遇到一系列问题,比如 API token 权限不足,遇到相关问题,可以查看 /var/log/certd.log。
|
||||||
|
|
@ -70,12 +72,12 @@ $ ./certbot-auto certonly -d *.example.com --manual --preferred-challenges dns
|
||||||
- 第三个参数是固定的(--manual-auth-hook中用add,--manual-clean-hook中用clean)
|
- 第三个参数是固定的(--manual-auth-hook中用add,--manual-clean-hook中用clean)
|
||||||
|
|
||||||
比如你要选择Python环境,可以将 --manual-auth-hook 输入修改为 "/脚本目录/au.sh python aly add",--manual-cleanup-hook 输入修改为 "/脚本目录/au.sh python aly clean"
|
比如你要选择Python环境,可以将 --manual-auth-hook 输入修改为 "/脚本目录/au.sh python aly add",--manual-cleanup-hook 输入修改为 "/脚本目录/au.sh python aly clean"
|
||||||
|
|
||||||
确认无误后,实际运行(去除 --dry-run 参数):
|
确认无误后,实际运行(去除 --dry-run 参数):
|
||||||
|
|
||||||
```
|
```
|
||||||
# 实际申请
|
# 实际申请
|
||||||
$ ./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"
|
$ ./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"
|
||||||
```
|
```
|
||||||
|
|
||||||
参数解释(可以不用关心):
|
参数解释(可以不用关心):
|
||||||
|
|
@ -91,7 +93,7 @@ $ ./certbot-auto certonly -d *.example.com --manual --preferred-challenges dns
|
||||||
如果你想为多个域名申请通配符证书(合并在一张证书中,也叫做 **SAN 通配符证书**),直接输入多个 -d 参数即可,比如:
|
如果你想为多个域名申请通配符证书(合并在一张证书中,也叫做 **SAN 通配符证书**),直接输入多个 -d 参数即可,比如:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./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"
|
$ ./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,7 +101,7 @@ $ ./certbot-auto certonly -d *.example.com -d *.example.org -d www.example.cn
|
||||||
1:对机器上所有证书 renew
|
1:对机器上所有证书 renew
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./certbot-auto renew --manual --preferred-challenges dns --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean"
|
$ ./certbot-auto renew --manual --preferred-challenges dns --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean"
|
||||||
```
|
```
|
||||||
|
|
||||||
2:对某一张证书进行续期
|
2:对某一张证书进行续期
|
||||||
|
|
@ -117,23 +119,23 @@ $ ./certbot-auto certificates
|
||||||
记住证书名,比如 simplehttps.com,然后运行下列命令 renew:
|
记住证书名,比如 simplehttps.com,然后运行下列命令 renew:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./certbot-auto renew --cert-name simplehttps.com --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean"
|
$ ./certbot-auto renew --cert-name simplehttps.com --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 加入 crontab
|
### 加入 crontab
|
||||||
|
|
||||||
编辑文件 /etc/crontab :
|
编辑文件 /etc/crontab :
|
||||||
|
|
||||||
```
|
```
|
||||||
#证书有效期<30天才会renew,所以crontab可以配置为1天或1周
|
#证书有效期<30天才会renew,所以crontab可以配置为1天或1周
|
||||||
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"
|
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"
|
||||||
```
|
```
|
||||||
|
|
||||||
如果是certbot 机器和运行web服务(比如 nginx,apache)的机器是同一台,那么成功renew证书后,可以启动对应的web 服务器,运行下列crontab :
|
如果是certbot 机器和运行web服务(比如 nginx,apache)的机器是同一台,那么成功renew证书后,可以启动对应的web 服务器,运行下列crontab :
|
||||||
|
|
||||||
```
|
```
|
||||||
# 注意只有成功renew证书,才会重新启动nginx
|
# 注意只有成功renew证书,才会重新启动nginx
|
||||||
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"
|
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"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -142,18 +144,19 @@ $ ./certbot-auto renew --cert-name simplehttps.com --manual-auth-hook "/脚本
|
||||||
### 贡献
|
### 贡献
|
||||||
|
|
||||||
- 阿里云 python 版 @Duke-Wu
|
- 阿里云 python 版 @Duke-Wu
|
||||||
- 腾讯云 python 版 @akgnah
|
- 腾讯云 python 版 @akgnah
|
||||||
|
- 华为云 python 版 @jinhucheung
|
||||||
- GoDaddy PHP 版 wlx_1990 (2019-01-11)
|
- GoDaddy PHP 版 wlx_1990 (2019-01-11)
|
||||||
|
|
||||||
### 其他
|
### 其他
|
||||||
|
|
||||||
- 可以关注公众号(虞大胆的叽叽喳喳,yudadanwx),了解更多密码学&HTTPS协议知识。
|
- 可以关注公众号(虞大胆的叽叽喳喳,yudadanwx),了解更多密码学&HTTPS协议知识。
|
||||||
- 我写了一本书[《深入浅出HTTPS:从原理到实战》](https://mp.weixin.qq.com/s/80oQhzmP9BTimoReo1oMeQ)了解更多关于HTTPS方面的知识。**如果你觉得本书还可以,希望能在豆瓣做个点评,以便让更多人了解,非常感谢。豆瓣评论地址:[https://book.douban.com/subject/30250772/](https://book.douban.com/subject/30250772/)**
|
- 我写了一本书[《深入浅出HTTPS:从原理到实战》](https://mp.weixin.qq.com/s/80oQhzmP9BTimoReo1oMeQ)了解更多关于HTTPS方面的知识。**如果你觉得本书还可以,希望能在豆瓣做个点评,以便让更多人了解,非常感谢。豆瓣评论地址:[https://book.douban.com/subject/30250772/](https://book.douban.com/subject/30250772/)**
|
||||||
|
|
||||||
公众号二维码:
|
公众号二维码:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
《深入浅出HTTPS:从原理到实战》二维码:
|
《深入浅出HTTPS:从原理到实战》二维码:
|
||||||
|
|
||||||

|

|
||||||
|
|
|
||||||
47
au.sh
47
au.sh
|
|
@ -4,9 +4,9 @@
|
||||||
|
|
||||||
###### 根据自己的情况修改 Begin ##############
|
###### 根据自己的情况修改 Begin ##############
|
||||||
|
|
||||||
#PHP 命令行路径,如果有需要可以修改
|
#PHP 命令行路径,如果有需要可以修改
|
||||||
phpcmd="/usr/bin/php"
|
phpcmd="/usr/bin/php"
|
||||||
#Python 命令行路径,如果有需要可以修改
|
#Python 命令行路径,如果有需要可以修改
|
||||||
pythoncmd="/usr/bin/python"
|
pythoncmd="/usr/bin/python"
|
||||||
|
|
||||||
#填写阿里云的AccessKey ID及AccessKey Secret
|
#填写阿里云的AccessKey ID及AccessKey Secret
|
||||||
|
|
@ -19,6 +19,11 @@ ALY_TOKEN=""
|
||||||
TXY_KEY=""
|
TXY_KEY=""
|
||||||
TXY_TOKEN=""
|
TXY_TOKEN=""
|
||||||
|
|
||||||
|
#填写华为云的 Access Key Id 及 Secret Access Key
|
||||||
|
#如何申请见https://support.huaweicloud.com/devg-apisign/api-sign-provide.html
|
||||||
|
HWY_KEY=""
|
||||||
|
HWY_TOKEN=""
|
||||||
|
|
||||||
#GoDaddy的SecretId及SecretKey
|
#GoDaddy的SecretId及SecretKey
|
||||||
#如何申请见https://developer.godaddy.com/getstarted
|
#如何申请见https://developer.godaddy.com/getstarted
|
||||||
GODADDY_KEY=""
|
GODADDY_KEY=""
|
||||||
|
|
@ -32,8 +37,8 @@ PATH=$(cd `dirname $0`; pwd)
|
||||||
# 第一个参数:使用什么语言环境
|
# 第一个参数:使用什么语言环境
|
||||||
# 第二个参数:使用那个 DNS 的 API
|
# 第二个参数:使用那个 DNS 的 API
|
||||||
# 第三个参数:add or clean
|
# 第三个参数:add or clean
|
||||||
plang=$1 #python or php
|
plang=$1 #python or php
|
||||||
pdns=$2 #aly or txy
|
pdns=$2 #aly, txy, hwy, godaddy
|
||||||
paction=$3 #add or clean
|
paction=$3 #add or clean
|
||||||
|
|
||||||
#内部变量
|
#内部变量
|
||||||
|
|
@ -45,43 +50,53 @@ if [[ "$paction" != "clean" ]]; then
|
||||||
paction="add"
|
paction="add"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case $plang in
|
case $plang in
|
||||||
"php")
|
"php")
|
||||||
|
|
||||||
cmd=$phpcmd
|
cmd=$phpcmd
|
||||||
if [[ "$pdns" == "aly" ]]; then
|
if [[ "$pdns" == "aly" ]]; then
|
||||||
dnsapi=$PATH"/php-version/alydns.php"
|
dnsapi=$PATH"/php-version/alydns.php"
|
||||||
key=$ALY_KEY
|
key=$ALY_KEY
|
||||||
token=$ALY_TOKEN
|
token=$ALY_TOKEN
|
||||||
elif [[ "$pdns" == "txy" ]] ;then
|
elif [[ "$pdns" == "txy" ]]; then
|
||||||
dnsapi="$PATH/php-version/txydns.php"
|
dnsapi="$PATH/php-version/txydns.php"
|
||||||
key=$TXY_KEY
|
key=$TXY_KEY
|
||||||
token=$TXY_TOKEN
|
token=$TXY_TOKEN
|
||||||
|
elif [[ "$pdns" == "hwy" ]]; then
|
||||||
|
# TODO
|
||||||
|
dnsapi=""
|
||||||
|
key=$HWY_KEY
|
||||||
|
token=$HWY_TOKEN
|
||||||
|
exit
|
||||||
else
|
else
|
||||||
dnsapi="$PATH/php-version/godaddydns.php"
|
dnsapi="$PATH/php-version/godaddydns.php"
|
||||||
key=$GODADDY_KEY
|
key=$GODADDY_KEY
|
||||||
token=$GODADDY_TOKEN
|
token=$GODADDY_TOKEN
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
"python")
|
"python")
|
||||||
|
|
||||||
cmd=$pythoncmd
|
cmd=$pythoncmd
|
||||||
if [[ "$pdns" == "aly" ]]; then
|
if [[ "$pdns" == "aly" ]]; then
|
||||||
dnsapi=$PATH"/python-version/alydns.py"
|
dnsapi=$PATH"/python-version/alydns.py"
|
||||||
key=$ALY_KEY
|
key=$ALY_KEY
|
||||||
token=$ALY_TOKEN
|
token=$ALY_TOKEN
|
||||||
elif [[ "$pdns" == "txy" ]] ;then
|
elif [[ "$pdns" == "txy" ]]; then
|
||||||
dnsapi=$PATH"/python-version/txydns.py"
|
dnsapi=$PATH"/python-version/txydns.py"
|
||||||
key=$TXY_KEY
|
key=$TXY_KEY
|
||||||
token=$TXY_TOKEN
|
token=$TXY_TOKEN
|
||||||
|
elif [[ "$pdns" == "hwy" ]]; then
|
||||||
|
dnsapi="$PATH/python-version/hwydns.py"
|
||||||
|
key=$HWY_KEY
|
||||||
|
token=$HWY_TOKEN
|
||||||
else
|
else
|
||||||
dnsapi=$PATH"/python-version/godaddydns.py"
|
dnsapi=$PATH"/python-version/godaddydns.py"
|
||||||
key=$GODADDY_KEY
|
key=$GODADDY_KEY
|
||||||
token=$GODADDY_TOKEN
|
token=$GODADDY_TOKEN
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
$cmd $dnsapi $paction $CERTBOT_DOMAIN "_acme-challenge" $CERTBOT_VALIDATION $key $token >>"/var/log/certd.log"
|
$cmd $dnsapi $paction $CERTBOT_DOMAIN "_acme-challenge" $CERTBOT_VALIDATION $key $token >>"/var/log/certd.log"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import random
|
||||||
import string
|
import string
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
pv = "python2"
|
pv = "python2"
|
||||||
#python2
|
#python2
|
||||||
|
|
@ -32,8 +32,8 @@ class AliDns:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDomain(domain):
|
def getDomain(domain):
|
||||||
domain_parts = domain.split('.')
|
domain_parts = domain.split('.')
|
||||||
|
|
||||||
|
|
||||||
if len(domain_parts) > 2:
|
if len(domain_parts) > 2:
|
||||||
dirpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
dirpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||||
domainfile = dirpath + "/domain.ini"
|
domainfile = dirpath + "/domain.ini"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import binascii
|
||||||
|
import json
|
||||||
|
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
import urllib2
|
||||||
|
import urllib
|
||||||
|
import urlparse
|
||||||
|
else:
|
||||||
|
import urllib.request as urllib2
|
||||||
|
import urllib.parse as urllib
|
||||||
|
|
||||||
|
class HwyDns:
|
||||||
|
__endpoint = 'dns.myhuaweicloud.com'
|
||||||
|
|
||||||
|
def __init__(self, access_key_id, secret_access_key):
|
||||||
|
self.access_key_id = access_key_id
|
||||||
|
self.secret_access_key = secret_access_key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getDomain(domain):
|
||||||
|
domain_parts = domain.split('.')
|
||||||
|
|
||||||
|
if len(domain_parts) > 2:
|
||||||
|
dirpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
domainfile = dirpath + '/domain.ini'
|
||||||
|
domainarr = []
|
||||||
|
with open(domainfile) as f:
|
||||||
|
for line in f:
|
||||||
|
val = line.strip()
|
||||||
|
domainarr.append(val)
|
||||||
|
|
||||||
|
index = -3 if '.'.join(domain_parts[-2:]).lower() in domainarr else -2
|
||||||
|
return ('.'.join(domain_parts[:index]), '.'.join(domain_parts[index:]))
|
||||||
|
return ('', domain)
|
||||||
|
|
||||||
|
# @example hwydns.add_domain_record("example.com", "_acme-challenge", "123456", "TXT")
|
||||||
|
def add_domain_record(self, domain, rr, value, _type = 'TXT'):
|
||||||
|
zone_id = self.get_domain_zone_id(domain)
|
||||||
|
|
||||||
|
if not zone_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__request('POST', '/v2/zones/%s/recordsets' % (zone_id), {
|
||||||
|
'name' : '%s.%s.' % (rr, domain),
|
||||||
|
'type' : _type,
|
||||||
|
'records' : [ "\"%s\"" % (value) ]
|
||||||
|
})
|
||||||
|
|
||||||
|
# @example hwydns.delete_domain_record("example.com", "_acme-challenge", "TXT")
|
||||||
|
def delete_domain_record(self, domain, rr, _type = 'TXT'):
|
||||||
|
zone_id = self.get_domain_zone_id(domain)
|
||||||
|
recordset_id = self.get_domain_recordset_id(domain, rr, _type)
|
||||||
|
|
||||||
|
if not (zone_id and recordset_id):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.__request('DELETE', '/v2/zones/%s/recordsets/%s' % (zone_id, recordset_id))
|
||||||
|
|
||||||
|
# @example hwydns.get_domain_record("example.com", "_acme-challenge", "TXT")
|
||||||
|
def get_domain_record(self, domain, rr, _type = 'TXT'):
|
||||||
|
try:
|
||||||
|
full_domain = '.'.join([rr, domain])
|
||||||
|
response = self.__request('GET', '/v2/recordsets?type=%s&name=%s' % (_type, full_domain))
|
||||||
|
content = json.loads(response)
|
||||||
|
return list(filter(lambda record: record['name'][:-1] == full_domain and record['type'] == _type, content['recordsets']))[0]
|
||||||
|
except Exception as e:
|
||||||
|
print('hwydns#get_domain_record raise: ' + str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
# @example hwydns.get_domain("example.com")
|
||||||
|
def get_domain(self, domain):
|
||||||
|
try:
|
||||||
|
response = self.__request('GET', '/v2/zones?type=public&name=%s' % (domain))
|
||||||
|
content = json.loads(response)
|
||||||
|
return list(filter(lambda item: item['name'][:-1] == domain, content['zones']))[0]
|
||||||
|
except Exception as e:
|
||||||
|
print('hwydns#get_domain raise: ' + str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_domain_recordset_id(self, domain, rr, _type = 'TXT'):
|
||||||
|
try:
|
||||||
|
record = self.get_domain_record(domain, rr, _type)
|
||||||
|
return record['id'] if record else None
|
||||||
|
except Exception as e:
|
||||||
|
print('hwydns#get_domain_recordset_id raise: ' + str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_domain_zone_id(self, domain):
|
||||||
|
try:
|
||||||
|
record = self.get_domain(domain)
|
||||||
|
return record['id'] if record else None
|
||||||
|
except Exception as e:
|
||||||
|
print('hwydns#get_domain_zone_id raise: ' + str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __request(self, method, path, payload={}):
|
||||||
|
url = 'https://%s%s?%s' % (self.__endpoint, self.__parse_path(path)[:-1], self.__parse_query_string(path))
|
||||||
|
data = json.dumps(payload).encode('utf8')
|
||||||
|
sdk_date = self.__build_sdk_date()
|
||||||
|
|
||||||
|
print('Request URL: ' + url)
|
||||||
|
print('Request Data: ' + str(data))
|
||||||
|
|
||||||
|
request = urllib2.Request(url=url, data=data)
|
||||||
|
request.get_method = lambda: method
|
||||||
|
request.add_header('Content-Type', 'application/json')
|
||||||
|
request.add_header('Host', self.__endpoint)
|
||||||
|
request.add_header('X-sdk-date', sdk_date)
|
||||||
|
request.add_header('Authorization', self.__build_authorization(request))
|
||||||
|
print('Request headers: ' + str(request.headers))
|
||||||
|
|
||||||
|
try:
|
||||||
|
f = urllib2.urlopen(request, timeout=45)
|
||||||
|
response = f.read().decode('utf-8')
|
||||||
|
print(response)
|
||||||
|
return response
|
||||||
|
except urllib2.HTTPError as e:
|
||||||
|
print('hwydns#__request raise urllib2.HTTPError: ' + str(e))
|
||||||
|
raise SystemExit(e)
|
||||||
|
|
||||||
|
def __build_sdk_date(self):
|
||||||
|
return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
|
||||||
|
|
||||||
|
def __build_authorization(self, request):
|
||||||
|
algorithm = 'SDK-HMAC-SHA256'
|
||||||
|
canonical_request = self.__build_canonical_request(request)
|
||||||
|
canonical_request_hexencode = self.__hexencode_sha256_hash(canonical_request.encode('utf-8'))
|
||||||
|
string2sign = "%s\n%s\n%s" % (algorithm, request.get_header('X-sdk-date'), canonical_request_hexencode)
|
||||||
|
sign = self.__build_sign(string2sign)
|
||||||
|
|
||||||
|
return "%s Access=%s, SignedHeaders=%s, Signature=%s" % (algorithm, self.access_key_id, self.__parse_header_keys(request.headers), sign)
|
||||||
|
|
||||||
|
def __build_canonical_request(self, request):
|
||||||
|
return "%(method)s\n%(path)s\n%(query_string)s\n%(headers)s\n%(header_keys)s\n%(data_hexencode)s" % {
|
||||||
|
'method': request.get_method().upper(),
|
||||||
|
'path': self.__parse_path(request.get_full_url()),
|
||||||
|
'query_string': self.__parse_query_string(request.get_full_url()),
|
||||||
|
'headers': self.__parse_headers(request.headers),
|
||||||
|
'header_keys': self.__parse_header_keys(request.headers),
|
||||||
|
'data_hexencode': self.__hexencode_sha256_hash(request.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
def __parse_path(self, url):
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
path = urlparse.urlsplit(url).path
|
||||||
|
else:
|
||||||
|
path = urllib.urlsplit(url).path
|
||||||
|
|
||||||
|
path = path if path else '/'
|
||||||
|
pattens = urllib.unquote(path).split('/')
|
||||||
|
|
||||||
|
tmp_paths = []
|
||||||
|
for v in pattens:
|
||||||
|
tmp_paths.append(self.__urlencode(v))
|
||||||
|
urlpath = '/'.join(tmp_paths)
|
||||||
|
if urlpath[-1] != '/':
|
||||||
|
urlpath = urlpath + '/'
|
||||||
|
return urlpath
|
||||||
|
|
||||||
|
def __parse_query_string(self, url):
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
query = urlparse.parse_qs(urlparse.urlsplit(url).query)
|
||||||
|
else:
|
||||||
|
query = urllib.parse_qs(urllib.urlsplit(url).query)
|
||||||
|
|
||||||
|
sorted_query = sorted(query.items(), key=lambda item: item[0])
|
||||||
|
sorted_query_string = ''
|
||||||
|
for (k, v) in sorted_query:
|
||||||
|
if type(v) is list:
|
||||||
|
v.sort()
|
||||||
|
for item in v:
|
||||||
|
sorted_query_string += '&' + self.__urlencode(k) + '=' + self.__urlencode(item)
|
||||||
|
else:
|
||||||
|
sorted_query_string += '&' + self.__urlencode(k) + '=' + self.__urlencode(v)
|
||||||
|
|
||||||
|
return sorted_query_string[1:]
|
||||||
|
|
||||||
|
def __parse_headers(self, headers):
|
||||||
|
format_headers = dict(((k.lower(), v.strip())) for (k, v) in headers.items())
|
||||||
|
|
||||||
|
header_string = ''
|
||||||
|
for (k, v) in sorted(format_headers.items(), key=lambda item: item[0]):
|
||||||
|
header_string += "%s:%s\n" % (k, v)
|
||||||
|
return header_string
|
||||||
|
|
||||||
|
def __parse_header_keys(self, headers):
|
||||||
|
return ';'.join(sorted(map(lambda key: key.lower(), headers.keys())))
|
||||||
|
|
||||||
|
def __build_sign(self, string2sign):
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
hm = hmac.new(self.secret_access_key, string2sign, digestmod=hashlib.sha256).digest()
|
||||||
|
else:
|
||||||
|
hm = hmac.new(self.secret_access_key.encode('utf-8'), string2sign.encode('utf-8'), digestmod=hashlib.sha256).digest()
|
||||||
|
return binascii.hexlify(hm).decode()
|
||||||
|
|
||||||
|
def __urlencode(self, string):
|
||||||
|
return urllib.quote(str(string), safe='~')
|
||||||
|
|
||||||
|
def __hexencode_sha256_hash(self, data):
|
||||||
|
sha256 = hashlib.sha256()
|
||||||
|
sha256.update(data)
|
||||||
|
return sha256.hexdigest()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('开始调用华为云 DNS API')
|
||||||
|
print('-'.join(sys.argv))
|
||||||
|
|
||||||
|
_, action, certbot_domain, acme_challenge, certbot_validation, api_key, api_secret = sys.argv
|
||||||
|
|
||||||
|
subdomain, main_domain = HwyDns.getDomain(certbot_domain)
|
||||||
|
if subdomain:
|
||||||
|
subdomain = acme_challenge + '.' + subdomain
|
||||||
|
else:
|
||||||
|
subdomain = acme_challenge
|
||||||
|
|
||||||
|
hwydns = HwyDns(api_key, api_secret)
|
||||||
|
|
||||||
|
if 'add' == action:
|
||||||
|
hwydns.add_domain_record(main_domain, subdomain, certbot_validation)
|
||||||
|
elif 'clean' == action:
|
||||||
|
hwydns.delete_domain_record(main_domain, subdomain)
|
||||||
|
|
||||||
|
print('结束调用华为云 DNS API')
|
||||||
Loading…
Reference in New Issue