这篇文章将介绍我的博客部署 SSL 服务的方法,包括自动化申请 SSL 证书并将证书应用到网站,以及上传并部署证书到 CDN 服务(也就是给网站使用的静态资源也加上 SSL)。
需求与整体设计
对于不同架构的服务和不同需求,部署 SSL 证书的方式也不一样。这里简单介绍下我的网站的技术架构和预期效果以及部署 SSL 的技术方案。
项目整体架构
我的网站域名托管在 DNS Pod,后端服务和前端内容则放在了腾讯云 CVM 上。体积较大的静态资源存储在腾讯云 COS 上并配置 CDN 以进行加速。
预期实现的效果
本着省(kou)钱(men)的原则,我打算用 Let’ s encrypt 提供的免费 SSL 证书服务;但是这个证书有效期很短,只有三个月,需要频繁地换发证书,所以我希望整个证书的签发流程和部署流程是全自动化的,最好是可以定时执行、完全摆脱人工干预的。
因为我想要部署的是一个站群,可能会有多个域名,所以我需要申请一个通配符证书。
CDN 服务也需要部署 SSL 证书,这个证书可以和网站本身共用同一个。所以我希望在为网站签发证书之后,同步为 CDN 部署上去。
SSL 部署流程
没有什么是比 CVM 更适合折腾了,如果有,那也只能是实体机。申请签发 SSL 证书的,显然应该是 CVM 机器。
CVM 完成网站本身证书的签发和部署之后,通过调用 CDN 的 API 来实现证书在 CDN 的部署。
流程实现
整个流程基本上分为三个步骤:SSL 证书的申请、本地部署和 CDN 部署。
SSL 证书的申请
Let’s encrypt 推荐了若干种用于申请证书及部署的客户端,其中 acme.sh 符合我几乎全部的要求。
因为 Let’s encrypt 对于通配符证书要求使用 DNS 验证所有权,而 acme.sh 支持配置 DNS API 进行证书自动化签发;我想偷懒完成定时全自动化签发,而 acme.sh 会在证书签发完成后自动部署并添加证书过期前自动重新申请的定时任务;我希望在证书签发和部署在服务器之后再部署一份到 CDN 上,而 acme.sh 支持自定义部署后执行的 ssh,可以让我自己编写部署到 CDN 的脚本。
需要通过 DNS 验证签发证书,你需要在服务区的环境变量中保存 DNS API 的 ID 和密钥。这里我以 DNS Pod 为例,使用其他 DNS 服务的可以参考 acme.sh 官网文档。
首先需要获取 DNS Pod 的 API ID 和密钥,可以在DNS Pod 管理控制台生成和获取。之后,在 CVM 中键入如下命令,把 DNS Pod 的 API ID 和密钥添加到环境变量:
export DPI_Id="你的 DNS Pod API ID"
export DPI_Key="你的 DNS Pod API Key"
之后,安装 acme.sh 到系统中:
curl https://get.acme.sh | sh -s email=你的邮箱
安装完成之后,用如下命令签发:
acme.sh --issue -d 你的域名.cn -d *.你的域名.cn --dns dns_dp
这里 -d
参数后面接的是域名,一个证书可以包含若干个域名,也可以包含通配符域名。而 --dns dns_dp
表示使用 DNS 进行域名所有权验证,并且使用的是 DNS Pod 的 API;如果使用的是其他的 DNS 服务,可以看它的官方文档。
SSL 证书的部署
证书签发完成后,可以继续使用 acme.sh 完成部署。部署的工作也会被加入定时任务,到期重新签发之后自动部署。
acme.sh --install-cert -d 你的域名.cn --cert-file /存放/证书/文件/的/绝对路径 --key-file /存放/Key/文件/的/绝对路径 --fullchain-file /存放/完整证书链/文件/的/绝对路径 --reloadcmd "部署证书完成后需要执行的命令"
如果你的证书有多个域名,这里 -d
后只需要填写签发时输入的第一个。存放证书和 Key 的路径按需填写,Nginx 也需要做相应配置:
ssl_certificate /存放/完整证书链/文件/的/绝对路径;
ssl_certificate_key /存放/Key/文件/的/绝对路径;
因为我的 Nginx 配置用的是完整证书链而非单一的证书,所以 --cert-file /存放/证书/文件/的/绝对路径
这个参数是可以不要的。
我需要在重新部署 SSL 证书后重启 Nginx 服务器,所以需要执行的命令有 service nginx restart
;因为我需要再把这个证书部署到 CDN 上,所以还需要 python3 /某个路径/deploy.py
。综上,这个参数应该填成这样:--reloadcmd "service nginx restart; python3 /某个路径/deploy.py"
。
部署到 CDN
腾讯云 SSL 和 CDN 提供了 Python SDK,所以可以很方便地写一个把证书部署到 CDN 的脚本。
第一步当然是装 SDK:
pip install tencentcloud-sdk-python
先读取 SSL 证书文件:
full_chain_path = "/存放/完整证书链/文件/的/绝对路径"
private_key_path = "/存放/Key/文件/的/绝对路径"
with open(full_chain_path, “r”) as full_chain_file:
with open(private_key_path, “r”) as private_key_file:
# 进行后面的操作
之后是对腾讯云 API 的操作,你需要获取 APP ID 和 Key,需要从控制台生成。
每次部署完新证书后,我都要删除旧的证书。所以,在上传证书以及部署之前,先保存一个旧证书的列表,以便完成操作之后逐个删除。
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk
from tencentcloud.ssl.v20191205 import ssl_client, models as ssl_models
# 腾讯云 APP ID 和 Key
secret_id = “你的 APP ID”
secret_key = “你的 APP Key”
ssl_api_address = “ssl.tencentcloudapi.com”
cdn_api_address = “cdn.tencentcloudapi.com”
# 初始化认证
cred = credential.Credential(secret_id, secret_key)
# 初始化客户端
ssl_http_profile = HttpProfile()
ssl_http_profile.endpoint = ssl_api_address
ssl_client_profile = ClientProfile()
ssl_client_profile.httpProfile = ssl_http_profile
ssl_client_instance = ssl_client.SslClient(cred, “”, ssl_client_profile)
# 获取证书列表
req = ssl_models.DescribeCertificatesRequest()
req.from_json_string(json.dumps({}))
resp = ssl_client_instance.DescribeCertificates(req)
certificates = json.loads(resp.to_json_string())[‘Certificates’]
之后是上传证书以及部署到 CDN:
# 将证书上传到腾讯云并获取证书ID
import json, time
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ssl.v20191205 import ssl_client, models as ssl_models
from tencentcloud.cdn.v20180606 import cdn_client, models as cdn_models
# 腾讯云 APP ID 和 Key
secret_id = “你的 APP ID”
secret_key = “你的 APP Key”
ssl_api_address = “ssl.tencentcloudapi.com”
cdn_api_address = “cdn.tencentcloudapi.com”
# 当天日期,用于证书命名
today = time.strftime(“%Y-%m-%d”, time.localtime())
req = ssl_models.UploadCertificateRequest()
params = {
# 之前打开的证书链文件
“CertificatePublicKey”: full_chain,
# 证书 Key 文件
“CertificatePrivateKey”: private_key,
# 证书命名
“Alias”: today
}
req.from_json_string(json.dumps(params))
resp = ssl_client_instance.UploadCertificate(req)
cert_id = json.loads(resp.to_json_string())[‘CertificateId’]
# 重新初始化客户端
cdn_http_profile = HttpProfile()
cdn_http_profile.endpoint = cdn_api_address
cdn_client_profile = ClientProfile()
cdn_client_profile.httpProfile = cdn_http_profile
cdn_client_instance = cdn_client.CdnClient(cred, “”, cdn_client_profile)
# 将新证书部署到CDN
req = cdn_models.UpdateDomainConfigRequest()
params = {
“Domain”: “你的 CDN 域名”,
“Https”: {
“Switch”: “on”,
“Http2”: “on”,
“CertInfo”: {
“CertId”: cert_id
}
}
}
req.from_json_string(json.dumps(params))
resp = cdn_client_instance.UpdateDomainConfig(req)
因为证书的上传和部署需要一定的时间,这里我们延迟一分钟删除原有证书:
import json, time
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ssl.v20191205 import ssl_client, models as ssl_models
from tencentcloud.cdn.v20180606 import cdn_client, models as cdn_models
# 腾讯云 APP ID 和 Key
secret_id = “你的 APP ID”
secret_key = “你的 APP Key”
ssl_api_address = “ssl.tencentcloudapi.com”
cdn_api_address = “cdn.tencentcloudapi.com”
time.sleep(60)
for certificate in certificates:
cert_id = certificate[‘CertificateId’]
req = ssl_models.DeleteCertificateRequest()
params = {
“CertificateId”: cert_id
}
req.from_json_string(json.dumps(params))
resp = ssl_client_instance.DeleteCertificate(req)
最后,加一个 try
和 catch
就是完整代码了:
import json, time
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ssl.v20191205 import ssl_client, models as ssl_models
from tencentcloud.cdn.v20180606 import cdn_client, models as cdn_models
# 腾讯云 APP ID 和 Key
secret_id = “你的 APP ID”
secret_key = “你的 APP Key”
full_chain_path = “/存放/完整证书链/文件/的/绝对路径”
private_key_path = “/存放/Key/文件/的/绝对路径”
ssl_api_address = “ssl.tencentcloudapi.com”
cdn_api_address = “cdn.tencentcloudapi.com”
with open(full_chain_path, “r”) as full_chain_file:
with open(private_key_path, “r”) as private_key_file:
full_chain = full_chain_file.read()
private_key = private_key_file.read()
# 当天日期,用于证书命名
today = time.strftime(“%Y-%m-%d”, time.localtime())
try:
# 初始化认证
cred = credential.Credential(secret_id, secret_key)
# 初始化客户端
ssl_http_profile = HttpProfile()
ssl_http_profile.endpoint = ssl_api_address
ssl_client_profile = ClientProfile()
ssl_client_profile.httpProfile = ssl_http_profile
ssl_client_instance = ssl_client.SslClient(cred, “”, ssl_client_profile)
# 获取证书列表
req = ssl_models.DescribeCertificatesRequest()
req.from_json_string(json.dumps({}))
resp = ssl_client_instance.DescribeCertificates(req)
certificates = json.loads(resp.to_json_string())[‘Certificates’]
# 将证书上传到腾讯云并获取证书ID
req = ssl_models.UploadCertificateRequest()
params = {
“CertificatePublicKey”: full_chain,
“CertificatePrivateKey”: private_key,
“Alias”: today
}
req.from_json_string(json.dumps(params))
resp = ssl_client_instance.UploadCertificate(req)
cert_id = json.loads(resp.to_json_string())[‘CertificateId’]
# 重新初始化客户端
cdn_http_profile = HttpProfile()
cdn_http_profile.endpoint = cdn_api_address
cdn_client_profile = ClientProfile()
cdn_client_profile.httpProfile = cdn_http_profile
cdn_client_instance = cdn_client.CdnClient(cred, “”, cdn_client_profile)
# 将新证书部署到CDN
req = cdn_models.UpdateDomainConfigRequest()
params = {
“Domain”: “你的 CDN 域名”,
“Https”: {
“Switch”: “on”,
“Http2”: “on”,
“CertInfo”: {
“CertId”: cert_id
}
}
}
req.from_json_string(json.dumps(params))
resp = cdn_client_instance.UpdateDomainConfig(req)
# 延迟一分钟后删除原有证书
time.sleep(60)
for certificate in certificates:
cert_id = certificate[‘CertificateId’]
req = ssl_models.DeleteCertificateRequest()
params = {
“CertificateId”: cert_id
}
req.from_json_string(json.dumps(params))
resp = ssl_client_instance.DeleteCertificate(req)
except TencentCloudSDKException as err:
# 对异常进行处
在 acme.sh 签发证书后,执行这个 Python 脚本,就可以把 CVM 上申请的证书部署到 CDN 服务使用了
原创文章,作者:速盾高防cdn,如若转载,请注明出处:https://www.sudun.com/ask/93535.html