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 968a005f..380e1115 100644 --- a/packages/plugins/plugin-lib/src/aliyun/lib/ssl-client.ts +++ b/packages/plugins/plugin-lib/src/aliyun/lib/ssl-client.ts @@ -29,6 +29,8 @@ export type AliyunSslUploadCertReq = { cert: AliyunCertInfo; }; +export type CasCertInfo = { certId: number; certName: string; certIdentifier: string }; + export class AliyunSslClient { opts: AliyunSslClientOpts; constructor(opts: AliyunSslClientOpts) { @@ -53,6 +55,22 @@ export class AliyunSslClient { return client; } + async getCertInfo(certId: number): Promise { + const client = await this.getClient(); + const params = { + CertId: certId, + }; + + const res = await client.request("GetUserCertificateDetail", params); + this.checkRet(res); + + return { + certId: certId, + certName: res.Name, + certIdentifier: res.CertIdentifier, + }; + } + async uploadCert(req: AliyunSslUploadCertReq) { const client = await this.getClient(); const params = { 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 9d80c31b..52373c06 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,22 +1,21 @@ -import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import { CertInfo } from '@certd/plugin-cert'; import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; -import { AbstractPlusTaskPlugin } from '@certd/plugin-plus'; @IsTaskPlugin({ - name: 'AliyunDeployCertToALB', - title: '阿里云-部署至阿里云ALB', + name: 'AliyunDeployCertToLB', + title: '阿里云-部署至ALB(应用负载均衡)', icon: 'ant-design:aliyun-outlined', group: pluginGroups.aliyun.key, - desc: '部署证书到阿里云ALB,仅更新监听器的默认证书', - needPlus: true, + desc: 'ALB,更新监听器的默认证书', + needPlus: false, default: { strategy: { runStrategy: RunStrategy.SkipWhenSucceed, }, }, }) -export class AliyunDeployCertToALB extends AbstractPlusTaskPlugin { +export class AliyunDeployCertToALB extends AbstractTaskPlugin { @TaskInput({ title: '域名证书', helper: '请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量', @@ -44,7 +43,7 @@ export class AliyunDeployCertToALB extends AbstractPlusTaskPlugin { @TaskInput( createRemoteSelectInputDefine({ - title: 'ALB所在地区', + title: 'LB所在地区', typeName: 'AliyunDeployCertToALB', multi: false, action: AliyunDeployCertToALB.prototype.onGetRegionList.name, @@ -56,7 +55,7 @@ export class AliyunDeployCertToALB extends AbstractPlusTaskPlugin { @TaskInput( createRemoteSelectInputDefine({ title: '负载均衡列表', - helper: '要部署证书的ALB负载均衡', + helper: '要部署证书的负载均衡ID', typeName: 'AliyunDeployCertToALB', action: AliyunDeployCertToALB.prototype.onGetLoadBalanceList.name, watches: ['regionId'], @@ -93,29 +92,31 @@ export class AliyunDeployCertToALB extends AbstractPlusTaskPlugin { async onInstance() {} - async getALBClient(access: AliyunAccess, region: string) { + async getLBClient(access: AliyunAccess, region: string) { const client = new AliyunClient({ logger: this.logger }); + + 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: '2020-06-16', + apiVersion: version, }); return client; } async execute(): Promise { - this.logger.info('开始部署证书到阿里云ALB'); + this.logger.info(`开始部署证书到阿里云(alb)`); const access = await this.accessService.getById(this.accessId); const certId = await this.getAliyunCertId(access); - const client = await this.getALBClient(access, this.regionId); + const client = await this.getLBClient(access, this.regionId); for (const listener of this.listeners) { //查询原来的证书 - - const params = { + let params: any = {}; + params = { ListenerId: listener, Certificates: [ { @@ -155,7 +156,7 @@ export class AliyunDeployCertToALB extends AbstractPlusTaskPlugin { throw new Error('请选择Access授权'); } const access = await this.accessService.getById(this.accessId); - const client = await this.getALBClient(access, 'cn-shanghai'); + const client = await this.getLBClient(access, 'cn-shanghai'); const res = await client.request('DescribeRegions', {}); this.checkRet(res); @@ -180,7 +181,7 @@ export class AliyunDeployCertToALB extends AbstractPlusTaskPlugin { throw new Error('请先选择地区'); } const access = await this.accessService.getById(this.accessId); - const client = await this.getALBClient(access, this.regionId); + const client = await this.getLBClient(access, this.regionId); const params = { MaxResults: 100, @@ -208,7 +209,7 @@ export class AliyunDeployCertToALB extends AbstractPlusTaskPlugin { throw new Error('请先选择地区'); } const access = await this.accessService.getById(this.accessId); - const client = await this.getALBClient(access, this.regionId); + const client = await this.getLBClient(access, this.regionId); const params: any = { MaxResults: 100, @@ -223,7 +224,7 @@ export class AliyunDeployCertToALB extends AbstractPlusTaskPlugin { } return res.Listeners.map((item: any) => { - const label = `${item.ListenerId}<${item.ListenerDescription}}:${item.LoadBalancerId}>`; + const label = `${item.ListenerId}<${item.ListenerDescription}@${item.LoadBalancerId}>`; return { label: label, value: item.ListenerId, 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 new file mode 100644 index 00000000..fef573a6 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-nlb/index.ts @@ -0,0 +1,239 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; +import { CertInfo } from '@certd/plugin-cert'; +import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; + +@IsTaskPlugin({ + name: 'AliyunDeployCertToLB', + title: '阿里云-部署至NLB(网络负载均衡)', + icon: 'ant-design:aliyun-outlined', + group: pluginGroups.aliyun.key, + desc: 'NLB,网络负载均衡,更新监听器的默认证书', + needPlus: false, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class AliyunDeployCertToNLB extends AbstractTaskPlugin { + @TaskInput({ + title: '域名证书', + helper: '请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量', + component: { + name: 'output-selector', + from: ['CertApply', 'CertApplyLego', 'uploadCertToAliyun'], + }, + required: true, + }) + cert!: CertInfo | number; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + @TaskInput({ + title: 'Access授权', + helper: '阿里云授权AccessKeyId、AccessKeySecret', + component: { + name: 'access-selector', + type: 'aliyun', + }, + required: true, + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: 'LB所在地区', + typeName: 'AliyunDeployCertToNLB', + multi: false, + action: AliyunDeployCertToNLB.prototype.onGetRegionList.name, + watches: ['accessId'], + }) + ) + regionId: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: '负载均衡列表', + helper: '要部署证书的负载均衡ID', + typeName: 'AliyunDeployCertToNLB', + action: AliyunDeployCertToNLB.prototype.onGetLoadBalanceList.name, + watches: ['regionId'], + }) + ) + loadBalancers!: string[]; + + @TaskInput( + createRemoteSelectInputDefine({ + title: '监听器列表', + helper: '要部署证书的监听器列表', + typeName: 'AliyunDeployCertToNLB', + action: AliyunDeployCertToNLB.prototype.onGetListenerList.name, + 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() {} + + async getLBClient(access: AliyunAccess, region: string) { + const client = new AliyunClient({ logger: this.logger }); + + const version = '2022-04-30'; + await client.init({ + accessKeyId: access.accessKeyId, + accessKeySecret: access.accessKeySecret, + //https://wafopenapi.cn-hangzhou.aliyuncs.com + endpoint: `https://nlb.${region}.aliyuncs.com`, + apiVersion: version, + }); + return client; + } + + async execute(): Promise { + this.logger.info(`开始部署证书到阿里云(nlb)`); + const access = await this.accessService.getById(this.accessId); + const certId = await this.getAliyunCertId(access); + + const client = await this.getLBClient(access, this.regionId); + + for (const listener of this.listeners) { + //查询原来的证书 + const params: any = { + RegionId: this.regionId, + ListenerId: listener, + CertificateIds: [certId], + }; + + const res = await client.request('UpdateListenerAttribute', params); + this.checkRet(res); + this.logger.info(`部署${listener}监听器证书成功`, JSON.stringify(res)); + + //删除旧证书关联 + } + this.logger.info('执行完成'); + } + + async getAliyunCertId(access: AliyunAccess) { + let certId: any = this.cert; + if (typeof this.cert === 'object') { + const sslClient = new AliyunSslClient({ + access, + logger: this.logger, + endpoint: this.casEndpoint, + }); + + certId = await sslClient.uploadCert({ + name: this.appendTimeSuffix('certd'), + cert: this.cert, + }); + } + return certId; + } + + async onGetRegionList(data: any) { + if (!this.accessId) { + throw new Error('请选择Access授权'); + } + const access = await this.accessService.getById(this.accessId); + const client = await this.getLBClient(access, 'cn-shanghai'); + + const res = await client.request('DescribeRegions', {}); + this.checkRet(res); + if (!res?.Regions || res?.Regions.length === 0) { + throw new Error('没有找到Regions列表'); + } + + return res.Regions.map((item: any) => { + return { + label: item.LocalName, + value: item.RegionId, + endpoint: item.RegionEndpoint, + }; + }); + } + + async onGetLoadBalanceList(data: any) { + if (!this.accessId) { + throw new Error('请先选择Access授权'); + } + if (!this.regionId) { + throw new Error('请先选择地区'); + } + const access = await this.accessService.getById(this.accessId); + const client = await this.getLBClient(access, this.regionId); + + const params = { + MaxResults: 100, + }; + const res = await client.request('ListLoadBalancers', params); + this.checkRet(res); + if (!res?.LoadBalancers || res?.LoadBalancers.length === 0) { + throw new Error('没有找到LoadBalancers'); + } + + return res.LoadBalancers.map((item: any) => { + const label = `${item.LoadBalancerId}<${item.LoadBalancerName}}>`; + return { + label: label, + value: item.LoadBalancerId, + }; + }); + } + + async onGetListenerList(data: any) { + if (!this.accessId) { + throw new Error('请先选择Access授权'); + } + if (!this.regionId) { + throw new Error('请先选择地区'); + } + const access = await this.accessService.getById(this.accessId); + const client = await this.getLBClient(access, this.regionId); + + const params: any = { + MaxResults: 100, + }; + if (this.loadBalancers && this.loadBalancers.length > 0) { + params.LoadBalancerIds = this.loadBalancers; + } + const res = await client.request('ListListeners', params); + this.checkRet(res); + if (!res?.Listeners || res?.Listeners.length === 0) { + throw new Error('没有找到TCPSSL监听器'); + } + + return res.Listeners.map((item: any) => { + const label = `${item.ListenerId}<${item.ListenerDescription}@${item.LoadBalancerId}>`; + return { + label: label, + value: item.ListenerId, + lbid: item.LoadBalancerId, + }; + }); + } + + checkRet(ret: any) { + if (ret.Code != null) { + throw new Error(ret.Message); + } + } +} + +new AliyunDeployCertToNLB(); 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 new file mode 100644 index 00000000..902ba32b --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-slb/index.ts @@ -0,0 +1,258 @@ +import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; +import { CertInfo } from '@certd/plugin-cert'; +import { AliyunAccess, AliyunClient, AliyunSslClient, CasCertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib'; + +@IsTaskPlugin({ + name: 'AliyunDeployCertToSLB', + title: '阿里云-部署至SLB(传统负载均衡)', + icon: 'ant-design:aliyun-outlined', + group: pluginGroups.aliyun.key, + desc: '部署证书到阿里云SLB(传统负载均衡)', + needPlus: false, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, +}) +export class AliyunDeployCertToSLB extends AbstractTaskPlugin { + @TaskInput({ + title: '域名证书', + helper: '请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量', + component: { + name: 'output-selector', + from: ['CertApply', 'CertApplyLego', 'uploadCertToAliyun'], + }, + required: true, + }) + cert!: CertInfo | number; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + @TaskInput({ + title: 'Access授权', + helper: '阿里云授权AccessKeyId、AccessKeySecret', + component: { + name: 'access-selector', + type: 'aliyun', + }, + required: true, + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: 'LB所在地区', + typeName: 'AliyunDeployCertToSLB', + multi: false, + action: AliyunDeployCertToSLB.prototype.onGetRegionList.name, + watches: ['accessId'], + }) + ) + regionId: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: '负载均衡列表', + helper: '要部署证书的负载均衡ID', + typeName: 'AliyunDeployCertToSLB', + action: AliyunDeployCertToSLB.prototype.onGetLoadBalanceList.name, + watches: ['regionId'], + }) + ) + loadBalancers!: string[]; + + @TaskInput( + createRemoteSelectInputDefine({ + title: '监听器列表', + helper: '要部署证书的监听器列表', + typeName: 'AliyunDeployCertToSLB', + action: AliyunDeployCertToSLB.prototype.onGetListenerList.name, + 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() {} + + async getLBClient(access: AliyunAccess, region: string) { + const client = new AliyunClient({ logger: this.logger }); + const version = '2014-05-15'; + await client.init({ + accessKeyId: access.accessKeyId, + accessKeySecret: access.accessKeySecret, + //https://wafopenapi.cn-hangzhou.aliyuncs.com + endpoint: `https://slb.${region}.aliyuncs.com`, + apiVersion: version, + }); + return client; + } + + async execute(): Promise { + this.logger.info(`开始部署证书到阿里云(slb)`); + const access = await this.accessService.getById(this.accessId); + + const client = await this.getLBClient(access, this.regionId); + const aliyunCert = await this.getAliyunCertId(access); + const slbServerCertId = await this.uploadServerCert(client, aliyunCert); + for (const listener of this.listeners) { + const arr = listener.split('_'); + const loadBalanceId = arr[0]; + const port = arr[2]; + const params = { + RegionId: this.regionId, + LoadBalancerId: loadBalanceId, + ListenerPort: parseInt(port), + ServerCertificateId: slbServerCertId, + }; + + const res = await client.request('SetLoadBalancerHTTPSListenerAttribute', params); + this.checkRet(res); + this.logger.info(`部署${listener}监听器证书成功`, JSON.stringify(res)); + } + this.logger.info('执行完成'); + } + + async uploadServerCert(client: any, aliyunCert: CasCertInfo) { + const params = { + RegionId: this.regionId, + AliCloudCertificateId: aliyunCert.certId, + AliCloudCertificateName: aliyunCert.certName, + }; + + const res = await client.request('UploadServerCertificate', params); + this.checkRet(res); + this.logger.info('SLBServerCertificate创建成功', res.ServerCertificateId); + return res.ServerCertificateId; + } + + async getAliyunCertId(access: AliyunAccess) { + let certId: any = this.cert; + + const sslClient = new AliyunSslClient({ + access, + logger: this.logger, + endpoint: this.casEndpoint, + }); + + if (typeof this.cert === 'object') { + const name = this.appendTimeSuffix('certd'); + certId = await sslClient.uploadCert({ + name: name, + cert: this.cert, + }); + } + + return await sslClient.getCertInfo(certId); + } + + async onGetRegionList(data: any) { + if (!this.accessId) { + throw new Error('请选择Access授权'); + } + const access = await this.accessService.getById(this.accessId); + const client = await this.getLBClient(access, 'cn-shanghai'); + + const res = await client.request('DescribeRegions', {}); + this.checkRet(res); + if (!res?.Regions?.Region || res?.Regions?.Region.length === 0) { + throw new Error('没有找到Regions列表'); + } + + return res.Regions.Region.map((item: any) => { + return { + label: item.LocalName, + value: item.RegionId, + endpoint: item.RegionEndpoint, + }; + }); + } + + async onGetLoadBalanceList(data: any) { + if (!this.accessId) { + throw new Error('请先选择Access授权'); + } + if (!this.regionId) { + throw new Error('请先选择地区'); + } + const access = await this.accessService.getById(this.accessId); + const client = await this.getLBClient(access, this.regionId); + + const params = { + RegionId: this.regionId, + MaxResults: 100, + }; + const res = await client.request('DescribeLoadBalancers', params); + this.checkRet(res); + if (!res?.LoadBalancers?.LoadBalancer || res?.LoadBalancers.LoadBalancer.length === 0) { + throw new Error('没有找到LoadBalancers'); + } + + return res.LoadBalancers.LoadBalancer.map((item: any) => { + const label = `${item.LoadBalancerId}<${item.LoadBalancerName}}>`; + return { + label: label, + value: item.LoadBalancerId, + }; + }); + } + + async onGetListenerList(data: any) { + if (!this.accessId) { + throw new Error('请先选择Access授权'); + } + if (!this.regionId) { + throw new Error('请先选择地区'); + } + const access = await this.accessService.getById(this.accessId); + const client = await this.getLBClient(access, this.regionId); + + const params: any = { + MaxResults: 100, + RegionId: this.regionId, + ListenerProtocol: 'HTTPS', + }; + if (this.loadBalancers && this.loadBalancers.length > 0) { + params.LoadBalancerId = this.loadBalancers; + } + const res = await client.request('DescribeLoadBalancerListeners', params); + this.checkRet(res); + if (!res?.Listeners || res?.Listeners.length === 0) { + throw new Error('没有找到HTTPS监听器'); + } + + return res.Listeners.map((item: any) => { + const value = `${item.LoadBalancerId}_${item.ListenerProtocol}_${item.ListenerPort}`; + const label = `${value}<${item.Description}>`; + return { + label: label, + value: value, + }; + }); + } + + checkRet(ret: any) { + if (ret.Code != null) { + throw new Error(ret.Message); + } + } +} + +new AliyunDeployCertToSLB();