昨天领导给我发消息说,有一个阿里云泛域名的 SSL 证书快要过期了,需要把用到该证书的子域名替换下。
伙计们,你们遇到过类似的问题吗?
一般如果用到的子域名比较少的话,直接替换就可以了,没什么操作难度。但是不同的是,我们一个域名下配置了很多子域名,这些子域名分布在不同的服务器上,有的子域名甚至配置了负载均衡,而并没有人能告诉我,有哪些子域名使用到了这个 SSL 证书,这可咋整?
我问领导之前这种情况一般是怎么处理的,领导告诉我说,没啥好招,全凭「手工处理」。把域名往部门群里一发,大家各自认领,谁有用到的谁负责替换。
这种做法,只能说:简单暴力。
有可能这样做能覆盖到 90% 的子域名,但是不排除有些子域名因为时间久远或者原来负责的人已经离职导致被忽略。如果证书过期而没有被替换的话,可能业务就会受到影响了。
有没有一种更好的办法来处理这种事情呢?
我们来梳理一下思路,为了更高效地处理这件事,我们需要解决掉以下几个问题:
整理出所有解析的子域名
每一个域名都需要检查是否配置了 SSL 证书
除此之外,还需要域名执行的服务器 IP 和 SSL 证书的一些详细信息
有了基本的思路,接下来我们就来看看如何一步步实现,把思路变成解决方案。
首先,我们需要做的就是获取到主域名下所有已经设置了 DNS 解析的子域名。
这里我们使用的是阿里云的 DNS 解析服务,我们可以在域名解析界面,导出所有 DNS 解析的记录,如下:
选择 Excel 格式导出:
导出后的 Excel 格式如下:
这里我们通过筛选记录类型为 A类型
的记录,即为所有子域名的解析记录。
到这里,我们已经解决了第一个问题:获取到主域名下所有的子域名。接下来要做的就是检查每一个子域名是否配置了 SSL 证书。
要做这件事,我们先考虑能不能通过一款插件,只要输入一个域名,就能判断是否配置了 SSL 证书,甚至还能拿到 SSL 证书的基本信息。有没有现成的好用的插件呢?
老样子,遇到此种情况,先到 packagist 市场逛一逛,看看能不能淘到什么神奇的宝贝。毕竟,有现成的好用的轮子,就没有必要重复造轮子了。
我们按照关键字「ssl」 来试一下:
这里,我们根据包的描述大概能够推测出包的用途来。
我们发现,排名第二的这个包提供了查询 SSL 证书属性的功能,我们点进去看看是不是我们想要的插件。
通过介绍信息,我们可以了解到这款扩展包可以很便捷地通过多种途径获取到 SSL 证书相关的信息,比如证书是否有效,证书过期时间等。看到这里,我们已经看到希望了:这不就是我们想要的功能么。
接下来,我们要做的就是测试一下这个扩展包,通过写一个脚本,看看能不能实现我们预期的效果。
这里,我们借助 Laravel 的 Artisan 来实现。无他,唯好用尔。
代码逻辑如下:
public function checkHttpsConnection(){//========== 定义源文件和目标文件 ==========$originFile = storage_path('tmp/domain-origin.csv');$targetFile = storage_path('tmp/domain-target.csv');//========== 获取目标文件句柄 ==========$handle = fopen($targetFile, 'w');//========== 文件中文编码兼容性处理 ==========fwrite($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));//========== 写入表头 ==========$head = ['域名', '服务器IP', '证书签发域名', '证书过期时间', '备注'];fputcsv($handle, $head);//========== 使用SplFileObject类处理文件 ==========$splFileObj = new \SplFileObject($originFile, 'r+');//========== 跳过行首 ==========$splFileObj->current();$splFileObj->next();while (!$splFileObj->eof()) {//========== 读取行并移动文件指针 ==========$lineString = trim($splFileObj->current());$splFileObj->next();//========== 文件行格式判断 ==========if (!$lineString) {continue;}$line = explode(',', $lineString);if (count($line) != 4) {Log::debug("Invalid line format for original domain file.//{$splFileObj->key()}//{$lineString}");continue;}//========== 初始化行内参数 ==========list($domain, $prefix, $ip, $remark) = $line;$totalDomain = "{$prefix}.{$domain}";try {//========== 通过域名实例化证书 ==========$certificate = SslCertificate::createForHostName("{$totalDomain}", 5);//========== 验证证书有效性 ==========if (!$certificate->isValid()) {Log::debug("无效证书:{$totalDomain}");continue;}//========== 验证证书是否过期 ==========if ($certificate->isExpired()) {Log::debug("证书已过期:{$totalDomain}");continue;}//========== 保存满足条件的域名信息 ==========fputcsv($handle, [ $totalDomain, $ip, $certificate->getDomain(), $certificate->expirationDate()->toDateString(), $remark ]);} catch (Exception $e) {Log::error("解析 SSL 证书失败:{$lineString}|{$e->getLine()}|{$e->getMessage()}");}}fclose($handle);}
这里,我们源文件的格式如下:
domain-origin.csv
主域名,主机记录,记录值,备注 xxx.com,a,127.0.0.1,测试域名
目标文件的格式如下:
domain-target.csv
域名,服务器IP,证书签发域名,证书过期时间,备注 a.xxx.com,127.0.0.1,*.xxx.com,2024-02-29,测试域名
代码的逻辑通过阅读注释基本不难理解,其主要逻辑无非就是从源文件中读取域名记录,然后借助 spatie/ssl-certificate 这个扩展包获取 SSL 证书相关的信息,如果获取不到则按异常处理,最后将获取到的证书信息输出到目标文件中。
这样,通过运行脚本,我们可以批量获取域名的 SSL 证书情况,然后对即将过期的证书进行替换。同时,我们根据域名指向的服务器 IP 信息还能定位到相关的服务器。
有了这些信息,就不需要再动员业务小伙伴去一个个域名排查了。剩下的替换工作,我一个人就可以搞定了,省时又省力,岂不美哉。
其实,纵观全文,我们从整理思路到最终实现我们预期的效果,并没有花费很大的功夫。但是,越是这种看似简单的需求,其实越值得我们思考。
如果我们通过手工的方式来处理的话,当解析的子域名比较多时,一是费时费力,二是容易产生遗漏。
而通过流水线工作流程的处理思路,我们可以把看似杂乱无章的需求,整理成一道道流程化的工序,把原本需要人工处理的事情,交给代码来做,这样整体效率就会提升很多。
初次处理可能会花一些时间整理思路,但是一旦做成功能性脚本以后,后续不论有多少域名,都可以当成流水化的作业处理,而且也不存在交接困难的问题。
其实,通过这个处理思路,我们还可以写一个定时监控的脚本。比如,当我们某个域名需要配置 SSL 证书时,我们就放在我们监控的配置项中,然后通过定时脚本来监控所有配置域名的 SSL 证书连通性及是否将要过期等信息,对出现的问题及时进行告警(尽管类似阿里云的云服务商本身提供了证书过期告警功能,但这并不妨碍我们作为补充性的告警策略来使用)。
用「二八定律」来讲就是:用 80% 的时间思考,用 20% 的时间做事。
如果觉得博客文章对您有帮助,异或土豪有钱任性,可以通过以下扫码向我捐助。也可以动动手指,帮我分享和传播。您的肯定,是我不懈努力的动力!感谢各位亲~