963 lines
42 KiB
PHP
963 lines
42 KiB
PHP
<?php
|
||
|
||
namespace app\controller;
|
||
|
||
use app\BaseController;
|
||
use app\lib\CertHelper;
|
||
use app\lib\DeployHelper;
|
||
use app\service\CertOrderService;
|
||
use app\service\CertDeployService;
|
||
use Exception;
|
||
use think\facade\Db;
|
||
use think\facade\View;
|
||
use think\facade\Cache;
|
||
|
||
class Cert extends BaseController
|
||
{
|
||
public function certaccount()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
return view();
|
||
}
|
||
|
||
public function deployaccount()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
return view();
|
||
}
|
||
|
||
public function account_data()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$deploy = input('get.deploy/d', 0);
|
||
$kw = $this->request->post('kw', null, 'trim');
|
||
$offset = input('post.offset/d');
|
||
$limit = input('post.limit/d');
|
||
|
||
$select = Db::name('cert_account')->where('deploy', $deploy);
|
||
if (!empty($kw)) {
|
||
$select->whereLike('name|remark', '%' . $kw . '%')->whereOr('id', $kw);
|
||
}
|
||
$total = $select->count();
|
||
$rows = $select->order('id', 'desc')->limit($offset, $limit)->select();
|
||
|
||
$list = [];
|
||
foreach ($rows as $row) {
|
||
if ($deploy == 1) {
|
||
if (!empty($row['type']) && isset(DeployHelper::$deploy_config[$row['type']])) {
|
||
$row['typename'] = DeployHelper::$deploy_config[$row['type']]['name'];
|
||
$row['icon'] = DeployHelper::$deploy_config[$row['type']]['icon'];
|
||
}
|
||
} else {
|
||
if (!empty($row['type']) && isset(CertHelper::$cert_config[$row['type']])) {
|
||
$row['typename'] = CertHelper::$cert_config[$row['type']]['name'];
|
||
$row['icon'] = CertHelper::$cert_config[$row['type']]['icon'];
|
||
}
|
||
}
|
||
$list[] = $row;
|
||
}
|
||
|
||
return json(['total' => $total, 'rows' => $list]);
|
||
}
|
||
|
||
public function account_op()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$action = input('param.action');
|
||
$deploy = input('post.deploy/d', 0);
|
||
$title = $deploy == 1 ? '自动部署账户' : 'SSL证书账户';
|
||
|
||
if ($action == 'add') {
|
||
$type = input('post.type');
|
||
$name = input('post.name', null, 'trim');
|
||
$config = input('post.config', null, 'trim');
|
||
$remark = input('post.remark', null, 'trim');
|
||
if ($type == 'local') $name = '复制到本机';
|
||
if (empty($name) || empty($config)) return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||
if (Db::name('cert_account')->where('type', $type)->where('config', $config)->find()) {
|
||
return json(['code' => -1, 'msg' => $title . '已存在']);
|
||
}
|
||
Db::startTrans();
|
||
$id = Db::name('cert_account')->insertGetId([
|
||
'type' => $type,
|
||
'name' => $name,
|
||
'config' => $config,
|
||
'remark' => $remark,
|
||
'deploy' => $deploy,
|
||
'addtime' => date('Y-m-d H:i:s'),
|
||
]);
|
||
try {
|
||
$this->checkAccount($id, $type, $deploy);
|
||
Db::commit();
|
||
return json(['code' => 0, 'msg' => '添加' . $title . '成功!']);
|
||
} catch (Exception $e) {
|
||
Db::rollback();
|
||
return json(['code' => -1, 'msg' => $e->getMessage()]);
|
||
}
|
||
} elseif ($action == 'edit') {
|
||
$id = input('post.id/d');
|
||
$row = Db::name('cert_account')->where('id', $id)->find();
|
||
if (!$row) return json(['code' => -1, 'msg' => $title . '不存在']);
|
||
$type = input('post.type');
|
||
$name = input('post.name', null, 'trim');
|
||
$config = input('post.config', null, 'trim');
|
||
$remark = input('post.remark', null, 'trim');
|
||
if ($type == 'local') $name = '复制到本机';
|
||
if (empty($name) || empty($config)) return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||
if (Db::name('cert_account')->where('type', $type)->where('config', $config)->where('id', '<>', $id)->find()) {
|
||
return json(['code' => -1, 'msg' => $title . '已存在']);
|
||
}
|
||
Db::startTrans();
|
||
Db::name('cert_account')->where('id', $id)->update([
|
||
'type' => $type,
|
||
'name' => $name,
|
||
'config' => $config,
|
||
'remark' => $remark,
|
||
]);
|
||
try {
|
||
$this->checkAccount($id, $type, $deploy);
|
||
Db::commit();
|
||
return json(['code' => 0, 'msg' => '修改' . $title . '成功!']);
|
||
} catch (Exception $e) {
|
||
Db::rollback();
|
||
return json(['code' => -1, 'msg' => $e->getMessage()]);
|
||
}
|
||
} elseif ($action == 'del') {
|
||
$id = input('post.id/d');
|
||
if ($deploy == 0) {
|
||
$dcount = DB::name('cert_order')->where('aid', $id)->count();
|
||
if ($dcount > 0) return json(['code' => -1, 'msg' => '该' . $title . '下存在证书订单,无法删除']);
|
||
} else {
|
||
$dcount = DB::name('cert_deploy')->where('aid', $id)->count();
|
||
if ($dcount > 0) return json(['code' => -1, 'msg' => '该' . $title . '下存在自动部署任务,无法删除']);
|
||
}
|
||
Db::name('cert_account')->where('id', $id)->delete();
|
||
return json(['code' => 0]);
|
||
}
|
||
return json(['code' => -3]);
|
||
}
|
||
|
||
public function account_form()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$action = input('param.action');
|
||
$deploy = input('get.deploy/d', 0);
|
||
$title = $deploy == 1 ? '自动部署账户' : 'SSL证书账户';
|
||
|
||
$account = null;
|
||
if ($action == 'edit') {
|
||
$id = input('get.id/d');
|
||
$account = Db::name('cert_account')->where('id', $id)->find();
|
||
if (empty($account)) return $this->alert('error', $title . '不存在');
|
||
}
|
||
|
||
$typeList = $deploy == 1 ? DeployHelper::getList() : CertHelper::getList();
|
||
$classList = $deploy == 1 ? DeployHelper::$class_config : CertHelper::$class_config;
|
||
|
||
View::assign('title', $title);
|
||
View::assign('info', $account);
|
||
View::assign('typeList', $typeList);
|
||
View::assign('classList', $classList);
|
||
View::assign('action', $action);
|
||
View::assign('deploy', $deploy);
|
||
return View::fetch();
|
||
}
|
||
|
||
private function checkAccount($id, $type, $deploy)
|
||
{
|
||
if ($deploy == 0) {
|
||
$mod = CertHelper::getModel($id);
|
||
if ($mod) {
|
||
try {
|
||
$ext = $mod->register();
|
||
if (is_array($ext)) {
|
||
Db::name('cert_account')->where('id', $id)->update(['ext' => json_encode($ext)]);
|
||
}
|
||
return true;
|
||
} catch (Exception $e) {
|
||
throw new Exception('验证SSL证书账户失败,' . $e->getMessage());
|
||
}
|
||
} else {
|
||
throw new Exception('SSL证书申请模块' . $type . '不存在');
|
||
}
|
||
} else {
|
||
$mod = DeployHelper::getModel($id);
|
||
if ($mod) {
|
||
try {
|
||
$mod->check();
|
||
return true;
|
||
} catch (Exception $e) {
|
||
throw new Exception('验证自动部署账户失败,' . $e->getMessage());
|
||
}
|
||
} else {
|
||
throw new Exception('SSL证书申请模块' . $type . '不存在');
|
||
}
|
||
}
|
||
}
|
||
|
||
public function certorder()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$types = [];
|
||
foreach (CertHelper::$cert_config as $key => $value) {
|
||
$types[$key] = $value['name'];
|
||
}
|
||
View::assign('types', $types);
|
||
return view();
|
||
}
|
||
|
||
public function order_data()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$domain = $this->request->post('domain', null, 'trim');
|
||
$id = input('post.id');
|
||
$aid = input('post.aid', null, 'trim');
|
||
$type = input('post.type', null, 'trim');
|
||
$status = input('post.status', null, 'trim');
|
||
$offset = input('post.offset/d');
|
||
$limit = input('post.limit/d');
|
||
|
||
$select = Db::name('cert_order')->alias('A')->leftJoin('cert_account B', 'A.aid = B.id');
|
||
if (!empty($id)) {
|
||
$select->where('A.id', $id);
|
||
} elseif (!empty($domain)) {
|
||
$oids = Db::name('cert_domain')->where('domain', 'like', '%' . $domain . '%')->column('oid');
|
||
$select->whereIn('A.id', $oids);
|
||
}
|
||
if (!empty($aid)) {
|
||
$select->where('A.aid', $aid);
|
||
}
|
||
if (!empty($type)) {
|
||
$select->where('B.type', $type);
|
||
}
|
||
if (!isNullOrEmpty($status)) {
|
||
if ($status == '5') {
|
||
$select->where('A.status', '<', 0);
|
||
} elseif ($status == '6') {
|
||
$select->where('A.expiretime', '<', date('Y-m-d H:i:s', time() + 86400 * 7))->where('A.expiretime', '>=', date('Y-m-d H:i:s'));
|
||
} elseif ($status == '7') {
|
||
$select->where('A.expiretime', '<', date('Y-m-d H:i:s'));
|
||
} else {
|
||
$select->where('A.status', $status);
|
||
}
|
||
}
|
||
$total = $select->count();
|
||
$rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->order('id', 'desc')->limit($offset, $limit)->select();
|
||
|
||
$list = [];
|
||
foreach ($rows as $row) {
|
||
if (!empty($row['type']) && isset(CertHelper::$cert_config[$row['type']])) {
|
||
$row['typename'] = CertHelper::$cert_config[$row['type']]['name'];
|
||
$row['icon'] = CertHelper::$cert_config[$row['type']]['icon'];
|
||
} else {
|
||
$row['typename'] = null;
|
||
}
|
||
$row['domains'] = Db::name('cert_domain')->where('oid', $row['id'])->order('sort', 'ASC')->column('domain');
|
||
$row['end_day'] = $row['expiretime'] ? ceil((strtotime($row['expiretime']) - time()) / 86400) : null;
|
||
if ($row['error']) $row['error'] = htmlspecialchars(str_replace("'", "\\'", $row['error']));
|
||
$list[] = $row;
|
||
}
|
||
|
||
return json(['total' => $total, 'rows' => $list]);
|
||
}
|
||
|
||
public function order_info()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$id = input('post.id/d');
|
||
$row = Db::name('cert_order')->where('id', $id)->find();
|
||
if (!$row) return json(['code' => -1, 'msg' => '证书订单不存在']);
|
||
$pfx = CertHelper::getPfx($row['fullchain'], $row['privatekey']);
|
||
$row['pfx'] = base64_encode($pfx);
|
||
return json(['code' => 0, 'data' => ['id' => $row['id'], 'crt' => $row['fullchain'], 'key' => $row['privatekey'], 'pfx' => $row['pfx'], 'issuetime' => $row['issuetime'], 'expiretime' => $row['expiretime'], 'domains' => Db::name('cert_domain')->where('oid', $row['id'])->order('sort', 'ASC')->column('domain')]]);
|
||
}
|
||
|
||
public function order_op()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$action = input('param.action');
|
||
|
||
if ($action == 'get') {
|
||
$id = input('post.id/d');
|
||
$row = Db::name('cert_order')->where('id', $id)->field('fullchain,privatekey')->find();
|
||
if (!$row) return $this->alert('error', '证书订单不存在');
|
||
$pfx = CertHelper::getPfx($row['fullchain'], $row['privatekey']);
|
||
$row['pfx'] = base64_encode($pfx);
|
||
return json(['code' => 0, 'data' => $row]);
|
||
} elseif ($action == 'add') {
|
||
$aid = input('post.aid/d');
|
||
|
||
if ($aid == -1) {
|
||
$fullchain = input('post.fullchain', null, 'trim');
|
||
$privatekey = input('post.privatekey', null, 'trim');
|
||
$certInfo = $this->parse_cert_key($fullchain, $privatekey);
|
||
if ($certInfo['code'] == -1) return json($certInfo);
|
||
$domains = $certInfo['domains'];
|
||
|
||
$order_ids = Db::name('cert_order')->where('issuetime', $certInfo['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' => 0,
|
||
'keytype' => $certInfo['keytype'],
|
||
'keysize' => $certInfo['keysize'],
|
||
'addtime' => date('Y-m-d H:i:s'),
|
||
'updatetime' => date('Y-m-d H:i:s'),
|
||
'issuetime' => $certInfo['issuetime'],
|
||
'expiretime' => $certInfo['expiretime'],
|
||
'issuer' => $certInfo['issuer'],
|
||
'status' => 3,
|
||
'isauto' => 1,
|
||
'fullchain' => $fullchain,
|
||
'privatekey' => $privatekey,
|
||
];
|
||
} else {
|
||
$order = [
|
||
'aid' => $aid,
|
||
'keytype' => input('post.keytype'),
|
||
'keysize' => input('post.keysize'),
|
||
'addtime' => date('Y-m-d H:i:s'),
|
||
'issuer' => '',
|
||
'status' => 0,
|
||
'isauto' => 1,
|
||
];
|
||
$domains = input('post.domains', [], 'trim');
|
||
$domains = array_map('trim', $domains);
|
||
$domains = array_filter($domains, function ($v) {
|
||
return !empty($v);
|
||
});
|
||
$domains = array_unique($domains);
|
||
if (empty($domains)) return json(['code' => -1, 'msg' => '绑定域名不能为空']);
|
||
$res = $this->check_order($order, $domains);
|
||
if (is_array($res)) return json($res);
|
||
}
|
||
if (empty($order['keytype']) || empty($order['keysize'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||
|
||
Db::startTrans();
|
||
$id = Db::name('cert_order')->insertGetId($order);
|
||
$domainList = [];
|
||
$i = 1;
|
||
foreach ($domains as $domain) {
|
||
$domainList[] = [
|
||
'oid' => $id,
|
||
'domain' => convertDomainToAscii($domain),
|
||
'sort' => $i++,
|
||
];
|
||
}
|
||
Db::name('cert_domain')->insertAll($domainList);
|
||
Db::commit();
|
||
return json(['code' => 0, 'msg' => '添加证书订单成功!']);
|
||
} elseif ($action == 'edit') {
|
||
$id = input('post.id/d');
|
||
$row = Db::name('cert_order')->where('id', $id)->find();
|
||
if (!$row) return json(['code' => -1, 'msg' => '证书订单不存在']);
|
||
|
||
$aid = input('post.aid/d');
|
||
if ($aid == -1) {
|
||
$fullchain = input('post.fullchain', null, 'trim');
|
||
$privatekey = input('post.privatekey', null, 'trim');
|
||
$certInfo = $this->parse_cert_key($fullchain, $privatekey);
|
||
if ($certInfo['code'] == -1) return json($certInfo);
|
||
$domains = $certInfo['domains'];
|
||
|
||
$order = [
|
||
'aid' => 0,
|
||
'keytype' => $certInfo['keytype'],
|
||
'keysize' => $certInfo['keysize'],
|
||
'updatetime' => date('Y-m-d H:i:s'),
|
||
'issuetime' => $certInfo['issuetime'],
|
||
'expiretime' => $certInfo['expiretime'],
|
||
'issuer' => $certInfo['issuer'],
|
||
'status' => 3,
|
||
'issend' => 0,
|
||
'fullchain' => $fullchain,
|
||
'privatekey' => $privatekey,
|
||
];
|
||
} else {
|
||
$domains = input('post.domains', [], 'trim');
|
||
$order = [
|
||
'aid' => $aid,
|
||
'keytype' => input('post.keytype'),
|
||
'keysize' => input('post.keysize'),
|
||
'updatetime' => date('Y-m-d H:i:s'),
|
||
];
|
||
$domains = array_map('trim', $domains);
|
||
$domains = array_filter($domains, function ($v) {
|
||
return !empty($v);
|
||
});
|
||
$domains = array_unique($domains);
|
||
if (empty($domains)) return json(['code' => -1, 'msg' => '绑定域名不能为空']);
|
||
$res = $this->check_order($order, $domains);
|
||
if (is_array($res)) return json($res);
|
||
}
|
||
if (empty($order['keytype']) || empty($order['keysize'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||
|
||
Db::startTrans();
|
||
Db::name('cert_order')->where('id', $id)->update($order);
|
||
Db::name('cert_domain')->where('oid', $id)->delete();
|
||
$domainList = [];
|
||
$i = 1;
|
||
foreach ($domains as $domain) {
|
||
$domainList[] = [
|
||
'oid' => $id,
|
||
'domain' => convertDomainToAscii($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();
|
||
if ($dcount > 0) return json(['code' => -1, 'msg' => '该证书关联了自动部署任务,无法删除']);
|
||
try {
|
||
(new CertOrderService($id))->cancel();
|
||
} catch (Exception $e) {
|
||
}
|
||
Db::name('cert_order')->where('id', $id)->delete();
|
||
Db::name('cert_domain')->where('oid', $id)->delete();
|
||
return json(['code' => 0]);
|
||
} elseif ($action == 'setauto') {
|
||
$id = input('post.id/d');
|
||
$isauto = input('post.isauto/d');
|
||
Db::name('cert_order')->where('id', $id)->update(['isauto' => $isauto]);
|
||
return json(['code' => 0]);
|
||
} elseif ($action == 'reset') {
|
||
$id = input('post.id/d');
|
||
try {
|
||
$service = new CertOrderService($id);
|
||
$service->cancel();
|
||
$service->reset();
|
||
return json(['code' => 0]);
|
||
} catch (Exception $e) {
|
||
return json(['code' => -1, 'msg' => $e->getMessage()]);
|
||
}
|
||
} elseif ($action == 'revoke') {
|
||
$id = input('post.id/d');
|
||
try {
|
||
$service = new CertOrderService($id);
|
||
$service->revoke();
|
||
return json(['code' => 0]);
|
||
} catch (Exception $e) {
|
||
return json(['code' => -1, 'msg' => $e->getMessage()]);
|
||
}
|
||
} elseif ($action == 'show_log') {
|
||
$processid = input('post.processid');
|
||
$file = app()->getRuntimePath() . 'log/' . $processid . '.log';
|
||
if (!file_exists($file)) return json(['code' => -1, 'msg' => '日志文件不存在']);
|
||
return json(['code' => 0, 'data' => file_get_contents($file), 'time' => filemtime($file)]);
|
||
} elseif ($action == 'operation') {
|
||
$ids = input('post.ids');
|
||
$success = 0;
|
||
foreach ($ids as $id) {
|
||
if (input('post.act') == 'delete') {
|
||
$dcount = DB::name('cert_deploy')->where('oid', $id)->count();
|
||
if ($dcount > 0) continue;
|
||
try {
|
||
(new CertOrderService($id))->cancel();
|
||
} catch (Exception $e) {
|
||
}
|
||
Db::name('cert_order')->where('id', $id)->delete();
|
||
Db::name('cert_domain')->where('oid', $id)->delete();
|
||
$success++;
|
||
} elseif (input('post.act') == 'reset') {
|
||
try {
|
||
$service = new CertOrderService($id);
|
||
$service->cancel();
|
||
$service->reset();
|
||
$success++;
|
||
} catch (Exception $e) {
|
||
}
|
||
} elseif (input('post.act') == 'open' || input('post.act') == 'close') {
|
||
$isauto = input('post.act') == 'open' ? 1 : 0;
|
||
Db::name('cert_order')->where('id', $id)->update(['isauto' => $isauto]);
|
||
$success++;
|
||
}
|
||
}
|
||
return json(['code' => 0, 'msg' => '成功操作' . $success . '个证书订单']);
|
||
}
|
||
return json(['code' => -3]);
|
||
}
|
||
|
||
private function check_order($order, $domains)
|
||
{
|
||
$account = Db::name('cert_account')->where('id', $order['aid'])->find();
|
||
if (!$account) return ['code' => -1, 'msg' => 'SSL证书账户不存在'];
|
||
$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) {
|
||
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' => '该证书账户类型不支持泛域名'];
|
||
$mainDomain = getMainDomain($domain);
|
||
$drow = Db::name('domain')->where('name', $mainDomain)->find();
|
||
if (!$drow) {
|
||
if (substr($domain, 0, 2) == '*.') $domain = substr($domain, 2);
|
||
if (!$cname || !Db::name('cert_cname')->where('domain', $domain)->where('status', 1)->find()) {
|
||
return ['code' => -1, 'msg' => '域名' . $domain . '未在本系统添加'];
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private function parse_cert_key($fullchain, $privatekey)
|
||
{
|
||
if (!openssl_x509_read($fullchain)) return ['code' => -1, 'msg' => '证书内容填写错误'];
|
||
if (!openssl_get_privatekey($privatekey)) return ['code' => -1, 'msg' => '私钥内容填写错误'];
|
||
if (!openssl_x509_check_private_key($fullchain, $privatekey)) return ['code' => -1, 'msg' => 'SSL证书与私钥不匹配'];
|
||
$certInfo = openssl_x509_parse($fullchain, true);
|
||
if (!$certInfo || !isset($certInfo['extensions']['subjectAltName'])) return ['code' => -1, 'msg' => '证书内容解析失败'];
|
||
|
||
$pubKey = openssl_pkey_get_public($fullchain);
|
||
if (!$pubKey) return ['code' => -1, 'msg' => '证书公钥解析失败'];
|
||
$keyDetails = openssl_pkey_get_details($pubKey);
|
||
$keytype = null;
|
||
$keysize = 0;
|
||
switch ($keyDetails['type']) {
|
||
case OPENSSL_KEYTYPE_RSA:
|
||
$keytype = 'RSA';
|
||
$keysize = $keyDetails['bits'];
|
||
break;
|
||
case OPENSSL_KEYTYPE_EC:
|
||
$keytype = 'ECC';
|
||
$keysize = $keyDetails['bits'];
|
||
break;
|
||
case OPENSSL_KEYTYPE_DSA:
|
||
$keytype = 'DSA';
|
||
$keysize = $keyDetails['bits'];
|
||
break;
|
||
default:
|
||
$keytype = 'Unknown';
|
||
}
|
||
|
||
$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 ['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'];
|
||
return [
|
||
'code' => 0,
|
||
'keytype' => $keytype,
|
||
'keysize' => $keysize,
|
||
'issuetime' => $issuetime,
|
||
'expiretime' => $expiretime,
|
||
'issuer' => $issuer,
|
||
'domains' => $domains,
|
||
];
|
||
}
|
||
|
||
public function order_process()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
if (function_exists("set_time_limit")) {
|
||
@set_time_limit(0);
|
||
}
|
||
if (function_exists("ignore_user_abort")) {
|
||
@ignore_user_abort(true);
|
||
}
|
||
$id = input('post.id/d');
|
||
$reset = input('post.reset/d', 0);
|
||
try {
|
||
$service = new CertOrderService($id);
|
||
if ($reset == 1) {
|
||
$service->reset();
|
||
}
|
||
$retcode = $service->process(true);
|
||
if ($retcode == 3) {
|
||
return json(['code' => 0, 'msg' => '证书已签发成功!']);
|
||
} elseif ($retcode == 1) {
|
||
return json(['code' => 0, 'msg' => '添加DNS记录成功!请等待DNS生效后点击验证']);
|
||
}
|
||
} catch (Exception $e) {
|
||
return json(['code' => -1, 'msg' => $e->getMessage(), 'trace' => $e->getTrace()]);
|
||
}
|
||
}
|
||
|
||
public function order_form()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$action = input('param.action');
|
||
|
||
$order = null;
|
||
if ($action == 'edit') {
|
||
$id = input('get.id/d');
|
||
$order = Db::name('cert_order')->where('id', $id)->fieldRaw('id,aid,keytype,keysize,status,fullchain,privatekey')->find();
|
||
if (empty($order)) return $this->alert('error', '证书订单不存在');
|
||
$order['domains'] = Db::name('cert_domain')->where('oid', $order['id'])->order('sort', 'ASC')->column('domain');
|
||
if ($order['aid'] == 0) $order['aid'] = -1;
|
||
}
|
||
|
||
$accounts = [];
|
||
foreach (Db::name('cert_account')->where('deploy', 0)->select() as $row) {
|
||
if (empty($row['type']) || !isset(CertHelper::$cert_config[$row['type']])) continue;
|
||
$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);
|
||
|
||
View::assign('info', $order);
|
||
View::assign('action', $action);
|
||
return View::fetch();
|
||
}
|
||
|
||
public function deploytask()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$types = [];
|
||
foreach (DeployHelper::$deploy_config as $key => $value) {
|
||
$types[$key] = $value['name'];
|
||
}
|
||
View::assign('types', $types);
|
||
return view();
|
||
}
|
||
|
||
public function deploy_data()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$domain = $this->request->post('domain', null, 'trim');
|
||
$oid = input('post.oid');
|
||
$aid = input('post.aid', null, 'trim');
|
||
$type = input('post.type', null, 'trim');
|
||
$status = input('post.status', null, 'trim');
|
||
$remark = input('post.remark', null, 'trim');
|
||
$offset = input('post.offset/d');
|
||
$limit = input('post.limit/d');
|
||
|
||
$select = Db::name('cert_deploy')->alias('A')->leftJoin('cert_account B', 'A.aid = B.id')->leftJoin('cert_order C', 'A.oid = C.id')->leftJoin('cert_account D', 'C.aid = D.id');
|
||
if (!empty($oid)) {
|
||
$select->where('A.oid', $oid);
|
||
} elseif (!empty($domain)) {
|
||
$oids = Db::name('cert_domain')->where('domain', 'like', '%' . $domain . '%')->column('oid');
|
||
$select->whereIn('oid', $oids);
|
||
}
|
||
if (!empty($aid)) {
|
||
$select->where('A.aid', $aid);
|
||
}
|
||
if (!empty($type)) {
|
||
$select->where('B.type', $type);
|
||
}
|
||
if (!isNullOrEmpty($status)) {
|
||
$select->where('A.status', $status);
|
||
}
|
||
if (!empty($remark)) {
|
||
$select->where('A.remark', $remark);
|
||
}
|
||
$total = $select->count();
|
||
$rows = $select->fieldRaw('A.*,B.type,B.remark aremark,B.name aname,D.type certtype,D.id certaid')->order('id', 'desc')->limit($offset, $limit)->select();
|
||
|
||
$list = [];
|
||
foreach ($rows as $row) {
|
||
if (!empty($row['type']) && isset(DeployHelper::$deploy_config[$row['type']])) {
|
||
$row['typename'] = DeployHelper::$deploy_config[$row['type']]['name'];
|
||
$row['icon'] = DeployHelper::$deploy_config[$row['type']]['icon'];
|
||
}
|
||
if (!empty($row['certtype']) && isset(CertHelper::$cert_config[$row['certtype']])) {
|
||
$row['certtypename'] = CertHelper::$cert_config[$row['certtype']]['name'];
|
||
} else {
|
||
$row['certtypename'] = '手动续期';
|
||
}
|
||
$row['domains'] = Db::name('cert_domain')->where('oid', $row['oid'])->order('sort', 'ASC')->column('domain');
|
||
if ($row['error']) $row['error'] = htmlspecialchars(str_replace("'", "\\'", $row['error']));
|
||
$list[] = $row;
|
||
}
|
||
|
||
return json(['total' => $total, 'rows' => $list]);
|
||
}
|
||
|
||
public function deploy_op()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$action = input('param.action');
|
||
|
||
if ($action == 'add') {
|
||
$task = [
|
||
'aid' => input('post.aid/d'),
|
||
'oid' => input('post.oid/d'),
|
||
'config' => input('post.config', null, 'trim'),
|
||
'remark' => input('post.remark', null, 'trim'),
|
||
'addtime' => date('Y-m-d H:i:s'),
|
||
'status' => 0,
|
||
'active' => 1
|
||
];
|
||
if (empty($task['aid']) || empty($task['oid']) || empty($task['config'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||
Db::name('cert_deploy')->insert($task);
|
||
return json(['code' => 0, 'msg' => '添加自动部署任务成功!']);
|
||
} elseif ($action == 'edit') {
|
||
$id = input('post.id/d');
|
||
$row = Db::name('cert_deploy')->where('id', $id)->find();
|
||
if (!$row) return json(['code' => -1, 'msg' => '自动部署任务不存在']);
|
||
|
||
$task = [
|
||
'aid' => input('post.aid/d'),
|
||
'oid' => input('post.oid/d'),
|
||
'config' => input('post.config', null, 'trim'),
|
||
'remark' => input('post.remark', null, 'trim'),
|
||
];
|
||
if (empty($task['aid']) || empty($task['oid']) || empty($task['config'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||
Db::name('cert_deploy')->where('id', $id)->update($task);
|
||
return json(['code' => 0, 'msg' => '修改自动部署任务成功!']);
|
||
} elseif ($action == 'del') {
|
||
$id = input('post.id/d');
|
||
Db::name('cert_deploy')->where('id', $id)->delete();
|
||
return json(['code' => 0]);
|
||
} elseif ($action == 'setactive') {
|
||
$id = input('post.id/d');
|
||
$active = input('post.active/d');
|
||
Db::name('cert_deploy')->where('id', $id)->update(['active' => $active]);
|
||
return json(['code' => 0]);
|
||
} elseif ($action == 'reset') {
|
||
$id = input('post.id/d');
|
||
try {
|
||
$service = new CertDeployService($id);
|
||
$service->reset();
|
||
return json(['code' => 0]);
|
||
} catch (Exception $e) {
|
||
return json(['code' => -1, 'msg' => $e->getMessage()]);
|
||
}
|
||
} elseif ($action == 'show_log') {
|
||
$processid = input('post.processid');
|
||
$file = app()->getRuntimePath() . 'log/' . $processid . '.log';
|
||
if (!file_exists($file)) return json(['code' => -1, 'msg' => '日志文件不存在']);
|
||
return json(['code' => 0, 'data' => file_get_contents($file), 'time' => filemtime($file)]);
|
||
} elseif ($action == 'operation') {
|
||
$ids = input('post.ids');
|
||
$success = 0;
|
||
$certid = 0;
|
||
if (input('post.action') == 'cert') {
|
||
$certid = input('post.certid/d');
|
||
$cert = Db::name('cert_order')->where('id', $certid)->find();
|
||
if (!$cert) return json(['code' => -1, 'msg' => '证书订单不存在']);
|
||
}
|
||
foreach ($ids as $id) {
|
||
if (input('post.act') == 'delete') {
|
||
Db::name('cert_deploy')->where('id', $id)->delete();
|
||
$success++;
|
||
} elseif (input('post.act') == 'reset') {
|
||
try {
|
||
$service = new CertDeployService($id);
|
||
$service->reset();
|
||
$success++;
|
||
} catch (Exception $e) {
|
||
}
|
||
} elseif (input('post.act') == 'open' || input('post.act') == 'close') {
|
||
$active = input('post.act') == 'open' ? 1 : 0;
|
||
Db::name('cert_deploy')->where('id', $id)->update(['active' => $active]);
|
||
$success++;
|
||
} elseif (input('post.act') == 'cert') {
|
||
Db::name('cert_deploy')->where('id', $id)->update(['oid' => $certid]);
|
||
$success++;
|
||
}
|
||
}
|
||
return json(['code' => 0, 'msg' => '成功操作' . $success . '个任务']);
|
||
}
|
||
return json(['code' => -3]);
|
||
}
|
||
|
||
public function deploy_process()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
if (function_exists("set_time_limit")) {
|
||
@set_time_limit(0);
|
||
}
|
||
if (function_exists("ignore_user_abort")) {
|
||
@ignore_user_abort(true);
|
||
}
|
||
$id = input('post.id/d');
|
||
$reset = input('post.reset/d', 0);
|
||
try {
|
||
$service = new CertDeployService($id);
|
||
if ($reset == 1) {
|
||
$service->reset();
|
||
}
|
||
$service->process(true);
|
||
return json(['code' => 0, 'msg' => 'SSL证书部署任务执行成功!']);
|
||
} catch (Exception $e) {
|
||
return json(['code' => -1, 'msg' => $e->getMessage(), 'trace' => $e->getTrace()]);
|
||
}
|
||
}
|
||
|
||
public function deploy_form()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$action = input('param.action');
|
||
|
||
$task = null;
|
||
if ($action == 'edit') {
|
||
$id = input('get.id/d');
|
||
$task = Db::name('cert_deploy')->alias('A')->join('cert_account B', 'A.aid = B.id')->where('A.id', $id)->fieldRaw('A.id,A.aid,A.oid,A.config,A.remark,B.type')->find();
|
||
if (empty($task)) return $this->alert('error', '自动部署任务不存在');
|
||
}
|
||
|
||
$accounts = [];
|
||
foreach (Db::name('cert_account')->where('deploy', 1)->select() as $row) {
|
||
if (empty($row['type']) || !isset(DeployHelper::$deploy_config[$row['type']])) continue;
|
||
$accounts[$row['id']] = ['name' => $row['id'] . '_' . DeployHelper::$deploy_config[$row['type']]['name'], 'type' => $row['type']];
|
||
if (!empty($row['remark'])) {
|
||
$accounts[$row['id']]['name'] .= '(' . $row['remark'] . ')';
|
||
}
|
||
}
|
||
View::assign('accounts', $accounts);
|
||
|
||
$orders = [];
|
||
foreach (Db::name('cert_order')->alias('A')->leftJoin('cert_account B', 'A.aid = B.id')->where('status', '<>', 4)->fieldRaw('A.id,A.aid,B.type,B.remark aremark')->order('id', 'desc')->select() as $row) {
|
||
$domains = Db::name('cert_domain')->where('oid', $row['id'])->order('sort', 'ASC')->column('domain');
|
||
$domainstr = count($domains) > 2 ? implode('、', array_slice($domains, 0, 2)) . '等' . count($domains) . '个域名' : implode('、', $domains);
|
||
if ($row['aid'] == 0) {
|
||
$name = $row['id'] . '_' . $domainstr . '(手动续期)';
|
||
} else {
|
||
$name = $row['id'] . '_' . $domainstr . '(' . CertHelper::$cert_config[$row['type']]['name'] . ')';
|
||
}
|
||
$orders[$row['id']] = ['name' => $name];
|
||
}
|
||
View::assign('orders', $orders);
|
||
|
||
View::assign('info', $task);
|
||
View::assign('action', $action);
|
||
View::assign('typeList', DeployHelper::getList());
|
||
return View::fetch();
|
||
}
|
||
|
||
public function cname()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$domains = [];
|
||
foreach (Db::name('domain')->field('id,name')->select() as $row) {
|
||
$domains[$row['id']] = $row['name'];
|
||
}
|
||
View::assign('domains', $domains);
|
||
return view();
|
||
}
|
||
|
||
public function cname_data()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$kw = $this->request->post('kw', null, 'trim');
|
||
$offset = input('post.offset/d');
|
||
$limit = input('post.limit/d');
|
||
|
||
$select = Db::name('cert_cname')->alias('A')->leftJoin('domain B', 'A.did = B.id');
|
||
if (!empty($kw)) {
|
||
$select->whereLike('A.domain', '%' . $kw . '%');
|
||
}
|
||
$total = $select->count();
|
||
$rows = $select->order('A.id', 'desc')->limit($offset, $limit)->field('A.*,B.name cnamedomain')->select();
|
||
|
||
$list = [];
|
||
foreach ($rows as $row) {
|
||
$row['host'] = $this->getCnameHost($row['domain']);
|
||
$row['record'] = $row['rr'] . '.' . $row['cnamedomain'];
|
||
$list[] = $row;
|
||
}
|
||
|
||
return json(['total' => $total, 'rows' => $list]);
|
||
}
|
||
|
||
private function getCnameHost($domain)
|
||
{
|
||
$main = getMainDomain($domain);
|
||
if ($main == $domain) {
|
||
return '_acme-challenge';
|
||
} else {
|
||
return '_acme-challenge.' . substr($domain, 0, -strlen($main) - 1);
|
||
}
|
||
}
|
||
|
||
public function cname_op()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
$action = input('param.action');
|
||
|
||
if ($action == 'add') {
|
||
$data = [
|
||
'domain' => input('post.domain', null, 'trim'),
|
||
'rr' => input('post.rr', null, 'trim'),
|
||
'did' => input('post.did/d'),
|
||
'addtime' => date('Y-m-d H:i:s'),
|
||
'status' => 0
|
||
];
|
||
if (empty($data['domain']) || empty($data['rr']) || empty($data['did'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||
if (!checkDomain($data['domain'])) return json(['code' => -1, 'msg' => '域名格式不正确']);
|
||
if (Db::name('cert_cname')->where('domain', $data['domain'])->find()) {
|
||
return json(['code' => -1, 'msg' => '域名' . $data['domain'] . '已存在']);
|
||
}
|
||
if (Db::name('cert_cname')->where('rr', $data['rr'])->where('did', $data['did'])->find()) {
|
||
return json(['code' => -1, 'msg' => '已存在相同CNAME记录值']);
|
||
}
|
||
Db::name('cert_cname')->insert($data);
|
||
return json(['code' => 0, 'msg' => '添加CMAME代理成功!']);
|
||
} elseif ($action == 'edit') {
|
||
$id = input('post.id/d');
|
||
$row = Db::name('cert_cname')->where('id', $id)->find();
|
||
if (!$row) return json(['code' => -1, 'msg' => 'CMAME代理不存在']);
|
||
|
||
$data = [
|
||
'rr' => input('post.rr', null, 'trim'),
|
||
'did' => input('post.did/d'),
|
||
];
|
||
if ($row['rr'] != $data['rr'] || $row['did'] != $data['did']) {
|
||
$data['status'] = 0;
|
||
}
|
||
if (empty($data['rr']) || empty($data['did'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||
if (Db::name('cert_cname')->where('rr', $data['rr'])->where('did', $data['did'])->where('id', '<>', $id)->find()) {
|
||
return json(['code' => -1, 'msg' => '已存在相同CNAME记录值']);
|
||
}
|
||
Db::name('cert_cname')->where('id', $id)->update($data);
|
||
return json(['code' => 0, 'msg' => '修改CMAME代理成功!']);
|
||
} elseif ($action == 'del') {
|
||
$id = input('post.id/d');
|
||
Db::name('cert_cname')->where('id', $id)->delete();
|
||
return json(['code' => 0]);
|
||
} elseif ($action == 'check') {
|
||
$id = input('post.id/d');
|
||
$row = Db::name('cert_cname')->alias('A')->join('domain B', 'A.did = B.id')->where('A.id', $id)->field('A.*,B.name cnamedomain')->find();
|
||
if (!$row) return json(['code' => -1, 'msg' => '自动部署任务不存在']);
|
||
|
||
$status = 1;
|
||
$domain = '_acme-challenge.' . $row['domain'];
|
||
$record = $row['rr'] . '.' . $row['cnamedomain'];
|
||
$result = \app\utils\DnsQueryUtils::get_dns_records($domain, 'CNAME');
|
||
if (!$result || !in_array($record, $result)) {
|
||
$result = \app\utils\DnsQueryUtils::query_dns_doh($domain, 'CNAME');
|
||
if (!$result || !in_array($record, $result)) {
|
||
$status = 0;
|
||
}
|
||
}
|
||
if ($status != $row['status']) {
|
||
Db::name('cert_cname')->where('id', $id)->update(['status' => $status]);
|
||
}
|
||
return json(['code' => 0, 'status' => $status]);
|
||
}
|
||
}
|
||
|
||
public function certset()
|
||
{
|
||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||
return View::fetch();
|
||
}
|
||
}
|