mirror of
https://github.com/netcccyun/dnsmgr.git
synced 2026-05-13 00:26:27 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0eb096873 | ||
|
|
ebdc34cf4b | ||
|
|
b19cabcbfd | ||
|
|
64b5221787 |
@@ -304,10 +304,6 @@ class Cert extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
if ($certInfo['keytype'] == 'ECC') {
|
||||
$privatekey = CertHelper::ensureECPrivateKeyFormat($privatekey);
|
||||
}
|
||||
|
||||
$order = [
|
||||
'aid' => 0,
|
||||
'keytype' => $certInfo['keytype'],
|
||||
@@ -371,10 +367,6 @@ class Cert extends BaseController
|
||||
if ($certInfo['code'] == -1) return json($certInfo);
|
||||
$domains = $certInfo['domains'];
|
||||
|
||||
if ($certInfo['keytype'] == 'ECC') {
|
||||
$privatekey = CertHelper::ensureECPrivateKeyFormat($privatekey);
|
||||
}
|
||||
|
||||
$order = [
|
||||
'aid' => 0,
|
||||
'keytype' => $certInfo['keytype'],
|
||||
|
||||
@@ -407,24 +407,6 @@ location / {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保ECC私钥使用EC专用格式标识
|
||||
* 某些程序需要EC标识才能正确识别ECC私钥
|
||||
*/
|
||||
public static function ensureECPrivateKeyFormat($private_key)
|
||||
{
|
||||
if (strpos($private_key, '-----BEGIN EC PRIVATE KEY-----') !== false) {
|
||||
return $private_key;
|
||||
}
|
||||
|
||||
if (strpos($private_key, '-----BEGIN PRIVATE KEY-----') !== false) {
|
||||
$private_key = preg_replace('/^-----BEGIN PRIVATE KEY-----$/m', '-----BEGIN EC PRIVATE KEY-----', $private_key);
|
||||
$private_key = preg_replace('/^-----END PRIVATE KEY-----$/m', '-----END EC PRIVATE KEY-----', $private_key);
|
||||
}
|
||||
|
||||
return $private_key;
|
||||
}
|
||||
|
||||
public static function getPfx($fullchain, $privatekey, $pwd = '123456')
|
||||
{
|
||||
openssl_pkcs12_export($fullchain, $pfx, $privatekey, $pwd);
|
||||
|
||||
@@ -614,9 +614,9 @@ class DeployHelper
|
||||
],
|
||||
'node_name' => [
|
||||
'name' => '子节点名称',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'note' => '不填写时,将替换主控节点证书;否则,将替换被控节点证书',
|
||||
'type' => 'textarea',
|
||||
'placeholder' => '每行一个节点名称',
|
||||
'note' => '不填写时,将替换主控节点证书;否则,将替换被控节点证书。多个节点请每行填写一个',
|
||||
'show' => 'type==0',
|
||||
],
|
||||
],
|
||||
@@ -1224,6 +1224,7 @@ ctrl+x 保存退出',
|
||||
['value'=>'tse', 'label'=>'云原生API网关TSE'],
|
||||
['value'=>'tcb', 'label'=>'云开发TCB'],
|
||||
['value'=>'lighthouse', 'label'=>'轻量应用服务器'],
|
||||
['value'=>'update', 'label'=>'更新证书内容(证书ID不变)'],
|
||||
],
|
||||
'value' => 'cdn',
|
||||
'required' => true,
|
||||
@@ -1327,6 +1328,14 @@ ctrl+x 保存退出',
|
||||
'note' => 'CDN、EO、WAF多个域名可用,隔开,其他只能填写1个域名',
|
||||
'required' => true,
|
||||
],
|
||||
'cert_id' => [
|
||||
'name' => '证书ID',
|
||||
'type' => 'input',
|
||||
'placeholder' => '要更新的证书ID,在我的证书列表查看',
|
||||
'show' => 'product==\'update\'',
|
||||
'required' => true,
|
||||
'note' => '当前接口需联系加白使用',
|
||||
],
|
||||
],
|
||||
],
|
||||
'huawei' => [
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace app\lib\acme;
|
||||
|
||||
use Exception;
|
||||
use stdClass;
|
||||
use app\lib\CertHelper;
|
||||
|
||||
/**
|
||||
* ACMECert
|
||||
@@ -369,12 +368,10 @@ class ACMECert extends ACMEv2
|
||||
if (version_compare(PHP_VERSION, '7.1.0') < 0) throw new Exception('PHP >= 7.1.0 required for EC keys !');
|
||||
$map = array('256' => 'prime256v1', '384' => 'secp384r1', '521' => 'secp521r1');
|
||||
if (isset($map[$curve_name])) $curve_name = $map[$curve_name];
|
||||
$pem = $this->generateKey(array(
|
||||
return $this->generateKey(array(
|
||||
'curve_name' => $curve_name,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_EC
|
||||
));
|
||||
|
||||
return CertHelper::ensureECPrivateKeyFormat($pem);
|
||||
}
|
||||
|
||||
public function parseCertificate($cert_pem)
|
||||
|
||||
@@ -11,7 +11,6 @@ class opanel implements DeployInterface
|
||||
private $url;
|
||||
private $key;
|
||||
private $proxy;
|
||||
private $nodeName;
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
@@ -28,7 +27,11 @@ class opanel implements DeployInterface
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
// 解析节点名称列表
|
||||
$nodeNames = $this->parseNodeNames($config);
|
||||
|
||||
if (isset($config['type']) && $config['type'] == '3') {
|
||||
// 面板本身的证书部署
|
||||
$params = [
|
||||
'cert' => $fullchain,
|
||||
'key' => $privatekey,
|
||||
@@ -36,18 +39,66 @@ class opanel implements DeployInterface
|
||||
'sslID' => null,
|
||||
'sslType' => 'import-paste',
|
||||
];
|
||||
try {
|
||||
$this->request('/core/settings/ssl/update', $params);
|
||||
$this->log("面板证书更新成功!");
|
||||
|
||||
if (empty($nodeNames)) {
|
||||
// 没有指定节点,部署到主控节点
|
||||
try {
|
||||
$this->request('/core/settings/ssl/update', $params);
|
||||
$this->log("面板证书更新成功!");
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("面板证书更新失败:" . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
// 部署到多个子节点
|
||||
$successCount = 0;
|
||||
$failCount = 0;
|
||||
foreach ($nodeNames as $nodeName) {
|
||||
try {
|
||||
$this->request('/core/settings/ssl/update', $params, $nodeName);
|
||||
$this->log("节点 [{$nodeName}] 面板证书更新成功!");
|
||||
$successCount++;
|
||||
} catch (Exception $e) {
|
||||
$this->log("节点 [{$nodeName}] 面板证书更新失败:" . $e->getMessage());
|
||||
$failCount++;
|
||||
}
|
||||
}
|
||||
if ($failCount > 0 && $successCount == 0) {
|
||||
throw new Exception("所有节点证书更新失败");
|
||||
}
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("面板证书更新失败:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['node_name'])) $this->nodeName = $config['node_name'];
|
||||
// 如果没有指定节点,则部署到主控节点
|
||||
if (empty($nodeNames)) {
|
||||
$this->deployToNode($fullchain, $privatekey, $config, null);
|
||||
} else {
|
||||
// 部署到多个子节点
|
||||
$successCount = 0;
|
||||
$failCount = 0;
|
||||
foreach ($nodeNames as $nodeName) {
|
||||
try {
|
||||
$this->deployToNode($fullchain, $privatekey, $config, $nodeName);
|
||||
$successCount++;
|
||||
} catch (Exception $e) {
|
||||
$this->log("节点 [{$nodeName}] 部署失败:" . $e->getMessage());
|
||||
$failCount++;
|
||||
}
|
||||
}
|
||||
if ($failCount > 0 && $successCount == 0) {
|
||||
throw new Exception("所有节点部署失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 部署到指定节点
|
||||
*/
|
||||
private function deployToNode($fullchain, $privatekey, $config, $nodeName = null)
|
||||
{
|
||||
if (!empty($config['id'])) {
|
||||
// 指定证书ID的情况
|
||||
$params = [
|
||||
'sslID' => intval($config['id']),
|
||||
'type' => 'paste',
|
||||
@@ -56,23 +107,28 @@ class opanel implements DeployInterface
|
||||
'description' => '',
|
||||
];
|
||||
try {
|
||||
$this->request('/websites/ssl/upload', $params);
|
||||
$this->log("证书ID:{$config['id']}更新成功!");
|
||||
$this->request('/websites/ssl/upload', $params, $nodeName);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新成功!" : "证书ID:{$config['id']}更新成功!";
|
||||
$this->log($logMsg);
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("证书ID:{$config['id']}更新失败:" . $e->getMessage());
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$config['id']}更新失败:" : "证书ID:{$config['id']}更新失败:";
|
||||
throw new Exception($logMsg . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 根据域名自动匹配证书
|
||||
$domains = $config['domainList'];
|
||||
if (empty($domains)) throw new Exception('没有设置要部署的域名');
|
||||
|
||||
$params = ['page' => 1, 'pageSize' => 500];
|
||||
try {
|
||||
$data = $this->request("/websites/ssl/search", $params);
|
||||
$this->log('获取证书列表成功(total=' . $data['total'] . ')');
|
||||
$data = $this->request("/websites/ssl/search", $params, $nodeName);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "";
|
||||
$this->log($logMsg . '获取证书列表成功(total=' . $data['total'] . ')');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取证书列表失败:' . $e->getMessage());
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] " : "";
|
||||
throw new Exception($logMsg . '获取证书列表失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$success = 0;
|
||||
@@ -99,12 +155,14 @@ class opanel implements DeployInterface
|
||||
'description' => '',
|
||||
];
|
||||
try {
|
||||
$this->request('/websites/ssl/upload', $params);
|
||||
$this->log("证书ID:{$row['id']}更新成功!");
|
||||
$this->request('/websites/ssl/upload', $params, $nodeName);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新成功!" : "证书ID:{$row['id']}更新成功!";
|
||||
$this->log($logMsg);
|
||||
$success++;
|
||||
} catch (Exception $e) {
|
||||
$errmsg = $e->getMessage();
|
||||
$this->log("证书ID:{$row['id']}更新失败:" . $errmsg);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书ID:{$row['id']}更新失败:" : "证书ID:{$row['id']}更新失败:";
|
||||
$this->log($logMsg . $errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,8 +175,9 @@ class opanel implements DeployInterface
|
||||
'privateKey' => $privatekey,
|
||||
'description' => '',
|
||||
];
|
||||
$this->request('/websites/ssl/upload', $params);
|
||||
$this->log("证书上传成功!");
|
||||
$this->request('/websites/ssl/upload', $params, $nodeName);
|
||||
$logMsg = $nodeName ? "节点 [{$nodeName}] 证书上传成功!" : "证书上传成功!";
|
||||
$this->log($logMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +193,32 @@ class opanel implements DeployInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function request($path, $params = null)
|
||||
/**
|
||||
* 解析节点名称列表
|
||||
*/
|
||||
private function parseNodeNames($config)
|
||||
{
|
||||
if (!isset($config['node_name']) || empty($config['node_name'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$nodeNameStr = trim($config['node_name']);
|
||||
if (empty($nodeNameStr)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 按行分割,过滤空行
|
||||
$nodeNames = array_filter(
|
||||
array_map('trim', explode("\n", $nodeNameStr)),
|
||||
function($name) {
|
||||
return !empty($name);
|
||||
}
|
||||
);
|
||||
|
||||
return array_values($nodeNames);
|
||||
}
|
||||
|
||||
private function request($path, $params = null, $nodeName = null)
|
||||
{
|
||||
$url = $this->url . $path;
|
||||
|
||||
@@ -144,8 +228,8 @@ class opanel implements DeployInterface
|
||||
'1Panel-Token' => $token,
|
||||
'1Panel-Timestamp' => $timestamp,
|
||||
];
|
||||
if (!empty($this->nodeName)) {
|
||||
$headers['CurrentNode'] = $this->nodeName;
|
||||
if (!empty($nodeName)) {
|
||||
$headers['CurrentNode'] = $nodeName;
|
||||
}
|
||||
$body = $params ? json_encode($params) : '{}';
|
||||
if ($body) $headers['Content-Type'] = 'application/json';
|
||||
|
||||
@@ -159,9 +159,14 @@ class ssh implements DeployInterface
|
||||
file_put_contents($privateKeyPath, $this->config['privatekey']);
|
||||
file_put_contents($publicKeyPath, $publicKey);
|
||||
umask($umask);
|
||||
$passphrase = $this->config['passphrase'] ?? null; // 私钥密码
|
||||
if (!ssh2_auth_pubkey_file($connection, $this->config['username'], $publicKeyPath, $privateKeyPath, $passphrase)) {
|
||||
throw new Exception('私钥认证失败');
|
||||
if (!empty($this->config['passphrase'])) {
|
||||
if (!ssh2_auth_pubkey_file($connection, $this->config['username'], $publicKeyPath, $privateKeyPath, $this->config['passphrase'])) {
|
||||
throw new Exception('私钥认证失败');
|
||||
}
|
||||
} else {
|
||||
if (!ssh2_auth_pubkey_file($connection, $this->config['username'], $publicKeyPath, $privateKeyPath)) {
|
||||
throw new Exception('私钥认证失败');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!ssh2_auth_password($connection, $this->config['username'], $this->config['password'])) {
|
||||
|
||||
@@ -31,6 +31,9 @@ class tencent implements DeployInterface
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
if ($config['product'] == 'update') {
|
||||
return $this->update_cert($fullchain, $privatekey, $config);
|
||||
}
|
||||
$cert_id = $this->get_cert_id($fullchain, $privatekey);
|
||||
if (!$cert_id) throw new Exception('证书ID获取失败');
|
||||
if ($config['product'] == 'cos') {
|
||||
@@ -281,6 +284,95 @@ class tencent implements DeployInterface
|
||||
$this->log('边缘安全加速域名 ' . $config['domain'] . ' 部署证书成功!');
|
||||
}
|
||||
|
||||
private function update_cert($fullchain, $privatekey, $config)
|
||||
{
|
||||
if (empty($config['cert_id'])) throw new Exception('证书ID不能为空');
|
||||
|
||||
$param = [
|
||||
'CertificateIds' => [$config['cert_id']],
|
||||
'IsCache' => 1,
|
||||
];
|
||||
try {
|
||||
$data = $this->client->request('CreateCertificateBindResourceSyncTask', $param);
|
||||
if (empty($data['CertTaskIds'])) throw new Exception('返回任务ID为空');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('创建关联云资源查询任务失败:' . $e->getMessage());
|
||||
}
|
||||
$task_id = $data['CertTaskIds'][0]['TaskId'];
|
||||
$this->log('创建关联云资源查询任务成功 TaskId=' . $task_id);
|
||||
|
||||
$retry = 0;
|
||||
$resource_result = null;
|
||||
while ($retry++ < 30) {
|
||||
sleep(2);
|
||||
$param = [
|
||||
'TaskIds' => [$task_id],
|
||||
];
|
||||
try {
|
||||
$data = $this->client->request('DescribeCertificateBindResourceTaskResult', $param);
|
||||
if (empty($data['SyncTaskBindResourceResult'])) throw new Exception('返回结果为空');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('查询关联云资源任务结果失败:' . $e->getMessage());
|
||||
}
|
||||
$taskResult = $data['SyncTaskBindResourceResult'][0];
|
||||
if ($taskResult['Status'] == 1) {
|
||||
$resource_result = $taskResult['BindResourceResult'];
|
||||
break;
|
||||
} elseif ($taskResult['Status'] == 2) {
|
||||
throw new Exception('关联云资源查询任务执行失败:' . isset($taskResult['Error']) ? $taskResult['Error']['Message'] : '未知错误');
|
||||
}
|
||||
};
|
||||
if (!$resource_result) {
|
||||
throw new Exception('关联云资源查询任务超时未完成,请稍后重试');
|
||||
}
|
||||
|
||||
$resourceTypes = [];
|
||||
$resourceTypesRegions = [];
|
||||
foreach ($resource_result as $res) {
|
||||
if ($res['ResourceType'] != 'clb') continue;
|
||||
$totalCount = 0;
|
||||
$regions = [];
|
||||
foreach ($res['BindResourceRegionResult'] as $regionRes) {
|
||||
if ($regionRes['TotalCount'] > 0) {
|
||||
$totalCount += $regionRes['TotalCount'];
|
||||
if (!empty($regionRes['Region'])) {
|
||||
$regions[] = $regionRes['Region'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($totalCount > 0) {
|
||||
$resourceTypes[] = $res['ResourceType'];
|
||||
if (!empty($regions)) {
|
||||
$resourceTypesRegions[] = [
|
||||
'ResourceType' => $res['ResourceType'],
|
||||
'Regions' => $regions,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$param = [
|
||||
'OldCertificateId' => $config['cert_id'],
|
||||
'CertificatePublicKey' => $fullchain,
|
||||
'CertificatePrivateKey' => $privatekey,
|
||||
'ResourceTypes' => $resourceTypes,
|
||||
'ResourceTypesRegions' => $resourceTypesRegions,
|
||||
];
|
||||
$retry = 0;
|
||||
while ($retry++ < 10) {
|
||||
try {
|
||||
$data = $this->client->request('UploadUpdateCertificateInstance', $param);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('更新证书内容失败:' . $e->getMessage());
|
||||
}
|
||||
if ($data['DeployStatus'] == 1) {
|
||||
break;
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
$this->log('更新证书内容成功,可能需要一些时间完成各资源的证书更新部署');
|
||||
}
|
||||
|
||||
public function setLogger($func)
|
||||
{
|
||||
$this->logger = $func;
|
||||
|
||||
@@ -31,9 +31,15 @@ class upyun implements DeployInterface
|
||||
$this->login();
|
||||
|
||||
$url = 'https://console.upyun.com/api/https/certificate/';
|
||||
// 如果是 EC 证书,调整私钥头为 EC PRIVATE KEY
|
||||
$privatekey_send = $privatekey;
|
||||
if ($this->isEcCertificate($fullchain)) {
|
||||
$privatekey_send = str_replace('-----BEGIN PRIVATE KEY-----', '-----BEGIN EC PRIVATE KEY-----', $privatekey_send);
|
||||
$privatekey_send = str_replace('-----END PRIVATE KEY-----', '-----END EC PRIVATE KEY-----', $privatekey_send);
|
||||
}
|
||||
$params = [
|
||||
'certificate' => $fullchain,
|
||||
'private_key' => $privatekey,
|
||||
'private_key' => $privatekey_send,
|
||||
];
|
||||
$response = http_request($url, http_build_query($params), null, $this->cookie, null, $this->proxy);
|
||||
$result = json_decode($response['body'], true);
|
||||
@@ -130,4 +136,22 @@ class upyun implements DeployInterface
|
||||
call_user_func($this->logger, $txt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 EC (ECDSA) 证书
|
||||
*/
|
||||
private function isEcCertificate($fullchain)
|
||||
{
|
||||
// 提取第一个证书
|
||||
if (!preg_match('/-----BEGIN CERTIFICATE-----\s*(.+?)\s*-----END CERTIFICATE-----/s', $fullchain, $m)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pubKey = openssl_pkey_get_public($m[0]);
|
||||
if (!$pubKey) return false;
|
||||
|
||||
$details = openssl_pkey_get_details($pubKey);
|
||||
|
||||
return $details && ($details['type'] ?? 0) === OPENSSL_KEYTYPE_EC;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ return [
|
||||
'show_error_msg' => true,
|
||||
'exception_tmpl' => \think\facade\App::getAppPath() . 'view/exception.tpl',
|
||||
|
||||
'version' => '1043',
|
||||
'version' => '1044',
|
||||
|
||||
'dbversion' => '1040'
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user