9 Commits
2.1.0 ... 2.3.1

Author SHA1 Message Date
net909
8980910d47 增加上次运行时间显示 2025-02-01 10:54:47 +08:00
net909
2ed8a717db 修复namesilo添加解析 2025-02-01 10:46:39 +08:00
net909
b4258dbc81 修复CF解析列表 2025-01-30 10:59:41 +08:00
net909
d1eb6267a2 支持火山引擎更多产品部署 2025-01-24 14:36:29 +08:00
net909
2c81b36249 增加导入已有证书,支持天翼云CDN部署 2025-01-10 14:54:45 +08:00
net909
8d5a9bc083 version 2025-01-05 10:57:43 +08:00
net909
31300d8a7b 优化SSL订单等待耗时 2025-01-05 10:56:02 +08:00
net909
300f2a9b92 修复部分接口请求异常 2024-12-31 15:30:59 +08:00
net909
865275c065 新增西部数码虚拟主机部署 2024-12-30 18:00:47 +08:00
36 changed files with 1162 additions and 98 deletions

View File

@@ -166,6 +166,11 @@ function getSubstr($str, $leftStr, $rightStr)
}
}
function arrays_are_equal($array1, $array2)
{
return empty(array_diff($array1, $array2)) && empty(array_diff($array2, $array1));
}
function checkRefererHost()
{
if (!Request::header('referer')) {

View File

@@ -53,7 +53,9 @@ class Auth extends BaseController
} else {
if ($user) {
Db::name('log')->insert(['uid' => $user['id'], 'action' => '登录失败', 'data' => 'IP:' . $this->clientip, 'addtime' => date("Y-m-d H:i:s")]);
if (isset($user['totp_open']) && $user['totp_open'] == 1 && !empty($user['totp_secret'])) $login_limit_count = 10;
if (isset($user['totp_open']) && $user['totp_open'] == 1 && !empty($user['totp_secret'])) {
return json(['code' => -1, 'msg' => '用户名或密码错误', 'vcode' => 1]);
}
}
if (!file_exists($login_limit_file)) {
$login_limit = ['count' => 0, 'time' => 0];

View File

@@ -317,6 +317,72 @@ class Cert extends BaseController
Db::name('cert_domain')->insertAll($domainList);
Db::commit();
return json(['code' => 0, 'msg' => '修改证书订单成功!']);
} elseif ($action == 'import') {
$fullchain = input('post.fullchain', null, 'trim');
$privatekey = input('post.privatekey', null, 'trim');
if (!openssl_x509_read($fullchain)) return json(['code' => -1, 'msg' => '证书内容填写错误']);
if (!openssl_get_privatekey($privatekey)) return json(['code' => -1, 'msg' => '私钥内容填写错误']);
if (!openssl_x509_check_private_key($fullchain, $privatekey)) return json(['code' => -1, 'msg' => 'SSL证书与私钥不匹配']);
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo || !isset($certInfo['extensions']['subjectAltName'])) return json(['code' => -1, 'msg' => '证书内容解析失败']);
$domains = [];
$subjectAltName = explode(',', $certInfo['extensions']['subjectAltName']);
foreach ($subjectAltName as $domain) {
$domain = trim($domain);
if (strpos($domain, 'DNS:') === 0) $domain = substr($domain, 4);
if (!empty($domain)) {
$domains[] = $domain;
}
}
$domains = array_unique($domains);
if (empty($domains)) return json(['code' => -1, 'msg' => '证书绑定域名不能为空']);
$issuetime = date('Y-m-d H:i:s', $certInfo['validFrom_time_t']);
$expiretime = date('Y-m-d H:i:s', $certInfo['validTo_time_t']);
$issuer = $certInfo['issuer']['CN'];
$order_ids = Db::name('cert_order')->where('issuetime', $issuetime)->column('id');
if (!empty($order_ids)) {
foreach ($order_ids as $order_id) {
$domains2 = Db::name('cert_domain')->where('oid', $order_id)->column('domain');
if (arrays_are_equal($domains2, $domains)) {
return json(['code' => -1, 'msg' => '该证书已存在,无需重复添加']);
}
}
}
$order = [
'aid' => input('post.aid/d'),
'keytype' => input('post.keytype'),
'keysize' => input('post.keysize'),
'addtime' => date('Y-m-d H:i:s'),
'updatetime' => date('Y-m-d H:i:s'),
'issuetime' => $issuetime,
'expiretime' => $expiretime,
'issuer' => $issuer,
'status' => 3,
'fullchain' => $fullchain,
'privatekey' => $privatekey,
];
if (empty($order['aid']) || empty($order['keytype']) || empty($order['keysize'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
$res = $this->check_order($order, $domains);
if (is_array($res)) return json($res);
Db::startTrans();
$id = Db::name('cert_order')->insertGetId($order);
$domainList = [];
$i = 1;
foreach ($domains as $domain) {
$domainList[] = [
'oid' => $id,
'domain' => $domain,
'sort' => $i++,
];
}
Db::name('cert_domain')->insertAll($domainList);
Db::commit();
return json(['code' => 0, 'msg' => '导入证书成功!']);
} elseif ($action == 'del') {
$id = input('post.id/d');
$dcount = DB::name('cert_deploy')->where('oid', $id)->count();
@@ -368,7 +434,11 @@ class Cert extends BaseController
$max_domains = CertHelper::$cert_config[$account['type']]['max_domains'];
$wildcard = CertHelper::$cert_config[$account['type']]['wildcard'];
$cname = CertHelper::$cert_config[$account['type']]['cname'];
if (count($domains) > $max_domains) return ['code' => -1, 'msg' => '域名数量不能超过'.$max_domains.'个'];
if (count($domains) > $max_domains) {
if (!(count($domains) == 2 && $max_domains == 1 && ltrim($domains[0], 'www.') == ltrim($domains[1], 'www.'))) {
return ['code' => -1, 'msg' => '域名数量不能超过'.$max_domains.'个'];
}
}
foreach($domains as $domain){
if(!$wildcard && strpos($domain, '*') !== false) return ['code' => -1, 'msg' => '该证书账户类型不支持泛域名'];
@@ -438,6 +508,20 @@ class Cert extends BaseController
return View::fetch();
}
public function order_import()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
$accounts = [];
foreach (Db::name('cert_account')->where('deploy', 0)->select() as $row) {
$accounts[$row['id']] = ['name'=>$row['id'].'_'.CertHelper::$cert_config[$row['type']]['name'], 'type'=>$row['type']];
if (!empty($row['remark'])) {
$accounts[$row['id']]['name'] .= '' . $row['remark'] . '';
}
}
View::assign('accounts', $accounts);
return View::fetch();
}
public function deploytask()
{

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,76 @@ 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,
],
],
],
'west' => [
'name' => '西部数码',
'class' => 2,
'icon' => 'west.ico',
'note' => '支持部署到西部数码虚拟主机',
'inputs' => [
'username' => [
'name' => '用户名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'api_password' => [
'name' => 'API密码',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'sitename' => [
'name' => 'FTP账号',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
],
@@ -978,6 +1052,48 @@ class DeployHelper
],
],
],
'ctyun' => [
'name' => '天翼云',
'class' => 2,
'icon' => 'ctyun.ico',
'note' => '支持部署到天翼云CDN',
'inputs' => [
'AccessKeyId' => [
'name' => 'AccessKeyId',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'SecretAccessKey' => [
'name' => 'SecretAccessKey',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
'name' => '产品',
'type' => 'hidden',
'value' => 'cdn',
],
'domain' => [
'name' => 'CDN域名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
],
],
'allwaf' => [
'name' => 'AllWAF',
'class' => 2,

View File

@@ -126,6 +126,7 @@ class DnsHelper
'huoshan' => ['DEF' => 'default', 'CT' => 'telecom', 'CU' => 'unicom', 'CM' => 'mobile', 'AB' => 'oversea'],
'baidu' => ['DEF' => 'default', 'CT' => 'ct', 'CU' => 'cnc', 'CM' => 'cmnet', 'AB' => ''],
'cloudflare' => ['DEF' => '0'],
'namesilo' => ['DEF' => '0'],
];
public static function getList()

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

@@ -144,6 +144,7 @@ class AWS
}
$path = '/' . $this->version . $path;
$body = '';
if ($method == 'GET' || $method == 'DELETE') {
$query = $params;
} else {
@@ -181,7 +182,7 @@ class AWS
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
@@ -221,6 +222,17 @@ class AWS
$replace = ['%20', '%2A', '~'];
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{

View File

@@ -76,7 +76,7 @@ class AliyunNew
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
@@ -108,6 +108,17 @@ class AliyunNew
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';

163
app/lib/client/Ctyun.php Normal file
View File

@@ -0,0 +1,163 @@
<?php
namespace app\lib\client;
use Exception;
/**
* 天翼云
*/
class Ctyun
{
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;
}
/**
* @param string $method 请求方法
* @param string $path 请求路径
* @param array|null $query 请求参数
* @param array|null $params 请求体
* @return array
* @throws Exception
*/
public function request($method, $path, $query = null, $params = null)
{
if (!empty($query)) {
$query = array_filter($query, function ($a) { return $a !== null;});
}
if (!empty($params)) {
$params = array_filter($params, function ($a) { return $a !== null;});
}
$time = time();
$date = date("Ymd\THis\Z", $time);
$body = !empty($params) ? json_encode($params) : '';
$headers = [
'Host' => $this->endpoint,
'Eop-date' => $date,
'ctyun-eop-request-id' => getSid(),
];
if ($body) {
$headers['Content-Type'] = 'application/json';
}
$authorization = $this->generateSign($query, $headers, $body, $date);
$headers['Eop-Authorization'] = $authorization;
$url = 'https://' . $this->endpoint . $path;
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
$header = [];
foreach ($headers as $key => $value) {
$header[] = $key . ': ' . $value;
}
return $this->curl($method, $url, $body, $header);
}
private function generateSign($query, $headers, $body, $date)
{
// step 1: build canonical request string
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
// step 2: build string to sign
$stringToSign = $canonicalHeaders . "\n"
. $canonicalQueryString . "\n"
. $hashedRequestPayload;
// step 3: sign string
$ktime = hash_hmac("sha256", $date, $this->SecretAccessKey, true);
$kAk = hash_hmac("sha256", $this->AccessKeyId, $ktime, true);
$kdate = hash_hmac("sha256", substr($date, 0, 8), $kAk, true);
$signature = hash_hmac("sha256", $stringToSign, $kdate, true);
$signature = base64_encode($signature);
// step 4: build authorization
$authorization = $this->AccessKeyId . " Headers=" . $signedHeaders . " Signature=" . $signature;
return $authorization;
}
private function escape($str)
{
$search = ['+', '*', '%7E'];
$replace = ['%20', '%2A', '~'];
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';
ksort($parameters);
$canonicalQueryString = '';
foreach ($parameters as $key => $value) {
$canonicalQueryString .= '&' . $this->escape($key) . '=' . $this->escape($value);
}
return substr($canonicalQueryString, 1);
}
private function getCanonicalHeaders($oldheaders)
{
$headers = array();
foreach ($oldheaders as $key => $value) {
$headers[strtolower($key)] = trim($value);
}
ksort($headers);
$canonicalHeaders = '';
$signedHeaders = '';
foreach ($headers as $key => $value) {
$canonicalHeaders .= $key . ':' . $value . "\n";
$signedHeaders .= $key . ';';
}
$signedHeaders = substr($signedHeaders, 0, -1);
return [$canonicalHeaders, $signedHeaders];
}
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);
if ($errno) {
curl_close($ch);
throw new Exception('Curl error: ' . curl_error($ch));
}
curl_close($ch);
$arr = json_decode($response, true);
if (isset($arr['statusCode']) && $arr['statusCode'] == 100000) {
return isset($arr['returnObj']) ? $arr['returnObj'] : true;
} elseif (isset($arr['errorMessage'])) {
throw new Exception($arr['errorMessage']);
} elseif (isset($arr['message'])) {
throw new Exception($arr['message']);
} else {
throw new Exception('返回数据解析失败');
}
}
}

View File

@@ -70,8 +70,7 @@ class HuaweiCloud
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
if (substr($canonicalUri, -1) != "/") $canonicalUri .= "/";
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
@@ -105,6 +104,18 @@ class HuaweiCloud
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
if (substr($canonicalURI, -1) != '/') $canonicalURI .= '/';
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';

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

@@ -52,14 +52,33 @@ class allwaf implements DeployInterface
$this->log('获取证书列表成功(total=' . count($list) . ')');
$certInfo = openssl_x509_parse($fullchain, true);
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
foreach ($list as $row) {
if (!empty($list)) {
foreach ($list as $row) {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
'timeBeginAt' => $certInfo['validFrom_time_t'],
'timeEndAt' => $certInfo['validTo_time_t'],
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
}
} else {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'name' => $cert_name,
'description' => $cert_name,
'serverName' => $certInfo['subject']['CN'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
@@ -68,8 +87,8 @@ class allwaf implements DeployInterface
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
$result = $this->request('/SSLCertService/createSSLCert', $params);
$this->log('证书ID:' . $result['sslCertId'] . '添加成功!');
}
}

View File

@@ -26,7 +26,7 @@ class btpanel implements DeployInterface
$path = '/config?action=get_config';
$response = $this->request($path, []);
$result = json_decode($response, true);
if (isset($result['status']) && $result['status'] == 1) {
if (isset($result['status']) && ($result['status']==1 || isset($result['sites_path']))) {
return true;
} else {
throw new Exception(isset($result['msg']) ? $result['msg'] : '面板地址无法连接');

81
app/lib/deploy/ctyun.php Normal file
View File

@@ -0,0 +1,81 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use app\lib\client\Ctyun as CtyunClient;
use Exception;
class ctyun implements DeployInterface
{
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
private $client;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'ctcdn-global.ctapi.ctyun.cn', $this->proxy);
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$this->client->request('GET', '/v1/cert/query-cert-list');
return true;
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$param = [
'name' => $cert_name,
'key' => $privatekey,
'certs' => $fullchain,
];
try {
$this->client->request('POST', '/v1/cert/creat-cert', null, $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '已存在重名的证书') !== false) {
$this->log('已存在重名的证书 cert_name=' . $cert_name);
} else {
throw new Exception('上传证书失败:' . $e->getMessage());
}
}
$this->log('上传证书成功 cert_name=' . $cert_name);
$param = [
'domain' => $config['domain'],
'https_status' => 'on',
'cert_name' => $cert_name,
];
try {
$this->client->request('POST', '/v1/domain/update-domain', null, $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '请求已提交,请勿重复操作!') === false) {
throw new Exception($e->getMessage());
}
}
$this->log('CDN域名 ' . $config['domain'] . ' 部署证书成功!');
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -50,20 +50,39 @@ class goedge implements DeployInterface
throw new Exception('获取证书列表失败:' . $e->getMessage());
}
$list = json_decode(base64_decode($data['sslCertsJSON']), true);
if (!$list || empty($list)) {
if ($list === false) {
throw new Exception('证书列表为空');
}
$this->log('获取证书列表成功(total=' . count($list) . ')');
$certInfo = openssl_x509_parse($fullchain, true);
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
foreach ($list as $row) {
if (!empty($list)) {
foreach ($list as $row) {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
'timeBeginAt' => $certInfo['validFrom_time_t'],
'timeEndAt' => $certInfo['validTo_time_t'],
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
}
} else {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'name' => $cert_name,
'description' => $cert_name,
'serverName' => $certInfo['subject']['CN'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
@@ -72,8 +91,8 @@ class goedge implements DeployInterface
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
$result = $this->request('/SSLCertService/createSSLCert', $params);
$this->log('证书ID:' . $result['sslCertId'] . '添加成功!');
}
}

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

@@ -40,33 +40,35 @@ class opanel implements DeployInterface
$success = 0;
$errmsg = null;
foreach ($data['items'] as $row) {
if (empty($row['primaryDomain'])) continue;
$cert_domains = [];
$cert_domains[] = $row['primaryDomain'];
if(!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains)) {
$flag = true;
break;
if (!empty($data['items'])) {
foreach ($data['items'] as $row) {
if (empty($row['primaryDomain'])) continue;
$cert_domains = [];
$cert_domains[] = $row['primaryDomain'];
if(!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains)) {
$flag = true;
break;
}
}
}
if ($flag) {
$params = [
'sslID' => $row['id'],
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
try {
$this->request('/api/v1/websites/ssl/upload', $params);
$this->log("证书ID:{$row['id']}更新成功!");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("证书ID:{$row['id']}更新失败:" . $errmsg);
if ($flag) {
$params = [
'sslID' => $row['id'],
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
try {
$this->request('/api/v1/websites/ssl/upload', $params);
$this->log("证书ID:{$row['id']}更新成功!");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("证书ID:{$row['id']}更新失败:" . $errmsg);
}
}
}
}

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

@@ -68,7 +68,15 @@ class safeline implements DeployInterface
}
}
if ($success == 0) {
throw new Exception($errmsg ? $errmsg : '没有要更新的证书');
$params = [
'manual' => [
'crt' => $fullchain,
'key' => $privatekey,
],
'type' => 2,
];
$this->request('/api/open/cert', $params);
$this->log("证书上传成功!");
}
}

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') {

131
app/lib/deploy/west.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class west implements DeployInterface
{
private $logger;
private $username;
private $api_password;
private $baseUrl = 'https://api.west.cn/api/v2';
private $proxy;
public function __construct($config)
{
$this->username = $config['username'];
$this->api_password = $config['api_password'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->username) || empty($this->api_password)) throw new Exception('用户名或API密码不能为空');
$this->execute('/vhost/', ['act' => 'products']);
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['sitename'])) throw new Exception('FTP账号不能为空');
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'info'
];
try {
$data = $this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('获取虚拟主机SSL配置失败:' . $e->getMessage());
}
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'import',
'keycontent' => $privatekey,
'certcontent' => $fullchain,
];
try {
$this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('上传SSL证书失败:' . $e->getMessage());
}
$this->log('SSL证书上传成功');
if (!isset($data['SSLEnabled']) || $data['SSLEnabled'] == 0) {
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'openssl',
];
try {
$this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('虚拟主机部署SSL失败:' . $e->getMessage());
}
} else {
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'info'
];
try {
$data = $this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('获取虚拟主机SSL配置失败:' . $e->getMessage());
}
if (!empty($data['sslcert']['ssl'])) {
foreach ($data['sslcert']['ssl'] as $domain => $row) {
if (!in_array($domain, $config['domainList'])) continue;
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'clearsslcache',
'sslid' => $row['sysid'],
'dm' => $domain,
];
try {
$this->execute('/vhost/', $params);
$this->log('更新' . $domain . '证书缓存成功');
} catch (Exception $e) {
$this->log('更新' . $domain . '证书缓存失败:' . $e->getMessage());
}
}
}
}
$this->log('虚拟主机' . $config['sitename'] . '部署SSL成功');
}
private function execute($path, $params)
{
$params['username'] = $this->username;
$params['time'] = getMillisecond();
$params['token'] = md5($this->username . $this->api_password . $params['time']);
$response = curl_client($this->baseUrl . $path, str_replace('+', '%20', http_build_query($params)), null, null, null, $this->proxy);
$response = mb_convert_encoding($response['body'], 'UTF-8', 'GBK');
$arr = json_decode($response, true);
if ($arr) {
if ($arr['result'] == 200) {
return isset($arr['data']) ? $arr['data'] : [];
} else {
throw new Exception($arr['msg']);
}
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -75,10 +75,10 @@ class cloudflare implements DnsInterface
if ($data) {
$list = [];
foreach ($data['result'] as $row) {
$name = $row['zone_name'] == $row['name'] ? '@' : str_replace('.'.$row['zone_name'], '', $row['name']);
$name = $this->domain == $row['name'] ? '@' : str_replace('.'.$this->domain, '', $row['name']);
$list[] = [
'RecordId' => $row['id'],
'Domain' => $row['zone_name'],
'Domain' => $this->domain,
'Name' => $name,
'Type' => $row['type'],
'Value' => $row['content'],
@@ -107,11 +107,11 @@ class cloudflare implements DnsInterface
{
$data = $this->send_reuqest('GET', '/zones/'.$this->domainid.'/dns_records/'.$RecordId);
if ($data) {
$name = $data['result']['zone_name'] == $data['result']['name'] ? '@' : str_replace('.' . $data['result']['zone_name'], '', $data['result']['name']);
$name = $this->domain == $data['result']['name'] ? '@' : str_replace('.' . $this->domain, '', $data['result']['name']);
return [
'RecordId' => $data['result']['id'],
'Domain' => $data['result']['zone_name'],
'Name' => str_replace('.'.$data['result']['zone_name'], '', $data['result']['name']),
'Domain' => $this->domain,
'Name' => $name,
'Type' => $data['result']['type'],
'Value' => $data['result']['content'],
'Line' => $data['result']['proxied'] ? '1' : '0',

View File

@@ -117,7 +117,9 @@ class CertOrderService
$this->saveLog(date('Y-m-d H:i:s').' - 开始添加DNS记录');
$this->addDns();
$this->saveLog('添加DNS记录成功请等待生效后进行验证...');
Db::name('cert_order')->where('id', $this->order['id'])->update(['retrytime' => date('Y-m-d H:i:s', time() + 300)]);
if (CertHelper::$cert_config[$this->atype]['cname']) {
Db::name('cert_order')->where('id', $this->order['id'])->update(['retrytime' => date('Y-m-d H:i:s', time() + 180)]);
}
return 1;
}
// step4: 查询DNS

View File

@@ -13,6 +13,7 @@ class CertTaskService
{
$this->execute_deploy();
$this->execute_order();
config_set('certtask_time', date("Y-m-d H:i:s"));
echo 'done'.PHP_EOL;
}

View File

@@ -70,7 +70,8 @@ class CertDnsUtils
}
}
$res = $dns->addDomainRecord($row['name'], $row['type'], $row['value'], DnsHelper::$line_name[$drow['type']]['DEF'], 600);
$ttl = $drow['type'] == 'namesilo' ? 3600 : 600;
$res = $dns->addDomainRecord($row['name'], $row['type'], $row['value'], DnsHelper::$line_name[$drow['type']]['DEF'], $ttl);
if (!$res && $row['type'] != 'CAA') throw new Exception('添加'.$domain.'解析记录失败,' . $dns->getError());
$log('Add DNS Record: '.$domain.' '.$row['type'].' '.$row['value']);
}

View File

@@ -74,9 +74,13 @@ class CheckUtils
public static function tcp($target, $port, $timeout)
{
if (substr($target, -1) == '.') $target = substr($target, 0, -1);
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'error' => 'DNS resolve failed', 'usetime' => 0];
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);
@@ -93,13 +97,14 @@ class CheckUtils
public static function ping($target)
{
if (!function_exists('exec')) return ['status' => false, 'error' => 'exec函数不可用', 'usetime' => 0];
if (!function_exists('exec')) return ['status' => false, 'errmsg' => 'exec函数不可用', 'usetime' => 0];
if (substr($target, -1) == '.') $target = substr($target, 0, -1);
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'error' => 'DNS resolve failed', 'usetime' => 0];
if (!$target) return ['status' => false, 'errmsg' => 'DNS resolve failed', 'usetime' => 0];
}
if (!filter_var($target, FILTER_VALIDATE_IP)) {
return ['status' => false, 'error' => 'Invalid IP address', 'usetime' => 0];
return ['status' => false, 'errmsg' => 'Invalid IP address', 'usetime' => 0];
}
$timeout = 1;
exec('ping -c 1 -w '.$timeout.' '.$target.'', $output, $return_var);

View File

@@ -229,7 +229,7 @@ class MsgNotice
'content' => $content,
],
];
} elseif (strpos($url, 'open.feishu.cn')) {
} elseif (strpos($url, 'open.feishu.cn') || strpos($url, 'open.larksuite.com')) {
$content = str_replace(['\*', '**'], ['*', ''], strip_tags($content));
$post = [
'msg_type' => 'text',

View File

@@ -29,7 +29,16 @@ pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51
</div>
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i> 搜索</button>
<a href="javascript:searchClear()" class="btn btn-default" title="刷新订单列表"><i class="fa fa-refresh"></i> 刷新</a>
<a href="/cert/order/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
<div class="btn-group">
<a href="/cert/order/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="/cert/order/import">导入已有证书</a></li>
</ul>
</div>
</form>
<table id="listTable">

View File

@@ -8,6 +8,7 @@
<div class="panel-body">
<p><li>计划任务将以下命令添加到计划任务1分钟1次</li></p>
<p><code>cd {:app()->getRootPath()} && php think certtask</code></p>
<p><li>上次运行时间:<font color="green">{:config_get('certtask_time', '未运行', true)}</font></li></p>
</div>
</div>

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

@@ -0,0 +1,167 @@
{extend name="common/layout" /}
{block name="title"}导入已有证书{/block}
{block name="main"}
<style>
.tips{color: #f6a838; padding-left: 5px;}
.control-label[is-required]:before {
content: "*";
color: #f56c6c;
margin-right: 4px;
}
.input-group-addon{padding: 6px 6px;}
</style>
<div class="row" id="app">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title"><a href="/cert/certorder" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>导入已有证书</h3></div>
<div class="panel-body">
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="taskform">
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>证书内容</label>
<div class="col-sm-6">
<div class="input-group">
<textarea name="fullchain" v-model="set.fullchain" class="form-control" rows="5" placeholder="输入PEM格式证书链" required></textarea>
<a class="btn btn-default input-group-addon" @click="upload('fullchain')" title="上传证书文件"><i class="fa fa-upload"></i></a>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>私钥内容</label>
<div class="col-sm-6">
<div class="input-group">
<textarea name="privatekey" v-model="set.privatekey" class="form-control" rows="5" placeholder="输入PEM格式私钥" required></textarea>
<a class="btn btn-default input-group-addon" @click="upload('privatekey')" title="上传私钥文件"><i class="fa fa-upload"></i></a>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 col-xs-12 control-label no-padding-right" is-required>证书续期账户</label>
<div class="col-sm-6"><select name="aid" v-model="set.aid" class="form-control" required>
<option value="">--选择证书账户--</option>
{foreach $accounts as $k=>$v}
<option value="{$k}" data-type="{$v.type}">{$v.name}</option>
{/foreach}
</select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>签名算法</label>
<div class="col-sm-6">
<label class="radio-inline" v-for="item in keytypeList">
<input type="radio" name="keytype" :value="item" v-model="set.keytype"> {{item}}
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>密钥长度</label>
<div class="col-sm-6">
<label class="radio-inline" v-for="item in keysizeList">
<input type="radio" name="keysize" :value="item.value" v-model="set.keysize"> {{item.label}}
</label>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" @click="submit">提交</button></div>
</div>
</form>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}vue/2.6.14/vue.min.js"></script>
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script src="/static/js/bootstrapValidator.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
type: '',
set: {
fullchain: '',
privatekey: '',
aid: '',
keytype: '',
keysize: '',
},
keytypeList: [
'RSA',
'ECC'
],
keysizeMap: [
{label:'2048 bit',value:'2048',type:'RSA'},
{label:'3072 bit',value:'3072',type:'RSA'},
{label:'P-256',value:'256',type:'ECC'},
{label:'P-384',value:'384',type:'ECC'},
],
keysizeList: [],
},
watch: {
'set.aid': function(val){
this.type = $('option:selected', 'select[name=aid]').data('type');
},
'set.keytype': function(val){
this.keysizeList = this.keysizeMap.filter((item) => {
return item.type == val;
})
if(!this.keysizeList.filter((item) => {return item.value == this.set.keysize}).length)
this.set.keysize = this.keysizeList[0].value;
},
},
mounted() {
this.set.keytype = 'RSA';
$("#taskform").bootstrapValidator({
live: 'submitted',
});
$('[data-toggle="tooltip"]').tooltip();
},
methods: {
submit(){
var that=this;
$("#taskform").data("bootstrapValidator").validate();
if(!$("#taskform").data("bootstrapValidator").isValid()){
return false;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type: "POST",
url: "",
data: this.set,
dataType: 'json',
success: function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {icon: 1}, function(){
if(document.referrer.indexOf('/cert/certorder?') > 0)
window.location.href = document.referrer;
else
window.location.href = '/cert/certorder';
});
}else{
layer.alert(data.msg, {icon: 2});
}
},
error: function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
},
upload(name){
//读取上传文件并填充到表单
var file = document.createElement('input');
file.type = 'file';
file.accept = '.pem,.crt,.key';
file.style.display = 'none';
file.onchange = function(){
var reader = new FileReader();
reader.onload = function(e){
this.set[name] = e.target.result;
}.bind(this);
reader.readAsText(file.files[0]);
}.bind(this);
document.body.appendChild(file);
file.click();
}
},
});
</script>
{/block}

View File

@@ -136,7 +136,7 @@
<li><a href="/optimizeip/opiplist"><i class="fa fa-circle-o"></i> 任务管理</a></li>
</ul>
</li>
<li class="treeview {:checkIfActive('certaccount,account_form,certorder,order_form,deployaccount,deploytask,deploy_form,certset,cname')}">
<li class="treeview {:checkIfActive('certaccount,account_form,certorder,order_form,order_import,deployaccount,deploytask,deploy_form,certset,cname')}">
<a href="javascript:;">
<i class="fa fa-expeditedssl fa-fw"></i>
<span>SSL证书</span>

View File

@@ -1,5 +1,5 @@
{extend name="common/layout" /}
{block name="title"}容灾切换代理设置{/block}
{block name="title"}代理设置{/block}
{block name="main"}
<div class="row">
<div class="col-xs-12 col-sm-8 col-lg-6 center-block" style="float: none;">

View File

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

View File

@@ -93,6 +93,7 @@ Route::group(function () {
Route::post('/cert/order/data', 'cert/order_data');
Route::post('/cert/order/process', 'cert/order_process');
Route::post('/cert/order/:action', 'cert/order_op');
Route::get('/cert/order/import', 'cert/order_import');
Route::get('/cert/order/:action', 'cert/order_form');
Route::get('/cert/deploytask', 'cert/deploytask');