From 4310ccb770a348bc6732d781cc871e8772d3e6e1 Mon Sep 17 00:00:00 2001 From: Hanada Date: Sat, 10 May 2025 21:09:12 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8DCloudFront=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/client/AWS.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/client/AWS.php b/app/lib/client/AWS.php index a67e1f6..e0bfe63 100644 --- a/app/lib/client/AWS.php +++ b/app/lib/client/AWS.php @@ -145,6 +145,7 @@ class AWS $path = '/' . $this->version . $path; $body = ''; + $query = []; if ($method == 'GET' || $method == 'DELETE') { $query = $params; } else { From 0752f07f7deb9e134825ad811597c82ddddad4c3 Mon Sep 17 00:00:00 2001 From: Hanada Date: Sun, 11 May 2025 19:56:34 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=BD=91=E5=AE=BFCDNPr?= =?UTF-8?q?o=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复spKey逻辑判断问题 支持网宿CDNPro部署 --- app/lib/DeployHelper.php | 42 +++++ app/lib/deploy/wangsu.php | 269 ++++++++++++++++++++++++++++++++ public/static/images/wangsu.ico | Bin 0 -> 4286 bytes 3 files changed, 311 insertions(+) create mode 100644 app/lib/deploy/wangsu.php create mode 100644 public/static/images/wangsu.ico diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index 569eb37..802ce49 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -1210,6 +1210,48 @@ class DeployHelper ], ], ], + 'wangsu' => [ + 'name' => '网宿科技', + 'class' => 2, + 'icon' => 'wangsu.ico', + 'note' => '支持网宿CDN Pro,暂不支持其他产品线,暂不支持AKSK鉴权', + 'inputs' => [ + 'username' => [ + 'name' => '账号', + 'type' => 'input', + 'placeholder' => '', + 'required' => true, + ], + 'apiKey' => [ + 'name' => 'APIKEY', + 'type' => 'input', + 'placeholder' => '自行联系提供商申请', + 'required' => true, + ], + 'spKey' => [ + 'name' => '特殊KEY', + 'type' => 'input', + 'placeholder' => '特殊场景下才需要使用的APIKEY,留空默认同APIKEY', + ], + 'proxy' => [ + 'name' => '使用代理服务器', + 'type' => 'radio', + 'options' => [ + '0' => '否', + '1' => '是', + ], + 'value' => '0' + ], + ], + 'taskinputs' => [ + 'domain' => [ + 'name' => '绑定的域名', + 'type' => 'input', + 'placeholder' => '', + 'required' => true, + ], + ], + ], 'baishan' => [ 'name' => '白山云', 'class' => 2, diff --git a/app/lib/deploy/wangsu.php b/app/lib/deploy/wangsu.php new file mode 100644 index 0000000..60f6933 --- /dev/null +++ b/app/lib/deploy/wangsu.php @@ -0,0 +1,269 @@ +username = $config['username']; + $this->apiKey = $config['apiKey']; + $this->spKey = $config['spKey']; + $this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false; + } + + public function check() + { + if (empty($this->username) || empty($this->apiKey)) throw new Exception('必填参数不能为空'); + $this->request('/cdn/certificates'); + return true; + } + + public function deploy($fullchain, $privatekey, $config, &$info) + { + $domain = $config['domain']; + if (empty($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']; + + $cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_name); + + $hostnameInfo = $this->request('/cdn/hostnames/' . $domain); + if (empty($hostnameInfo["propertyInProduction"])) { + throw new Exception('域名 ' . $domain . ' 不存在或未部署到生产环境'); + } else { + $this->log('CDN域名 ' . $domain . ' 对应的加速项目ID:' . $hostnameInfo["propertyInProduction"]["propertyId"]); + $this->log('CDN域名 ' . $domain . ' 对应的加速项目生产版本:' . $hostnameInfo["propertyInProduction"]["version"]); + } + + if ($hostnameInfo["propertyInProduction"]["certificateId"] == $cert_id) { + $this->log('CDN域名 ' . $domain . ' 已绑定证书:' . $cert_name); + return; + } + + $properity = $this->request('/cdn/properties/' . $hostnameInfo["propertyInProduction"]["propertyId"] . '/versions/' . $hostnameInfo["propertyInProduction"]["version"]); + $properityConfig = $properity["configs"]; + $properityConfig["tlsCertificateId"] = $cert_id; + + $data = $this->request('/cdn/properties/' . $hostnameInfo["propertyInProduction"]["propertyId"] . '/versions', $properityConfig, true); + $url_parts = parse_url($data); + $path_parts = explode('/', $url_parts['path']); + $newVersion = end($path_parts); + + $data = $this->request('/cdn/validations', [ + 'propertyId' => $hostnameInfo["propertyInProduction"]["propertyId"], + 'version' => intval($newVersion), + ], true); + $url_parts = parse_url($data); + $path_parts = explode('/', $url_parts['path']); + $validationTaskId = end($path_parts); + $this->log('验证任务ID:' . $validationTaskId); + + $attempts = 0; + $maxAttempts = 12; + $status = null; + + do { + sleep(5); + $data = $this->request('/cdn/validations/' . $validationTaskId); + $status = $data['status']; + + if ($status === 'failed') { + throw new Exception('证书绑定失败,加速项目验证失败'); + } + + if ($status === 'succeeded') { + break; // 验证成功立即退出循环 + } + + $attempts++; + } while ($attempts < $maxAttempts); + + if ($status !== 'succeeded') { + throw new Exception('证书绑定超时,加速项目验证时间过长'); + } + + $this->log('加速项目验证成功,开始部署...'); + $deploymentTasks = [ + 'target' => 'production', + 'actions' => [ + [ + 'action' => 'deploy_cert', + 'certificateId' => $cert_id, + 'version' => 1, + ], + [ + 'action' => 'deploy_property', + 'propertyId' => $hostnameInfo["propertyInProduction"]["propertyId"], + 'version' => intval($newVersion), + ] + ], + 'name' => 'Deploy certificate and property for ' . $hostnameInfo["propertyInProduction"]["propertyId"], + ]; + + $data = $this->request('/cdn/deploymentTasks', $deploymentTasks, true, null, ['Check-Certificate' => 'no', 'Check-Usage' => 'no']); + $url_parts = parse_url($data); + $path_parts = explode('/', $url_parts['path']); + $deploymentTaskId = end($path_parts); + $this->log('CDN域名 ' . $domain . ' 绑定证书部署任务下发成功,部署任务ID:' . $deploymentTaskId); + $info['cert_id'] = $cert_id; + } + + private function get_cert_id($fullchain, $privatekey, $cert_name) + { + $cert_id = null; + + $data = $this->request('/cdn/certificates?search=' . urlencode($cert_name)); + if ($data['count'] > 0) { + foreach ($data['certificates'] as $cert) { + if ($cert_name == $cert['name']) { + $cert_id = $cert['certificateId']; + $this->log('证书' . $cert_name . '已存在,证书ID:' . $cert_id); + } + } + } + + if (!$cert_id) { + $date = gmdate("D, d M Y H:i:s T"); + $encryptedKey = $this->encryptPrivateKey($privatekey, $date); + $param = [ + 'name' => $cert_name, + 'autoRenew' => 'Off', + 'newVersion' => [ + 'privateKey' => $encryptedKey, + 'certificate' => $fullchain, + ] + ]; + try { + $data = $this->request('/cdn/certificates', $param, true, $date); + } catch (Exception $e) { + throw new Exception('上传证书失败:' . $e->getMessage()); + } + $url_parts = parse_url($data); + $path_parts = explode('/', $url_parts['path']); + $cert_id = end($path_parts); + $this->log('上传证书成功,证书ID:' . $cert_id); + usleep(500000); + } + return $cert_id; + } + + private function encryptPrivateKey($privateKey, $date = null) + { + // 获取当前 GMT 时间(DATE) + if (empty($date)) { + $date = gmdate("D, d M Y H:i:s T"); + } + + // 生成 HMAC-SHA256 密钥材料 + if (!empty($this->spKey)) { + $apiKey = $this->spKey; + } else { + $apiKey = $this->apiKey; + } + $hmac = hash_hmac('sha256', $date, $apiKey, true); + $aesIvKeyHex = bin2hex($hmac); + + if (strlen($aesIvKeyHex) != 64) { + throw new Exception("Invalid HMAC length: " . strlen($aesIvKeyHex)); + } + + // 提取 IV 和 Key + $ivHex = substr($aesIvKeyHex, 0, 32); + $keyHex = substr($aesIvKeyHex, 32, 64); + + $iv = hex2bin($ivHex); + $key = hex2bin($keyHex); + + $blockSize = 16; // AES 块大小为 16 字节 + $plainLen = strlen($privateKey); + $padLen = $blockSize - ($plainLen % $blockSize); + $padding = str_repeat(chr($padLen), $padLen); + $plainText = $privateKey . $padding; + + // AES-128-CBC 加密 + $encrypted = openssl_encrypt( + $plainText, + 'AES-128-CBC', + $key, + OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, + $iv + ); + + if ($encrypted === false) { + throw new Exception("Encryption failed: " . openssl_error_string()); + } + + // 返回 Base64 编码结果 + return base64_encode($encrypted); + } + + private function request($path, $data = null, $json = false, $date = null, $headers = []) + { + $body = null; + if ($data) { + $body = $json ? json_encode($data) : http_build_query($data); + } + if (empty($date)) { + $date = gmdate("D, d M Y H:i:s T"); + } + $hmac = hash_hmac('sha1', $date, $this->apiKey, true); + $signature = base64_encode($hmac); + $authorization = 'Basic ' . base64_encode($this->username . ':' . $signature); + if (empty($headers)) { + $headers = ['Authorization: ' . $authorization, 'Date: ' . $date]; + } else { + $headers[] = 'Authorization: ' . $authorization; + $headers[] = 'Date: ' . $date; + } + if ($body && $json) { + $headers[] = 'Content-Type: application/json'; + } + $url = 'https://open.chinanetcenter.com' . $path; + $response = curl_client($url, $body, null, null, $headers, $this->proxy, null, 30); + $result = json_decode($response['body'], true); + if (isset($response['code']) && $response['code'] == 201) { + if (preg_match('/Location:\s*(.*)/i', $response['header'], $matches)) { + $location = trim($matches[1]); // 提取 Location 头部的值并去除多余空格 + if (!empty($location)) { + return $location; + } + } + // 如果没有找到 Location 头部,返回默认值 true + return true; + } elseif (isset($response['code']) && $response['code'] == 200) { + return isset($result) ? $result : true; + } elseif (isset($result['message'])) { + throw new Exception($result['message']); + } else { + throw new Exception('请求失败'); + } + } + + public function setLogger($func) + { + $this->logger = $func; + } + + private function log($txt) + { + if ($this->logger) { + call_user_func($this->logger, $txt); + } + } +} diff --git a/public/static/images/wangsu.ico b/public/static/images/wangsu.ico new file mode 100644 index 0000000000000000000000000000000000000000..7f1b01f42b446fd0273334fa07d52ff459b1e46b GIT binary patch literal 4286 zcmeH{Pe@cz6o-!~F+@xvAre7DgouO-X(1swNVsSrArdhoGK7c>uA)T?p+dw(YgZC2 z6~-2=LrNqnqJ@M+L`V!nxT;N*SY5y0o4GLzKIaYLqIgd}f9IWd{@i=+Wz3rROQlTz zZMoH~-ORcGm!(1e;Ne^R#1n`o5KrKLo4`uek}DbOMXy1=khLpl+hOdJx(Vo^UmCpu zHz5ytKdqkhPho2f`Zf($;UXmdW=-T@hFdTKeQ*+vRM+G^&Dvh08|XMXSV3#hS~{!a zquAuA?;`X=63(+n`6gio<|;fwci}xu(LaYOW>|et<>v5TLXSr5Jw$8~w&21a!kFm$ObXriIM*Wy2M%w z=mQuHsXw%zUp{iK2l*K*p&xm#?O*SGGOTu(pC0X?`}_uWU=_N9^=P)`nZ0Fyr9#G@ z@UF9-uOQp6a~nIz3+q+uXOC%ebgr7AqsJ@stdGM!2dl*WLf4=bf_snZPEC;W4%Iyw z^Lh1@*f#QXXKLrEJG21{a1V5rKISR*eOQ7m@UgSl$$CAjzY{-8|8JaNAF+c4A#lhUN1!HaZn7kbl1Z7T%L_T2i?s+=GAdf%|^aaFG0Ot$g!3g z^ftaRZ2flkG*T;$jVBOKAfCX#Gy&Jp^V7L6YtFsgaqh0&`EAYa$Jb`}srcDFvlGs2 Mz*&=Xb|!k!J!?_uo&W#< literal 0 HcmV?d00001 From 333aacaab9aeab1f7f61115bba72c6487fafb0d2 Mon Sep 17 00:00:00 2001 From: Hanada Date: Sun, 11 May 2025 21:12:38 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E7=BD=91=E5=AE=BF=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=A7=E5=93=81=E5=AD=97=E6=AE=B5=EF=BC=8C?= =?UTF-8?q?=E6=96=B9=E4=BE=BF=E5=90=8E=E7=BB=AD=E6=89=A9=E5=85=85=E5=85=B6?= =?UTF-8?q?=E4=BB=96=E4=BA=A7=E5=93=81=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/DeployHelper.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index 802ce49..53bf0e1 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -1244,6 +1244,15 @@ class DeployHelper ], ], 'taskinputs' => [ + 'product' => [ + 'name' => '要部署的产品', + 'type' => 'select', + 'options' => [ + ['value'=>'cdnpro', 'label'=>'CDN Pro'] + ], + 'value' => 'cdnpro', + 'required' => true, + ], 'domain' => [ 'name' => '绑定的域名', 'type' => 'input', From fa47ffb0808968b53b8a2e932904a420fdddb207 Mon Sep 17 00:00:00 2001 From: Hanada Date: Sun, 11 May 2025 23:16:15 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAWS=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/client/AWS.php | 21 ++++++++++++++++++--- app/lib/deploy/aws.php | 7 +++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/app/lib/client/AWS.php b/app/lib/client/AWS.php index e0bfe63..36b841d 100644 --- a/app/lib/client/AWS.php +++ b/app/lib/client/AWS.php @@ -328,16 +328,31 @@ class AWS return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true); } - private function array2xml($array, $xml = null) + private function array2xml($array, $xml = null, $parentTagName = 'root') { if ($xml === null) { $xml = new \SimpleXMLElement(''); } foreach ($array as $key => $value) { + // 确定当前标签名:如果是数字键名,使用父级标签名,否则使用当前键名 + $tagName = is_numeric($key) ? $parentTagName : $key; + if (is_array($value)) { - $subNode = $xml->addChild($key); - $this->array2xml($value, $subNode); + // 检查数组的第一个子节点的键是否为0 + $firstKey = array_key_first($value); + $isFirstKeyZero = ($firstKey === 0 || $firstKey === '0'); + + if ($isFirstKeyZero) { + // 如果第一个子节点的键是0,则不生成当前节点标签,直接递归子节点 + $this->array2xml($value, $xml, $tagName); + + } else { + // 否则生成当前节点标签,并递归子节点 + $subNode = $xml->addChild($tagName); + $this->array2xml($value, $subNode, $tagName); + } + } else { $xml->addChild($key, $value); } diff --git a/app/lib/deploy/aws.php b/app/lib/deploy/aws.php index 74773ff..9aa40c4 100644 --- a/app/lib/deploy/aws.php +++ b/app/lib/deploy/aws.php @@ -54,8 +54,11 @@ class aws implements DeployInterface } $data['ViewerCertificate']['ACMCertificateArn'] = $cert_id; - $data['ViewerCertificate']['CloudFrontDefaultCertificate'] = false; - $xml = new \SimpleXMLElement(''); + $data['ViewerCertificate']['CloudFrontDefaultCertificate'] = 'false'; + unset($data['ViewerCertificate']['Certificate']); + unset($data['ViewerCertificate']['CertificateSource']); + + $xml = new \SimpleXMLElement(''); $client->requestXmlN('PUT', '/distribution/' . $config['distribution_id'] . '/config', $data, $xml); $this->log('分配ID: ' . $config['distribution_id'] . ' 证书部署成功!'); } From a4d3cdd612640403b05c5cc1993fd7d29933b3b1 Mon Sep 17 00:00:00 2001 From: Hanada Date: Sun, 11 May 2025 23:40:35 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0AWS=20ACM=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/deploy/aws.php | 57 ++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/app/lib/deploy/aws.php b/app/lib/deploy/aws.php index 9aa40c4..a92220c 100644 --- a/app/lib/deploy/aws.php +++ b/app/lib/deploy/aws.php @@ -33,18 +33,9 @@ class aws implements DeployInterface if (empty($config['distribution_id'])) throw new Exception('分配ID不能为空'); $certInfo = openssl_x509_parse($fullchain, true); if (!$certInfo) throw new Exception('证书解析失败'); - $config['cert_name'] = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t']; - if (isset($info['cert_id']) && isset($info['cert_name']) && $info['cert_name'] == $config['cert_name']) { - $cert_id = $info['cert_id']; - $this->log('证书已上传:' . $cert_id); - } else { - $cert_id = $this->get_cert_id($fullchain, $privatekey); - $this->log('证书上传成功:' . $cert_id); - $info['cert_id'] = $cert_id; - $info['cert_name'] = $config['cert_name']; - usleep(500000); - } + $cert_id = $this->get_cert_id($fullchain, $privatekey, $info['cert_id']); + usleep(500000); $client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'cloudfront.amazonaws.com', 'cloudfront', '2020-05-31', 'us-east-1', $this->proxy); try { @@ -63,14 +54,49 @@ class aws implements DeployInterface $this->log('分配ID: ' . $config['distribution_id'] . ' 证书部署成功!'); } - private function get_cert_id($fullchain, $privatekey) + private function get_cert_id($fullchain, $privatekey, $cert_id = null) { - $cert = explode('-----END CERTIFICATE-----', $fullchain)[0] . '-----END CERTIFICATE-----'; + $client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1', $this->proxy); + + if (!empty($cert_id)) { + try { + $data = $client->request('POST', 'CertificateManager.GetCertificate', [ + 'CertificateArn' => $cert_id + ]); + // 如果成功获取证书信息,说明证书存在,直接返回cert_id + if (isset($data['Certificate'])) { + $this->log('证书已存在:' . $cert_id); + return $cert_id; + } + } catch (Exception $e) { + $this->log('证书已被删除:' . $cert_id. ',准备重新上传'); + } + } + + $this->log('证书尚未存在,开始上传到AWS ACM'); + + $certificates = explode('-----END CERTIFICATE-----', $fullchain); + $cert = $certificates[0] . '-----END CERTIFICATE-----'; + $certificateChain = ''; + if (count($certificates) > 1) { + // 从第二个证书开始,重新拼接中间证书链 + for ($i = 1; $i < count($certificates); $i++) { + if (trim($certificates[$i]) !== '') { // 忽略空字符串(可能由末尾分割产生) + $certificateChain .= $certificates[$i] . '-----END CERTIFICATE-----'; + } + } + } + $param = [ 'Certificate' => base64_encode($cert), 'PrivateKey' => base64_encode($privatekey), ]; + // 如果有中间证书链,则添加到参数中 + if (!empty($certificateChain)) { + $param['CertificateChain'] = base64_encode($certificateChain); + } + $client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1', $this->proxy); try { $data = $client->request('POST', 'CertificateManager.ImportCertificate', $param); @@ -78,6 +104,11 @@ class aws implements DeployInterface } catch (Exception $e) { throw new Exception('上传证书失败:' . $e->getMessage()); } + + $this->log('证书上传成功:' . $cert_id); + + $info['cert_id'] = $cert_id; + return $cert_id; } From dcc440c1f95c2fc2d85d923434e84f2ce5704d22 Mon Sep 17 00:00:00 2001 From: Hanada Date: Mon, 12 May 2025 00:02:16 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=9D=E6=AC=A1?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=AF=81=E4=B9=A6=E5=88=B0ACM=E7=9A=84?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/deploy/aws.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/lib/deploy/aws.php b/app/lib/deploy/aws.php index a92220c..73b0ccc 100644 --- a/app/lib/deploy/aws.php +++ b/app/lib/deploy/aws.php @@ -34,7 +34,8 @@ class aws implements DeployInterface $certInfo = openssl_x509_parse($fullchain, true); if (!$certInfo) throw new Exception('证书解析失败'); - $cert_id = $this->get_cert_id($fullchain, $privatekey, $info['cert_id']); + $cert_id = isset($info['cert_id']) ? $info['cert_id'] : null; + $cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_id, $config['cert_name']); usleep(500000); $client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'cloudfront.amazonaws.com', 'cloudfront', '2020-05-31', 'us-east-1', $this->proxy); From f8add88e3d9b6da02456a1dbc5197e0d4bc1703d Mon Sep 17 00:00:00 2001 From: Hanada Date: Mon, 12 May 2025 00:43:33 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E8=AF=81=E4=B9=A6=E5=88=B0AWS=20ACM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/DeployHelper.php | 14 ++++++++++++-- app/lib/deploy/aws.php | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index 569eb37..851f765 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -1371,7 +1371,7 @@ class DeployHelper 'name' => 'AWS', 'class' => 2, 'icon' => 'aws.ico', - 'note' => '支持部署到Amazon CloudFront', + 'note' => '支持部署到Amazon CloudFront、AWS Certificate Manager', 'inputs' => [ 'AccessKeyId' => [ 'name' => 'AccessKeyId', @@ -1401,14 +1401,24 @@ class DeployHelper 'type' => 'select', 'options' => [ ['value'=>'cloudfront', 'label'=>'CloudFront'], + ['value'=>'acm', 'label'=>'AWS Certificate Manager'], ], - 'value' => 'cloudfront', + 'value' => 'acm', 'required' => true, ], 'distribution_id' => [ 'name' => '分配ID', 'type' => 'input', 'placeholder' => 'distributions id', + 'show' => 'product==\'cloudfront\'', + 'required' => true, + ], + 'acm_arn' => [ + 'name' => 'ACM ARN', + 'type' => 'input', + 'placeholder' => '', + 'show' => 'product==\'acm\'', + 'note' => '在AWS Certificate Manager控制台查看证书的ARN', 'required' => true, ], ], diff --git a/app/lib/deploy/aws.php b/app/lib/deploy/aws.php index 73b0ccc..1909668 100644 --- a/app/lib/deploy/aws.php +++ b/app/lib/deploy/aws.php @@ -29,6 +29,16 @@ class aws implements DeployInterface } public function deploy($fullchain, $privatekey, $config, &$info) + { + if ($config['product'] == 'acm') { + if (empty($config['acm_arn'])) throw new Exception('ACM ARN不能为空'); + $this->get_cert_id($fullchain, $privatekey, $config['acm_arn'], true); + } else { + $this->deploy_cloudfront($fullchain, $privatekey, $config, $info); + } + } + + private function deploy_cloudfront($fullchain, $privatekey, $config, &$info) { if (empty($config['distribution_id'])) throw new Exception('分配ID不能为空'); $certInfo = openssl_x509_parse($fullchain, true); @@ -55,8 +65,15 @@ class aws implements DeployInterface $this->log('分配ID: ' . $config['distribution_id'] . ' 证书部署成功!'); } - private function get_cert_id($fullchain, $privatekey, $cert_id = null) + private function get_cert_id($fullchain, $privatekey, $cert_id = null, $acm = false) { + if ($acm === true && $cert_id == null) { + throw new Exception('ACM ARN不能为空'); + } + + $certificates = explode('-----END CERTIFICATE-----', $fullchain); + $cert = $certificates[0] . '-----END CERTIFICATE-----'; + $client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1', $this->proxy); if (!empty($cert_id)) { @@ -65,19 +82,20 @@ class aws implements DeployInterface 'CertificateArn' => $cert_id ]); // 如果成功获取证书信息,说明证书存在,直接返回cert_id - if (isset($data['Certificate'])) { - $this->log('证书已存在:' . $cert_id); + if (isset($data['Certificate']) && trim($data['Certificate']) == trim($cert)) { + $this->log('证书已是最新,ACM ARN:' . $cert_id); return $cert_id; + } else { + $this->log('证书已过期或被删除,准备更新或者重新上传'); } } catch (Exception $e) { + if ($acm === true) { + throw new Exception('获取证书信息失败,请检查ACM ARN是否正确:' . $e->getMessage()); + } $this->log('证书已被删除:' . $cert_id. ',准备重新上传'); } } - $this->log('证书尚未存在,开始上传到AWS ACM'); - - $certificates = explode('-----END CERTIFICATE-----', $fullchain); - $cert = $certificates[0] . '-----END CERTIFICATE-----'; $certificateChain = ''; if (count($certificates) > 1) { // 从第二个证书开始,重新拼接中间证书链 @@ -98,6 +116,11 @@ class aws implements DeployInterface $param['CertificateChain'] = base64_encode($certificateChain); } + // 如果是ACM,则添加ARN参数,用于更新证书 + if ($acm === true) { + $param['CertificateArn'] = $cert_id; + } + $client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1', $this->proxy); try { $data = $client->request('POST', 'CertificateManager.ImportCertificate', $param); From 842b2aa2d924df6b4ca4edd5dc783de3faf40b46 Mon Sep 17 00:00:00 2001 From: Hanada Date: Mon, 12 May 2025 02:28:13 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=E5=88=B0=E9=98=BF=E9=87=8C=E4=BA=91OSS?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/deploy/aliyun.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/deploy/aliyun.php b/app/lib/deploy/aliyun.php index 4a255e4..7e361b9 100644 --- a/app/lib/deploy/aliyun.php +++ b/app/lib/deploy/aliyun.php @@ -236,7 +236,7 @@ class aliyun implements DeployInterface if (empty($config['oss_endpoint'])) throw new Exception('OSS Endpoint不能为空'); if (empty($config['oss_bucket'])) throw new Exception('OSS Bucket不能为空'); $client = new AliyunOSS($this->AccessKeyId, $this->AccessKeySecret, $config['oss_endpoint']); - $client->addBucketCnameCert($config['oss_bucket'], $config['domain'], $cert_id); + $client->addBucketCnameCert($config['oss_bucket'], $config['domain'], $cert_id . '-cn-hangzhou'); $this->log('OSS域名 ' . $config['domain'] . ' 部署证书成功!'); } From c141089c692132b364c4de7ad8a05ed66513e6a0 Mon Sep 17 00:00:00 2001 From: Hanada Date: Tue, 13 May 2025 02:00:37 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=BD=91=E5=AE=BFCDN?= =?UTF-8?q?=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common.php | 16 ++- app/lib/DeployHelper.php | 23 ++- app/lib/deploy/wangsu.php | 284 ++++++++++++++++++++++++++++++++------ 3 files changed, 273 insertions(+), 50 deletions(-) diff --git a/app/common.php b/app/common.php index f654125..3815d78 100644 --- a/app/common.php +++ b/app/common.php @@ -392,7 +392,7 @@ function clearDirectory($dir): bool return true; } -function curl_client($url, $data = null, $referer = null, $cookie = null, $headers = null, $proxy = false, $method = null, $timeout = 5) +function curl_client($url, $data = null, $referer = null, $cookie = null, $headers = null, $proxy = false, $method = null, $timeout = 5, $default_headers = true) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); @@ -400,11 +400,15 @@ function curl_client($url, $data = null, $referer = null, $cookie = null, $heade curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - $httpheader[] = "Accept: */*"; - $httpheader[] = "Accept-Language: zh-CN,zh;q=0.8"; - $httpheader[] = "Connection: close"; - if ($headers) { - $httpheader = array_merge($httpheader, $headers); + if ($default_headers === true) { + $httpheader[] = "Accept: */*"; + $httpheader[] = "Accept-Language: zh-CN,zh;q=0.8"; + $httpheader[] = "Connection: close"; + if ($headers) { + $httpheader = array_merge($headers, $httpheader); + } + } else { + $httpheader = $headers; } curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader); curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36"); diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index 737c349..3abb493 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -1214,7 +1214,7 @@ class DeployHelper 'name' => '网宿科技', 'class' => 2, 'icon' => 'wangsu.ico', - 'note' => '支持网宿CDN Pro,暂不支持其他产品线,暂不支持AKSK鉴权', + 'note' => '适用产品:网页加速、下载分发、全站加速、点播分发、直播分发、上传加速、移动加速、上网加速、S-P2P、PCDN、应用性能管理、WEB应用防火墙、BotGuard爬虫管理、WSS、DMS、DDoS云清洗、应用加速、应用安全加速解决方案、IPv6一体化解决方案、电商安全加速解决方案、金融安全加速解决方案、政企安全加速解决方案、DDoS云清洗(非网站业务)、区块链安全加速解决方案、IPv6安全加速解决方案、CDN Pro。暂不支持AKSK鉴权。', 'inputs' => [ 'username' => [ 'name' => '账号', @@ -1248,14 +1248,31 @@ class DeployHelper 'name' => '要部署的产品', 'type' => 'select', 'options' => [ - ['value'=>'cdnpro', 'label'=>'CDN Pro'] + ['value'=>'cdn', 'label'=>'CDN'], + ['value'=>'cdnpro', 'label'=>'CDN Pro'], + ['value'=>'certificate', 'label'=>'证书管理'] ], - 'value' => 'cdnpro', + 'value' => 'cdn', + 'required' => true, + ], + 'domains' => [ + 'name' => '绑定的域名', + 'type' => 'input', + 'show' => 'product==\'cdn\'', + 'placeholder' => '多个域名可使用,分隔', 'required' => true, ], 'domain' => [ 'name' => '绑定的域名', 'type' => 'input', + 'show' => 'product==\'cdnpro\'', + 'placeholder' => '不支持输入多个域名', + 'required' => true, + ], + 'cert_id' => [ + 'name' => '证书ID', + 'type' => 'input', + 'show' => 'product==\'certificate\'', 'placeholder' => '', 'required' => true, ], diff --git a/app/lib/deploy/wangsu.php b/app/lib/deploy/wangsu.php index 60f6933..fa7e2b0 100644 --- a/app/lib/deploy/wangsu.php +++ b/app/lib/deploy/wangsu.php @@ -30,20 +30,79 @@ class wangsu implements DeployInterface public function deploy($fullchain, $privatekey, $config, &$info) { - $domain = $config['domain']; - if (empty($domain)) { + if ($config['product'] == 'cdnpro') { + $this->deploy_cdnpro($fullchain, $privatekey, $config, $info); + + } elseif ($config['product'] == 'cdn') { + $this->deploy_cdn($fullchain, $privatekey, $config, $info); + + } elseif ($config['product'] == 'certificate') { + $certInfo = openssl_x509_parse($fullchain, true); + if (!$certInfo) { + throw new Exception('证书解析失败'); + } + $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t']; + $serial_no = strtolower($certInfo['serialNumberHex']); + $this->get_cert_id($fullchain, $privatekey, $cert_name, $config['cert_id'], $serial_no, true); + } else { + throw new Exception('未知的产品类型'); + } + } + + public function deploy_cdn($fullchain, $privatekey, $config, &$info) + { + if (empty($config['domains'])) { throw new Exception('绑定的域名不能为空'); } + $domains = explode(',', $config['domains']); $certInfo = openssl_x509_parse($fullchain, true); if (!$certInfo) { throw new Exception('证书解析失败'); } - $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t']; - - $cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_name); - $hostnameInfo = $this->request('/cdn/hostnames/' . $domain); + $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t']; + $serial_no = strtolower($certInfo['serialNumberHex']); + $this->log('证书序列号:' . $serial_no); + $cert_id = isset($info['cert_id']) ? $info['cert_id'] : null; + $cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_name, $cert_id, $serial_no, false); + + $param = [ + 'certificateId' => $cert_id, + 'domainNames' => $domains + ]; + + try { + $data = $this->request('/api/config/certificate/batch', $param, true, null, 'PUT'); + } catch (Exception $e) { + throw new Exception('绑定域名失败:' . $e->getMessage()); + } + + $this->log('绑定证书成功,证书ID:' . $cert_id); + $info['cert_id'] = $cert_id; + } + + public function deploy_cdnpro($fullchain, $privatekey, $config, &$info) + { + if (empty($config['domain'])) { + throw new Exception('绑定的域名不能为空'); + } + $domain = $config['domain']; + + $certInfo = openssl_x509_parse($fullchain, true); + if (!$certInfo) { + throw new Exception('证书解析失败'); + } + + $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t']; + $cert_id = $this->get_cert_id_cdnpro($fullchain, $privatekey, $cert_name); + + try { + $hostnameInfo = $this->request('/cdn/hostnames/' . $domain); + } catch (Exception $e) { + throw new Exception('获取域名信息失败:' . $e->getMessage()); + } + if (empty($hostnameInfo["propertyInProduction"])) { throw new Exception('域名 ' . $domain . ' 不存在或未部署到生产环境'); } else { @@ -56,19 +115,36 @@ class wangsu implements DeployInterface return; } - $properity = $this->request('/cdn/properties/' . $hostnameInfo["propertyInProduction"]["propertyId"] . '/versions/' . $hostnameInfo["propertyInProduction"]["version"]); + try { + $properity = $this->request('/cdn/properties/' . $hostnameInfo["propertyInProduction"]["propertyId"] . '/versions/' . $hostnameInfo["propertyInProduction"]["version"]); + } catch (Exception $e) { + throw new Exception('获取加速项目版本信息失败:' . $e->getMessage()); + } + $properityConfig = $properity["configs"]; $properityConfig["tlsCertificateId"] = $cert_id; - $data = $this->request('/cdn/properties/' . $hostnameInfo["propertyInProduction"]["propertyId"] . '/versions', $properityConfig, true); + try { + $data = $this->request('/cdn/properties/' . $hostnameInfo["propertyInProduction"]["propertyId"] . '/versions', $properityConfig, true); + } catch (Exception $e) { + throw new Exception('新增加速项目版本失败:' . $e->getMessage()); + } + $url_parts = parse_url($data); $path_parts = explode('/', $url_parts['path']); $newVersion = end($path_parts); - $data = $this->request('/cdn/validations', [ + $param = [ 'propertyId' => $hostnameInfo["propertyInProduction"]["propertyId"], 'version' => intval($newVersion), - ], true); + ]; + + try { + $data = $this->request('/cdn/validations', $param, true); + } catch (Exception $e) { + throw new Exception('发起加速项目验证失败:' . $e->getMessage()); + } + $url_parts = parse_url($data); $path_parts = explode('/', $url_parts['path']); $validationTaskId = end($path_parts); @@ -80,9 +156,15 @@ class wangsu implements DeployInterface do { sleep(5); - $data = $this->request('/cdn/validations/' . $validationTaskId); + + try { + $data = $this->request('/cdn/validations/' . $validationTaskId); + } catch (Exception $e) { + throw new Exception('获取验证任务状态失败:' . $e->getMessage()); + } + $status = $data['status']; - + if ($status === 'failed') { throw new Exception('证书绑定失败,加速项目验证失败'); } @@ -99,6 +181,7 @@ class wangsu implements DeployInterface } $this->log('加速项目验证成功,开始部署...'); + $deploymentTasks = [ 'target' => 'production', 'actions' => [ @@ -116,50 +199,153 @@ class wangsu implements DeployInterface 'name' => 'Deploy certificate and property for ' . $hostnameInfo["propertyInProduction"]["propertyId"], ]; - $data = $this->request('/cdn/deploymentTasks', $deploymentTasks, true, null, ['Check-Certificate' => 'no', 'Check-Usage' => 'no']); + try { + $data = $this->request('/cdn/deploymentTasks', $deploymentTasks, true, null, 'POST', false, ['Check-Certificate' => 'no', 'Check-Usage' => 'no']); + } catch (Exception $e) { + throw new Exception('下发证书部署任务失败:' . $e->getMessage()); + } + $url_parts = parse_url($data); $path_parts = explode('/', $url_parts['path']); $deploymentTaskId = end($path_parts); + $this->log('CDN域名 ' . $domain . ' 绑定证书部署任务下发成功,部署任务ID:' . $deploymentTaskId); $info['cert_id'] = $cert_id; } - private function get_cert_id($fullchain, $privatekey, $cert_name) + private function get_cert_id($fullchain, $privatekey, $cert_name, $cert_id = null, $serial_no = null, $overwrite = false) + { + if ($cert_id) { + try { + $data = $this->request('/api/certificate/' . $cert_id); + } catch (Exception $e) { + throw new Exception('获取证书详情失败:' . $e->getMessage()); + } + + if (isset($data['message']) && $data['message'] == 'success' && $data['data']['name'] == $cert_name && $data['data']['serial'] == $serial_no) { + $this->log('证书已是最新,证书ID:' . $cert_id); + return $cert_id; + } + + $this->log('证书已过期或被删除,准备重新上传'); + + } elseif ($overwrite === true) { + throw new Exception('证书ID不能为空'); + } + + if ($overwrite === true) { + $param = [ + 'name' => $cert_name, + 'certificate' => $fullchain, + 'privateKey' => $privatekey, + ]; + + try { + $data = $this->request('/api/certificate/' . $cert_id, $param, true, null, 'PUT'); + $this->log('更新证书成功,证书ID:' . $cert_id); + return $cert_id; + } catch (Exception $e) { + throw new Exception('更新证书失败:' . $e->getMessage()); + } + } + + try { + $data = $this->request('/api/ssl/certificate'); + } catch (Exception $e) { + throw new Exception('获取证书列表失败:' . $e->getMessage()); + } + + $certificates = $data['ssl-certificate']; + + if (!empty($certificates)) { + foreach ($certificates as $cert) { + if ($serial_no == $cert['certificate-serial']) { + $cert_id = $cert['certificate-id']; + $this->log('证书' . $cert_name . '已存在,新证书ID:' . $cert_id); + try { + $this->request('/api/certificate/' . $cert_id, ['name' => $cert_name], true, null, 'PUT'); + } catch (Exception $e) { + throw new Exception('证书更名失败:' . $e->getMessage()); + } + $this->log('将证书ID为' . $cert_id . '的证书更名为:' . $cert_name); + return $cert_id; + + } elseif ($cert_name == $cert['name']) { + $this->log('证书' . $cert_name . '已存在,但序列号(' . $cert['certificate-id'] . ')不匹配,准备重新上传'); + try { + $this->request('/api/certificate/' . $cert['certificate-id'], [['name'] => $cert_name . '-bak'], true, null, 'PUT'); + } catch (Exception $e) { + throw new Exception('证书更名失败:' . $e->getMessage()); + } + $this->log('将证书ID为' . $cert['certificate-id'] . '的证书更名为:' . $cert_name . '-bak'); + } + } + } + + $param = [ + 'name' => $cert_name, + 'certificate' => $fullchain, + 'privateKey' => $privatekey, + ]; + + try { + $data = $this->request('/api/certificate', $param, true, null, 'POST', true); + } catch (Exception $e) { + throw new Exception('上传证书失败:' . $e->getMessage()); + } + + $url_parts = parse_url($data); + $path_parts = explode('/', $url_parts['path']); + $cert_id = end($path_parts); + $this->log('上传证书成功,证书ID:' . $cert_id); + + return $cert_id; + } + + private function get_cert_id_cdnpro($fullchain, $privatekey, $cert_name) { $cert_id = null; - $data = $this->request('/cdn/certificates?search=' . urlencode($cert_name)); + try { + $data = $this->request('/cdn/certificates?search=' . urlencode($cert_name)); + } catch (Exception $e) { + throw new Exception('获取证书列表失败:' . $e->getMessage()); + } + if ($data['count'] > 0) { foreach ($data['certificates'] as $cert) { if ($cert_name == $cert['name']) { $cert_id = $cert['certificateId']; $this->log('证书' . $cert_name . '已存在,证书ID:' . $cert_id); + return $cert_id; } } } - if (!$cert_id) { - $date = gmdate("D, d M Y H:i:s T"); - $encryptedKey = $this->encryptPrivateKey($privatekey, $date); - $param = [ - 'name' => $cert_name, - 'autoRenew' => 'Off', - 'newVersion' => [ - 'privateKey' => $encryptedKey, - 'certificate' => $fullchain, - ] - ]; - try { - $data = $this->request('/cdn/certificates', $param, true, $date); - } catch (Exception $e) { - throw new Exception('上传证书失败:' . $e->getMessage()); - } - $url_parts = parse_url($data); - $path_parts = explode('/', $url_parts['path']); - $cert_id = end($path_parts); - $this->log('上传证书成功,证书ID:' . $cert_id); - usleep(500000); + $date = gmdate("D, d M Y H:i:s T"); + $encryptedKey = $this->encryptPrivateKey($privatekey, $date); + $param = [ + 'name' => $cert_name, + 'autoRenew' => 'Off', + 'newVersion' => [ + 'privateKey' => $encryptedKey, + 'certificate' => $fullchain, + ] + ]; + + try { + $data = $this->request('/cdn/certificates', $param, true, $date); + } catch (Exception $e) { + throw new Exception('上传证书失败:' . $e->getMessage()); } + + $url_parts = parse_url($data); + $path_parts = explode('/', $url_parts['path']); + $cert_id = end($path_parts); + $this->log('上传证书成功,证书ID:' . $cert_id); + + usleep(500000); + return $cert_id; } @@ -213,31 +399,44 @@ class wangsu implements DeployInterface return base64_encode($encrypted); } - private function request($path, $data = null, $json = false, $date = null, $headers = []) + private function request($path, $data = null, $json = false, $date = null, $method = null, $getLocation = false, $headers = []) { $body = null; if ($data) { $body = $json ? json_encode($data) : http_build_query($data); } + if (empty($date)) { $date = gmdate("D, d M Y H:i:s T"); } + $hmac = hash_hmac('sha1', $date, $this->apiKey, true); $signature = base64_encode($hmac); $authorization = 'Basic ' . base64_encode($this->username . ':' . $signature); + if (empty($headers)) { - $headers = ['Authorization: ' . $authorization, 'Date: ' . $date]; + $headers = [ + 'Authorization: ' . $authorization, + 'Date: ' . $date, + 'Accept: application/json', + 'Connection: close', + ]; } else { $headers[] = 'Authorization: ' . $authorization; $headers[] = 'Date: ' . $date; + $headers[] = 'Accept: application/json'; + $headers[] = 'Connection: close'; } + if ($body && $json) { $headers[] = 'Content-Type: application/json'; } + $url = 'https://open.chinanetcenter.com' . $path; - $response = curl_client($url, $body, null, null, $headers, $this->proxy, null, 30); + $response = curl_client($url, $body, null, null, $headers, $this->proxy, $method, 30, false); $result = json_decode($response['body'], true); - if (isset($response['code']) && $response['code'] == 201) { + + if ((isset($response['code']) && $response['code'] == 201) || (isset($response['code']) && $response['code'] == 200 && $getLocation === true)) { if (preg_match('/Location:\s*(.*)/i', $response['header'], $matches)) { $location = trim($matches[1]); // 提取 Location 头部的值并去除多余空格 if (!empty($location)) { @@ -246,10 +445,13 @@ class wangsu implements DeployInterface } // 如果没有找到 Location 头部,返回默认值 true return true; - } elseif (isset($response['code']) && $response['code'] == 200) { + + } elseif (isset($response['code']) && $response['code'] >= 200 && $response['code'] <= 299) { return isset($result) ? $result : true; + } elseif (isset($result['message'])) { throw new Exception($result['message']); + } else { throw new Exception('请求失败'); } From a13fb38e66a5ed6a3aea8bd809757f23b8c7f5e2 Mon Sep 17 00:00:00 2001 From: Hanada Date: Tue, 13 May 2025 02:04:28 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAWS=20CloudFront?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E5=87=BD=E6=95=B0=E4=B8=AD=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E5=85=A5=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/deploy/aws.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/deploy/aws.php b/app/lib/deploy/aws.php index 1909668..9470c25 100644 --- a/app/lib/deploy/aws.php +++ b/app/lib/deploy/aws.php @@ -45,7 +45,7 @@ class aws implements DeployInterface if (!$certInfo) throw new Exception('证书解析失败'); $cert_id = isset($info['cert_id']) ? $info['cert_id'] : null; - $cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_id, $config['cert_name']); + $cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_id); usleep(500000); $client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'cloudfront.amazonaws.com', 'cloudfront', '2020-05-31', 'us-east-1', $this->proxy);