mirror of https://github.com/certd/certd
perf: 密钥备份
parent
28bb4856be
commit
1c6028abcf
|
@ -307,10 +307,16 @@ export class Executor {
|
|||
//更新pipeline vars
|
||||
if (Object.keys(instance._result.pipelineVars).length > 0) {
|
||||
// 判断 pipelineVars 有值时更新
|
||||
const vars = this.pipelineContext.getObj("vars");
|
||||
const vars = await this.pipelineContext.getObj("vars");
|
||||
merge(vars, instance._result.pipelineVars);
|
||||
await this.pipelineContext.setObj("vars", vars);
|
||||
}
|
||||
if (Object.keys(instance._result.pipelinePrivateVars).length > 0) {
|
||||
// 判断 pipelineVars 有值时更新
|
||||
const vars = await this.pipelineContext.getObj("privateVars");
|
||||
merge(vars, instance._result.pipelinePrivateVars);
|
||||
await this.pipelineContext.setObj("privateVars", vars);
|
||||
}
|
||||
}
|
||||
|
||||
async notification(when: NotificationWhen, error?: any) {
|
||||
|
|
|
@ -60,6 +60,7 @@ export type TaskResult = {
|
|||
clearLastStatus?: boolean;
|
||||
files?: FileItem[];
|
||||
pipelineVars: Record<string, any>;
|
||||
pipelinePrivateVars?: Record<string, any>;
|
||||
};
|
||||
export type TaskInstanceContext = {
|
||||
//流水线定义
|
||||
|
@ -97,7 +98,7 @@ export type TaskInstanceContext = {
|
|||
};
|
||||
|
||||
export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||
_result: TaskResult = { clearLastStatus: false, files: [], pipelineVars: {} };
|
||||
_result: TaskResult = { clearLastStatus: false, files: [], pipelineVars: {}, pipelinePrivateVars: {} };
|
||||
ctx!: TaskInstanceContext;
|
||||
logger!: ILogger;
|
||||
accessService!: IAccessService;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export class BaseSettings {
|
||||
static __key__: string;
|
||||
static __title__: string;
|
||||
|
@ -29,8 +31,10 @@ export class SysPrivateSettings extends BaseSettings {
|
|||
httpProxy? = '';
|
||||
|
||||
removeSecret() {
|
||||
delete this.jwtKey;
|
||||
delete this.encryptSecret;
|
||||
const clone = cloneDeep(this);
|
||||
delete clone.jwtKey;
|
||||
delete clone.encryptSecret;
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,6 +87,14 @@ export class SysSiteInfo extends BaseSettings {
|
|||
loginLogo?: string;
|
||||
}
|
||||
|
||||
export class SysSecretBackup extends BaseSettings {
|
||||
static __title__ = '密钥信息备份';
|
||||
static __key__ = 'sys.secret';
|
||||
static __access__ = 'private';
|
||||
siteId?: string;
|
||||
encryptSecret?: string;
|
||||
}
|
||||
|
||||
export class SysSiteEnv {
|
||||
agent?: {
|
||||
enabled?: boolean;
|
||||
|
|
|
@ -3,10 +3,10 @@ import { InjectEntityModel } from '@midwayjs/typeorm';
|
|||
import { Repository } from 'typeorm';
|
||||
import { SysSettingsEntity } from '../entity/sys-settings.js';
|
||||
import { CacheManager } from '@midwayjs/cache';
|
||||
import { BaseSettings, SysPrivateSettings, SysPublicSettings } from './models.js';
|
||||
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecretBackup } from './models.js';
|
||||
import * as _ from 'lodash-es';
|
||||
import { BaseService } from '../../../basic/index.js';
|
||||
import { setGlobalProxy } from '@certd/basic';
|
||||
import { logger, setGlobalProxy } from '@certd/basic';
|
||||
|
||||
/**
|
||||
* 设置
|
||||
|
@ -146,4 +146,21 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
|||
}
|
||||
await this.cache.del(`settings.${key}`);
|
||||
}
|
||||
|
||||
async backupSecret() {
|
||||
const settings = await this.getSettingByKey(SysSecretBackup.__key__);
|
||||
if (settings == null) {
|
||||
const backup = new SysSecretBackup();
|
||||
const privateSettings = await this.getPrivateSettings();
|
||||
const installInfo = await this.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||
if (installInfo.siteId == null || privateSettings.encryptSecret == null) {
|
||||
logger.error('备份密钥失败,siteId或encryptSecret为空');
|
||||
return;
|
||||
}
|
||||
backup.siteId = installInfo.siteId;
|
||||
backup.encryptSecret = privateSettings.encryptSecret;
|
||||
await this.saveSetting(backup);
|
||||
logger.info('备份密钥成功');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,6 +144,10 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
|
|||
this.cert = cert;
|
||||
|
||||
this._result.pipelineVars.certExpiresTime = dayjs(certReader.detail.notAfter).valueOf();
|
||||
if (!this._result.pipelinePrivateVars) {
|
||||
this._result.pipelinePrivateVars = {};
|
||||
}
|
||||
this._result.pipelinePrivateVars.cert = cert;
|
||||
|
||||
if (cert.pfx == null || cert.der == null) {
|
||||
try {
|
||||
|
|
|
@ -146,6 +146,13 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||
})
|
||||
googleCommonEabAccessId!: number;
|
||||
|
||||
@TaskInput({
|
||||
title: "ZeroSSL公共EAB授权",
|
||||
isSys: true,
|
||||
show: false,
|
||||
})
|
||||
zerosslCommonEabAccessId!: number;
|
||||
|
||||
@TaskInput({
|
||||
title: "EAB授权",
|
||||
component: {
|
||||
|
@ -159,7 +166,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.sslProvider === 'zerossl' || (form.sslProvider === 'google' && !form.googleCommonEabAccessId)
|
||||
return (form.sslProvider === 'zerossl' || !form.zerosslCommonEabAccessId) || (form.sslProvider === 'google' && !form.googleCommonEabAccessId)
|
||||
})
|
||||
}
|
||||
`,
|
||||
|
@ -266,7 +273,11 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
|||
}
|
||||
} else if (this.sslProvider === "zerossl") {
|
||||
if (this.eabAccessId) {
|
||||
this.logger.info("当前正在使用 zerossl EAB授权");
|
||||
eab = await this.ctx.accessService.getById(this.eabAccessId);
|
||||
} else if (this.zerosslCommonEabAccessId) {
|
||||
this.logger.info("当前正在使用 zerossl 公共EAB授权");
|
||||
eab = await this.ctx.accessService.getById(this.zerosslCommonEabAccessId);
|
||||
} else {
|
||||
this.logger.error("zerossl需要配置EAB授权");
|
||||
return;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { request } from "/src/api/service";
|
|||
|
||||
const apiPrefix = "/pi/pipeline";
|
||||
const historyApiPrefix = "/pi/history";
|
||||
const certApiPrefix = "/pi/cert";
|
||||
|
||||
export async function GetList(query: any) {
|
||||
return await request({
|
||||
|
@ -82,3 +83,19 @@ export async function GetFiles(pipelineId: number) {
|
|||
params: { pipelineId }
|
||||
});
|
||||
}
|
||||
|
||||
export type CertInfo = {
|
||||
crt: string;
|
||||
key: string;
|
||||
ic: string;
|
||||
der: string;
|
||||
pfx: string;
|
||||
};
|
||||
|
||||
export async function GetCert(pipelineId: number): Promise<CertInfo> {
|
||||
return await request({
|
||||
url: certApiPrefix + "/get",
|
||||
method: "post",
|
||||
params: { id: pipelineId }
|
||||
});
|
||||
}
|
||||
|
|
|
@ -148,6 +148,69 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
|||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||
});
|
||||
}
|
||||
|
||||
const viewCert = async (row: any) => {
|
||||
const cert = await api.GetCert(row.id);
|
||||
if (!cert) {
|
||||
notification.error({ message: "还没有产生证书,请先运行流水线" });
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.success({
|
||||
title: "查看证书",
|
||||
maskClosable: true,
|
||||
okText: "关闭",
|
||||
content: () => {
|
||||
return (
|
||||
<div class={"view-cert"}>
|
||||
<div class={"cert-txt"}>
|
||||
<h3>fullchain.pem</h3>
|
||||
<div>{cert.crt}</div>
|
||||
</div>
|
||||
<div class={"cert-txt"}>
|
||||
<h3>private.pem</h3>
|
||||
<div>{cert.key}</div>
|
||||
</div>
|
||||
<div class={"cert-txt"}>
|
||||
<h3>中间证书.pem</h3>
|
||||
<div>{cert.ic}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const downloadCert = async (row: any) => {
|
||||
const files = await api.GetFiles(row.id);
|
||||
Modal.success({
|
||||
title: "文件下载",
|
||||
maskClosable: true,
|
||||
okText: "↑↑↑ 点击上面链接下载",
|
||||
content: () => {
|
||||
const children = [];
|
||||
for (const file of files) {
|
||||
const downloadUrl = `${env.API}/pi/history/download?pipelineId=${row.id}&fileId=${file.id}`;
|
||||
children.push(
|
||||
<div>
|
||||
<div>
|
||||
<a href={downloadUrl} target={"_blank"}>
|
||||
{file.filename}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a-button type={"primary"} onClick={viewCert}>
|
||||
直接查看证书
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div class={"mt-3"}>{children}</div>;
|
||||
}
|
||||
});
|
||||
};
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
return {
|
||||
|
@ -243,27 +306,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
|||
type: "link",
|
||||
icon: "ant-design:download-outlined",
|
||||
async click({ row }) {
|
||||
const files = await api.GetFiles(row.id);
|
||||
Modal.success({
|
||||
title: "文件下载",
|
||||
maskClosable: true,
|
||||
okText: "↑↑↑ 点击上面链接下载",
|
||||
content: () => {
|
||||
const children = [];
|
||||
for (const file of files) {
|
||||
const downloadUrl = `${env.API}/pi/history/download?pipelineId=${row.id}&fileId=${file.id}`;
|
||||
children.push(
|
||||
<p>
|
||||
<a href={downloadUrl} target={"_blank"}>
|
||||
{file.filename}
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <div class={"mt-3"}>{children}</div>;
|
||||
}
|
||||
});
|
||||
downloadCert(row);
|
||||
}
|
||||
},
|
||||
remove: {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { PipelineService } from '../../modules/pipeline/service/pipeline-service.js';
|
||||
import { BaseController, Constants } from '@certd/lib-server';
|
||||
import { StorageService } from '../../modules/pipeline/service/storage-service.js';
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/pi/cert')
|
||||
export class CertController extends BaseController {
|
||||
@Inject()
|
||||
pipelineService: PipelineService;
|
||||
@Inject()
|
||||
storeService: StorageService;
|
||||
|
||||
@Post('/get', { summary: Constants.per.authOnly })
|
||||
async getCert(@Query('id') id: number) {
|
||||
const userId = this.getUserId();
|
||||
await this.pipelineService.checkUserId(id, userId);
|
||||
const privateVars = this.storeService.getPipelinePrivateVars(id);
|
||||
return this.ok(privateVars);
|
||||
}
|
||||
}
|
|
@ -80,8 +80,8 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
|||
@Post('/getSysSettings', { summary: 'sys:settings:edit' })
|
||||
async getSysSettings() {
|
||||
const publicSettings = await this.service.getPublicSettings();
|
||||
const privateSettings = await this.service.getPrivateSettings();
|
||||
privateSettings.removeSecret();
|
||||
let privateSettings = await this.service.getPrivateSettings();
|
||||
privateSettings = privateSettings.removeSecret();
|
||||
return this.ok({ public: publicSettings, private: privateSettings });
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ export class AutoInitSite {
|
|||
await this.sysSettingsService.saveSetting(privateInfo);
|
||||
}
|
||||
|
||||
await this.sysSettingsService.backupSecret();
|
||||
|
||||
await this.sysSettingsService.reloadPrivateSettings();
|
||||
|
||||
// 授权许可
|
||||
|
|
|
@ -44,6 +44,9 @@ export class StorageService extends BaseService<StorageEntity> {
|
|||
}
|
||||
|
||||
async findPipelineVars(pipelineIds: number[]) {
|
||||
if (pipelineIds == null || pipelineIds.length === 0) {
|
||||
throw new Error('pipelineIds 不能为空');
|
||||
}
|
||||
return await this.repository.find({
|
||||
where: {
|
||||
scope: 'pipeline',
|
||||
|
@ -52,4 +55,17 @@ export class StorageService extends BaseService<StorageEntity> {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getPipelinePrivateVars(pipelineId: number) {
|
||||
if (pipelineId == null) {
|
||||
throw new Error('pipelineId 不能为空');
|
||||
}
|
||||
return await this.repository.find({
|
||||
where: {
|
||||
scope: 'pipeline',
|
||||
namespace: pipelineId + '',
|
||||
key: 'privateVars',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue