支持火山引擎更多产品部署

This commit is contained in:
net909 2025-01-24 14:36:29 +08:00
parent 2c81b36249
commit d1eb6267a2
10 changed files with 286 additions and 34 deletions

View File

@ -667,7 +667,7 @@ class DeployHelper
'name' => '实例ID',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'lighthouse\'',
'show' => 'product==\'lighthouse\'||product==\'ddos\'',
'required' => true,
],
'domain' => [
@ -816,10 +816,18 @@ class DeployHelper
'options' => [
['value'=>'cdn', 'label'=>'CDN'],
['value'=>'oss', 'label'=>'OSS'],
['value'=>'pili', 'label'=>'视频直播'],
],
'value' => 'cdn',
'required' => true,
],
'pili_hub' => [
'name' => '直播空间名称',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'pili\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
@ -931,10 +939,39 @@ class DeployHelper
],
],
'taskinputs' => [
'product' => [
'name' => '要部署的产品',
'type' => 'select',
'options' => [
['value'=>'cdn', 'label'=>'内容分发网络CDN'],
['value'=>'dcdn', 'label'=>'全站加速DCDN'],
['value'=>'clb', 'label'=>'负载均衡CLB'],
['value'=>'tos', 'label'=>'对象存储TOS'],
['value'=>'live', 'label'=>'视频直播'],
['value'=>'imagex', 'label'=>'veImageX'],
],
'value' => 'cdn',
'required' => true,
],
'bucket_domain' => [
'name' => 'Bucket域名',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'tos\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '多个域名可使用,分隔',
'show' => 'product!=\'clb\'',
'required' => true,
],
'listener_id' => [
'name' => '监听器ID',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'clb\'',
'required' => true,
],
],

View File

@ -227,7 +227,7 @@ class ACMECert extends ACMEv2
public function authOrder($order)
{
if ($order['status'] != 'ready' && empty($order['challenges'])) {
if ($order['status'] != 'pending' && $order['status'] != 'ready' && empty($order['challenges'])) {
throw new Exception('No challenges available');
}

View File

@ -61,6 +61,39 @@ class Qiniu
return $this->curl($method, $url, $body, $header);
}
public function pili_request($method, $path, $query = null, $params = null)
{
$this->ApiUrl = 'https://pili.qiniuapi.com';
$url = $this->ApiUrl . $path;
$query_str = null;
$body = null;
if (!empty($query)) {
$query = array_filter($query, function ($a) {
return $a !== null;
});
$query_str = http_build_query($query);
$url .= '?' . $query_str;
}
if (!empty($params)) {
$params = array_filter($params, function ($a) {
return $a !== null;
});
$body = json_encode($params);
}
$sign_str = $method . ' ' . $path . ($query_str ? '?' . $query_str : '') . "\nHost: pili.qiniuapi.com" . ($body ? "\nContent-Type: application/json" : '') . "\n\n" . $body;
$hmac = hash_hmac('sha1', $sign_str, $this->SecretKey, true);
$sign = $this->AccessKey . ':' . $this->base64_urlSafeEncode($hmac);
$header = [
'Authorization: Qiniu ' . $sign,
];
if ($body) {
$header[] = 'Content-Type: application/json';
}
return $this->curl($method, $url, $body, $header);
}
private function base64_urlSafeEncode($data)
{
$find = array('+', '/');
@ -95,7 +128,7 @@ class Qiniu
if ($httpCode == 200) {
$arr = json_decode($response, true);
if($arr) return $arr;
if ($arr) return $arr;
return true;
} else {
$arr = json_decode($response, true);

View File

@ -38,7 +38,9 @@ class Volcengine
public function request($method, $action, $params = [], $querys = [])
{
if (!empty($params)) {
$params = array_filter($params, function ($a) { return $a !== null;});
$params = array_filter($params, function ($a) {
return $a !== null;
});
}
$query = [
@ -78,9 +80,51 @@ class Volcengine
return $this->curl($method, $url, $body, $header);
}
/**
* @param string $method 请求方法
* @param string $action 方法名称
* @param array $params 请求参数
* @return array
* @throws Exception
*/
public function tos_request($method, $params = [], $query = [])
{
if (!empty($params)) {
$params = array_filter($params, function ($a) {
return $a !== null;
});
}
$body = '';
if ($method != 'GET') {
$body = !empty($params) ? json_encode($params) : '';
}
$time = time();
$headers = [
'Host' => $this->endpoint,
'X-Tos-Date' => gmdate("Ymd\THis\Z", $time),
'X-Tos-Content-Sha256' => hash("sha256", $body),
];
if ($body) {
$headers['Content-Type'] = 'application/json';
}
$path = '/';
$authorization = $this->generateSign($method, $path, $query, $headers, $body, $time);
$headers['Authorization'] = $authorization;
$url = 'https://' . $this->endpoint . $path . '?' . http_build_query($query);
$header = [];
foreach ($headers as $key => $value) {
$header[] = $key . ': ' . $value;
}
return $this->curl($method, $url, $body, $header);
}
private function generateSign($method, $path, $query, $headers, $body, $time)
{
$algorithm = "HMAC-SHA256";
$algorithm = $this->service == 'tos' ? "TOS4-HMAC-SHA256" : "HMAC-SHA256";
// step 1: build canonical request string
$httpRequestMethod = $method;
@ -177,21 +221,27 @@ class Volcengine
curl_close($ch);
throw new Exception('Curl error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$arr = json_decode($response, true);
if ($arr) {
if ($httpCode == 200) {
if (isset($arr['Result'])) {
return $arr['Result'];
}
return true;
} else {
if (isset($arr['ResponseMetadata']['Error']['MessageCN'])) {
throw new Exception($arr['ResponseMetadata']['Error']['MessageCN']);
} elseif (isset($arr['ResponseMetadata']['Error']['Message'])) {
throw new Exception($arr['ResponseMetadata']['Error']['Message']);
} elseif (isset($arr['Result'])) {
return $arr['Result'];
} elseif (isset($arr['Message'])) {
throw new Exception($arr['Message']);
} elseif (isset($arr['message'])) {
throw new Exception($arr['message']);
} else {
return true;
throw new Exception('返回数据解析失败(http_code=' . $httpCode . ')');
}
} else {
throw new Exception('返回数据解析失败');
}
}
}

View File

@ -23,18 +23,31 @@ class huoshan implements DeployInterface
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1', $this->proxy);
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1', $this->proxy);
$client->request('POST', 'ListCertInfo', ['Source' => 'volc_cert_center']);
return true;
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$cert_id = $this->get_cert_id($fullchain, $privatekey);
if (!$cert_id) throw new Exception('获取证书ID失败');
$info['cert_id'] = $cert_id;
$this->deploy_cdn($cert_id, $config);
if ($config['product'] == 'live') {
$this->deploy_live($fullchain, $privatekey, $config);
} else {
$cert_id = $this->get_cert_id($fullchain, $privatekey);
if (!$cert_id) throw new Exception('获取证书ID失败');
$info['cert_id'] = $cert_id;
if (!isset($config['product']) || $config['product'] == 'cdn') {
$this->deploy_cdn($cert_id, $config);
} elseif ($config['product'] == 'dcdn') {
$this->deploy_dcdn($cert_id, $config);
} elseif ($config['product'] == 'tos') {
$this->deploy_tos($cert_id, $config);
} elseif ($config['product'] == 'imagex') {
$this->deploy_imagex($cert_id, $config);
} elseif ($config['product'] == 'clb') {
$this->deploy_clb($cert_id, $config);
}
}
}
private function deploy_cdn($cert_id, $config)
@ -51,37 +64,137 @@ class huoshan implements DeployInterface
if ($row['Status'] == 'success') {
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书成功!');
} else {
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书失败:' . isset($row['ErrorMsg']) ? $row['ErrorMsg'] : '');
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书失败:' . (isset($row['ErrorMsg']) ? $row['ErrorMsg'] : ''));
}
}
}
private function deploy_dcdn($cert_id, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'dcdn', '2021-04-01', 'cn-north-1', $this->proxy);
$param = [
'CertId' => $cert_id,
'DomainNames' => explode(',', $config['domain']),
];
$client->request('POST', 'CreateCertBind', $param);
$this->log('DCDN域名 ' . $config['domain'] . ' 部署证书成功!');
}
private function deploy_tos($cert_id, $config)
{
if (empty($config['bucket_domain'])) throw new Exception('Bucket域名不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $config['bucket_domain'], 'tos', '2021-04-01', 'cn-beijing', $this->proxy);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
'CustomDomainRule' => [
'Domain' => $domain,
'CertId' => $cert_id,
]
];
$query = ['customdomain' => ''];
$client->tos_request('PUT', $param, $query);
$this->log('对象存储域名 ' . $config['domain'] . ' 部署证书成功!');
}
}
private function deploy_live($fullchain, $privatekey, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'live.volcengineapi.com', 'live', '2023-01-01', 'cn-north-1', $this->proxy);
$param = [
'CertName' => $cert_name,
'Rsa' => [
'Pubkey' => $fullchain,
'Prikey' => $privatekey,
],
'UseWay' => 'https',
];
$result = $client->request('POST', 'CreateCert', $param);
$this->log('上传证书成功 ChainID=' . $result['ChainID']);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
'ChainID' => $result['ChainID'],
'Domain' => $domain,
'HTTPS' => true,
'HTTP2' => true,
];
$client->request('POST', 'BindCert', $param);
$this->log('视频直播域名 ' . $domain . ' 部署证书成功!');
}
}
private function deploy_imagex($cert_id, $config)
{
if (empty($config['bucket_domain'])) throw new Exception('Bucket域名不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'imagex.volcengineapi.com', 'imagex', '2018-08-01', 'cn-north-1', $this->proxy);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
[
'domain' => $domain,
'cert_id' => $cert_id,
]
];
$result = $client->request('POST', 'UpdateImageBatchDomainCert', $param);
if (isset($result['SuccessDomains']) && count($result['SuccessDomains']) > 0) {
$this->log('veImageX域名 ' . $domain . ' 部署证书成功!');
} elseif (isset($result['FailedDomains']) && count($result['FailedDomains']) > 0) {
$errmsg = $result['FailedDomains'][0]['ErrMsg'];
$this->log('veImageX域名 ' . $domain . ' 部署证书失败:' . $errmsg);
} else {
$this->log('veImageX域名 ' . $domain . ' 部署证书失败');
}
}
}
private function deploy_clb($cert_id, $config)
{
if (empty($config['listener_id'])) throw new Exception('监听器ID不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'clb', '2020-04-01', 'cn-beijing', $this->proxy);
$param = [
'ListenerId' => $config['listener_id'],
'CertificateSource' => 'cert_center',
'CertCenterCertificateId' => $cert_id,
];
$client->request('GET', 'ModifyListenerAttributes', $param);
$this->log('CLB监听器 ' . $config['listener_id'] . ' 部署证书成功!');
}
private function get_cert_id($fullchain, $privatekey)
{
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1', $this->proxy);
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'certificate_service', '2024-10-01', 'cn-beijing', $this->proxy);
$param = [
'Source' => 'volc_cert_center',
'Certificate' => $fullchain,
'PrivateKey' => $privatekey,
'Desc' => $cert_name,
'Tag' => $cert_name,
'Repeatable' => false,
'CertificateInfo' => [
'CertificateChain' => $fullchain,
'PrivateKey' => $privatekey,
],
];
try {
$data = $client->request('POST', 'AddCertificate', $param);
$data = $client->request('POST', 'ImportCertificate', $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '证书已存在ID为') !== false) {
$cert_id = trim(getSubstr($e->getMessage(), '证书已存在ID为', '。'));
$this->log('证书已存在 CertId=' . $cert_id);
return $cert_id;
}
throw new Exception('上传证书失败:' . $e->getMessage());
}
$this->log('上传证书成功 CertId=' . $data['CertId']);
return $data['CertId'];
if (!empty($data['InstanceId'])) {
$cert_id = $data['InstanceId'];
} else {
$cert_id = $data['RepeatId'];
}
$this->log('上传证书成功 CertId=' . $cert_id);
return $cert_id;
}
public function setLogger($func)

View File

@ -42,6 +42,8 @@ class qiniu implements DeployInterface
$this->deploy_cdn($domain, $cert_id);
} elseif ($config['product'] == 'oss') {
$this->deploy_oss($domain, $cert_id);
} elseif ($config['product'] == 'pili') {
$this->deploy_pili($config['pili_hub'], $domain, $cert_name);
} else {
throw new Exception('未知的产品类型');
}
@ -87,6 +89,15 @@ class qiniu implements DeployInterface
$this->log('OSS域名 ' . $domain . ' 证书部署成功!');
}
private function deploy_pili($hub, $domain, $cert_name)
{
$param = [
'CertName' => $cert_name,
];
$this->client->pili_request('POST', '/v2/hubs/'.$hub.'/domains/'.$domain.'/cert', null, $param);
$this->log('视频直播域名 ' . $domain . ' 证书部署成功!');
}
private function get_cert_id($fullchain, $privatekey, $common_name, $cert_name)
{
$cert_id = null;

View File

@ -37,7 +37,7 @@ class tencent implements DeployInterface
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['cos_bucket'])) throw new Exception('存储桶名称不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['regionid'] . '#' . $config['cos_bucket'] . '#' . $config['domain'];
$instance_id = $config['regionid'] . '|' . $config['cos_bucket'] . '|' . $config['domain'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
} elseif ($config['product'] == 'tke') {
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
@ -52,6 +52,10 @@ class tencent implements DeployInterface
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['regionid'] . '|' . $config['lighthouse_id'] . '|' . $config['domain'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
} elseif ($config['product'] == 'ddos') {
if (empty($config['lighthouse_id'])) throw new Exception('实例ID不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['lighthouse_id'] . '|' . $config['domain'] . '|443';
} elseif ($config['product'] == 'clb') {
return $this->deploy_clb($cert_id, $config);
} elseif ($config['product'] == 'scf') {

View File

@ -79,6 +79,9 @@ class CheckUtils
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'errmsg' => 'DNS resolve failed', 'usetime' => 0];
}
if (filter_var($target, FILTER_VALIDATE_IP) && strpos($target, ':') !== false) {
$target = '['.$target.']';
}
$starttime = getMillisecond();
$fp = @fsockopen($target, $port, $errCode, $errStr, $timeout);
if ($fp) {

View File

@ -4,6 +4,7 @@
<style>
.copy-btn{color:#52c41a;cursor:pointer;margin-right: 5px;}
.copy-btn:hover{color:#85ef79;}
.btn-refresh{margin-left:5px;font-size:10px;background-color:#6896cf}
tbody tr>td:nth-child(3){word-break:break-all;max-width:180px;}
tbody tr>td:nth-child(4){word-break:break-all;max-width:260px;}
</style>
@ -120,7 +121,7 @@ $(document).ready(function(){
} else {
html += '<span class="label label-warning">未验证</span>';
}
html += '&nbsp;<a href="javascript:checkItem('+row.id+')" title="立即验证" class="btn btn-primary btn-xs"><i class="fa fa-refresh"></i></a>';
html += '<a href="javascript:checkItem('+row.id+')" title="立即验证" class="btn btn-primary btn-xs btn-refresh"><i class="fa fa-refresh"></i></a>';
return html;
}
},

View File

@ -31,7 +31,7 @@ return [
'show_error_msg' => true,
'exception_tmpl' => \think\facade\App::getAppPath() . 'view/exception.tpl',
'version' => '1026',
'version' => '1027',
'dbversion' => '1023'
];