前言
我的网站证书三个月更新一次.由于懒癌, 现在连证书都不想手动部署了, 于是决定写一个脚本配合 acme
自动更新我的网站证书
本文默认读者会使用acme签发域名证书, 环境为已经签发了一次证书后的linux服务器.
证书关联与需求分析
我有两个域名
一个域名证书在七牛云cdn
上会用到
另一个域名在nginx
上会使用到
于是流程分为
1.acme签发证书
2.七牛云上传证书
3.七牛云部署证书
4.将证书存储到nginx配置文件指定的ssl证书位置
5.nginx重启
七牛云自动部署
根据官方文档需要接口
这里我考虑的是运用七牛云官方python sdk
一部分.官方sdk不支持这两个功能,需要手动获取token后调用接口
这里可以看七牛云官方python sdk
的源码, auth部分, 位于lib/sites-packages/qiniu/auth.py
先配置虚拟环境然后安装应该就不用我说了
python -m venv .venv && .venv/bin/pip install qiniu
关键代码如下
class Auth(object): """七牛安全机制类
该类主要内容是七牛上传凭证、下载凭证、管理凭证三种凭证的签名接口的实现,以及回调验证。
Attributes: __access_key: 账号密钥对中的accessKey,详见 https://portal.qiniu.com/user/key __secret_key: 账号密钥对重的secretKey,详见 https://portal.qiniu.com/user/key """
def __init__(self, access_key, secret_key, disable_qiniu_timestamp_signature=None): """初始化Auth类""" self.__checkKey(access_key, secret_key) self.__access_key = access_key self.__secret_key = b(secret_key) self.disable_qiniu_timestamp_signature = disable_qiniu_timestamp_signature
def get_access_key(self): return self.__access_key
def get_secret_key(self): return self.__secret_key
def __token(self, data): data = b(data) hashed = hmac.new(self.__secret_key, data, sha1) return urlsafe_base64_encode(hashed.digest())
def token(self, data): return '{0}:{1}'.format(self.__access_key, self.__token(data))
def token_of_request(self, url, body=None, content_type=None): """带请求体的签名(本质上是管理凭证的签名)
Args: url: 待签名请求的url body: 待签名请求的body content_type: 待签名请求的body的Content-Type
Returns: 管理凭证 """ parsed_url = urlparse(url) query = parsed_url.query path = parsed_url.path data = path if query != '': data = ''.join([data, '?', query]) data = ''.join([data, "\n"])
if body: mimes = [ 'application/x-www-form-urlencoded' ] if content_type in mimes: data += body
return '{0}:{1}'.format(self.__access_key, self.__token(data))
|
那么获取token的示例就是
from qiniu import Auth import requests AccessKey = "your_access_key" SecretKey = "your_access_secret_key" q = Auth(access_key=AccessKey, secret_key=SecretKey) def uploadCert(key, crt): host = "api.qiniu.com" method = "POST"
data = { "name": str(uuid.uuid1()), "common_name": "*.voidval.com", "pri": key, "ca": crt }
header = { 'Content-Type': 'application/json', } path = "/sslcert" url = f"https://{host}{path}" token = q.token_of_request(url=url, body=data, content_type="application/json") header['Authorization'] = f"QBox {token}" resp = requests.post(url, json=data, headers=header, verify=False) print(resp.json()) return resp.json()['certID']
|
同理写出刷新域名证书代码即可
完整代码如下
# @Time : 2024/12/12 13:59 # @Author : TwoOnefour # @File : refreshcert.py import hmac import hashlib import base64 import json import requests import uuid import urllib.parse
import urllib3 from qiniu import Auth AccessKey = "xxxxx" SecretKey = "xxxxxx" q = Auth(access_key=AccessKey, secret_key=SecretKey)
urllib3.disable_warnings() def uploadCert(key, crt): host = "api.qiniu.com" method = "POST"
data = { "name": str(uuid.uuid1()), "common_name": "*.pursuecode.cn", "pri": key, "ca": crt }
header = { 'Content-Type': 'application/json', } path = "/sslcert" url = f"https://{host}{path}" # token = getAuthToken(method=method, path=path, body=data, header=header, host=host)
token = q.token_of_request(url=url, body=data, content_type="application/json") header['Authorization'] = f"QBox {token}" resp = requests.post(url, json=data, headers=header, verify=False) print(resp.json()) return resp.json()['certID']
def setcert(CertID): host = "api.qiniu.com" method = "PUT" header = { 'Content-Type': 'application/json', }
domains = [ "www.example.com", "bucket.example.com" ]
paths = [ f"/domain/{domain}/httpsconf" for domain in domains ]
data = { "certId": CertID, "forceHttps": True, "http2Enable": True } for path in paths: url = f"https://{host}{path}" token = q.token_of_request(url=url, body=data, content_type="application/json") header['Authorization'] = f"QBox {token}" resp = requests.put(url, headers=header, json=data, verify=False) print(resp.json())
if __name__ == "__main__": cer = None key = None with open(r"/root/.acme.sh/*.example.com/fullchain.cer") as f: cer = f.read().strip() with open(r"/root/.acme.sh/*.example.com/_.example.com.key") as f: key = f.read().strip() certid = uploadCert(crt=cer, key=key) setcert(certid)
|
这里后面open语句填acme生成得到的证书路径,将此python路径记住备用,我这里是/root/qiniu/refreshcert.py
acme签发证书
如果你已经运行过一次acme且成功签发证书,在证书签发的文件夹可以找到*.example.com.conf
这个配置
这里主要是看Le_reloadCmd
和Le_realKeyPath
Le_reloadCmd 是在执行完acme签发证书命令后会执行的命令, 这里是经过base64编码的, 格式如下
__ACME_BASE64__START_base64(cmd_plain_string)__ACME_BASE64__END_
也就是说要将命令经过一次base64编码
例如我的需求是systemctl restart nginx
将他编码为base64后就是
c3lzdGVtY3RsIHJlc3RhcnQgeHJheSYmc3lzdGVtY3RsIHJlc3RhcnQgbmdpbng=
一整串就是
__ACME_BASE64__START_c3lzdGVtY3RsIHJlc3RhcnQgeHJheSYmc3lzdGVtY3RsIHJlc3RhcnQgbmdpbng=__ACME_BASE64__END_
Le_realKeyPath 是acme签发证书的文件位置
这里就把nginx
的ssl证书位置填上对应的即可
比如我的证书配置是这样的
那么 Le_realKeyPath
就填/etc/nginx/cerkey.key
接下来crontab中一般会含有如下语句,这是acme用来自动刷新证书的
这样就部署好了,可以尝试运行一下crontab
里写的这串命令
"/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" --force
至此懒人部署证书逻辑大功告成