Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加腾讯云 DNS API #5

Merged
merged 1 commit into from
Nov 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Let's Certbot 是一个基于 [Certbot](https://certbot.eff.org/) 用于自动
目前支持的域名服务商:

- [阿里云](https://www.aliyun.com/)
- [腾讯云](https://cloud.tencent.com/)

## 安装

Expand Down Expand Up @@ -50,8 +51,11 @@ $ cp config.json.example config.json
| 名称 | 必须 | 描述 | 默认 |
| ---------------------------- | ----- | ------------------------------------------------------------------ | --------------------- |
| base.email | true | 邮箱地址,用于接收续期等通知 | |
| api.aliyun.access_key_id | true | 阿里云帐号的 AccessKey ID | |
| api.aliyun.access_key_secret | true | 阿里云帐号的 AccessKey Secret | |
| api | true | 提供任一个域名服务商的访问密钥 | |
| api.aliyun.access_key_id | false | 阿里云帐号的 AccessKey ID | |
| api.aliyun.access_key_secret | false | 阿里云帐号的 AccessKey Secret | |
| api.qcloud.secret_id | false | 腾讯云帐号的 SecretId | |
| api.qcloud.secret_key | false | 腾讯云帐号的 SecretKey | |
| log.enable | false | 是否启用日志跟踪 | false |
| log.logfile | false | 日志文件路径 | ./log/application.log |
| deploy.servers | false | 部署服务器列表 | |
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ On dns challenge, you need to set a TXT DNS record with specific contents on dom
Supports domain name registrar at persent:

- [Aliyun](https://www.aliyun.com/)
- [Tencent Cloud](https://cloud.tencent.com/)

## Installation

Expand Down Expand Up @@ -52,8 +53,11 @@ Before running Let's Certbot, you have the following configuration to change:
| Name | Required | Description | Default |
| ---------------------------- | -------- | ------------------------------------------------------------------------------------ | --------------------- |
| base.email | true | Email address for important renewal notifications | |
| api.aliyun.access_key_id | true | AccessKey ID of Aliyun account | |
| api.aliyun.access_key_secret | true | AccessKey Secret of Aliyun account | |
| api | true | Provide access keys for supported domain name registrar | |
| api.aliyun.access_key_id | false | AccessKey ID of Aliyun account | |
| api.aliyun.access_key_secret | false | AccessKey Secret of Aliyun account | |
| api.qcloud.secret_id | false | SecretId of Tencent Cloud account | |
| api.qcloud.secret_key | false | SecretKey Secret of Tencent Cloud account | |
| log.enable | false | Whether to enable log tracker | false |
| log.logfile | false | The path of log file | ./log/application.log |
| deploy.servers | false | The deployment servers | |
Expand Down
5 changes: 3 additions & 2 deletions api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

__all__ = ['Aliyun']
__all__ = ['Aliyun', 'Qcloud']

from api.aliyun import Aliyun
from api.aliyun import Aliyun
from api.qcloud import Qcloud
29 changes: 17 additions & 12 deletions api/aliyun.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-

import sys
import os
import time
import urllib
import base64
Expand All @@ -16,7 +17,10 @@
import urllib.request as urllib2
import urllib.parse as urllib

logger = logging.getLogger('logger')
root_path = os.path.sep.join([os.path.split(os.path.realpath(__file__))[0], '..'])

sys.path.append(root_path)
from lib import Logger

class Aliyun:
__endpoint = 'https://alidns.aliyuncs.com'
Expand Down Expand Up @@ -51,13 +55,15 @@ def to_string(self):

def __request(self, params):
url = self.__compose_url(params)
Logger.info('Request URL: ' + url)
request = urllib2.Request(url)
try:
f = urllib2.urlopen(request, timeout=45)
response = f.read().decode('utf-8')
logger.info(response)
Logger.info(response)
return response
except urllib2.HTTPError as e:
logger.error('aliyun#__request raise urllib2.HTTPError: ' + e.read().strip().decode('utf-8'))
Logger.error('aliyun#__request raise urllib2.HTTPError: ' + str(e))
raise SystemExit(e)

def __compose_url(self, params):
Expand All @@ -75,7 +81,7 @@ def __compose_url(self, params):
final_params.update(params)

final_params['Signature'] = self.__compute_signature(final_params)
logger.info('Signature' + str(final_params['Signature']))
Logger.info('Signature ' + str(final_params['Signature']))

url = '%s/?%s' % (self.__endpoint, urllib.urlencode(final_params))

Expand All @@ -88,15 +94,14 @@ def __compute_signature(self, params):
for (k, v) in sorted_params:
query_string += '&' + self.__percent_encode(k) + '=' + self.__percent_encode(str(v))


string_to_sign = 'GET&%2F&' + self.__percent_encode(query_string[1:])
try:
if sys.version_info < (3,0):
digest = hmac.new(str(self.access_key_secret + "&"), str(string_to_sign), hashlib.sha1).digest()
else:
digest = hmac.new((self.access_key_secret + "&").encode(encoding="utf-8"), string_to_sign.encode(encoding="utf-8"), hashlib.sha1).digest()
except Exception as e:
logger.error(e)
Logger.error(e)

if sys.version_info < (3,1):
signature = base64.encodestring(digest).strip()
Expand All @@ -105,11 +110,11 @@ def __compute_signature(self, params):

return signature

def __percent_encode(self, str):
def __percent_encode(self, string):
if sys.version_info <(3,0):
res = urllib.quote(str.decode(sys.stdin.encoding).encode('utf8'), '')
res = urllib.quote(string.decode(sys.stdin.encoding).encode('utf8'), '')
else:
res = urllib.quote(str.encode('utf8'))
res = urllib.quote(string.encode('utf8'))
res = res.replace('+', '%20')
res = res.replace('\'', '%27')
res = res.replace('\"', '%22')
Expand All @@ -119,8 +124,8 @@ def __percent_encode(self, str):
return res

if __name__ == '__main__':
logger.info('开始调用阿里云 DNS API')
logger.info('-'.join(sys.argv))
Logger.info('开始调用阿里云 DNS API')
Logger.info('-'.join(sys.argv))

_, action, certbot_domain, acme_challenge, certbot_validation, access_key_id, access_key_secret = sys.argv

Expand All @@ -131,4 +136,4 @@ def __percent_encode(self, str):
elif 'delete' == action:
aliyun.delete_domain_record(certbot_domain, acme_challenge)

logger.info('结束调用阿里云 DNS API')
Logger.info('结束调用阿里云 DNS API')
145 changes: 145 additions & 0 deletions api/qcloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os
import time
import urllib
import base64
import hashlib
import hmac
import logging
import json

if sys.version_info < (3,0):
import urllib2
import urllib
else:
import urllib.request as urllib2
import urllib.parse as urllib

root_path = os.path.sep.join([os.path.split(os.path.realpath(__file__))[0], '..'])

sys.path.append(root_path)
from lib import Logger

class Qcloud:
__host = 'cns.api.qcloud.com'
__path = '/v2/index.php'

def __init__(self, secret_id, secret_key):
self.secret_id = secret_id
self.secret_key = secret_key

# @example qcloud.add_domain_record("example.com", "_acme-challenge", "123456", "TXT")
def add_domain_record(self, domain, rr, value, _type = 'TXT'):
params = {
'Action' : 'RecordCreate',
'domain' : domain,
'subDomain' : rr,
'recordType' : _type,
'recordLine' : '默认',
'value' : value
}
self.__request(params)

# @example qcloud.delete_domain_record("example.com", "_acme-challenge", "TXT")
def delete_domain_record(self, domain, rr, _type = 'TXT'):
result = self.get_domain_records(domain, rr, _type)
result = json.loads(result)

for record in result['data']['records']:
self.delete_domain_record_by_id(domain, record['id'])

def delete_domain_record_by_id(self, domain, _id):
params = {
'Action' : 'RecordDelete',
'domain' : domain,
'recordId' : _id
}
self.__request(params)

def get_domain_records(self, domain, rr, _type = 'TXT'):
params = {
'Action' : 'RecordList',
'domain' : domain,
'subDomain' : rr,
'recordType' : _type
}
return self.__request(params)

def to_string(self):
return 'qcloud[secret_id=' + self.secret_id + ', secret_key=' + self.secret_key + ']'

def __request(self, params):
url = self.__compose_url(params)
Logger.info('Request URL: ' + url)
request = urllib2.Request(url)
try:
f = urllib2.urlopen(request, timeout=45)
response = f.read().decode('utf-8')
Logger.info(response)
return response
except urllib2.HTTPError as e:
Logger.error('aliyun#__request raise urllib2.HTTPError: ' + str(e))
raise SystemExit(e)

def __compose_url(self, params):
common_params = {
'SecretId' : self.secret_id,
'SignatureMethod' : 'HmacSHA1',
'Nonce' : int(round(time.time() * 1000)),
'Timestamp' : int(time.time())
}

final_params = common_params.copy()
final_params.update(params)

final_params['Signature'] = self.__compute_signature(final_params)
Logger.info('Signature ' + str(final_params['Signature']))

url = 'https://%s%s?%s' % (self.__host, self.__path, urllib.urlencode(final_params))

return url

def __compute_signature(self, params):
sorted_params = sorted(params.items(), key=lambda params: params[0])

query_string = ''
for (k, v) in sorted_params:
query_string += '&' + self.__percent_encode(k) + '=' + str(v)

string_to_sign = 'GET' + self.__host + self.__path + '?' + query_string[1:]

try:
if sys.version_info < (3,0):
digest = hmac.new(str(self.secret_key), str(string_to_sign), hashlib.sha1).digest()
else:
digest = hmac.new(self.secret_key.encode(encoding="utf-8"), string_to_sign.encode(encoding="utf-8"), hashlib.sha1).digest()
except Exception as e:
Logger.error(e)

if sys.version_info < (3,1):
signature = base64.encodestring(digest).strip()
else:
signature = base64.encodebytes(digest).strip()

return signature

def __percent_encode(self, string):
return string.replace('_', '.')

if __name__ == '__main__':
Logger.info('开始调用腾讯云 DNS API')
Logger.info('-'.join(sys.argv))

_, action, certbot_domain, acme_challenge, certbot_validation, secret_id, secret_key = sys.argv

qcloud = Qcloud(secret_id, secret_key)

if 'add' == action:
qcloud.add_domain_record(certbot_domain, acme_challenge, certbot_validation)
elif 'delete' == action:
qcloud.delete_domain_record(certbot_domain, acme_challenge)

Logger.info('结束调用腾讯云 DNS API')
16 changes: 6 additions & 10 deletions bin/manual.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,17 @@ def test(domain, api_type = 'aliyun'):

def __get_api_client(api_type = 'aliyun'):
try:
switch = {
'aliyun': __get_aliyun_client
}
return switch[api_type]()
key = Config['api'][api_type]

if 'aliyun' == api_type:
return api.Aliyun(key['access_key_id'], key['access_key_secret'])
elif 'qcloud' == api_type:
return api.Qcloud(key['secret_id'], key['secret_key'])
except KeyError as e:
print('The ' + api_type + ' DNS API is not be supported at persent')
Logger.error('manual#get_api raise KeyError: ' + str(e))
sys.exit()

def __get_aliyun_client():
access_key_id = Config['api']['aliyun']['access_key_id']
access_key_secret = Config['api']['aliyun']['access_key_secret']

return api.Aliyun(access_key_id, access_key_secret)

def __extract_maindomain_and_challenge(domain):
sudomain, maindomain = Utils.extract_domain(domain)

Expand Down
6 changes: 4 additions & 2 deletions bin/obtain.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
--server https://acme-v02.api.letsencrypt.org/directory \
--manual \
--manual-public-ip-logging-ok \
--manual-auth-hook "python %(manual_path)s --auth" \
--manual-cleanup-hook "python %(manual_path)s --cleanup" \
--manual-auth-hook "python %(manual_path)s --auth --api %(api)s" \
--manual-cleanup-hook "python %(manual_path)s --cleanup --api %(api)s" \
%(deploy_hook)s \
%(domains)s
'''
Expand All @@ -45,6 +45,7 @@ def run(args):
'cert_name': cert_name,
'force_renewal': force_renewal,
'manual_path': manual_path,
'api': args.api,
'deploy_hook': deploy_hook,
'domains': domains
}
Expand All @@ -59,6 +60,7 @@ def main():
parser.add_argument('-d', '--domains', help='domain list', required=True, nargs='+')
parser.add_argument('-c', '--cert', help='certificate name, e.g. domain.com')
parser.add_argument('-f', '--force', help='force renewal', default=False, action='store_true')
parser.add_argument('--api', help='api type, default: aliyun', default='aliyun')

args = parser.parse_args()

Expand Down
6 changes: 4 additions & 2 deletions bin/renewal.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
--agree-tos \
--manual \
--manual-public-ip-logging-ok \
--manual-auth-hook "python %(manual_path)s --auth" \
--manual-cleanup-hook "python %(manual_path)s --cleanup" \
--manual-auth-hook "python %(manual_path)s --auth --api %(api)s" \
--manual-cleanup-hook "python %(manual_path)s --cleanup --api %(api)s" \
%(deploy_hook)s \
%(cert_names)s \
%(force_renewal)s
Expand All @@ -38,6 +38,7 @@ def run(args):

certbot_cmd = certbot_cmd_template % {
'manual_path': manual_path,
'api': args.api,
'deploy_hook': deploy_hook,
'cert_names': cert_names,
'force_renewal': force_renewal
Expand All @@ -52,6 +53,7 @@ def main():

parser.add_argument('-f', '--force', help='force renewal', default=False, action='store_true')
parser.add_argument('-c', '--certs', help='certificates, e.g. domain.com', default=[], nargs='*')
parser.add_argument('--api', help='api type, default: aliyun', default='aliyun')

args = parser.parse_args()

Expand Down
4 changes: 4 additions & 0 deletions config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
"aliyun": {
"access_key_id": "your_access_key_id",
"access_key_secret": "your_access_key_secret"
},
"qcloud": {
"secret_id": "your_secret_id",
"secret_key": "your_secret_key"
}
},
"log": {
Expand Down