新增华为云OBS、天翼云函数计算部署,阿里云、腾讯云等新增上传到证书管理选项

This commit is contained in:
net909 2025-12-29 22:29:54 +08:00
parent 137193d465
commit fb69ed702b
16 changed files with 470 additions and 46 deletions

View File

@ -370,9 +370,8 @@ class DeployHelper
'id' => [
'name' => '证书ID',
'type' => 'input',
'placeholder' => '',
'placeholder' => '留空则为添加证书',
'note' => '在网站管理->证书管理查看证书的ID注意域名是否与证书匹配',
'required' => true,
],
],
],
@ -435,9 +434,8 @@ class DeployHelper
'id' => [
'name' => '证书ID',
'type' => 'input',
'placeholder' => '',
'placeholder' => '留空则为添加证书',
'note' => '在站点->证书管理查看证书的ID注意域名是否与证书匹配',
'required' => true,
],
],
],
@ -1060,6 +1058,7 @@ ctrl+x 保存退出',
['value'=>'vod', 'label'=>'视频点播'],
['value'=>'fc', 'label'=>'函数计算3.0'],
['value'=>'fc2', 'label'=>'函数计算2.0'],
['value'=>'upload', 'label'=>'上传到证书管理'],
],
'value' => 'cdn',
'required' => true,
@ -1171,7 +1170,7 @@ ctrl+x 保存退出',
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '',
'show' => 'product!=\'esa\'&&product!=\'clb\'&&product!=\'alb\'&&product!=\'nlb\'',
'show' => 'product!=\'esa\'&&product!=\'clb\'&&product!=\'alb\'&&product!=\'nlb\'&&product!=\'upload\'',
'required' => true,
],
],
@ -1224,6 +1223,7 @@ ctrl+x 保存退出',
['value'=>'tse', 'label'=>'云原生API网关TSE'],
['value'=>'tcb', 'label'=>'云开发TCB'],
['value'=>'lighthouse', 'label'=>'轻量应用服务器'],
['value'=>'upload', 'label'=>'上传到证书管理'],
['value'=>'update', 'label'=>'更新证书内容证书ID不变'],
],
'value' => 'cdn',
@ -1324,7 +1324,7 @@ ctrl+x 保存退出',
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '',
'show' => 'product!=\'clb\'&&product!=\'tke\'',
'show' => 'product!=\'clb\'&&product!=\'tke\'&&product!=\'upload\'',
'note' => 'CDN、EO、WAF多个域名可用,隔开其他只能填写1个域名',
'required' => true,
],
@ -1375,15 +1375,31 @@ ctrl+x 保存退出',
['value'=>'cdn', 'label'=>'内容分发网络CDN'],
['value'=>'elb', 'label'=>'弹性负载均衡ELB'],
['value'=>'waf', 'label'=>'Web应用防火墙WAF'],
['value'=>'obs', 'label'=>'对象存储服务OBS'],
['value'=>'upload', 'label'=>'上传到证书管理'],
],
'value' => 'cdn',
'required' => true,
],
'obs_endpoint' => [
'name' => 'Endpoint地址',
'type' => 'input',
'placeholder' => '填写示例obs.cn-north-4.myhuaweicloud.com',
'show' => 'product==\'obs\'',
'required' => true,
],
'obs_bucket' => [
'name' => '桶名称',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'obs\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '多个域名可使用,分隔',
'show' => 'product==\'cdn\'',
'show' => 'product==\'cdn\'||product==\'obs\'',
'required' => true,
],
'project_id' => [
@ -1478,6 +1494,7 @@ ctrl+x 保存退出',
['value'=>'cdn', 'label'=>'CDN'],
['value'=>'oss', 'label'=>'OSS'],
['value'=>'pili', 'label'=>'视频直播'],
['value'=>'upload', 'label'=>'上传到证书管理'],
],
'value' => 'cdn',
'required' => true,
@ -1493,6 +1510,7 @@ ctrl+x 保存退出',
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '多个域名可使用,分隔',
'show' => 'product!=\'upload\'',
'required' => true,
],
],
@ -1603,6 +1621,7 @@ ctrl+x 保存退出',
['value'=>'cdn', 'label'=>'CDN'],
['value'=>'blb', 'label'=>'普通型BLB'],
['value'=>'appblb', 'label'=>'应用型BLB'],
['value'=>'upload', 'label'=>'上传到证书管理'],
],
'value' => 'cdn',
'required' => true,
@ -1737,6 +1756,7 @@ ctrl+x 保存退出',
['value'=>'tos', 'label'=>'对象存储TOS'],
['value'=>'live', 'label'=>'视频直播'],
['value'=>'imagex', 'label'=>'veImageX'],
['value'=>'upload', 'label'=>'上传到证书管理'],
],
'value' => 'cdn',
'required' => true,
@ -1752,7 +1772,7 @@ ctrl+x 保存退出',
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '多个域名可使用,分隔',
'show' => 'product!=\'clb\'&&product!=\'alb\'',
'show' => 'product!=\'clb\'&&product!=\'alb\'&&product!=\'upload\'',
'required' => true,
],
'listener_id' => [
@ -1948,10 +1968,22 @@ ctrl+x 保存退出',
['value'=>'cdn', 'label'=>'CDN加速'],
['value'=>'icdn', 'label'=>'全站加速'],
['value'=>'accessone', 'label'=>'边缘安全加速平台'],
['value'=>'cf', 'label'=>'函数计算'],
],
'value' => 'cdn',
'required' => true,
],
'region_id' => [
'name' => '所属地域',
'type' => 'select',
'options' => [
['value'=>'bb9fdb42056f11eda1610242ac110002', 'label'=>'华东1'],
['value'=>'200000002368', 'label'=>'西南1'],
],
'value' => 'bb9fdb42056f11eda1610242ac110002',
'show' => 'product==\'cf\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
@ -2034,9 +2066,8 @@ ctrl+x 保存退出',
'id' => [
'name' => '证书ID',
'type' => 'input',
'placeholder' => '',
'placeholder' => '留空则为添加证书',
'note' => '在SSL证书->我的证书页面查看,注意域名是否与证书匹配',
'required' => true,
],
],
],

View File

@ -30,7 +30,7 @@ class Ctyun
* @return array
* @throws Exception
*/
public function request($method, $path, $query = null, $params = null)
public function request($method, $path, $query = null, $params = null, $header = null)
{
if (!empty($query)) {
$query = array_filter($query, function ($a) { return $a !== null;});
@ -50,6 +50,11 @@ class Ctyun
if ($body) {
$headers['Content-Type'] = 'application/json';
}
if (!empty($header)) {
foreach ($header as $key => $value) {
$headers[$key] = $value;
}
}
$authorization = $this->generateSign($query, $headers, $body, $date);
$headers['Eop-Authorization'] = $authorization;
@ -151,7 +156,7 @@ class Ctyun
curl_close($ch);
$arr = json_decode($response, true);
if (isset($arr['statusCode']) && $arr['statusCode'] == 100000) {
if (isset($arr['statusCode']) && ($arr['statusCode'] == 100000 || $arr['statusCode'] == 0 && $this->endpoint == 'cf-global.ctapi.ctyun.cn')) {
return isset($arr['returnObj']) ? $arr['returnObj'] : true;
} elseif (isset($arr['errorMessage'])) {
throw new Exception($arr['errorMessage']);

View File

@ -0,0 +1,232 @@
<?php
namespace app\lib\client;
use Exception;
/**
* 华为云OBS
*/
class HuaweiOBS
{
private $AccessKeyId;
private $SecretAccessKey;
private $Endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $Endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
$this->Endpoint = $Endpoint;
$this->proxy = $proxy;
}
public function setBucketCustomdomain($bucket, $domain, $cert_name, $fullchain, $privatekey)
{
$strXml = <<<EOF
<CustomDomainConfiguration>
</CustomDomainConfiguration>
EOF;
$xml = new \SimpleXMLElement($strXml);
$xml->addChild('Name', $cert_name);
$xml->addChild('Certificate', $fullchain);
$xml->addChild('PrivateKey', $privatekey);
$body = $xml->asXML();
$options = [
'bucket' => $bucket,
'key' => '',
];
$query = [
'customdomain' => $domain
];
return $this->request('PUT', '/', $query, $body, $options);
}
public function deleteBucketCustomdomain($bucket, $domain)
{
$options = [
'bucket' => $bucket,
'key' => '',
];
$query = [
'customdomain' => $domain
];
return $this->request('DELETE', '/', $query, '', $options);
}
public function getBucketCustomdomain($bucket)
{
$options = [
'bucket' => $bucket,
'key' => '',
];
$query = [
'customdomain' => '',
];
return $this->request('GET', '/', $query, '', $options);
}
private function request($method, $path, $query, $body, $options)
{
$hostname = $options['bucket'] . '.' . $this->Endpoint;
$query_string = $this->toQueryString($query);
$query_string = empty($query_string) ? '' : '?' . $query_string;
$requestUrl = 'https://' . $hostname . $path . $query_string;
$headers = [
'Content-Type' => 'application/xml',
'Content-MD5' => base64_encode(md5($body, true)),
'Date' => gmdate('D, d M Y H:i:s \G\M\T'),
];
$headers['Authorization'] = $this->getAuthorization($method, $path, $query, $headers, $options);
$header = [];
foreach ($headers as $key => $value) {
$header[] = $key . ': ' . $value;
}
return $this->curl($method, $requestUrl, $body, $header);
}
private function curl($method, $url, $body, $header)
{
$ch = curl_init($url);
if ($this->proxy) {
curl_set_proxy($ch);
}
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_CUSTOMREQUEST, $method);
if (!empty($body)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$errno = curl_errno($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($errno) {
$errmsg = curl_error($ch);
curl_close($ch);
throw new Exception('Curl error: ' . $errmsg);
}
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
if (empty($response)) return true;
return $this->xml2array($response);
}
$arr = $this->xml2array($response);
if (isset($arr['Message'])) {
throw new Exception($arr['Message']);
} else {
throw new Exception('HTTP Code: ' . $httpCode);
}
}
private function toQueryString($params = array())
{
$temp = array();
uksort($params, 'strnatcasecmp');
foreach ($params as $key => $value) {
if (is_string($key) && !is_array($value)) {
if (strlen($value) > 0) {
$temp[] = rawurlencode($key) . '=' . rawurlencode($value);
} else {
$temp[] = rawurlencode($key);
}
}
}
return implode('&', $temp);
}
private function xml2array($xml)
{
if (!$xml) {
return false;
}
LIBXML_VERSION < 20900 && libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
}
private function getAuthorization($method, $url, $query, $headers, $options)
{
$method = strtoupper($method);
$date = $headers['Date'];
$resourcePath = $this->getResourcePath($options);
$stringToSign = $this->calcStringToSign($method, $date, $headers, $resourcePath, $query);
$signature = base64_encode(hash_hmac('sha1', $stringToSign, $this->SecretAccessKey, true));
return 'OBS ' . $this->AccessKeyId . ':' . $signature;
}
private function getResourcePath(array $options)
{
$resourcePath = '/';
if (strlen($options['bucket']) > 0) {
$resourcePath .= $options['bucket'] . '/';
}
if (strlen($options['key']) > 0) {
$resourcePath .= $options['key'];
}
return $resourcePath;
}
private function calcStringToSign($method, $date, array $headers, $resourcePath, array $query)
{
/*
SignToString =
VERB + "\n"
+ Content-MD5 + "\n"
+ Content-Type + "\n"
+ Date + "\n"
+ CanonicalizedOSSHeaders
+ CanonicalizedResource
Signature = base64(hmac-sha1(AccessKeySecret, SignToString))
*/
$contentMd5 = '';
$contentType = '';
// CanonicalizedOSSHeaders
$signheaders = array();
foreach ($headers as $key => $value) {
$lowk = strtolower($key);
if (strncmp($lowk, "x-obs-", 6) == 0) {
$signheaders[$lowk] = $value;
} else if ($lowk === 'content-md5') {
$contentMd5 = $value;
} else if ($lowk === 'content-type') {
$contentType = $value;
}
}
ksort($signheaders);
$canonicalizedOSSHeaders = '';
foreach ($signheaders as $key => $value) {
$canonicalizedOSSHeaders .= $key . ':' . $value . "\n";
}
// CanonicalizedResource
$signquery = array();
foreach ($query as $key => $value) {
if (in_array($key, $this->signKeyList)) {
$signquery[$key] = $value;
}
}
ksort($signquery);
$sortedQueryList = array();
foreach ($signquery as $key => $value) {
if (strlen($value) > 0) {
$sortedQueryList[] = $key . '=' . $value;
} else {
$sortedQueryList[] = $key;
}
}
$queryStringSorted = implode('&', $sortedQueryList);
$canonicalizedResource = $resourcePath;
if (!empty($queryStringSorted)) {
$canonicalizedResource .= '?' . $queryStringSorted;
}
return $method . "\n" . $contentMd5 . "\n" . $contentType . "\n" . $date . "\n" . $canonicalizedOSSHeaders . $canonicalizedResource;
}
private $signKeyList = array(
'acl', 'policy', 'torrent', 'logging', 'location', 'storageinfo', 'quota', 'storagepolicy', 'requestpayment', 'versions', 'versioning', 'versionid', 'uploads', 'uploadid', 'partnumber', 'website', 'notification', 'lifecycle', 'deletebucket', 'delete', 'cors', 'restore', 'tagging', 'response-content-type', 'response-content-language', 'response-expires', 'response-cache-control', 'response-content-disposition', 'response-content-encoding', 'x-image-process', 'backtosource', 'storageclass', 'replication', 'append', 'position', 'x-oss-process', 'CDNNotifyConfiguration', 'attname', 'customdomain', 'directcoldaccess', 'encryption', 'inventory', 'length', 'metadata', 'modify', 'name', 'rename', 'truncate', 'x-image-save-bucket', 'x-image-save-object', 'x-obs-security-token', 'x-obs-callback'
);
}

View File

@ -66,6 +66,7 @@ class aliyun implements DeployInterface
$this->deploy_alb($cert_id, $config);
} elseif ($config['product'] == 'nlb') {
$this->deploy_nlb($cert_id, $config);
} elseif ($config['product'] == 'upload') {
} else {
throw new Exception('未知的产品类型');
}

View File

@ -39,6 +39,7 @@ class baidu implements DeployInterface
$this->deploy_blb($cert_id, $config);
} elseif ($config['product'] == 'appblb') {
$this->deploy_appblb($cert_id, $config);
} elseif ($config['product'] == 'upload') {
} else {
throw new Exception('不支持的产品类型');
}

View File

@ -43,7 +43,39 @@ class cdnfly implements DeployInterface
public function deploy($fullchain, $privatekey, $config, &$info)
{
$id = $config['id'];
if (empty($id)) throw new Exception('证书ID不能为空');
if (empty($id)) {
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$params = [
'type' => 'custom',
'name' => $cert_name,
'cert' => $fullchain,
'key' => $privatekey,
];
if ($this->auth == 1) {
$access_token = $this->login();
$url = $this->url . '/v1/certs';
$body = json_encode($params);
$headers = [
'Access-Token' => $access_token,
];
$response = http_request($url, $body, null, null, $headers, $this->proxy, 'POST');
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 0) {
$id = $result['data'];
} elseif (isset($result['msg'])) {
throw new Exception('证书添加失败,' . $result['msg']);
} else {
throw new Exception('证书添加失败,返回数据解析失败');
}
} else {
$id = $this->request('/v1/certs', $params, 'POST');
}
$this->log("证书ID:{$id}添加成功!");
$info['config']['id'] = $id;
return;
}
$params = [
'type' => 'custom',

View File

@ -39,6 +39,8 @@ class ctyun implements DeployInterface
$this->deploy_icdn($fullchain, $privatekey, $config);
} elseif ($config['product'] == 'accessone') {
$this->deploy_accessone($fullchain, $privatekey, $config);
} elseif ($config['product'] == 'cf') {
$this->deploy_cf($fullchain, $privatekey, $config);
}
}
@ -170,6 +172,44 @@ class ctyun implements DeployInterface
$this->log('边缘安全加速域名 ' . $config['domain'] . ' 部署证书成功!');
}
private function deploy_cf($fullchain, $privatekey, $config)
{
$client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'cf-global.ctapi.ctyun.cn', $this->proxy);
try {
$data = $client->request('GET', '/openapi/v1/domains/customdomains/' . $config['domain'], null, null, ['regionId' => $config['region_id']]);
} catch (Exception $e) {
throw new Exception('获取自定义域名配置失败:' . $e->getMessage());
}
if (isset($data['certConfig']['certificate']) && trim($data['certConfig']['certificate']) == trim($fullchain)) {
$this->log('函数计算域名 ' . $config['domain'] . ' 证书已部署,无需重复操作!');
return;
}
if ($data['protocol'] == 'HTTP') $data['protocol'] = 'HTTP,HTTPS';
$param = [
'domainName' => $config['domain'],
'description' => $data['description'],
'protocol' => $data['protocol'],
'certConfig' => [
'certName' => 'cert' . substr($config['cert_name'], strpos($config['cert_name'], '-') + 1),
'certificate' => $fullchain,
'privateKey' => $privatekey,
],
'authConfig' => $data['authConfig'],
'routeConfig' => $data['routeConfig'],
];
try {
$client->request('PUT', '/openapi/v1/domains/customdomains/' . $config['domain'], null, $param, ['regionId' => $config['region_id']]);
} catch (Exception $e) {
if (strpos($e->getMessage(), '请求已提交,请勿重复操作!') === false) {
throw new Exception($e->getMessage());
}
}
$this->log('函数计算域名 ' . $config['domain'] . ' 部署证书成功!');
}
public function setLogger($func)
{
$this->logger = $func;

View File

@ -4,6 +4,7 @@ namespace app\lib\deploy;
use app\lib\DeployInterface;
use app\lib\client\HuaweiCloud;
use app\lib\client\HuaweiOBS;
use Exception;
class huawei implements DeployInterface
@ -39,6 +40,11 @@ class huawei implements DeployInterface
$this->deploy_elb($fullchain, $privatekey, $config);
} elseif ($config['product'] == 'waf') {
$this->deploy_waf($fullchain, $privatekey, $config);
} elseif ($config['product'] == 'obs') {
$this->deploy_obs($fullchain, $privatekey, $config);
} elseif ($config['product'] == 'upload') {
$cert_id = $this->get_cert_id($fullchain, $privatekey);
$info['cert_id'] = $cert_id;
}
}
@ -117,6 +123,19 @@ class huawei implements DeployInterface
$this->log('WAF证书ID ' . $config['cert_id'] . ' 更新证书成功!');
}
private function deploy_obs($fullchain, $privatekey, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
if (empty($config['obs_endpoint'])) throw new Exception('OBS Endpoint不能为空');
if (empty($config['obs_bucket'])) throw new Exception('OBS 桶名称不能为空');
$obsClient = new HuaweiOBS($this->AccessKeyId, $this->SecretAccessKey, $config['obs_endpoint'], $this->proxy);
foreach (explode(',', $config['domain']) as $domain) {
if (empty($domain)) continue;
$obsClient->setBucketCustomdomain($config['obs_bucket'], $domain, $config['cert_name'], $fullchain, $privatekey);
$this->log('OSS域名 ' . $domain . ' 部署证书成功!');
}
}
private function get_cert_id($fullchain, $privatekey)
{
$certInfo = openssl_x509_parse($fullchain, true);

View File

@ -191,7 +191,7 @@ class huoshan implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'certificate_service', '2024-10-01', 'cn-beijing', $this->proxy);
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'certificate-service.volcengineapi.com', 'certificate_service', '2024-10-01', 'cn-beijing', $this->proxy);
$param = [
'Tag' => $cert_name,
'Repeatable' => false,
@ -207,10 +207,20 @@ class huoshan implements DeployInterface
}
if (!empty($data['InstanceId'])) {
$cert_id = $data['InstanceId'];
$this->log('上传证书成功 CertId=' . $cert_id);
$param = [
'InstanceId' => $cert_id,
'Options' => [
'ExpiredNotice' => 'Disabled',
],
];
$client->request('POST', 'CertificateUpdateInstance', $param);
} else {
$cert_id = $data['RepeatId'];
$this->log('找到已上传的证书 CertId=' . $cert_id);
}
$this->log('上传证书成功 CertId=' . $cert_id);
return $cert_id;
}

View File

@ -41,13 +41,29 @@ class lecdn implements DeployInterface
public function deploy($fullchain, $privatekey, $config, &$info)
{
$id = $config['id'];
if (empty($id)) throw new Exception('证书ID不能为空');
if ($this->auth == 0) {
$this->login();
}
$id = $config['id'];
if (empty($id)) {
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$params = [
'name' => $cert_name,
'type' => 'upload',
'ssl_pem' => base64_encode($fullchain),
'ssl_key' => base64_encode($privatekey),
'auto_renewal' => false,
];
$data = $this->request('/prod-api/certificate', $params, 'POST');
$id = $data['id'];
$this->log("证书ID:{$id}添加成功!");
$info['config']['id'] = $id;
return;
}
try {
$data = $this->request('/prod-api/certificate/' . $id);
} catch (Exception $e) {

View File

@ -44,10 +44,10 @@ class opanel implements DeployInterface
// 没有指定节点,只部署到主控节点
try {
$this->request('/core/settings/ssl/update', $params);
$this->log("主节点面板证书更新成功!");
$this->log("面板证书更新成功!");
return;
} catch (Exception $e) {
throw new Exception("主节点面板证书更新失败:" . $e->getMessage());
throw new Exception("面板证书更新失败:" . $e->getMessage());
}
} else {
// 同时部署到主节点和所有指定的子节点
@ -131,11 +131,11 @@ class opanel implements DeployInterface
];
try {
$this->request('/websites/ssl/upload', $params, $nodeName);
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新成功!" : "主节点 证书ID:{$config['id']}更新成功!";
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新成功!" : "证书ID:{$config['id']}更新成功!";
$this->log($logMsg);
return;
} catch (Exception $e) {
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新失败:" : "主节点 证书ID:{$config['id']}更新失败:";
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新失败:" : "证书ID:{$config['id']}更新失败:";
throw new Exception($logMsg . $e->getMessage());
}
}
@ -147,10 +147,10 @@ class opanel implements DeployInterface
$params = ['page' => 1, 'pageSize' => 500];
try {
$data = $this->request("/websites/ssl/search", $params, $nodeName);
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "主节点 ";
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "";
$this->log($logMsg . '获取证书列表成功(total=' . $data['total'] . ')');
} catch (Exception $e) {
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "主节点 ";
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "";
throw new Exception($logMsg . '获取证书列表失败:' . $e->getMessage());
}
@ -179,12 +179,12 @@ class opanel implements DeployInterface
];
try {
$this->request('/websites/ssl/upload', $params, $nodeName);
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新成功!" : "主节点 证书ID:{$row['id']}更新成功!";
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新成功!" : "证书ID:{$row['id']}更新成功!";
$this->log($logMsg);
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新失败:" : "主节点 证书ID:{$row['id']}更新失败:";
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新失败:" : "证书ID:{$row['id']}更新失败:";
$this->log($logMsg . $errmsg);
}
}
@ -199,7 +199,7 @@ class opanel implements DeployInterface
'description' => '',
];
$this->request('/websites/ssl/upload', $params, $nodeName);
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书上传成功!" : "主节点 证书上传成功!";
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书上传成功!" : "证书上传成功!";
$this->log($logMsg);
}
}

View File

@ -37,6 +37,9 @@ class qiniu implements DeployInterface
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$cert_id = $this->get_cert_id($fullchain, $privatekey, $certInfo['subject']['CN'], $cert_name);
$info['cert_id'] = $cert_id;
$info['cert_name'] = $cert_name;
if ($config['product'] == 'upload') return;
foreach (explode(',', $domains) as $domain) {
if (empty($domain)) continue;
@ -50,8 +53,6 @@ class qiniu implements DeployInterface
throw new Exception('未知的产品类型');
}
}
$info['cert_id'] = $cert_id;
$info['cert_name'] = $cert_name;
}
private function deploy_cdn($domain, $cert_id)

View File

@ -26,19 +26,43 @@ class rainyun implements DeployInterface
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['id'])) throw new Exception('证书ID不能为空');
if (empty($config['id'])) {
$params = [
'cert' => $fullchain,
'key' => $privatekey,
];
try {
$this->request('/product/sslcenter/', $params, 'POST');
} catch (Exception $e) {
throw new Exception('上传证书失败,' . $e->getMessage());
}
$params = [
'cert' => $fullchain,
'key' => $privatekey,
];
try {
$this->request('/product/sslcenter/' . $config['id'], $params, 'PUT');
} catch (Exception $e) {
throw new Exception($e->getMessage());
$params = [
'options' => '{"columnFilters":{"Domain":""},"sort":[],"page":1,"perPage":1}',
];
try {
$data = $this->request('/product/sslcenter/?' . http_build_query($params), null, 'GET');
} catch (Exception $e) {
throw new Exception('获取证书列表失败,' . $e->getMessage());
}
if (empty($data['Records'])) throw new Exception('未找到已上传的证书');
$cert_id = $data['Records'][0]['ID'];
$info['config']['id'] = $cert_id;
$this->log('证书ID:' . $cert_id . '添加成功!');
} else {
$params = [
'cert' => $fullchain,
'key' => $privatekey,
];
try {
$this->request('/product/sslcenter/' . $config['id'], $params, 'PUT');
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$this->log('证书ID:' . $config['id'] . '更新成功!');
}
$this->log('证书ID:' . $config['id'] . '更新成功!');
}
private function request($path, $params = null, $method = null)
@ -55,7 +79,7 @@ class rainyun implements DeployInterface
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 200) {
return $result;
return isset($result['data']) ? $result['data'] : null;
} elseif (isset($result['message'])) {
throw new Exception($result['message']);
} else {

View File

@ -36,6 +36,7 @@ class tencent implements DeployInterface
}
$cert_id = $this->get_cert_id($fullchain, $privatekey);
if (!$cert_id) throw new Exception('证书ID获取失败');
$info['cert_id'] = $cert_id;
if ($config['product'] == 'cos') {
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['cos_bucket'])) throw new Exception('存储桶名称不能为空');
@ -65,6 +66,8 @@ class tencent implements DeployInterface
return $this->deploy_scf($cert_id, $config);
} elseif ($config['product'] == 'teo' && isset($config['site_id'])) {
return $this->deploy_teo($cert_id, $config);
} elseif ($config['product'] == 'upload') {
return;
} else {
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
if ($config['product'] == 'waf') {
@ -77,7 +80,6 @@ class tencent implements DeployInterface
}
try {
$record_id = $this->deploy_common($config['product'], $cert_id, $instance_id);
$info['cert_id'] = $cert_id;
$info['record_id'] = $record_id;
} catch (Exception $e) {
if (isset($info['record_id'])) {

View File

@ -92,8 +92,11 @@ class upyun implements DeployInterface
}
}
if ($i == 0) throw new Exception('未找到可迁移的证书');
$this->log('共迁移' . $i . '个证书,关联域名' . $d . '个');
if ($i == 0) {
$this->log('未找到可迁移的证书');
} else {
$this->log('共迁移' . $i . '个证书,关联域名' . $d . '个');
}
}
private function login()

View File

@ -70,8 +70,15 @@ class CertDeployService
$this->saveResult(-1, $e->getMessage(), date('Y-m-d H:i:s', time() + (array_key_exists($this->task['retry'], self::$retry_interval) ? self::$retry_interval[$this->task['retry']] : 3600)));
throw $e;
} finally {
if($this->info){
Db::name('cert_deploy')->where('id', $this->task['id'])->update(['info' => json_encode($this->info)]);
if ($this->info && is_array($this->info)) {
if (isset($this->info['config']) && is_array($this->info['config'])) {
$config = array_merge(json_decode($this->task['config'], true), $this->info['config']);
Db::name('cert_deploy')->where('id', $this->task['id'])->update(['config' => json_encode($config)]);
unset($this->info['config']);
}
if (!empty($this->info)) {
Db::name('cert_deploy')->where('id', $this->task['id'])->update(['info' => json_encode($this->info)]);
}
}
}
}