diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index 53bf0e1..737c349 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -1422,7 +1422,7 @@ class DeployHelper 'name' => 'AWS', 'class' => 2, 'icon' => 'aws.ico', - 'note' => '支持部署到Amazon CloudFront', + 'note' => '支持部署到Amazon CloudFront、AWS Certificate Manager', 'inputs' => [ 'AccessKeyId' => [ 'name' => 'AccessKeyId', @@ -1452,14 +1452,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 9aa40c4..1909668 100644 --- a/app/lib/deploy/aws.php +++ b/app/lib/deploy/aws.php @@ -29,22 +29,24 @@ 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); 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 = 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); try { @@ -63,14 +65,62 @@ 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, $acm = false) { - $cert = explode('-----END CERTIFICATE-----', $fullchain)[0] . '-----END CERTIFICATE-----'; + 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)) { + try { + $data = $client->request('POST', 'CertificateManager.GetCertificate', [ + 'CertificateArn' => $cert_id + ]); + // 如果成功获取证书信息,说明证书存在,直接返回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. ',准备重新上传'); + } + } + + $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); + } + + // 如果是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); @@ -78,6 +128,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; }