pull/101/head
xiaojunnuo 2024-07-18 11:17:13 +08:00
parent a438028002
commit 303097b835
14 changed files with 133 additions and 106 deletions

View File

@ -21,7 +21,6 @@
"qs": "^6.11.2" "qs": "^6.11.2"
}, },
"devDependencies": { "devDependencies": {
"@certd/acme-client": "workspace:^1.21.2",
"@rollup/plugin-commonjs": "^23.0.4", "@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-json": "^6.0.0", "@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",

View File

@ -282,12 +282,16 @@ export class Executor {
continue; continue;
} }
if (notification.type === "email") { if (notification.type === "email") {
this.options.emailService?.send({ try {
userId: this.pipeline.userId, this.options.emailService?.send({
subject, userId: this.pipeline.userId,
content, subject,
receivers: notification.options.receivers, content,
}); receivers: notification.options.receivers,
});
} catch (e) {
logger.error("send email error", e);
}
} }
} }
} }

View File

@ -23,4 +23,6 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
test/user.secret.ts src/test/user.secret.ts
src/**/*.js

View File

@ -2,7 +2,6 @@ import kubernetesClient from 'kubernetes-client';
import dns from 'dns'; import dns from 'dns';
import { logger } from '@certd/pipeline'; import { logger } from '@certd/pipeline';
// @ts-ignore
const { KubeConfig, Client, Request } = kubernetesClient; const { KubeConfig, Client, Request } = kubernetesClient;
export class K8sClient { export class K8sClient {

View File

@ -36,6 +36,6 @@
"*.ts", "*.ts",
"dist", "dist",
"node_modules", "node_modules",
"test" "src/test"
], ],
} }

View File

@ -44,7 +44,7 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
"3、多级子域名要分成多个域名输入*.foo.com的证书不能用于xxx.yyy.foo.com、foo.com\n" + "3、多级子域名要分成多个域名输入*.foo.com的证书不能用于xxx.yyy.foo.com、foo.com\n" +
"4、输入一个回车之后再输入下一个", "4、输入一个回车之后再输入下一个",
}) })
domains!: string; domains!: string[];
@TaskInput({ @TaskInput({
title: "邮箱", title: "邮箱",
@ -143,9 +143,20 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
forceUpdate!: string; forceUpdate!: string;
@TaskInput({ @TaskInput({
title: "CsrInfo", title: "成功后邮件通知",
helper: "暂时没有用", value: true,
component: {
name: "a-switch",
vModel: "checked",
},
helper: "申请成功后是否发送邮件通知",
}) })
successNotify = true;
// @TaskInput({
// title: "CsrInfo",
// helper: "暂时没有用",
// })
csrInfo!: string; csrInfo!: string;
@TaskInput({ @TaskInput({
@ -196,6 +207,10 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
await this.output(cert, true); await this.output(cert, true);
//清空后续任务的状态,让后续任务能够重新执行 //清空后续任务的状态,让后续任务能够重新执行
this.clearLastStatus(); this.clearLastStatus();
if (this.successNotify) {
await this.sendSuccessEmail();
}
} else { } else {
throw new Error("申请证书失败"); throw new Error("申请证书失败");
} }
@ -296,16 +311,25 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
dnsProvider.setCtx(context); dnsProvider.setCtx(context);
await dnsProvider.onInstance(); await dnsProvider.onInstance();
const cert = await this.acme.order({ try {
email, const cert = await this.acme.order({
domains, email,
dnsProvider, domains,
csrInfo, dnsProvider,
isTest: false, csrInfo,
}); isTest: false,
});
const certInfo = this.formatCerts(cert); const certInfo = this.formatCerts(cert);
return new CertReader(certInfo); return new CertReader(certInfo);
} catch (e: any) {
const message: string = e.message;
if (message.indexOf("redundant with a wildcard domain in the same request") >= 0) {
this.logger.error(e);
throw new Error(`通配符域名已经包含了普通域名,请删除其中一个(${message}`);
}
throw e;
}
} }
formatCert(pem: string) { formatCert(pem: string) {
@ -349,6 +373,21 @@ export class CertApplyPlugin extends AbstractTaskPlugin {
leftDays, leftDays,
}; };
} }
private async sendSuccessEmail() {
try {
this.logger.info("发送成功邮件通知:" + this.email);
const subject = `【CertD】证书申请成功【${this.domains[0]}`;
await this.ctx.emailService.send({
userId: this.ctx.pipeline.userId,
receivers: [this.email],
subject: subject,
content: `证书申请成功,域名:${this.domains.join(",")}`,
});
} catch (e) {
this.logger.error("send email error", e);
}
}
} }
new CertApplyPlugin(); new CertApplyPlugin();

View File

@ -2,3 +2,4 @@ INSERT INTO sys_settings (key, title, setting,access) VALUES ('sys.install','安
ALTER TABLE sys_user ADD COLUMN password_version integer DEFAULT 1; ALTER TABLE sys_user ADD COLUMN password_version integer DEFAULT 1;
ALTER TABLE sys_user ADD COLUMN password_salt varchar(36); ALTER TABLE sys_user ADD COLUMN password_salt varchar(36);
ALTER TABLE sys_user ALTER COLUMN password varchar(100);

View File

@ -31,9 +31,11 @@ export function load(config, env = '') {
// Get document, or throw exception on error // Get document, or throw exception on error
logger.info('load config', env); logger.info('load config', env);
const yamlPath = path.join(process.cwd(), `.env.${env}.yaml`); const yamlPath = path.join(process.cwd(), `.env.${env}.yaml`);
const doc = yaml.load(fs.readFileSync(yamlPath, 'utf8')); if (fs.existsSync(yamlPath)) {
_.merge(doc, parseEnv(config)); const doc = yaml.load(fs.readFileSync(yamlPath, 'utf8'));
return doc; return _.merge(doc, parseEnv(config));
}
return parseEnv(config);
} }
export function mergeConfig(config: any, envType: string) { export function mergeConfig(config: any, envType: string) {

View File

@ -21,6 +21,11 @@ import ProductionConfig from './config/config.production.js';
import PreviewConfig from './config/config.preview.js'; import PreviewConfig from './config/config.preview.js';
import UnittestConfig from './config/config.unittest.js'; import UnittestConfig from './config/config.unittest.js';
process.on('uncaughtException', error => {
console.error('未捕获的异常:', error);
// 在这里可以添加日志记录、发送错误通知等操作
});
@Configuration({ @Configuration({
imports: [ imports: [
koa, koa,
@ -50,7 +55,7 @@ export class MainConfiguration {
async onReady() { async onReady() {
// add middleware // add middleware
this.app.useMiddleware([ReportMiddleware]); // this.app.useMiddleware([ReportMiddleware]);
// add filter // add filter
// this.app.useFilter([NotFoundFilter, DefaultErrorFilter]); // this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
//跨域 //跨域

View File

@ -13,6 +13,8 @@ import { Constants } from '../../../basic/constants.js';
import { UserRoleEntity } from '../entity/user-role.js'; import { UserRoleEntity } from '../entity/user-role.js';
import { randomText } from 'svg-captcha'; import { randomText } from 'svg-captcha';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { SysSettingsService } from '../../system/service/sys-settings-service.js';
import { SysInstallInfo } from '../../system/service/models.js';
/** /**
* *
@ -29,6 +31,9 @@ export class UserService extends BaseService<UserEntity> {
@Inject() @Inject()
userRoleService: UserRoleService; userRoleService: UserRoleService;
@Inject()
sysSettingsService: SysSettingsService;
getRepository() { getRepository() {
return this.repository; return this.repository;
} }
@ -88,7 +93,7 @@ export class UserService extends BaseService<UserEntity> {
delete param.username; delete param.username;
if (!_.isEmpty(param.password)) { if (!_.isEmpty(param.password)) {
param.passwordVersion = 2; param.passwordVersion = 2;
param.password = this.genPassword(param.password, param.passwordVersion); param.password = await this.genPassword(param.password, param.passwordVersion);
} else { } else {
delete param.password; delete param.password;
} }
@ -96,30 +101,33 @@ export class UserService extends BaseService<UserEntity> {
await this.roleService.updateRoles(param.id, param.roles); await this.roleService.updateRoles(param.id, param.roles);
} }
private genPassword(plainPassword: any, passwordVersion: number) { private async genPassword(rawPassword: any, passwordVersion: number) {
if (passwordVersion == null || passwordVersion <= 1) { if (passwordVersion == null || passwordVersion <= 1) {
return md5(plainPassword); return md5(rawPassword);
} }
const salt = bcrypt.genSaltSync(10); const salt = bcrypt.genSaltSync(10);
const plainPassword = await this.buildPlainPassword(rawPassword);
return bcrypt.hashSync(plainPassword, salt); return bcrypt.hashSync(plainPassword, salt);
} }
async findOne(param) { async findOne(param: any) {
return this.repository.findOne({ return this.repository.findOne({
where: param, where: param,
}); });
} }
async checkPassword( async checkPassword(rawPassword: any, hashPassword: any, passwordVersion: number) {
rawPassword: any,
hashPassword: any,
passwordVersion: number
) {
if (passwordVersion == null || passwordVersion <= 1) { if (passwordVersion == null || passwordVersion <= 1) {
return this.genPassword(rawPassword, passwordVersion) === hashPassword; return (await this.genPassword(rawPassword, passwordVersion)) === hashPassword;
} }
const plainPassword = await this.buildPlainPassword(rawPassword);
return bcrypt.compareSync(plainPassword, hashPassword);
}
return bcrypt.compareSync(rawPassword, hashPassword); // true async buildPlainPassword(rawPassword: string) {
const setting: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
const prefixSiteId = setting.siteId.substring(1, 5);
return rawPassword + prefixSiteId;
} }
/** /**
@ -147,17 +155,14 @@ export class UserService extends BaseService<UserEntity> {
status: 1, status: 1,
passwordVersion: 2, passwordVersion: 2,
}); });
newUser.password = this.genPassword( if (!newUser.password) {
newUser.password, newUser.password = randomText(6);
newUser.passwordVersion }
); newUser.password = await this.genPassword(newUser.password, newUser.passwordVersion);
await this.transaction(async txManager => { await this.transaction(async txManager => {
newUser = await txManager.save(newUser); newUser = await txManager.save(newUser);
const userRole: UserRoleEntity = UserRoleEntity.of( const userRole: UserRoleEntity = UserRoleEntity.of(newUser.id, Constants.role.defaultUser);
newUser.id,
Constants.role.defaultUser
);
await txManager.save(userRole); await txManager.save(userRole);
}); });
@ -167,11 +172,7 @@ export class UserService extends BaseService<UserEntity> {
async changePassword(userId: any, form: any) { async changePassword(userId: any, form: any) {
const user = await this.info(userId); const user = await this.info(userId);
const passwordChecked = await this.checkPassword( const passwordChecked = await this.checkPassword(form.password, user.password, user.passwordVersion);
form.password,
user.password,
user.passwordVersion
);
if (!passwordChecked) { if (!passwordChecked) {
throw new CommonException('原密码错误'); throw new CommonException('原密码错误');
} }

View File

@ -137,15 +137,14 @@ export class PipelineService extends BaseService<PipelineEntity> {
name: `pipeline.${id}.trigger.once`, name: `pipeline.${id}.trigger.once`,
cron: null, cron: null,
job: async () => { job: async () => {
logger.info('job准备启动,当前定时器数量:', this.cron.getListSize()); logger.info('用户手动启动job');
try { try {
await this.run(id, null); await this.run(id, null);
} catch (e) { } catch (e) {
logger.error('定时job执行失败', e); logger.error('手动job执行失败', e);
} }
}, },
}); });
logger.info('定时器数量:', this.cron.getListSize());
} }
async delete(id: number) { async delete(id: number) {
@ -182,7 +181,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
cron: cron, cron: cron,
job: async () => { job: async () => {
logger.info('定时任务触发:', pipelineId, trigger.id); logger.info('定时任务触发:', pipelineId, trigger.id);
await this.run(pipelineId, trigger.id); try {
await this.run(pipelineId, trigger.id);
} catch (e) {
logger.error('定时job执行失败', e);
}
}, },
}); });
logger.info('当前定时器数量:', this.cron.getListSize()); logger.info('当前定时器数量:', this.cron.getListSize());
@ -244,17 +247,16 @@ export class PipelineService extends BaseService<PipelineEntity> {
const executor = runningTasks.get(historyId); const executor = runningTasks.get(historyId);
if (executor) { if (executor) {
await executor.cancel(); await executor.cancel();
} else {
const entity = await this.historyService.info(historyId);
if (entity == null) {
return;
}
const pipeline: Pipeline = JSON.parse(entity.pipeline);
pipeline.status.status = ResultType.canceled;
pipeline.status.result = ResultType.canceled;
const runtime = new RunHistory(historyId, null, pipeline);
await this.saveHistory(runtime);
} }
const entity = await this.historyService.info(historyId);
if (entity == null) {
return;
}
const pipeline: Pipeline = JSON.parse(entity.pipeline);
pipeline.status.status = ResultType.canceled;
pipeline.status.result = ResultType.canceled;
const runtime = new RunHistory(historyId, null, pipeline);
await this.saveHistory(runtime);
} }
private getTriggerType(triggerId, pipeline) { private getTriggerType(triggerId, pipeline) {

View File

@ -1,15 +1,9 @@
import { import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
ALL,
Body,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/core';
import { CrudController } from '../../../basic/crud-controller.js'; import { CrudController } from '../../../basic/crud-controller.js';
import { SysSettingsService } from '../service/sys-settings-service.js'; import { SysSettingsService } from '../service/sys-settings-service.js';
import { SysSettingsEntity } from '../entity/sys-settings.js'; import { SysSettingsEntity } from '../entity/sys-settings.js';
import { SysPublicSettings } from '../service/models.js';
import * as _ from 'lodash-es';
/** /**
*/ */
@ -74,7 +68,9 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
// savePublicSettings // savePublicSettings
@Post('/savePublicSettings', { summary: 'sys:settings:edit' }) @Post('/savePublicSettings', { summary: 'sys:settings:edit' })
async savePublicSettings(@Body(ALL) body) { async savePublicSettings(@Body(ALL) body) {
await this.service.savePublicSettings(body); const setting = new SysPublicSettings();
_.merge(setting, body);
await this.service.savePublicSettings(setting);
return this.ok({}); return this.ok({});
} }
} }

View File

@ -1,12 +1,4 @@
import { import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput, utils } from '@certd/pipeline';
AbstractTaskPlugin,
IAccessService,
ILogger,
IsTaskPlugin,
RunStrategy,
TaskInput,
utils,
} from '@certd/pipeline';
// @ts-ignore // @ts-ignore
import { ROAClient } from '@alicloud/pop-core'; import { ROAClient } from '@alicloud/pop-core';
import { AliyunAccess } from '../../access/index.js'; import { AliyunAccess } from '../../access/index.js';
@ -122,17 +114,10 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
} }
async execute(): Promise<void> { async execute(): Promise<void> {
console.log('开始部署证书到阿里云cdn'); console.log('开始部署证书到阿里云cdn');
const { regionId, ingressClass, clusterId, isPrivateIpAddress, cert } = const { regionId, ingressClass, clusterId, isPrivateIpAddress, cert } = this;
this; const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
const access = (await this.accessService.getById(
this.accessId
)) as AliyunAccess;
const client = this.getClient(access, regionId); const client = this.getClient(access, regionId);
const kubeConfigStr = await this.getKubeConfig( const kubeConfigStr = await this.getKubeConfig(client, clusterId, isPrivateIpAddress);
client,
clusterId,
isPrivateIpAddress
);
this.logger.info('kubeconfig已成功获取'); this.logger.info('kubeconfig已成功获取');
const k8sClient = new K8sClient(kubeConfigStr); const k8sClient = new K8sClient(kubeConfigStr);
@ -224,11 +209,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
}); });
} }
async getKubeConfig( async getKubeConfig(client: any, clusterId: string, isPrivateIpAddress = false) {
client: any,
clusterId: string,
isPrivateIpAddress = false
) {
const httpMethod = 'GET'; const httpMethod = 'GET';
const uriPath = `/k8s/${clusterId}/user_config`; const uriPath = `/k8s/${clusterId}/user_config`;
const queries = { const queries = {
@ -241,14 +222,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
const requestOption = {}; const requestOption = {};
try { try {
const res = await client.request( const res = await client.request(httpMethod, uriPath, queries, body, headers, requestOption);
httpMethod,
uriPath,
queries,
body,
headers,
requestOption
);
return res.config; return res.config;
} catch (e) { } catch (e) {
console.error('请求出错:', e); console.error('请求出错:', e);

View File

@ -1,5 +1,6 @@
import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput } from '@certd/pipeline'; import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert'; import { CertInfo, CertReader } from '@certd/plugin-cert';
import { K8sClient } from '@certd/lib-k8s';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'demoTest', name: 'demoTest',
@ -87,6 +88,8 @@ export class DemoTestPlugin extends AbstractTaskPlugin {
this.logger.info('switch:', this.switch); this.logger.info('switch:', this.switch);
this.logger.info('授权id:', accessId); this.logger.info('授权id:', accessId);
//TODO 这里实现你要部署的执行方法 //TODO 这里实现你要部署的执行方法
new K8sClient('111');
} }
} }
//TODO 这里实例化插件,进行注册 //TODO 这里实例化插件,进行注册