背景
笔者使用的轻量应用服务器安装的Windows系统(集成环境没有更新证书功能),SSL证书是acme.sh生成的多域名证书,有效期90天。因为使用了腾讯云的CDN,每次生成新证书需要手动上传到腾讯云,太麻烦了。既然官方提供了上传证书的Api(UploadCertificate),服务器是php环境,就想用php撸个小工具,实现自动上传SSL证书到腾讯云。
主要思路
acme.sh(计划任务)更新证书 -> 证书更新成功hook工具网址实现上传
核心代码
执行acme.sh时,额外增加如下参数:(5秒延时可取消,网址换成自己脚本存放位置)
代码语言:javascript复制--renew-hook "sleep 5s && curl http://127.0.0.1/Qcloud/ssl_update.php"
简版腾讯云SDK文件:Qcloud.php
代码语言:javascript复制<?php
//腾讯云SDK
class Qcloud {
private $SecretId;
private $SecretKey;
private $endpoint = "scf.tencentcloudapi.com"; //接口请求域名
private $service = "scf"; //服务名称
private $version = "2018-04-16"; //API版本号
private $region = "ap-shanghai"; //地域参数
/**
* SecretId 腾讯云SecretId
* SecretKey 腾讯云SecretKey
*/
function __construct($SecretId, $SecretKey){
$this->SecretId = $SecretId;
$this->SecretKey = $SecretKey;
}
/**
* $api 接口请求域名
*/
public function SetApi($api){
$this->endpoint = $api;
$ex = explode('.', $api);
$this->service = $ex[0];
//print_r($ex);
}
/**
* $api 服务名称
*/
public function SetService($ser){
$this->service = $ser;
}
/**
* $api API版本号
*/
public function SetVersion($ver){
$this->version = $ver;
}
/**
* $api 地域参数
*/
public function SetRegion($reg=null){
$this->region = $reg;
}
public function send_reuqest($action, $param){
$payload = json_encode($param);
$time = time();
$authorization = $this->generateSign($payload, $time);
$header = [
'Authorization: '.$authorization,
'Content-Type: application/json; charset=utf-8',
'X-TC-Action: '.$action,
'X-TC-Timestamp: '.$time,
'X-TC-Version: '.$this->version,
'X-TC-Region: '.$this->region,
];
return $this->curl_post($payload, $header);
}
private function generateSign($payload, $time){
$algorithm = "TC3-HMAC-SHA256";
// step 1: build canonical request string
$httpRequestMethod = "POST";
$canonicalUri = "/";
$canonicalQueryString = "";
$canonicalHeaders = "content-type:application/json; charset=utf-8n"."host:".$this->endpoint."n";
$signedHeaders = "content-type;host";
$hashedRequestPayload = hash("SHA256", $payload);
$canonicalRequest = $httpRequestMethod."n"
.$canonicalUri."n"
.$canonicalQueryString."n"
.$canonicalHeaders."n"
.$signedHeaders."n"
.$hashedRequestPayload;
// step 2: build string to sign
$date = gmdate("Y-m-d", $time);
$credentialScope = $date."/".$this->service."/tc3_request";
$hashedCanonicalRequest = hash("SHA256", $canonicalRequest);
$stringToSign = $algorithm."n"
.$time."n"
.$credentialScope."n"
.$hashedCanonicalRequest;
// step 3: sign string
$secretDate = hash_hmac("SHA256", $date, "TC3".$this->SecretKey, true);
$secretService = hash_hmac("SHA256", $this->service, $secretDate, true);
$secretSigning = hash_hmac("SHA256", "tc3_request", $secretService, true);
$signature = hash_hmac("SHA256", $stringToSign, $secretSigning);
// step 4: build authorization
$authorization = $algorithm
." Credential=".$this->SecretId."/".$credentialScope
.", SignedHeaders=content-type;host, Signature=".$signature;
return $authorization;
}
private function curl_post($payload, $header){
$url = 'https://'.$this->endpoint.'/';
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
$json=curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if($httpCode==200){
$arr=json_decode($json,true);
return $arr['Response'];
}else{
return false;
}
}
}
受访文件:ssl_upload.php
代码语言:javascript复制<?php
include 'Qcloud.php';
$SecretId = 'xxxx';
$SecretKey='xxxxxx';
$param['pbk'] = file_get_contents('证书路径');
$param['pak'] = file_get_contents('私钥路径');
new ssl_upload($param, $SecretId, $SecretKey);
//输出json信息
function msg($msg='ok', $code=200){
$arr = array(
'code'=>$code,
'msg'=>$msg,
);
exit(json_encode($arr, JSON_UNESCAPED_UNICODE));
}
//上传ssl证书
class ssl_upload{
function __construct($opt, $SecretId, $SecretKey){
$QC = new Qcloud($SecretId, $SecretKey);
//公共参数
$api = 'ssl.tencentcloudapi.com';
$ver = '2019-12-05';
$pbk = $opt['pbk'];
$pak = isset($opt['pak']) ? $opt['pak']: null;
$type = isset($opt['type']) ? $opt['type']:'SVR';
$alias = isset($opt['alias']) ? $opt['alias']:'upload_'.date('Ymd');
//功能参数
$action = 'UploadCertificate'; //公共参数 功能
$param['CertificatePublicKey']=$pbk; //证书内容
$param['CertificatePrivateKey']=$pak; //私钥内容,证书类型为 SVR 时必填,为 CA 时可不填。
$param['CertificateType']=$type; //证书类型,默认 SVR。CA = 客户端证书,SVR = 服务器证书。
$param['Alias']=$alias; //备注名称
$QC->SetApi($api); //接口请求域名
$QC->SetVersion($ver); //API版本号
$rsp = $QC->send_reuqest($action, $param);
if(isset($rsp['CertificateId'])){
//上传成功
$result = $rsp['CertificateId'];
msg($result);
}else if(isset($rsp['Error'])){
//上传失败
$code = $rsp['Error']['Code'];
$msg = $rsp['Error']['Message'];
msg($msg, $code);
}else{
//未知错误
$msg = $rsp;
$code = 500;
msg($msg, $code);
}
}
}
后记
其实更新成功后,还可以做一些事情,比如通过Email/短信发送更新成功的信息,甚至可以再写代码,遍历CDN加速的域名,实现自动更换所有网站的ssl证书(主要用到的接口有DescribeDomainsConfig、UpdateDomainConfig),可以自行拓展。