diff --git a/packages/core/basic/src/utils/util.string.ts b/packages/core/basic/src/utils/util.string.ts index 591cc99d0..b3320d247 100644 --- a/packages/core/basic/src/utils/util.string.ts +++ b/packages/core/basic/src/utils/util.string.ts @@ -1,8 +1,17 @@ +import dayjs from "dayjs"; + export const stringUtils = { maxLength(str?: string, length = 100) { if (str) { - return str.length > length ? str.slice(0, length) + '...' : str; + return str.length > length ? str.slice(0, length) + "..." : str; } - return ''; + return ""; + }, + + appendTimeSuffix(str?: string) { + if (str) { + return `${str}-${dayjs().format("YYYYMMDDHHmmssSSS")}`; + } + return ""; }, }; diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts index 8c1e2415b..fec035cbd 100644 --- a/packages/core/pipeline/src/plugin/api.ts +++ b/packages/core/pipeline/src/plugin/api.ts @@ -249,10 +249,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin { abstract execute(): Promise; appendTimeSuffix(name?: string) { - if (name == null) { - name = "certd"; - } - return name + "_" + dayjs().format("YYYYMMDDHHmmssSSS"); + return utils.string.appendTimeSuffix(name); } buildCertName(domain: string, prefix = "") { @@ -297,6 +294,10 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin { getStepIdFromRefInput(ref = ".") { return ref.split(".")[1]; } + + buildDomainGroupOptions(options: any[], domains: string[]) { + return utils.options.buildGroupOptions(options, domains); + } } export type OutputVO = { diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts index 45c157db4..5bc18f23e 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-alb/index.ts @@ -305,11 +305,13 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin { }); const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)); - certId = await sslClient.uploadCert({ + const certIdRes = await sslClient.uploadCert({ name: certName, cert: this.cert }); + certId = certIdRes.certId; } + return certId; } diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-all/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-all/index.ts index f3f1e749c..885ffd55c 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-all/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-all/index.ts @@ -155,10 +155,11 @@ export class AliyunDeployCertToAll extends AbstractTaskPlugin { // let certId: any = this.cert; if (typeof this.cert === "object") { - certId = await sslClient.uploadCert({ + const certIdRes = await sslClient.uploadCert({ name: this.appendTimeSuffix("certd"), cert: this.cert, }); + certId = certIdRes.certId; } const jobId = await this.createDeployJob(sslClient, certId); diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts index a5ec16f65..72c4205c3 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts @@ -115,10 +115,11 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin { if (typeof this.cert === 'object') { // @ts-ignore const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)) - certId = await sslClient.uploadCert({ + const certIdRes = await sslClient.uploadCert({ name:certName, cert: this.cert, }); + certId = certIdRes.certId; } const client = await this.getClient(access); diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts index fbbf173af..be3c5fadd 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-esa/index.ts @@ -119,10 +119,11 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin { certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)); - certId = await sslClient.uploadCert({ + const certIdRes = await sslClient.uploadCert({ name: certName, cert: this.cert }); + certId = certIdRes.certId; this.logger.info("上传证书成功", certId, certName); } return { diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ga/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ga/index.ts new file mode 100644 index 000000000..8c621a963 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ga/index.ts @@ -0,0 +1,330 @@ +import { AbstractTaskPlugin, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { + createCertDomainGetterInputDefine, + createRemoteSelectInputDefine +} from "@certd/plugin-lib"; +import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; +import { AliyunSslClient } from "../../../plugin-lib/aliyun/lib/ssl-client.js"; + +@IsTaskPlugin({ + name: "AliyunDeployCertToGA", + title: "阿里云-部署至GA", + icon: "svg:icon-aliyun", + group: pluginGroups.aliyun.key, + desc: "部署证书到阿里云GA(全球加速),支持更新默认证书和扩展证书", + needPlus: false, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed + } + } +}) +export class AliyunDeployCertToGA extends AbstractTaskPlugin { + @TaskInput({ + title: "域名证书", + helper: "请选择证书申请任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames, 'uploadCertToAliyun'] + }, + required: true + }) + cert!: CertInfo|number; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + @TaskInput({ + title: "证书接入点", + helper: "不会选就保持默认即可", + value: "cas.aliyuncs.com", + component: { + name: "a-select", + options: [ + { value: "cas.aliyuncs.com", label: "中国大陆" }, + { value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" } + ] + }, + required: true + }) + casEndpoint!: string; + + @TaskInput({ + title: "Access授权", + helper: "阿里云授权AccessKeyId、AccessKeySecret", + component: { + name: "access-selector", + type: "aliyun" + }, + required: true + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "全球加速实例", + helper: "请选择要部署证书的全球加速实例", + action: AliyunDeployCertToGA.prototype.onGetAcceleratorList.name, + watches: ["accessId"], + multi: false, + }) + ) + acceleratorId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "监听", + helper: "请选择要部署证书的监听", + action: AliyunDeployCertToGA.prototype.onGetListenerList.name, + watches: ["accessId", "acceleratorId"] + }) + ) + listenerIds!: string[]; + + @TaskInput({ + title: "证书类型", + helper: "选择更新默认证书还是扩展证书", + value: "default", + component: { + name: "a-select", + options: [ + { value: "default", label: "默认证书" }, + { value: "additional", label: "扩展证书" } + ] + }, + required: true, + }) + certType!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "扩展证书域名", + helper: "将证书里的域名扩展绑定到监听器中", + action: AliyunDeployCertToGA.prototype.onGetAdditionalDomainList.name, + watches: ["accessId", "acceleratorId", "listenerIds", "certType"], + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return form.certType === "additional"; + }) + } + ` + }) + ) + additionalDomains!: string[]; + + async onInstance() { + } + + async getAliyunCertId(access: AliyunAccess) { + const sslClient = new AliyunSslClient({ + access, + logger: this.logger, + endpoint: this.casEndpoint + }); + return await sslClient.uploadCertOrGet(this.cert) + } + + async execute(): Promise { + this.logger.info("开始部署证书到阿里云GA"); + const access = await this.getAccess(this.accessId); + + const client = await this.getClient(access); + + const { certIdentifier } = await this.getAliyunCertId(access); + + for (const listenerId of this.listenerIds) { + if (this.certType === "default") { + // 更新默认证书 + this.logger.info(`开始更新默认证书到实例[${this.acceleratorId}]监听[${listenerId}]`); + const res = await client.doRequest({ + action: "UpdateListener", + version: "2019-11-20", + data: { + query: { + RegionId: "cn-hangzhou", + AcceleratorId: this.acceleratorId, + ListenerId: listenerId, + Certificates: [ + { Id: certIdentifier}, + ] + } + } + }); + this.logger.info(`部署默认证书到实例[${this.acceleratorId}]监听[${listenerId}]成功:${JSON.stringify(res)}`); + } else if (this.certType === "additional") { + // 处理扩展证书 + for (const domain of this.additionalDomains) { + // 先检查域名是否已存在 + this.logger.info(`开始检查域名[${domain}]是否已存在于实例[${this.acceleratorId}]监听[${listenerId}]`); + const existingCerts = await client.doRequest({ + action: "ListListenerCertificates", + version: "2019-11-20", + method: "GET", + data: { + query: { + RegionId: "cn-hangzhou", + AcceleratorId: this.acceleratorId, + ListenerId: listenerId + } + } + }); + + const domainExists = existingCerts.Certificates?.some((cert: any) => + cert.Domain === domain + ); + + if (domainExists) { + // 更新扩展证书 + this.logger.info(`域名[${domain}]已存在,开始更新扩展证书到实例[${this.acceleratorId}]监听[${listenerId}]`); + const res = await client.doRequest({ + action: "UpdateAdditionalCertificateWithListener", + version: "2019-11-20", + data: { + query: { + RegionId: "cn-hangzhou", + AcceleratorId: this.acceleratorId, + ListenerId: listenerId, + Domain: domain, + CertificateId: certIdentifier + } + } + }); + this.logger.info(`更新扩展证书到实例[${this.acceleratorId}]监听[${listenerId}]域名[${domain}]成功:${JSON.stringify(res)}`); + } else { + // 新增扩展证书绑定 + this.logger.info(`域名[${domain}]不存在,开始新增扩展证书绑定到实例[${this.acceleratorId}]监听[${listenerId}]`); + const res = await client.doRequest({ + action: "AssociateAdditionalCertificatesWithListener", + version: "2019-11-20", + data: { + query: { + RegionId: "cn-hangzhou", + AcceleratorId: this.acceleratorId, + ListenerId: listenerId, + Certificates: [{ + Id: certIdentifier, + Domain: domain + }] + } + } + }); + this.logger.info(`新增扩展证书绑定到实例[${this.acceleratorId}]监听[${listenerId}]域名[${domain}]成功:${JSON.stringify(res)}`); + } + } + } + await this.ctx.utils.sleep(3000); + } + } + + async getClient(access: AliyunAccess) { + const endpoint = `ga.cn-hangzhou.aliyuncs.com`; + return access.getClient(endpoint); + } + + async onGetAcceleratorList(data: PageSearch) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + + const pager = new Pager(data) + pager.pageSize = 50 + const access = await this.getAccess(this.accessId); + + const client = await this.getClient(access); + const res = await client.doRequest({ + action: "ListAccelerators", + version: "2019-11-20", + method: "GET", + data: { + query: { + RegionId: "cn-hangzhou", + PageNumber: pager.pageNo, + PageSize: pager.pageSize, + State: "active" + } + } + }); + + const list = res?.Accelerators; + if (!list || list.length === 0) { + throw new Error("没有找到全球加速实例,请先创建实例"); + } + + const options = list.map((item: any) => { + const label = `${item.Name} (${item.AcceleratorId})` + return { + label: label, + value: item.AcceleratorId, + }; + }); + return options; + } + + async onGetListenerList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + if (!this.acceleratorId) { + throw new Error("请先选择全球加速实例"); + } + const access = await this.getAccess(this.accessId); + + const client = await this.getClient(access); + const res = await client.doRequest({ + action: "ListListeners", + version: "2019-11-20", + method: "GET", + data: { + query: { + RegionId: "cn-hangzhou", + AcceleratorId: this.acceleratorId + } + } + }); + + const listeners = res?.Listeners; + if (!listeners || listeners.length === 0) { + throw new Error("没有找到监听,请先创建监听"); + } + + const options = listeners.map((item: any) => { + return { + label: `${item.ListenerId} (${item.Protocol}})`, + value: item.ListenerId, + }; + }); + + return options; + } + + async onGetAdditionalDomainList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + if (!this.acceleratorId) { + throw new Error("请先选择全球加速实例"); + } + if (!this.listenerIds || this.listenerIds.length === 0) { + throw new Error("请先选择监听"); + } + if (this.certType !== "additional") { + throw new Error("请选择扩展证书类型"); + } + + // 获取当前监听已绑定的证书域名 + const list = this.certDomains || []; + + const options = list.map((item: any) => { + return { + label: item, + value: item, + domain: item, + }; + }); + return options; + } +} + +new AliyunDeployCertToGA(); \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts index 9e38109d8..956e96092 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts @@ -294,10 +294,11 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin { const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)) - certId = await sslClient.uploadCert({ + const certIdRes = await sslClient.uploadCert({ name: certName, cert: this.cert, }); + certId = certIdRes.certId; } return certId; } diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts index 6469a3b1f..b5967abba 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-oss/index.ts @@ -212,10 +212,11 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin { certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)); - certId = await sslClient.uploadCert({ + const certIdRes = await sslClient.uploadCert({ name: certName, cert: this.cert }); + certId = certIdRes.certId; this.logger.info("上传证书成功", certId, certName); } return { diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts index 04b3e3486..564e3123e 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts @@ -251,10 +251,11 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin { if (typeof this.cert === 'object') { const name = this.appendTimeSuffix('certd'); - certId = await sslClient.uploadCert({ + const certIdRes = await sslClient.uploadCert({ name: name, cert: this.cert, }); + certId = certIdRes.certId; } return await sslClient.getCertInfo(certId); diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/index.ts index 201c3dc76..bed07278b 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-waf/index.ts @@ -152,10 +152,11 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin { endpoint: this.casEndpoint, }); - certId = await sslClient.uploadCert({ + const certIdRes = await sslClient.uploadCert({ name: this.buildCertName(CertReader.getMainDomain(this.cert.crt)), cert: this.cert, }); + certId = certIdRes.certId; } const client = await this.getWafClient(access); diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/index.ts index 483e97655..a1f43b4e2 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/index.ts @@ -8,6 +8,7 @@ export * from './deploy-to-nlb/index.js'; export * from './deploy-to-slb/index.js'; export * from './deploy-to-fc/index.js'; export * from './deploy-to-esa/index.js'; +export * from './deploy-to-ga/index.js'; export * from './deploy-to-vod/index.js'; export * from './deploy-to-apigateway/index.js'; export * from './deploy-to-apig/index.js'; diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts index ba3f27443..a5bf6df30 100644 --- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts @@ -104,10 +104,11 @@ export class UploadCertToAliyun extends AbstractTaskPlugin { }else{ certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)) } - this.aliyunCertId = await client.uploadCert({ + const certIdRes = await client.uploadCert({ name: certName, cert: this.cert, }); + this.aliyunCertId = certIdRes.certId as any; } } //注册插件 diff --git a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts index df57e7ab5..c3ddc5505 100644 --- a/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-lib/aliyun/lib/ssl-client.ts @@ -1,6 +1,7 @@ -import { ILogger } from "@certd/basic"; +import { ILogger, utils } from "@certd/basic"; import { AliyunAccess } from "../access/index.js"; import { AliyunClient } from "./index.js"; +import { CertInfo, CertReader } from "@certd/plugin-lib"; export type AliyunCertInfo = { crt: string; //fullchain证书 @@ -32,6 +33,11 @@ export type AliyunSslUploadCertReq = { export type CasCertInfo = { certId: number; certName: string; certIdentifier: string; notAfter: number; casRegion: string }; +export type CasCertId = { + certId: number; + certIdentifier: string; + certName: string; +} export class AliyunSslClient { opts: AliyunSslClientOpts; logger: ILogger; @@ -85,7 +91,14 @@ export class AliyunSslClient { }; } - async uploadCert(req: AliyunSslUploadCertReq) { + getCertIdentifier(certId: number | string) { + if (typeof certId === "string" && certId.indexOf("-") > 0) { + return certId; + } + return `${certId}-${this.getCasRegionFromEndpoint(this.opts.endpoint)}`; + } + + async uploadCert(req: AliyunSslUploadCertReq) : Promise { const client = await this.getClient(); const params = { Name: req.name, @@ -102,7 +115,33 @@ export class AliyunSslClient { this.checkRet(ret); this.opts.logger.info("证书上传成功:aliyunCertId=", ret.CertId); //output - return ret.CertId; + return { + certId: ret.CertId, + certName: req.name, + certIdentifier: this.getCertIdentifier(ret.CertId), + } + } + + async uploadCertOrGet(cert: CertInfo | number ) :Promise{ + if (typeof cert === "object") { + // 上传证书到阿里云 + this.logger.info(`开始上传证书`); + const certName = CertReader.buildCertName(cert); + const res = await this.uploadCert({ + name: certName, + cert: cert + }); + this.logger.info("上传证书成功", JSON.stringify(res)); + return res + } + const certId = cert as any; + let certName: any = utils.string.appendTimeSuffix(certId); + const certIdentifier = this.getCertIdentifier(certId); + return { + certId, + certIdentifier, + certName + } } async getResourceList(req: AliyunSslGetResourceListReq) {