金山云SLB域名(cloudflare托管)证书自动更新记录

整体步骤

  1. 利用certbot创建/续期证书
  2. 利用金山云API接口更新证书

环境准备

  1. Python≥3.7
  2. snap安装管理certbot以及插件

安装certbot以及插件

sudo snap install --classic certbot
sudo snap install certbot-dns-cloudflare

安装ksyun的SDK
pip install --upgrade kingsoftcloud-sdk-python

创建证书

sudo certbot certonly --key-type rsa --rsa-key-size 2048 --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.int --dns-cloudflare-propagation-seconds 60 -d huzi-baozi.com

主要是--dns-cloudflare 插件。

因为cloudflare代理、80端口被占、SLB没开80,所以用DNS验证的方式。

证书续期

sudo certbot renew

有三个钩子参数

  • --pre-hook:更新证书之前调用
  • --post-hook:更新证书之后调用
  • --deploy-hook:成功更新证书之后调用

配置文件位于

/etc/letsencrypt/renewal/huzi-baozi.com.conf

金山云更新证书

API链接

特别注意,来来回回调用都过不了,因为要把cert.pem和privkey.pem的换行都改为\n

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import json
from ksyun.common import credential
from ksyun.common.profile.client_profile import ClientProfile
from ksyun.common.profile.http_profile import HttpProfile
from ksyun.common.exception.ksyun_sdk_exception import KsyunSDKException
# 导入 KCM 客户端和模型
from ksyun.client.kcm.v20160304 import client as kcm_client, models as kcm_models

# ================= 配置区域 =================

# 1. 金山云 API 凭证 (推荐使用环境变量)
# 如果环境变量不存在,将使用默认值,请替换为您的实际密钥
KSYUN_ACCESS_KEY = os.environ.get("KSYUN_SECRET_ID", "默认")
KSYUN_SECRET_KEY = os.environ.get("KSYUN_SECRET_KEY", "默认")
REGION = 'cn-beijing-6'  # KCM 服务没有地域限制,但 SDK 需要指定一个

# 2. 目标证书 ID (请填入您 KCM 中需要更新的证书 ID)
# 脚本将直接覆盖这个 ID 下的证书内容
CERTIFICATE_ID = '你的证书ID' 

# 3. Certbot 证书路径
# 请修改 DOMAIN_NAME 为您的域名目录名
DOMAIN_NAME = '你的域名'
CERT_PATH = f'/etc/letsencrypt/live/{DOMAIN_NAME}/cert.pem'
KEY_PATH = f'/etc/letsencrypt/live/{DOMAIN_NAME}/privkey.pem'

# ===========================================

def read_file_content(file_path):
    """读取文件内容"""
    if not os.path.exists(file_path):
        print(f"Error: 文件不存在: {file_path}")
        sys.exit(1)
    try:
        with open(file_path, 'r') as f:
            return f.read()
    except Exception as e:
        print(f"Error: 无法读取文件 {file_path}. 错误: {e}")
        sys.exit(1)

def get_kcm_client():
    """初始化 KCM 客户端"""
    if KSYUN_ACCESS_KEY == "KSYUN_SECRET_ID_HERE" or KSYUN_SECRET_KEY == "KSYUN_SECRET_KEY_HERE":
        print("Error: 请在脚本中或环境变量中配置正确的 KSYUN_SECRET_ID 和 KSYUN_SECRET_KEY。")
        sys.exit(1)
        
    cred = credential.Credential(KSYUN_ACCESS_KEY, KSYUN_SECRET_KEY)
    
    httpProfile = HttpProfile()
    httpProfile.endpoint = "kcm.api.ksyun.com"  # KCM API 端点
    httpProfile.reqMethod = "POST"
    httpProfile.reqTimeout = 60
    httpProfile.scheme = "http" # 保持与官方案例一致
    
    clientProfile = ClientProfile()
    clientProfile.httpProfile = httpProfile
    
    # KCMClient 使用 kcm_client.KcmClient
    return kcm_client.KcmClient(cred, REGION, profile=clientProfile)

def modify_certificate_content(client, cert_id, cert_content, key_content):
    """调用 ModifyCertificate 接口直接更新 KCM 证书内容"""
    print(f"正在更新 KCM 证书 ID: {cert_id} ...")
    
    # 构造请求参数字典
    params = {
        "CertificateId": cert_id,
        "PublicKey": cert_content.replace('\n', '\\n'),
        "PrivateKey": key_content.replace('\n', '\\n')
    }
    
    try:
        # 使用 call 方法执行 API 调用
        resp = client.call("ModifyCertificate", params)
        resp_json = json.loads(resp)
        
        # KCM ModifyCertificate 接口返回为空,成功即表示更新完成
        print(f"成功: KCM 证书 {cert_id} 内容已更新。所有关联的负载均衡或服务将自动使用新证书。")
        
    except KsyunSDKException as err:
        print(f"更新 KCM 证书失败: {err}")
        # 如果报错,检查 KCM 接口文档确认参数名是否为 PublicKey 或 CertificateContent
        sys.exit(1)

def main():
    # 1. 读取本地最新证书
    print(f"读取本地证书: {DOMAIN_NAME}")
    cert_content = read_file_content(CERT_PATH)
    key_content = read_file_content(KEY_PATH)
#print(cert_content )

    # 2. 初始化客户端
    client = get_kcm_client()

    # 3. 执行更新
    modify_certificate_content(client, CERTIFICATE_ID, cert_content, key_content)

if __name__ == "__main__":
    main()

定时任务

可以写一个cron任务,certbot renew --deploy-hook来更新金山云证书,然后发送消息之类的。

Leave a Reply

Your email address will not be published. Required fields are marked *