大大更新,操作更简单,如果运行有问题,欢迎@我,或者使用老版本(v2018-12 tag)

This commit is contained in:
ywdblog 2019-03-23 22:33:59 +08:00
commit f20385e95e
7 changed files with 269 additions and 111 deletions

223
: Normal file
View File

@ -0,0 +1,223 @@
# coding:utf-8
import base64
import urllib
import hmac
import pytz
import datetime
import random
import string
import json
import sys
pv = "python2"
#python2
if sys.version_info[0] < 3:
from urllib import quote
from urllib import urlencode
import hashlib
else:
from urllib.parse import quote
from urllib.parse import urlencode
from urllib import request
pv = "python3"
class AliDns:
def __init__(self, access_key_id, access_key_secret, domain_name):
self.access_key_id = access_key_id
self.access_key_secret = access_key_secret
self.domain_name = domain_name
@staticmethod
def getDomain(domain):
domain_parts = domain.split('.')
if len(domain_parts) > 2:
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):])
selfdomain=domain.split(rootdomain)[0]
return (selfdomain[0:len(selfdomain)-1],rootdomain)
return ("",domain)
@staticmethod
def generate_random_str(length=14):
"""
生成一个指定长度(默认14位)的随机数值,其中
string.digits = "0123456789'
"""
str_list = [random.choice(string.digits) for i in range(length)]
random_str = ''.join(str_list)
return random_str
@staticmethod
def percent_encode(str):
res = quote(str.encode('utf-8'), '')
res = res.replace('+', '%20')
res = res.replace('*', '%2A')
res = res.replace('%7E', '~')
return res
@staticmethod
def utc_time():
"""
请求的时间戳。日期格式按照ISO8601标准表示
并需要使用UTC时间。格式为YYYY-MM-DDThh:mm:ssZ
例如2015-01-09T12:00:00Z为UTC时间2015年1月9日12点0分0秒
:return:
"""
utc_tz = pytz.timezone('UTC')
time = datetime.datetime.now(tz=utc_tz).strftime('%Y-%m-%dT%H:%M:%SZ')
return time
@staticmethod
def sign_string(url_param):
percent_encode = AliDns.percent_encode
sorted_url_param = sorted(url_param.items(), key=lambda x: x[0])
can_string = ''
for k, v in sorted_url_param:
can_string += '&' + percent_encode(k) + '=' + percent_encode(v)
string_to_sign = 'GET' + '&' + '%2F' + '&' + percent_encode(can_string[1:])
return string_to_sign
@staticmethod
def access_url(url):
if pv == "python2" :
f = urllib.urlopen(url)
result = f.read().decode('utf-8')
#print(result)
return json.loads(result)
else :
req = request.Request(url)
with request.urlopen(req) as f:
result = f.read().decode('utf-8')
#print(result)
return json.loads(result)
def visit_url(self, action_param):
common_param = {
'Format': 'json',
'Version': '2015-01-09',
'AccessKeyId': self.access_key_id,
'SignatureMethod': 'HMAC-SHA1',
'Timestamp': AliDns.utc_time(),
'SignatureVersion': '1.0',
'SignatureNonce': AliDns.generate_random_str(),
'DomainName': self.domain_name,
}
url_param = dict(common_param, **action_param)
string_to_sign = AliDns.sign_string(url_param)
hash_bytes = self.access_key_secret + "&"
if pv == "python2":
h = hmac.new(hash_bytes, string_to_sign, digestmod=hashlib.sha1)
else :
h = hmac.new(hash_bytes.encode('utf-8'), string_to_sign.encode('utf-8'), digestmod='SHA1')
signature = base64.encodestring(h.digest()).strip()
url_param.setdefault('Signature', signature)
url = 'https://alidns.aliyuncs.com/?' + urlencode(url_param)
#print(url)
return AliDns.access_url(url)
# 显示所有
def describe_domain_records(self):
"""
最多只能查询此域名的 500条解析记录
PageNumber 当前页数起始值为1默认为1
PageSize 分页查询时设置的每页行数最大值500默认为20
:return:
"""
action_param = dict(
Action='DescribeDomainRecords',
PageNumber='1',
PageSize='500',
)
result = self.visit_url(action_param)
return result
# 增加解析
def add_domain_record(self, type, rr, value):
action_param = dict(
Action='AddDomainRecord',
RR=rr,
Type=type,
Value=value,
)
result = self.visit_url(action_param)
return result
# 修改解析
def update_domain_record(self, id, type, rr, value):
action_param = dict(
Action="UpdateDomainRecord",
RecordId=id,
RR=rr,
Type=type,
Value=value,
)
result = self.visit_url(action_param)
return result
# 删除解析
def delete_domain_record(self, id):
action_param = dict(
Action="DeleteDomainRecord",
RecordId=id,
)
result = self.visit_url(action_param)
return result
if __name__ == '__main__':
#filename,ACCESS_KEY_ID, ACCESS_KEY_SECRET = sys.argv
#domain = AliDns(ACCESS_KEY_ID, ACCESS_KEY_SECRET, 'simplehttps.com')
#domain.describe_domain_records()
#增加记录
#print(domain.add_domain_record("TXT", "test", "test"))
# 修改解析
#domain.update_domain_record('4011918010876928', 'TXT', 'test2', 'text2')
# 删除解析记录
# data = domain.describe_domain_records()
# record_list = data["DomainRecords"]["Record"]
# for item in record_list:
# if 'test' in item['RR']:
# domain.delete_domain_record(item['RecordId'])
# 第一个参数是 action代表 (add/clean)
# 第二个参数是域名
# 第三个参数是主机名(第三个参数+第二个参数组合起来就是要添加的 TXT 记录)
# 第四个参数是 TXT 记录值
# 第五个参数是 APPKEY
# 第六个参数是 APPTOKEN
#sys.exit(0)
print(sys.argv)
file_name, cmd ,certbot_domain, acme_challenge, certbot_validation,ACCESS_KEY_ID, ACCESS_KEY_SECRET = sys.argv
certbot_domain=AliDns.getDomain(certbot_domain)
# print (certbot_domain)
if ertbot_domain[0]=="":
selfdomain = acme_challenge
else:
selfdomain = acme_challenge + "." + certbot_domain[0]
print(selfdomain)
domain = AliDns(ACCESS_KEY_ID, ACCESS_KEY_SECRET, certbot_domain[1])
if cmd == "add":
result = (domain.add_domain_record("TXT",selfdomain, certbot_validation))
if "Code" in result:
print ("aly dns 域名增加失败-"+str(result["Code"]) + ":" + str(result["Message"]))
elif cmd == "clean":
data = domain.describe_domain_records()
print (data)
record_list = data["DomainRecords"]["Record"]
sys.exit(0)
if record_list:
for item in record_list:
if (item['RR'] == selfdomain):
domain.delete_domain_record(item['RecordId'])

View File

@ -19,39 +19,46 @@ $ git clone https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-a
$ cd certbot-letencrypt-wildcardcertificates-alydns-au
$ chmod 0777 au.sh autxy.sh augodaddy.sh python-version/au.sh
$ chmod 0777 au.sh
```
2配置
目前该工具支持四种运行环境和场景
1DNS API 密钥
- au.sh操作阿里云 DNS hook shellPHP 环境)。
- autxy.sh操作腾讯云 DNS hook shellPHP 环境)。
- python-version/au.py操作阿里云 DNS hook shell兼容**Python 2/3**,感谢 @Duke-Wu 的 PR。
- augodaddy.sh操作 GoDaddy DNS hook shellPHP 环境),感谢 wlx_1990微信号的 PR。【2019-01-11】
这个 API 密钥什么意思呢?由于需要通过 API 操作阿里云 DNS 或腾讯云 DNS 的记录,所以需要去域名服务商哪儿获取 API 密钥。,然后配置在 au.sh 文件中
这四种运行环境和场景什么意思呢?就是可根据自己服务器环境和域名服务商选择任意一个 hook shell操作的时候任选其一即可
- 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)。
DNS API 密钥
2目前该工具支持四种运行环境和场景通过 hook 文件和参数来调用
- alydns.php修改 accessKeyId、accessSecrec 变量,阿里云 [API key 和 Secrec 官方申请文档](https://help.aliyun.com/knowledge_detail/38738.html)。
- txydns.php修改 txyaccessKeyId、txyaccessSecrec 变量,腾讯云 [API 密钥官方申请文档](https://console.cloud.tencent.com/cam/capi)。
- python-version/alydns.py修改 ACCESS_KEY_ID、ACCESS_KEY_SECRET阿里云 [API key 和 Secrec 官方申请文档](https://help.aliyun.com/knowledge_detail/38738.html)。
- godaddydns.php修改 accessKeyId、accessSecrec 变量GoDaddy [API 密钥官方申请文档](https://developer.godaddy.com/keys)。
- PHP
- au.sh php aly add/cleanPHP 表示选择PHP命令行操作阿里云DNS增加/清空指定 DNS TXT 记录。
- au.sh php txy add/cleanPHP 表示选择PHP命令行操作腾讯云DNS增加/清空指定 DNS TXT 记录。
- Python
- au.sh python aly add/cleanPHP 表示选择PHP命令行操作阿里云DNS增加/清空指定 DNS TXT 记录。
- au.sh python txy add/cleanPHP 表示选择PHP命令行操作腾讯云DNS增加/清空指定 DNS TXT 记录。
这个 API 密钥什么意思呢?由于需要通过 API 操作阿里云 DNS 或腾讯云 DNS 的记录,所以需要去域名服务商哪儿获取 API 密钥。
四种运行环境和场景什么意思呢?就是可根据自己服务器环境和域名服务商选择任意一个 hook shell包含相应参数
3申请证书
**特别说明:** --manual-auth-hook 指定的 hook 文件四个任选其一au.sh、autxy.sh、augodaddy.sh、python-version/au.sh其他操作完全相同。
**特别说明:** --manual-auth-hook 根据运行环境语言DNS产商add/clean指定 hook 文件和操作参数。
测试是否有错误:
```
# 测试是否有错误
$ ./certbot-auto certonly -d *.example.com --manual --preferred-challenges dns --dry-run --manual-auth-hook /脚本目录/au.shautxy.sh 或 python-version/au.sh下面统一以 au.sh 介绍)
$ ./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"
```
**如果你要选择Python环境**,可以将 --manual-auth-hook 和 --manual-cleanup-hook 的输入修改为 "/脚本目录/au.sh python aly clean"
确认无误后,实际运行(去除 --dry-run 参数):
```
# 实际申请
$ ./certbot-auto certonly -d *.example.com --manual --preferred-challenges dns --manual-auth-hook /脚本目录/au.sh
$ ./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"
```
参数解释(可以不用关心):
@ -62,19 +69,22 @@ $ ./certbot-auto certonly -d *.example.com --manual --preferred-challenges dns
- --dry-run在实际申请/更新证书前进行测试,强烈推荐
- -d表示需要为那个域名申请证书可以有多个。
- --manual-auth-hook在执行命令的时候调用一个 hook 文件
- --manual-cleanup-hook清除 DNS 添加的 TXT 值
如果你想为多个域名申请通配符证书(合并在一张证书中,也叫做 **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
$ ./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"
```
### 续期证书
**注意根据自己的环境和DNS厂商选择 hook 和参数**
1对机器上所有证书 renew
```
$ ./certbot-auto renew --manual --preferred-challenges dns --manual-auth-hook /脚本目录/au.sh
$ ./certbot-auto renew --manual --preferred-challenges dns --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean"
```
2对某一张证书进行续期
@ -92,7 +102,7 @@ $ ./certbot-auto certificates
记住证书名,比如 simplehttps.com然后运行下列命令 renew
```
$ ./certbot-auto renew --cert-name simplehttps.com --manual-auth-hook /脚本目录/au.sh
$ ./certbot-auto renew --cert-name simplehttps.com --manual-auth-hook "/脚本目录/au.sh php aly add" --manual-cleanup-hook "/脚本目录/au.sh php aly clean"
```
### 加入 crontab
@ -101,25 +111,31 @@ $ ./certbot-auto renew --cert-name simplehttps.com --manual-auth-hook /脚本
```
#证书有效期<30天才会renew所以crontab可以配置为1天或1周
1 1 */1 * * root certbot-auto renew --manual --preferred-challenges dns --manual-auth-hook /脚本目录/au.sh
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服务比如 nginxapache的机器是同一台那么成功renew证书后可以启动对应的web 服务器运行下列crontab :
```
# 注意只有成功renew证书才会重新启动nginx
1 1 */1 * * root certbot-auto renew --manual --preferred-challenges dns -deploy-hook "service nginx restart" --manual-auth-hook /脚本目录/au.sh
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"
```
**注意只有单机建议这样运行如果要将证书同步到多台web服务器需要有别的方案**
### ROADMAP
1: 关于申请 SAN 证书
1: ~~关于申请 SAN 证书~~
如果你想为 example.com,*.example.com 生成一张证书目前会有Bug可以查看下面的 [issues]( https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au/issues/21) 临时解决。
~~如果你想为 example.com,*.example.com 生成一张证书目前会有Bug可以查看下面的 [issues]( https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au/issues/21) 临时解决。~~
2rsync 证书
2~~代码优化~~
- ~~入口文件修改为一个 au.shAPI Key 统一写在 au.sh 文件~~
- ~~增加调试log可查看 /var/log/certd.log~~
- ~~修复 python 版本的 Bug~~
3rsync 证书
本工具只是生成或renew证书一旦成功后需要将证书同步到其他服务器上大型应用肯定有多台机器比如nginxapachehaproxy应用场景不一样所以很难有统一的方案后面可以考虑写个 github 仓库解决下。

10
au.sh
View File

@ -1,16 +1,16 @@
#!/bin/bash
#ywdblog@gmail.com 欢迎关注我的书
#ywdblog@gmail.com 欢迎关注我的书《深入浅出HTTPS从原理到实战》
#填写腾讯云的AccessKey ID及AccessKey Secret
#如何申请见https://help.aliyun.com/knowledge_detail/38738.html
ALY_KEY="LTAIkLV6coSSKklZ"
ALY_TOKEN="YEGDVHQV4oBC6AGQM9BWaHStUtNE5M"
ALY_KEY=""
ALY_TOKEN=""
#填写腾讯云的SecretId及SecretKey
#如何申请见https://console.cloud.tencent.com/cam/capi
TXY_KEY="AKIDwlPr7DUpLgpZBb4tlT0MWUHtIVXOJwxm"
TXY_TOKEN="mMkxzoTxOirrfJlFYfbS7g7792jEi5GG"
TXY_KEY=""
TXY_TOKEN=""
#GoDaddy的SecretId及SecretKey
#如何申请见https://developer.godaddy.com/getstarted

View File

@ -1,14 +0,0 @@
import sys
def getDomain(domain):
domain_parts = domain.split('.')
if len(domain_parts) > 2:
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):])
selfdomain=domain.split(rootdomain)[0]
return (selfdomain[0:len(selfdomain)-1],rootdomain)
return ("",domain)
print (getDomain(sys.argv[1]))

16
python-version/alydns.py Executable file → Normal file
View File

@ -184,7 +184,6 @@ if __name__ == '__main__':
# domain.delete_domain_record(item['RecordId'])
<<<<<<< HEAD
# 第一个参数是 action代表 (add/clean)
# 第二个参数是域名
# 第三个参数是主机名(第三个参数+第二个参数组合起来就是要添加的 TXT 记录)
@ -226,18 +225,3 @@ if __name__ == '__main__':
print ("域名 API 调用结束")
=======
#print(sys.argv)
file_name, cmd, certbot_domain, acme_challenge, certbot_validation = sys.argv
domain = AliDns(ACCESS_KEY_ID, ACCESS_KEY_SECRET, certbot_domain)
if cmd == "add":
domain.add_domain_record("TXT", acme_challenge, certbot_validation)
elif cmd == "delete":
data = domain.describe_domain_records()
record_list = data["DomainRecords"]["Record"]
if record_list:
for item in record_list:
if (item['RR'] == acme_challenge and item['Value'] == certbot_validation):
domain.delete_domain_record(item['RecordId'])
>>>>>>> 修改python版本的脚本,自动添加和删除DNS记录,解决同时多个子域名的问题

View File

@ -1,28 +0,0 @@
#!/bin/bash
path=$(cd `dirname $0`; pwd)
cmd=$1
echo $path"/alydns.py"
# 调用 python 脚本,自动设置 DNS TXT 记录。
# 第一个参数:命令 add 和 delete
# 第二个参数:需要为那个域名设置 DNS 记录
# 第三个参数: 需要为具体那个 RR 设置
# 第四个参数: letsencrypt 动态传递的 RR 值
echo $cmd $CERTBOT_DOMAIN "_acme-challenge" $CERTBOT_VALIDATION
if [[ -n "$cmd" ]]; then
# 根据自己机器的python环境选择python版本
python $path"/alydns.py" $cmd $CERTBOT_DOMAIN "_acme-challenge" $CERTBOT_VALIDATION >"/var/log/certdebug.log"
if [[ "$cmd" == "add" ]]; then
# DNS TXT 记录刷新时间
/bin/sleep 10
fi
fi
echo "END"
###

View File

@ -1,23 +0,0 @@
#!/bin/bash
path=$(cd `dirname $0`; pwd)
qcloud="${path}/qcloud-dns.py"
option=$1
# 调用 Python 脚本,自动设置 DNS TXT 记录。
# 第一个参数:命令 add 或 delete
# 第二个参数:需要为那个域名设置 DNS 记录
# 第三个参数: 需要为具体那个 RR 设置
# 第四个参数: letsencrypt 动态传递的 RR 值
echo $qcloud $option $CERTBOT_DOMAIN "_acme-challenge" $CERTBOT_VALIDATION
if [[ -n "$option" ]]; then
# 根据自己机器的环境选择 Python 版本
python3 $qcloud $option $CERTBOT_DOMAIN "_acme-challenge" $CERTBOT_VALIDATION > "/var/log/certdebug.log"
if [[ "$option" == "add" ]]; then
# DNS TXT 记录刷新时间
/bin/sleep 10
fi
fi