mirror of https://github.com/certd/certd
perf: 支持公共cname服务
parent
fdc6eef921
commit
3c919ee5d1
|
@ -1,3 +1,4 @@
|
||||||
export function isDev() {
|
export function isDev() {
|
||||||
return process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'local';
|
const nodeEnv = process.env.NODE_ENV || '';
|
||||||
|
return nodeEnv === 'development' || nodeEnv.indexOf('local') >= 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ import { IAccess } from "../access";
|
||||||
export type CnameProvider = {
|
export type CnameProvider = {
|
||||||
id: any;
|
id: any;
|
||||||
domain: string;
|
domain: string;
|
||||||
dnsProviderType: string;
|
title?: string;
|
||||||
|
dnsProviderType?: string;
|
||||||
access?: IAccess;
|
access?: IAccess;
|
||||||
accessId: any;
|
accessId?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CnameRecord = {
|
export type CnameRecord = {
|
||||||
|
@ -15,6 +16,7 @@ export type CnameRecord = {
|
||||||
recordValue: string;
|
recordValue: string;
|
||||||
cnameProvider: CnameProvider;
|
cnameProvider: CnameProvider;
|
||||||
status: string;
|
status: string;
|
||||||
|
commonDnsProvider?: any;
|
||||||
};
|
};
|
||||||
export type ICnameProxyService = {
|
export type ICnameProxyService = {
|
||||||
getByDomain: (domain: string) => Promise<CnameRecord>;
|
getByDomain: (domain: string) => Promise<CnameRecord>;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import { AppKey, PlusRequestService } from '@certd/plus-core';
|
import { AppKey, PlusRequestService } from '@certd/plus-core';
|
||||||
import { http, HttpRequestConfig, logger } from '@certd/basic';
|
import { cache, http, HttpRequestConfig, logger } from '@certd/basic';
|
||||||
import { SysInstallInfo, SysLicenseInfo, SysSettingsService } from '../../settings/index.js';
|
import { SysInstallInfo, SysLicenseInfo, SysSettingsService } from '../../settings/index.js';
|
||||||
import { merge } from 'lodash-es';
|
import { merge } from 'lodash-es';
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ export class PlusService {
|
||||||
data: {
|
data: {
|
||||||
userId,
|
userId,
|
||||||
appKey: AppKey,
|
appKey: AppKey,
|
||||||
subjectId: this.getSubjectId(),
|
subjectId: plusRequestService.getSubjectId(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -93,9 +93,19 @@ export class PlusService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccessToken() {
|
async getAccessToken() {
|
||||||
|
const cacheKey = 'certd:subject:access_token';
|
||||||
|
const token = cache.get(cacheKey);
|
||||||
|
if (token) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
const plusRequestService = await this.getPlusRequestService();
|
const plusRequestService = await this.getPlusRequestService();
|
||||||
await this.register();
|
await this.register();
|
||||||
return await plusRequestService.getAccessToken();
|
const res = await plusRequestService.getAccessToken();
|
||||||
|
const ttl = res.expiresIn * 1000 - Date.now().valueOf();
|
||||||
|
cache.set(cacheKey, res.accessToken, {
|
||||||
|
ttl,
|
||||||
|
});
|
||||||
|
return res.accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestWithToken(config: HttpRequestConfig) {
|
async requestWithToken(config: HttpRequestConfig) {
|
||||||
|
@ -103,10 +113,15 @@ export class PlusService {
|
||||||
const token = await this.getAccessToken();
|
const token = await this.getAccessToken();
|
||||||
merge(config, {
|
merge(config, {
|
||||||
baseURL: plusRequestService.getBaseURL(),
|
baseURL: plusRequestService.getBaseURL(),
|
||||||
|
method: 'post',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: token,
|
Authorization: `Berear ${token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return await http.request(config);
|
const res = await http.request(config);
|
||||||
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.message);
|
||||||
|
}
|
||||||
|
return res.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,10 +378,14 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
||||||
} else {
|
} else {
|
||||||
for (const key in domainVerifyPlan.cnameVerifyPlan) {
|
for (const key in domainVerifyPlan.cnameVerifyPlan) {
|
||||||
const cnameRecord = await this.ctx.cnameProxyService.getByDomain(key);
|
const cnameRecord = await this.ctx.cnameProxyService.getByDomain(key);
|
||||||
|
let dnsProvider = cnameRecord.commonDnsProvider;
|
||||||
|
if (cnameRecord.cnameProvider.id > 0) {
|
||||||
|
dnsProvider = await this.createDnsProvider(cnameRecord.cnameProvider.dnsProviderType, cnameRecord.cnameProvider.access);
|
||||||
|
}
|
||||||
cnameVerifyPlan[key] = {
|
cnameVerifyPlan[key] = {
|
||||||
domain: cnameRecord.cnameProvider.domain,
|
domain: cnameRecord.cnameProvider.domain,
|
||||||
fullRecord: cnameRecord.recordValue,
|
fullRecord: cnameRecord.recordValue,
|
||||||
dnsProvider: await this.createDnsProvider(cnameRecord.cnameProvider.dnsProviderType, cnameRecord.cnameProvider.access),
|
dnsProvider,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<cname-tip :record="cnameRecord"></cname-tip>
|
<cname-tip :record="cnameRecord"></cname-tip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-else class="helper">不要删除CNAME</div>
|
<div v-else class="helper" title="后续自动申请证书需要">不要删除CNAME</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -37,7 +37,8 @@ const statusDict = dict({
|
||||||
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
||||||
{ label: "验证中", value: "validating", color: "blue" },
|
{ label: "验证中", value: "validating", color: "blue" },
|
||||||
{ label: "验证成功", value: "valid", color: "green" },
|
{ label: "验证成功", value: "valid", color: "green" },
|
||||||
{ label: "验证失败", value: "failed", color: "red" }
|
{ label: "验证失败", value: "failed", color: "red" },
|
||||||
|
{ label: "验证超时", value: "timeout", color: "red" }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -67,12 +68,24 @@ function onRecordChange() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let refreshIntervalId: any = null;
|
||||||
async function doRefresh() {
|
async function doRefresh() {
|
||||||
if (!props.domain) {
|
if (!props.domain) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cnameRecord.value = await GetByDomain(props.domain);
|
cnameRecord.value = await GetByDomain(props.domain);
|
||||||
onRecordChange();
|
onRecordChange();
|
||||||
|
|
||||||
|
if (cnameRecord.value.status === "validating") {
|
||||||
|
if (!refreshIntervalId) {
|
||||||
|
refreshIntervalId = setInterval(async () => {
|
||||||
|
await doRefresh();
|
||||||
|
}, 9000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clearInterval(refreshIntervalId);
|
||||||
|
refreshIntervalId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
|
@ -171,7 +171,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
||||||
{ label: "验证中", value: "validating", color: "blue" },
|
{ label: "验证中", value: "validating", color: "blue" },
|
||||||
{ label: "验证成功", value: "valid", color: "green" },
|
{ label: "验证成功", value: "valid", color: "green" },
|
||||||
{ label: "验证失败", value: "failed", color: "red" }
|
{ label: "验证失败", value: "failed", color: "red" },
|
||||||
|
{ label: "验证超时", value: "timeout", color: "red" }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
addForm: {
|
addForm: {
|
||||||
|
@ -204,7 +205,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
if (res === true) {
|
if (res === true) {
|
||||||
message.success("验证成功");
|
message.success("验证成功");
|
||||||
row.status = "valid";
|
row.status = "valid";
|
||||||
|
} else if (res === false) {
|
||||||
|
message.success("验证超时");
|
||||||
|
row.status = "timeout";
|
||||||
|
} else {
|
||||||
|
message.success("开始验证,请耐心等待");
|
||||||
}
|
}
|
||||||
|
await crudExpose.doRefresh();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
message.error(e.message);
|
message.error(e.message);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
export type CnameRecordStatusType = 'cname' | 'validating' | 'valid' | 'error';
|
export type CnameRecordStatusType = 'cname' | 'validating' | 'valid' | 'error' | 'timeout';
|
||||||
/**
|
/**
|
||||||
* cname record配置
|
* cname record配置
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -4,11 +4,11 @@ import { Repository } from 'typeorm';
|
||||||
import { BaseService, PlusService, ValidateException } from '@certd/lib-server';
|
import { BaseService, PlusService, ValidateException } from '@certd/lib-server';
|
||||||
import { CnameRecordEntity, CnameRecordStatusType } from '../entity/cname-record.js';
|
import { CnameRecordEntity, CnameRecordStatusType } from '../entity/cname-record.js';
|
||||||
import { createDnsProvider, IDnsProvider, parseDomain } from '@certd/plugin-cert';
|
import { createDnsProvider, IDnsProvider, parseDomain } from '@certd/plugin-cert';
|
||||||
import { CnameProvider } from '@certd/pipeline';
|
import { CnameProvider, CnameRecord } from '@certd/pipeline';
|
||||||
import { cache, http, logger, utils } from '@certd/basic';
|
import { cache, http, logger, utils } from '@certd/basic';
|
||||||
|
|
||||||
import { AccessService } from '../../pipeline/service/access-service.js';
|
import { AccessService } from '../../pipeline/service/access-service.js';
|
||||||
import { isDev } from '../../../utils/env.js';
|
import { isDev } from '@certd/basic';
|
||||||
import { walkTxtRecord } from '@certd/acme-client';
|
import { walkTxtRecord } from '@certd/acme-client';
|
||||||
import { CnameProviderService } from './cname-provider-service.js';
|
import { CnameProviderService } from './cname-provider-service.js';
|
||||||
import { CnameProviderEntity } from '../entity/cname-provider.js';
|
import { CnameProviderEntity } from '../entity/cname-provider.js';
|
||||||
|
@ -128,8 +128,17 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async getWithAccessByDomain(domain: string, userId: number) {
|
async getWithAccessByDomain(domain: string, userId: number) {
|
||||||
const record = await this.getByDomain(domain, userId);
|
const record: CnameRecord = await this.getByDomain(domain, userId);
|
||||||
record.cnameProvider.access = await this.accessService.getAccessById(record.cnameProvider.accessId, false);
|
if (record.cnameProvider.id > 0) {
|
||||||
|
//自定义cname服务
|
||||||
|
record.cnameProvider.access = await this.accessService.getAccessById(record.cnameProvider.accessId, false);
|
||||||
|
} else {
|
||||||
|
record.commonDnsProvider = new CommonDnsProvider({
|
||||||
|
config: record.cnameProvider,
|
||||||
|
plusService: this.plusService,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +167,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
cnameProvider: {
|
cnameProvider: {
|
||||||
...provider,
|
...provider,
|
||||||
} as CnameProvider,
|
} as CnameProvider,
|
||||||
};
|
} as CnameRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -184,17 +193,20 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
startTime: new Date().getTime(),
|
startTime: new Date().getTime(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let ttl = 60 * 60 * 15 * 1000;
|
let ttl = 15 * 60 * 1000;
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
ttl = 30 * 1000;
|
ttl = 30 * 1000;
|
||||||
}
|
}
|
||||||
const recordValue = bean.recordValue.substring(0, bean.recordValue.indexOf('.'));
|
const testRecordValue = 'certd-cname-verify';
|
||||||
|
|
||||||
const buildDnsProvider = async () => {
|
const buildDnsProvider = async () => {
|
||||||
const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId);
|
const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId);
|
||||||
if (cnameProvider == null) {
|
if (cnameProvider == null) {
|
||||||
throw new ValidateException(`CNAME服务:${bean.cnameProviderId} 已被删除,请修改CNAME记录,重新选择CNAME服务`);
|
throw new ValidateException(`CNAME服务:${bean.cnameProviderId} 已被删除,请修改CNAME记录,重新选择CNAME服务`);
|
||||||
}
|
}
|
||||||
|
if (cnameProvider.disabled === true) {
|
||||||
|
throw new Error(`CNAME服务:${bean.cnameProviderId} 已被禁用`);
|
||||||
|
}
|
||||||
|
|
||||||
if (cnameProvider.id < 0) {
|
if (cnameProvider.id < 0) {
|
||||||
//公共CNAME
|
//公共CNAME
|
||||||
|
@ -218,16 +230,16 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (value.startTime + ttl < new Date().getTime()) {
|
if (value.startTime + ttl < new Date().getTime()) {
|
||||||
logger.warn(`cname验证超时,停止检查,${bean.domain} ${recordValue}`);
|
logger.warn(`cname验证超时,停止检查,${bean.domain} ${testRecordValue}`);
|
||||||
clearInterval(value.intervalId);
|
clearInterval(value.intervalId);
|
||||||
await this.updateStatus(bean.id, 'cname');
|
await this.updateStatus(bean.id, 'timeout');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const originDomain = parseDomain(bean.domain);
|
const originDomain = parseDomain(bean.domain);
|
||||||
const fullDomain = `${bean.hostRecord}.${originDomain}`;
|
const fullDomain = `${bean.hostRecord}.${originDomain}`;
|
||||||
|
|
||||||
logger.info(`检查CNAME配置 ${fullDomain} ${recordValue}`);
|
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
|
||||||
|
|
||||||
// const txtRecords = await dns.promises.resolveTxt(fullDomain);
|
// const txtRecords = await dns.promises.resolveTxt(fullDomain);
|
||||||
// if (txtRecords.length) {
|
// if (txtRecords.length) {
|
||||||
|
@ -240,10 +252,10 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
logger.error(`获取TXT记录失败,${e.message}`);
|
logger.error(`获取TXT记录失败,${e.message}`);
|
||||||
}
|
}
|
||||||
logger.info(`检查到TXT记录 ${JSON.stringify(records)}`);
|
logger.info(`检查到TXT记录 ${JSON.stringify(records)}`);
|
||||||
const success = records.includes(recordValue);
|
const success = records.includes(testRecordValue);
|
||||||
if (success) {
|
if (success) {
|
||||||
clearInterval(value.intervalId);
|
clearInterval(value.intervalId);
|
||||||
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${recordValue}`);
|
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`);
|
||||||
await this.updateStatus(bean.id, 'valid');
|
await this.updateStatus(bean.id, 'valid');
|
||||||
value.pass = true;
|
value.pass = true;
|
||||||
cache.delete(cacheKey);
|
cache.delete(cacheKey);
|
||||||
|
@ -257,8 +269,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`删除CNAME的校验DNS记录失败, ${e.message},req:${JSON.stringify(value.recordReq)},recordRes:${JSON.stringify(value.recordRes)}`, e);
|
logger.error(`删除CNAME的校验DNS记录失败, ${e.message},req:${JSON.stringify(value.recordReq)},recordRes:${JSON.stringify(value.recordRes)}`, e);
|
||||||
}
|
}
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
return success;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (value.validating) {
|
if (value.validating) {
|
||||||
|
@ -278,7 +290,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
fullRecord: fullRecord,
|
fullRecord: fullRecord,
|
||||||
hostRecord: hostRecord,
|
hostRecord: hostRecord,
|
||||||
type: 'TXT',
|
type: 'TXT',
|
||||||
value: recordValue,
|
value: testRecordValue,
|
||||||
};
|
};
|
||||||
const dnsProvider = await buildDnsProvider();
|
const dnsProvider = await buildDnsProvider();
|
||||||
const recordRes = await dnsProvider.createRecord(req);
|
const recordRes = await dnsProvider.createRecord(req);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { CreateRecordOptions, DnsProviderContext, IDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
import { CreateRecordOptions, DnsProviderContext, IDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||||
import { PlusService } from '@certd/lib-server';
|
import { PlusService } from '@certd/lib-server';
|
||||||
|
|
||||||
export type CnameProvider = {
|
export type CommonCnameProvider = {
|
||||||
id: number;
|
id: number;
|
||||||
domain: string;
|
domain: string;
|
||||||
title: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
export const CommonProviders = [
|
export const CommonProviders = [
|
||||||
{
|
{
|
||||||
|
@ -16,10 +16,10 @@ export const CommonProviders = [
|
||||||
|
|
||||||
export class CommonDnsProvider implements IDnsProvider {
|
export class CommonDnsProvider implements IDnsProvider {
|
||||||
ctx: DnsProviderContext;
|
ctx: DnsProviderContext;
|
||||||
config: CnameProvider;
|
config: CommonCnameProvider;
|
||||||
plusService: PlusService;
|
plusService: PlusService;
|
||||||
|
|
||||||
constructor(opts: { config: CnameProvider; plusService: PlusService }) {
|
constructor(opts: { config: CommonCnameProvider; plusService: PlusService }) {
|
||||||
this.config = opts.config;
|
this.config = opts.config;
|
||||||
this.plusService = opts.plusService;
|
this.plusService = opts.plusService;
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,9 @@ export class CommonDnsProvider implements IDnsProvider {
|
||||||
|
|
||||||
const res = await this.plusService.requestWithToken({
|
const res = await this.plusService.requestWithToken({
|
||||||
url: '/activation/certd/cname/recordCreate',
|
url: '/activation/certd/cname/recordCreate',
|
||||||
|
method: 'post',
|
||||||
data: {
|
data: {
|
||||||
subjectId: this.plusService.getSubjectId(),
|
subjectId: await this.plusService.getSubjectId(),
|
||||||
domain: options.domain,
|
domain: options.domain,
|
||||||
hostRecord: options.hostRecord,
|
hostRecord: options.hostRecord,
|
||||||
recordValue: options.value,
|
recordValue: options.value,
|
||||||
|
@ -47,12 +48,13 @@ export class CommonDnsProvider implements IDnsProvider {
|
||||||
async removeRecord(options: RemoveRecordOptions<any>) {
|
async removeRecord(options: RemoveRecordOptions<any>) {
|
||||||
const res = await this.plusService.requestWithToken({
|
const res = await this.plusService.requestWithToken({
|
||||||
url: '/activation/certd/cname/recordRemove',
|
url: '/activation/certd/cname/recordRemove',
|
||||||
|
method: 'post',
|
||||||
data: {
|
data: {
|
||||||
subjectId: this.plusService.getSubjectId(),
|
subjectId: await this.plusService.getSubjectId(),
|
||||||
domain: options.recordReq.domain,
|
domain: options.recordReq.domain,
|
||||||
hostRecord: options.recordReq.hostRecord,
|
hostRecord: options.recordReq.hostRecord,
|
||||||
recordValue: options.recordReq.value,
|
recordValue: options.recordReq.value,
|
||||||
recordId: options.recordRes.id,
|
recordId: options.recordRes.recordId,
|
||||||
providerId: this.config.id,
|
providerId: this.config.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue