0x01 前言

我的博客和腾讯云CDN都是用let’s encrypt的数字证书,每次签发的有效期为90天,也就是说每年我至少需要手动更换4次数字证书。

这里有个大问题,我都懒得出汁了,监控数字证书的有效期都要依靠腾讯云的服务,每次需要更新我都是等到最后几天才进行操作。为此,我决定写个简单的脚本,实现自动更换腾讯云CDN的数字证书。

0x02 准备

在使用该脚本前需要先在腾讯云CDN服务中完成域名的初步配置,也就是完成域名的添加并处于正常服务状态。完成添加的域名是否已启用HTTPS并不重要,在本脚本中的配置文件有相关设置内容,请注意本文的相关内容。

确认腾讯云CDN的服务处于正常状态后,需要准备调用API时用于验证的SecretId与SecretKey,该部分请根据腾讯云的文档进行安全配置,在这里我给出最快捷但不安全的获取方式:

登入腾讯云的管理界面后,将鼠标放置在用户名上,在弹出的菜单中选择“访问管理”打开管理页面,然后单击“云API密钥”中的“API密钥管理”,在打开的页面中新建或查看相关API密钥对。

注意!在此处建立API密钥对将有可能让你的腾讯云账户处于危险的处境,请根据腾讯云的相关手册进行安全配置!

将此密钥对记录到本地,稍后需要填写到脚本中。

第三步需要准备有效的数字证书,数字证书必须处于有效期内,必须是由可信的颁发机构颁发的且格式为pem。

当数字证书未生效、已过期或不受信任将使脚本运行错误并使腾讯云返还错误信息。有误的数字证书并不会对生产环境造成影响,因为它无法通过脚本和腾讯云的验证。

0x03 脚本

0x03.1 逻辑

我这个脚本的逻辑很简单,主要是校验证书的有效期与相关域名是否在该证书的授权范围内。如果一切正常、有效,则调用腾讯云的API。

脚本内并无校验证书有效性的模块,如果证书是由不受信任的颁发机构颁发的,调用腾讯云API后将返还错误信息。

0x03.2 配置文件

以下是配置文件的内容,json格式,可添加多个域名:

相关字段的意义如下:

腾讯云CDN HTTPS配置API相关文档地址:HTTPS 配置

  • https_type:对应以上文档地址中的输入参数“httpsType”,“2”为上传自有证书,并协议跟随回源;
  • https_force_switch:对应以上文档地址中的输入参数“forceSwitch”,“2”开启 https 强制跳转(302);
  • http2:是否开启HTTP/2的支持;
  • secret_id:腾讯云API密钥对中的“SecretId”;
  • secret_key:腾讯云API密钥对中的“SecretKey”;
  • cert_filename:定义数字证书的文件名;
  • key_filename定义数字证书私钥的文件名;
  • validity:如果证书有效期大于设定值将执行脚本。

以上内容请根据实际情况进行配置,在我的使用环境中使用了通配数字证书,所以可以在各个域名下的配置文件中配置不同的证书与私钥文件名。

0x03.3 证书SAN字段

Subject Alternative Name简称为:SAN,该字段中记录着数字证书授权的所有域名:

在脚本中将读取该字段的内容,与配置文件中的key,也就是域名相比对。如果配置文件中的域名在该数字证书的授权范围内,则通过验证,否则将抛出错误并跳过。

在提取该字段的模块中,我是用cryptography这个函数:

该自定义函数输出字典格式的内容,内容如下:

返还的内容中除了SAN字段的域名以外,还包含该证书的序列号与有效期。其中有效期为datetime类型,方便传递到下一个函数进行运算。

0x03.4 证书有效期

首先需要确认证书已生效,然后判断证书的失效期大于配置文件中“validity”的值,确认无误后分别将证书与私钥的内容进行base64编码:

如果相关的时间有误,则返还False。

0x03.5 检查CDN域名

在调试中我觉得需要增加一个检查腾讯CDN域名的模块,以此进一步提升可靠性:

该模块会调用腾讯云CDN的API,获取CDN服务中所有的域名并以字典的形式输出,方便后续的模块检查配置文件中的域名是否已完成配置。

0x03.6 SAN校验

既然获得了证书中SAN字段的内容,接下来就需要进行校验,以下是校验模块的代码:

一开始我是使用正则来进行校验的,但实现起来过于复杂,最终形成了以上的形式。首先分为两种情况,一种是适配通配域名,另一种是普通域名。

首先函数接收两个传入参数,使用遍历功能逐一匹配alt_name变量中的域名。

  1. 如果第1个字符为“*”,则为通配域名,选取第3个到最后一个字符的内容传递给变量“i”;
  2. 将该变量中与变量“domain”相同的部分删除并重新赋值给变量“domain”;
  3. 统计变量“domain”中“.”的数量,如果为1则返还True,否则跳过此次循环。

第二种情况比较简单,如果变量“domain”匹配上列表“alt_name”中的某个值,则返还True否则跳过此次循环。

如果循环结束还没返还True,则返还False。

在这里演示下比较复杂的部分,举个例子,通配域名是这样的:*.ngx.hk,而我的CDN域名是这样的:cdn.ngx.hk,我需要怎样才能判断CDN域名在通配域名的子集里?

首先通配域名有一个特征,第一个字符肯定是星号“*”,后面跟着一个半个字符“.”,这样从第3个字符开始到最后一个字符为域名,这里成为域名i。

此时只需要将CDN域名中与域名i相同的部分删除,就会剩下“cdn.”。如果不是,则说明该CDN域名不在通配域名的子集里!

最后只需要计算删除后剩下的部分中的半个字符“.”的数量,如果为1则通过,否则为False。

0x03.7 临时文件

最后还需要一个临时文件的帮助,减少不必要的API调用。因为API调用次数在某一时段内是有限制的,所需要记录下域名与数字证书的序列号。

当脚本执行时或检索该临时文件,找到相关的序列号,如果临时文件中的序列号与证书文件中的一致,则不调用腾讯云CDN API,因为正是并未发生改动。

0x04 结语

该脚本尚待完善,但基本功能已经实现,只需要将证书放置在cert目录中并填写配置文件,通过cronjob定时调用即可。

具体、最新的代码可关注我的私有gitlab: