From 2a19b61b7a78620c06396c2cc37cc77d738b6d12 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sat, 7 Jun 2025 00:15:16 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20aliyun=20alb=E6=94=AF=E6=8C=81=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=89=A9=E5=B1=95=E8=AF=81=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/plugins/plugin-lib/package.json | 1 + .../src/aliyun/access/aliyun-access.ts | 6 +- .../plugin-lib/src/aliyun/lib/ssl-client.ts | 7 +- .../monitor/service/site-info-service.ts | 2 +- .../plugin/deploy-to-alb/index.ts | 303 +++++++++++++----- pnpm-lock.yaml | 3 + 6 files changed, 240 insertions(+), 82 deletions(-) diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json index 5d041900..2a0ac630 100644 --- a/packages/plugins/plugin-lib/package.json +++ b/packages/plugins/plugin-lib/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@alicloud/openapi-client": "^0.4.14", + "@alicloud/openapi-util": "^0.3.2", "@alicloud/pop-core": "^1.7.10", "@alicloud/tea-util": "^1.4.10", "@aws-sdk/client-s3": "^3.787.0", diff --git a/packages/plugins/plugin-lib/src/aliyun/access/aliyun-access.ts b/packages/plugins/plugin-lib/src/aliyun/access/aliyun-access.ts index 6b0ce7ca..364e9f32 100644 --- a/packages/plugins/plugin-lib/src/aliyun/access/aliyun-access.ts +++ b/packages/plugins/plugin-lib/src/aliyun/access/aliyun-access.ts @@ -54,7 +54,7 @@ export class AliyunClientV2 { const $OpenApi = await import("@alicloud/openapi-client"); const $Util = await import("@alicloud/tea-util"); - + const OpenApiUtil = await import("@alicloud/openapi-util"); const params = new $OpenApi.Params({ // 接口名称 action: req.action, @@ -74,6 +74,10 @@ export class AliyunClientV2 { bodyType: "json", }); + if (req.data?.query) { + //@ts-ignore + req.data.query = OpenApiUtil.default.default.query(req.data.query); + } const runtime = new $Util.RuntimeOptions({}); const request = new $OpenApi.OpenApiRequest(req.data); // 复制代码运行请自行打印 API 的返回值 diff --git a/packages/plugins/plugin-lib/src/aliyun/lib/ssl-client.ts b/packages/plugins/plugin-lib/src/aliyun/lib/ssl-client.ts index acffc414..c7208fd1 100644 --- a/packages/plugins/plugin-lib/src/aliyun/lib/ssl-client.ts +++ b/packages/plugins/plugin-lib/src/aliyun/lib/ssl-client.ts @@ -29,7 +29,7 @@ export type AliyunSslUploadCertReq = { cert: AliyunCertInfo; }; -export type CasCertInfo = { certId: number; certName: string; certIdentifier: string }; +export type CasCertInfo = { certId: number; certName: string; certIdentifier: string; notAfter: number }; export class AliyunSslClient { opts: AliyunSslClientOpts; @@ -68,6 +68,7 @@ export class AliyunSslClient { certId: certId, certName: res.Name, certIdentifier: res.CertIdentifier, + notAfter: res.NotAfter, }; } @@ -148,4 +149,8 @@ export class AliyunSslClient { this.checkRet(res); return res; } + + async deleteCert(certId: any) { + await this.doRequest("DeleteUserCertificate", { CertId: certId }, { method: "POST" }); + } } diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts index d0ed98c8..c6700421 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts @@ -334,7 +334,7 @@ export class SiteInfoService extends BaseService { //设置了cron,跳过公共检查 continue; } - retryTimes = setting.retryTimes + retryTimes = setting.retryTimes??retryTimes } this.doCheck(site,true,retryTimes).catch(e => { logger.error(`检查站点证书失败,${site.domain}`, e.message); 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 2e1cc40b..b4f536f7 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 @@ -1,29 +1,36 @@ -import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; -import { CertInfo ,CertApplyPluginNames, CertReader} from '@certd/plugin-cert'; -import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; +import { + AliyunAccess, + AliyunClient, + AliyunClientV2, + AliyunSslClient, + createCertDomainGetterInputDefine, + createRemoteSelectInputDefine +} from "@certd/plugin-lib"; @IsTaskPlugin({ - name: 'AliyunDeployCertToALB', - title: '阿里云-部署至ALB(应用负载均衡)', - icon: 'svg:icon-aliyun', + name: "AliyunDeployCertToALB", + title: "阿里云-部署至ALB(应用负载均衡)", + icon: "svg:icon-aliyun", group: pluginGroups.aliyun.key, - desc: 'ALB,更新监听器的默认证书', + desc: "ALB,更新监听器的默认证书", needPlus: false, default: { strategy: { - runStrategy: RunStrategy.SkipWhenSucceed, - }, - }, + runStrategy: RunStrategy.SkipWhenSucceed + } + } }) export class AliyunDeployCertToALB extends AbstractTaskPlugin { @TaskInput({ - title: '域名证书', - helper: '请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量', + title: "域名证书", + helper: "请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量", component: { - name: 'output-selector', - from: [...CertApplyPluginNames, 'uploadCertToAliyun'], + name: "output-selector", + from: [...CertApplyPluginNames, "uploadCertToAliyun"] }, - required: true, + required: true }) cert!: CertInfo | number; @@ -31,122 +38,257 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin { certDomains!: string[]; @TaskInput({ - title: 'Access授权', - helper: '阿里云授权AccessKeyId、AccessKeySecret', + title: "证书接入点", + helper: "不会选就保持默认即可", + value: "cas.aliyuncs.com", component: { - name: 'access-selector', - type: 'aliyun', + name: "a-select", + options: [ + { value: "cas.aliyuncs.com", label: "中国大陆" }, + { value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" }, + { value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" } + ] }, - required: true, + required: true + }) + casEndpoint!: string; + + @TaskInput({ + title: "Access授权", + helper: "阿里云授权AccessKeyId、AccessKeySecret", + component: { + name: "access-selector", + type: "aliyun" + }, + required: true }) accessId!: string; @TaskInput( createRemoteSelectInputDefine({ - title: 'ALB所在地区', - typeName: 'AliyunDeployCertToALB', + title: "ALB所在地区", + typeName: "AliyunDeployCertToALB", multi: false, action: AliyunDeployCertToALB.prototype.onGetRegionList.name, - watches: ['accessId'], + watches: ["accessId"] }) ) regionId: string; @TaskInput( createRemoteSelectInputDefine({ - title: '负载均衡列表', - helper: '要部署证书的负载均衡ID', - typeName: 'AliyunDeployCertToALB', + title: "负载均衡列表", + helper: "要部署证书的负载均衡ID", + typeName: "AliyunDeployCertToALB", action: AliyunDeployCertToALB.prototype.onGetLoadBalanceList.name, - watches: ['regionId'], + watches: ["regionId"] }) ) loadBalancers!: string[]; @TaskInput( createRemoteSelectInputDefine({ - title: '监听器列表', - helper: '要部署证书的监听器列表', - typeName: 'AliyunDeployCertToALB', + title: "监听器列表", + helper: "要部署证书的监听器列表", + typeName: "AliyunDeployCertToALB", action: AliyunDeployCertToALB.prototype.onGetListenerList.name, - watches: ['loadBalancers'], + watches: ["loadBalancers"] }) ) listeners!: 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: '新加坡' }, - { value: 'cas.eu-central-1.aliyuncs.com', label: '德国(法兰克福)' }, - ], - }, - required: true, - }) - casEndpoint!: string; - async onInstance() {} + @TaskInput({ + title: "部署证书类型", + value: "default", + component: { + name: "a-select", + vModel: "value", + options: [ + { + label: "默认证书", + value: "default" + }, + { + label: "扩展证书", + value: "extension" + } + ] + } + } + ) + deployType!: string; + + + async onInstance() { + } async getLBClient(access: AliyunAccess, region: string) { const client = new AliyunClient({ logger: this.logger }); - const version = '2020-06-16'; + const version = "2020-06-16"; await client.init({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, //https://wafopenapi.cn-hangzhou.aliyuncs.com endpoint: `https://alb.${region}.aliyuncs.com`, - apiVersion: version, + apiVersion: version }); return client; } + getALBClientV2(access: AliyunAccess) { + return access.getClient(`alb.${this.regionId}.aliyuncs.com`); + } + async execute(): Promise { this.logger.info(`开始部署证书到阿里云(alb)`); const access = await this.getAccess(this.accessId); const certId = await this.getAliyunCertId(access); - const client = await this.getLBClient(access, this.regionId); + if (this.deployType === "extension") { + //部署扩展证书 + const client = this.getALBClientV2(access); + await this.deployExtensionCert(client, certId); + } else { + const client = await this.getLBClient(access, this.regionId); + await this.deployDefaultCert(certId, client); + } + + + this.logger.info("执行完成"); + } + + private async deployDefaultCert(certId: any, client: AliyunClient) { for (const listener of this.listeners) { - //查询原来的证书 let params: any = {}; params = { ListenerId: listener, Certificates: [ { - CertificateId: certId, - }, - ], + CertificateId: certId + } + ] }; - const res = await client.request('UpdateListenerAttribute', params); + const res = await client.request("UpdateListenerAttribute", params); this.checkRet(res); this.logger.info(`部署${listener}监听器证书成功`, JSON.stringify(res)); - - //删除旧证书关联 } - this.logger.info('执行完成'); + } + + async deployExtensionCert(client: AliyunClientV2, certId: any) { + for (const listenerId of this.listeners) { + this.logger.info(`开始部署监听器${listenerId}的扩展证书`); + await client.doRequest({ + // 接口名称 + action: "AssociateAdditionalCertificatesWithListener", + // 接口版本 + version: "2020-06-16", + data: { + query: { + ListenerId: listenerId, + Certificates: [ + { + CertificateId: certId + } + ] + } + } + }); + + this.logger.info(`部署监听器${listenerId}的扩展证书成功`); + } + + await this.ctx.utils.sleep(10000) + + for (const listener of this.listeners) { + await this.clearInvalidCert(client, listener); + } + } + + async clearInvalidCert(client: AliyunClientV2, listener: string) { + const req = { + // 接口名称 + action: "ListListenerCertificates", + // 接口版本 + version: "2020-06-16", + data: { + query: { + ListenerId: listener + } + } + }; + const res = await client.doRequest(req); + const list = res.Certificates; + if (list.length === 0) { + this.logger.info(`监听器${listener}没有绑定证书`); + return + } + + const sslClient = new AliyunSslClient({ + access: client.access, + logger: this.logger, + endpoint: this.casEndpoint + }); + + + const certIds = []; + for (const item of list) { + if (item.Status !== "Associated") { + continue; + } + if (item.IsDefault) { + continue; + } + certIds.push( parseInt(item.CertificateId)); + } + //检查是否过期,过期则删除 + const invalidCertIds = []; + for (const certId of certIds) { + const res = await sslClient.getCertInfo(certId); + if (res.notAfter < new Date().getTime()) { + invalidCertIds.push(certId); + } + } + if (invalidCertIds.length === 0) { + this.logger.info(`监听器${listener}没有过期的证书`); + return + } + this.logger.info(`开始解绑过期的证书:${invalidCertIds}`); + await client.doRequest({ + // 接口名称 + action: "DissociateAdditionalCertificatesFromListener", + // 接口版本 + version: "2020-06-16", + data: { + query: { + ListenerId: listener, + Certificates: invalidCertIds.map((item) => { + return { + CertificateId: item + } + }) + } + } + }); + this.logger.info(`解绑过期证书成功`); } async getAliyunCertId(access: AliyunAccess) { let certId: any = this.cert; - if (typeof this.cert === 'object') { + if (typeof this.cert === "object") { const sslClient = new AliyunSslClient({ access, logger: this.logger, - endpoint: this.casEndpoint, + endpoint: this.casEndpoint }); - const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)) + const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt)); certId = await sslClient.uploadCert({ name: certName, - cert: this.cert, + cert: this.cert }); } return certId; @@ -154,74 +296,74 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin { async onGetRegionList(data: any) { if (!this.accessId) { - throw new Error('请选择Access授权'); + throw new Error("请选择Access授权"); } const access = await this.getAccess(this.accessId); - const client = await this.getLBClient(access, 'cn-shanghai'); + const client = await this.getLBClient(access, "cn-shanghai"); - const res = await client.request('DescribeRegions', {}); + const res = await client.request("DescribeRegions", {}); this.checkRet(res); if (!res?.Regions || res?.Regions.length === 0) { - throw new Error('没有找到Regions列表'); + throw new Error("没有找到Regions列表"); } return res.Regions.map((item: any) => { return { label: item.LocalName, value: item.RegionId, - endpoint: item.RegionEndpoint, + endpoint: item.RegionEndpoint }; }); } async onGetLoadBalanceList(data: any) { if (!this.accessId) { - throw new Error('请先选择Access授权'); + throw new Error("请先选择Access授权"); } if (!this.regionId) { - throw new Error('请先选择地区'); + throw new Error("请先选择地区"); } const access = await this.getAccess(this.accessId); const client = await this.getLBClient(access, this.regionId); const params = { - MaxResults: 100, + MaxResults: 100 }; - const res = await client.request('ListLoadBalancers', params); + const res = await client.request("ListLoadBalancers", params); this.checkRet(res); if (!res?.LoadBalancers || res?.LoadBalancers.length === 0) { - throw new Error('没有找到LoadBalancers'); + throw new Error("没有找到LoadBalancers"); } return res.LoadBalancers.map((item: any) => { const label = `${item.LoadBalancerId}<${item.LoadBalancerName}}>`; return { label: label, - value: item.LoadBalancerId, + value: item.LoadBalancerId }; }); } async onGetListenerList(data: any) { if (!this.accessId) { - throw new Error('请先选择Access授权'); + throw new Error("请先选择Access授权"); } if (!this.regionId) { - throw new Error('请先选择地区'); + throw new Error("请先选择地区"); } const access = await this.getAccess(this.accessId); const client = await this.getLBClient(access, this.regionId); const params: any = { - MaxResults: 100, + MaxResults: 100 }; if (this.loadBalancers && this.loadBalancers.length > 0) { params.LoadBalancerIds = this.loadBalancers; } - const res = await client.request('ListListeners', params); + const res = await client.request("ListListeners", params); this.checkRet(res); if (!res?.Listeners || res?.Listeners.length === 0) { - throw new Error('没有找到HTTPS监听器'); + throw new Error("没有找到HTTPS监听器"); } return res.Listeners.map((item: any) => { @@ -229,16 +371,19 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin { return { label: label, value: item.ListenerId, - lbid: item.LoadBalancerId, + lbid: item.LoadBalancerId }; }); } + checkRet(ret: any) { if (ret.Code != null) { throw new Error(ret.Message); } } + + } new AliyunDeployCertToALB(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdb5ca9a..2c8f065b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -685,6 +685,9 @@ importers: '@alicloud/openapi-client': specifier: ^0.4.14 version: 0.4.14 + '@alicloud/openapi-util': + specifier: ^0.3.2 + version: 0.3.2 '@alicloud/pop-core': specifier: ^1.7.10 version: 1.8.0