希望添加SSL证书挂载目录
因为没有公网80和443端口,现阶段无法使用南墙代替acme。 但南墙手动添加的证书是写入数据库中,无法通过替换文件的方式更新最新的证书。 希望能给个文件目录,系统自动将目录中的证书导入到数据库。
我们会视情况而定
https://gitee.com/lsmir2/docker-uuwaf 我做了处理 data/updateWafssl 程序可以更新数据库证书
#配置路径
wafupdate=/opt/docker/docker-uuwaf/data/updateWafssl
#修改证书的路径, sql 账号密码
$wafupdate -n "server.crt" -k "server.key" -s "uuwaf:[email protected]:3306/uuwaf" --id 1
抛砖引玉吧,写了个python脚本,青龙面板每个月执行一次
from datetime import datetime
import os
import requests
import json
import time
# 读取当前目录下指定文件名的文件,返回字符串
def read_file(filename):
return open(os.path.join(os.path.dirname(__file__), filename), 'r').read()
# 提取acme证书目录配置中的时间戳
def get_time(time):
return time.split('Le_CertCreateTime')[1].split('\'')[1]
cert = read_file('fullchain.cer')
key = read_file('com.key')
dt = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(get_time(read_file('com.conf')))))
ct= datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 组合成请求体
post_json = {
"id": "0",
"sni": "[\"*.证书域名.com\",\"证书域名.com\"]",
"cert": cert,
"key": key,
"expire_time": dt,
"update_time": ct
}
pwd = '{"usr":"南墙管理员","pwd":"密码","otp":""}'
# 获取token
token = requests.post('https://192.168.20.112:4443/api/v1/users/login',data=pwd,verify=False).json()['token']
# 提交证书数据
requests.post('https://192.168.20.112:4443/api/v1/certs/config',data=json.dumps(post_json),headers={"authorization": token},verify=False)
用上述大佬的脚本存在一个问题:提交证书数据如果用post方法的话是新增证书,更新证书需要用put方法。 脚本使用:python3 auto_update_ssl.py --cert-dir ./ --base-url https://waf服务器IP地址:4443 --cert-id 1 以下是我修正后的脚本: `# !/usr/bin/env python3
-- coding: utf-8 --
import argparse import json import logging import os import sys from datetime import datetime from typing import Optional
import pytz import requests import urllib3 from cryptography import x509 from cryptography.hazmat._oid import ExtensionOID from cryptography.hazmat.backends import default_backend
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def setup_logging() -> None: """配置日志记录器""" logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[ logging.FileHandler('ssl_update.log'), logging.StreamHandler() ] )
def read_file(filepath: str) -> str: """读取文件内容并验证非空""" try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read().strip() if not content: raise ValueError(f"Empty file: {filepath}") return content except (IOError, ValueError) as e: logging.error("File read failed: %s", str(e)) sys.exit(1)
def get_cert_expiry(cert_data: str) -> str: """获取证书过期时间(上海时区)""" try: cert = x509.load_pem_x509_certificate( cert_data.encode('utf-8'), default_backend() ) sans = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME).value dns_names = [san.value for san in sans if isinstance(san, x509.DNSName)] print(dns_names) return cert.not_valid_after_utc.astimezone( pytz.timezone('Asia/Shanghai') ).strftime('%Y-%m-%d %H:%M:%S') except Exception as e: logging.error("证书过期时间获取失败: %s", str(e)) sys.exit(1)
def get_cert_sni(cert_data: str) -> list[str]: """获取证书SNI信息""" try: cert = x509.load_pem_x509_certificate( cert_data.encode('utf-8'), default_backend() ) sans = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME).value dns_names = [san.value for san in sans if isinstance(san, x509.DNSName)]
return dns_names
except Exception as e:
logging.error("证书SNI获取失败: %s", str(e))
sys.exit(1)
def get_auth_token(base_url: str, user: str, pwd: str) -> Optional[str]: """获取WAF认证token""" try: resp = requests.post( f"{base_url}/api/v1/users/login", json={"usr": user, "pwd": pwd, "otp": ""}, headers={"Content-Type": "application/json"}, verify=False, timeout=10 ) return resp.json().get('token') except Exception as e: logging.error("Auth failed: %s", str(e)) sys.exit(1)
def update_waf_cert( cert_dir: str, base_url: str, cert_id: int, user: str, pwd: str ) -> bool: """更新WAF证书""" try: cert = read_file(os.path.join(cert_dir, 'fullchain.cer')) key = read_file(os.path.join(cert_dir, 'ipiginc.com.key'))
domains = get_cert_sni(cert)
resp = requests.put(
f"{base_url}/api/v1/certs/config",
json={
"id": cert_id,
"sni": json.dumps(domains),
"cert": cert,
"key": key,
"expire_time": get_cert_expiry(cert),
"update_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
},
headers={
"Authorization": get_auth_token(base_url, user, pwd),
"Content-Type": "application/json"
},
verify=False,
timeout=30
)
return resp.status_code == 200
except Exception as e:
logging.error("Update failed: %s", str(e))
return False
if name == "main": setup_logging() parser = argparse.ArgumentParser( description="WAF SSL证书自动更新工具" ) parser.add_argument("--cert-dir", required=True, help="证书目录路径") parser.add_argument("--base-url", required=True, help="WAF API基础URL") parser.add_argument("--cert-id", required=True, type=int, help="证书ID") waf_user ="waf管理员账号" waf_pwd ="waf管理员密码"
args = parser.parse_args()
status = update_waf_cert(
args.cert_dir,
args.base_url,
args.cert_id,
waf_user,
waf_pwd
)
logging.info("WAF证书更新: %s", "成功" if status else "失败")`
v7更新了接口,并添加了api token,脚本也同步更新一下
` import os import requests import json
requests.packages.urllib3.disable_warnings()
def read_file(filename): return open(os.path.join(os.path.dirname(file), filename), 'r', encoding='utf-8').read()
cert = read_file('fullchain.cer') key = read_file('hutnis.com.key') post_json = {"id":3,"name":"域名","type":1,"email":"","dns_challenge":False,"dns_provider":"","dns_credential":"","sni":"[域名]","crt":cert,"key":key} headers={'Api-Token': 'token','content-type': 'application/json'} updata = requests.put('https://192.168.20.112:4443/api/v1/certs',data=json.dumps(post_json),headers=headers,verify=False) `
这边做一个优化的版本,可以提示错误 updatecert.py 需要修改mysite、mytoken、证书路径、uusec url
import os
import requests
import json
requests.packages.urllib3.disable_warnings()
def read_file(filename):
return open(os.path.join(os.path.dirname(__file__), filename), 'r', encoding='utf-8').read()
cert = read_file('./nginx/cert/mysite.top.crt')
key = read_file('./nginx/cert/mysite.top.key')
post_json = {
"id": 2,
"name": "*.mysite.top",
"type": 1,
"email": "",
"dns_challenge": False,
"dns_provider": "",
"dns_credential": "",
"sni": "[\"*.mysite.top\"]",
"crt": cert,
"key": key
}
headers = {
'Api-Token': 'mytoken',
'Content-Type': 'application/json'
}
url = 'https://192.168.100.47:4443/api/v1/certs'
print(f"➡️ Sending PUT request to {url} ...")
try:
response = requests.put(url, data=json.dumps(post_json), headers=headers, verify=False)
print(f"✅ Status Code: {response.status_code}")
print("🔹 Response Body:")
print(response.text)
# 如果返回JSON,也可以尝试解析
try:
print("🔹 Parsed JSON:")
print(json.dumps(response.json(), indent=4, ensure_ascii=False))
except Exception:
pass
except requests.exceptions.RequestException as e:
print(f"❌ Request failed: {e}")
shell脚本,同样修改mysite、mytoken、证书路径、uusec url,已在neilpang/acme.sh docker容器中校验过
#!/bin/sh
set -e
API_TOKEN="mytoken"
API_URL="https://192.168.100.47:4443/api/v1/certs"
CRT_FILE="./nginx/cert/mysite.top.crt"
KEY_FILE="./nginx/cert/mysite.top.key"
# 读取证书内容并转义为 JSON 安全格式(换行 → \n,双引号 → \")
CRT_CONTENT=$(sed ':a;N;$!ba;s/\\/\\\\/g;s/"/\\"/g;s/\n/\\n/g' "$CRT_FILE")
KEY_CONTENT=$(sed ':a;N;$!ba;s/\\/\\\\/g;s/"/\\"/g;s/\n/\\n/g' "$KEY_FILE")
# 组装 JSON(用 printf 避免变量展开中的空格问题)
POST_JSON=$(printf '{
"id": 2,
"name": "*.mysite.top",
"type": 1,
"email": "",
"dns_challenge": false,
"dns_provider": "",
"dns_credential": "",
"sni": "[\"*.mysite.top\"]",
"crt": "%s",
"key": "%s"
}' "$CRT_CONTENT" "$KEY_CONTENT")
echo "➡️ Sending PUT request to $API_URL ..."
RESPONSE=$(curl -sk -X PUT "$API_URL" \
-H "Api-Token: $API_TOKEN" \
-H "Content-Type: application/json" \
-d "$POST_JSON")
echo "✅ Response:"
echo "$RESPONSE"
Nice work!