利用腾讯云API更新域名解析记录

之前在腾讯云注册了域名,最近想让自己的一台主机(有公网IP,但不固定)也能通过域名访问。于是就想腾讯云有没有相关接口来实现域名解析记录的修改更新操作,这样的话,一旦本地主机的IP地址发生了变化,就可以了通过它自动完成记录的修改。结果确实有的,文档参见:https://cloud.tencent.com/document/api/302/4032

但是这套API的使用却很复杂,估计这也就是为什么腾讯云现在搞了一套API 3.0的东西。看了下,3.0版本的API直接提供了相关SDK,相对来说就很好使用了。但是!API 3.0没有域名解析相关的API!所以还得用2.0。

1. 签名鉴权

API调用方面,其他部分倒也不是什么问题,关键在于请求参数中Signature参数的计算。该参数用来验证调用者的身份合法性,但签名的过程却比较复杂,经常会出错。网上的教程包括官方文档都是从怎么生成这个签名的角度进行讲解,看了半天也是知其然不知其所以然。这里反向地看待这个问题,看看调用请求被腾讯云服务器接收后,它会怎么进行验证。

假设腾讯云收到了我们发出了一个请求:

https://cns.api.qcloud.com/v2/index.php?Bar=1111&Foo=222&Signature=xxxxxx&SignatureMethod=HmacSHA256

  • 首先作为服务器,他可以知道请求的方式(GET/POST)

  • 然后它先会把Signature的值(xxxxxx)记录下来

  • 最后对除了Signature外的所有请求参数(key=value),按key的字典顺序进行排序

可以生成一个如下格式(忽略空格)的字符串(称为签名原文字符串):

请求方式 请求地址 ? 参数1=值1 & 参数2=值2 ……

如上述示例中为:

GETcns.api.qcloud.com/v2/index.php?Bar=1111&Foo=222&SignatureMethod=HmacSHA256

注意以下几点:

  • 严格区分大小写
  • 不能有任何其他无关符号
  • 参数必须按照字典序排序
  • Signature参数不在其中
  • 请求地址不包括协议(https://)

然后,腾讯服务器会将上述字符串进行加密,加密方式(HmacSHA256/HmacSHA1)由请求参数中的SignatureMethod指定,加密使用的密钥为请求者的SecretKey,然后对加密后的数据进行base64编码。到此服务器可以得到一个签名,将这个签名与请求参数中的Signature比较,如果一致,认证通过。

因为HmacSHA加密的不可逆性,不难知道为什么在生成上述签名原文字符串时为什么必须严格遵守那些约定,因为任何一个字节的错误都会导致加密所得结果完全不同。

2. 代码实现

2.1. API请求

假设已经申请了如下的API密钥:

SecretId: AKIDz8krbsJ5yKBZQpn74WFkmLPx3gnPhESA

SecreKey: Gu5t9xGARNpq86cd98joQYCN3Cozk1qA

import base64         
import hashlib
import hmac
import json
import random
from datetime import datetime
from urllib.parse import urlencode
from urllib.request import urlopen

host = 'cns.api.qcloud.com/v2/index.php'
secret_id = 'AKIDz8krbsJ5yKBZQpn74WFkmLPx3gnPhESA'
secret_key = 'Gu5t9xGARNpq86cd98joQYCN3Cozk1qA'


def request(action, region=None, dict_arg=None, **kw_arg):
params = dict(dict_arg) if dict_arg is not None else {}
params.update(kw_arg)

# 公共参数
params['Action'] = action
if region is not None:
params['Region'] = region
params['Timestamp'] = int(datetime.timestamp(datetime.now()))
params['Nonce'] = random.randint(1, 2 ** 16 - 1)
params['SecretId'] = secret_id
params['SignatureMethod'] = 'HmacSHA256'
params = {str(k): str(v) for k, v in params.items()}

# 签名原文字符串
text = 'GET' + host + '?' + '&'.join(k + '=' + v for k, v in sorted(params.items()))
# 加密
signature = hmac.new(secret_key.encode(), text.encode(), hashlib.sha256).digest()
# base64编码并添加到参数列表中
params['Signature'] = base64.b64encode(signature).decode()

# 生成url,参数中可能会有特殊字符,所以需要urlencode
url = 'https://' + host + '?' + urlencode(sorted(params.items()))
# 发出请求
contents = json.loads(urlopen(url).read().decode('unicode-escape'))
# 校验是否成功
if contents['code'] != 0:
raise Exception(contents['message'])
# 返回数据
return contents['data']

参数:

  • action根据腾讯云文档确定
  • 对于域名解析相关API操作,region不用填写(毕竟又不像云主机在某个地区的机房)
  • dict_arg已字典的形式传入接口请求参数(不需要公共请求参数)
  • kw_arg作用与dict_arg等价,但是可以关键字参数的形式指定

返回:API返回数据中的data字段

异常:当API返回的code不为0(调用失败)时

2.2. 获取本机IP

Python标准库没有直接的实现,不过可以利用Socket建立一个外部连接,间接获取本机的IP地址。

至于外部连接的目标可以任意,比如可以使用谷歌的DNS服务器:

import socket
family = socket.AF_INET # ipv6时改为socket.AF_INET6
server = '8.8.8.8' # ipv6时改为'2001:4860:4860::8888'
s = socket.socket(family, socket.SOCK_DGRAM)
s.connect((server, 80))
my_ip = s.getsockname()[0]

2.3. 修改记录值

假设你在腾讯云拥有域名yourdomain.com,然后已经添加了DNS解析记录home(记录值随便填,待会儿修改)。

# 获取原记录值列表
records = request('RecordList', domain=domain)['records']
for record in records:
# 找到对应的记录值
if record['name'] == 'home':
# 如果有修改的必要的话
if record['value'] != my_ip:
# 在原记录值的基础上修改
request('RecordModify',
domain=domain,
recordId=record['id'],
subDomain=record['name'],
recordType=record['type'],
recordLine=record['line'],
value=my_ip,
ttl=record['ttl'],
mx=record['mx'])

通过运行这段代码,home.yourdomain.com将被定向到代码中my_ip指定的地址,完成记录值修改操作。

最后,将上述程序设置成为每隔一定时间自动运行(linux的crontab),就可以实现域名到本地可变IP主机的绑定。