在 TCB 云函数里用 Puppeteer 自动更新 SSL 证书


本文尝试通过 Puppeteer + 腾讯云 CloudBase(TCB)云函数,实现 TCB 自定义域名 SSL 证书的自动更新与绑定。

目前,腾讯云已不再提供一年期免费 SSL 证书,转而采用短期证书,并配套提供了证书自动续期能力。然而,TCB 的自定义域名在证书更新后,仍需手动将新证书“应用”到域名绑定上——这一环节并未自动化(即使证书已自动续期,也不会自动生效于 TCB 域名)。

虽然 90 天的周期提供了较宽裕的容错窗口,但未来证书有效期将缩短至 47 天。对于拥有多个环境、多个自定义域名的项目而言,频繁的手动操作无疑是一场运维噩梦。

因此,本文探索一种无需人工干预的自动化方案:利用 TCB 云函数内置的 Puppeteer 模拟登录控制台,捕获鉴权参数,并调用内部 API,实现证书的自动绑定更新。


环境前提

项目说明
RuntimeNode.js 10
subAccount权限要求: QcloudSSLReadOnlyAccessQcloudCamReadOnlyAccessQcloudTCBFullAccess
Packagepuppeteernode-fetch(node 18 才引入fetch)

核心思路

  1. Puppeteer 登录子账号 打开 https://cloud.tencent.com/login/subAccount/...,自动填密码并跳转。
  2. 捕获关键鉴权参数 监听 DescribeUserNavigation 请求,提取cookies 以及csrfCodeuint 等 Param 字段,拼接为 flag
  3. 调用内部 APIfetch 模拟浏览器请求,调用 ModifyCloudBaseAccessDomain 更新证书。

关键代码片段

启动 Puppeteer(适配 TCB)

const browser = await puppeteer.launch({
  headless: true,
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-dev-shm-usage',
    '--disable-blink-features=AutomationControlled'
  ],
  ignoreDefaultArgs: ['--enable-automation']
});

捕获内部API参数

page.on('request', req => {
  if (req.url().includes('cmd=DescribeUserNavigation') && req.url().includes('csrfCode=')) {
    const url = new URL(req.url());
    fullQueryParams = Object.fromEntries(url.searchParams);
  }
  req.continue();
});
await page.setRequestInterception(true);

调用证书更新 API

const payload = {
  serviceType: "tcb",
  cmd: "ModifyCloudBaseAccessDomain",
   {
    EnvId: envId,
    Domain: domain,
    CertId: certId
  },
  regionId: 4 // 上海
};

const res = await fetch(`https://console.cloud.tencent.com/cgi/capi?cmd=ModifyCloudBaseAccessDomain${flag}`, {
  method: 'POST',
  headers: {
    'cookie': cookie,
    'content-type': 'application/json',
    'origin': 'https://console.cloud.tencent.com',
    'referer': `https://console.cloud.tencent.com/tcb/env/access?envId=${envId}`
  },
  body: JSON.stringify(payload)
});

上述一切在本地work很好,但是当我尝试部署到TCB时发生了噩梦,puppeteer依赖chrome,而一个可用的chrome在云环境上是一个问题,经提交工单,被告知应该采用层来解决大文件部署问题,但是经我测试发现,环境限制压缩后代码体系不得超过50MB,而单chrome文件就超过这一限制,层也不能解决需求,工程师建议我采用Docker解决。我对该方案不太满意,后来发现,TCB例程里有一个调用puppeteer的项目,参考其运行时可能解决调用chrome问题。检查其环境为node 10(很古老的版本⊙﹏⊙∥),甚至不支持fetch请求,需要安装node-fetch解决。

处理完降级node版本带来的各种问题后,测试更新证书成功。


总结

想起了今年一个较为热门言论,小项目不应该使用云原生,避免增加成本,事实上一般环境下通过acme.sh即可解决证书更新问题,若使用 Cloudflare 等老牌CDN业务也完全不需要操心证书问题,而全部云原生却给给我创造了这个需求。事实上,对用户透明的FaaS于我而言反而增加了运维成本。

如需完整可部署代码,欢迎留言!

Comments

Leave a comment