perf: 密钥备份

pull/213/head
xiaojunnuo 2024-10-15 12:59:40 +08:00
parent 28bb4856be
commit 1c6028abcf
12 changed files with 180 additions and 30 deletions

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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('备份密钥成功');
}
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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 }
});
}

View File

@ -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: {

View File

@ -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);
}
}

View File

@ -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 });
}

View File

@ -43,6 +43,8 @@ export class AutoInitSite {
await this.sysSettingsService.saveSetting(privateInfo);
}
await this.sysSettingsService.backupSecret();
await this.sysSettingsService.reloadPrivateSettings();
// 授权许可

View File

@ -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',
},
});
}
}