import { AbstractTaskPlugin, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert"; import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js"; import { AliyunClient, AliyunSslClient, CasCertInfo } from "../../../plugin-lib/aliyun/lib/index.js"; @IsTaskPlugin({ name: "AliyunDeployCertToWaf", title: "阿里云-部署至阿里云WAF(cname接入)", icon: "svg:icon-aliyun", group: pluginGroups.aliyun.key, desc: "部署证书到阿里云WAF(cname接入),云资源的请选择另外一个waf插件", needPlus: false, default: { strategy: { runStrategy: RunStrategy.SkipWhenSucceed, }, }, }) export class AliyunDeployCertToWaf extends AbstractTaskPlugin { @TaskInput({ title: "域名证书", helper: "请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量", component: { name: "output-selector", from: [...CertApplyPluginNames, "uploadCertToAliyun"], }, required: true, }) cert!: CertInfo | number | CasCertInfo; @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) certDomains!: string[]; @TaskInput({ title: "WAF接入点", helper: "不会选就按默认", value: "cn-hangzhou", component: { name: "a-select", options: [ { value: "cn-hangzhou", label: "中国大陆-华东1(杭州)" }, { value: "ap-southeast-1", label: "新加坡" }, ], }, required: true, }) regionId!: 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; @TaskInput({ title: "Access授权", helper: "阿里云授权AccessKeyId、AccessKeySecret", component: { name: "access-selector", type: "aliyun", }, required: true, }) accessId!: string; @TaskInput( createRemoteSelectInputDefine({ title: "CNAME站点", helper: "请选择要部署证书的CNAME站点", action: AliyunDeployCertToWaf.prototype.onGetCnameList.name, watches: ["accessId", "regionId"], pager: true, search: true, }) ) cnameDomains!: string[]; @TaskInput({ title: "TLS版本", value: "tlsv1.2", component: { name: "a-select", options: [ { value: "tlsv1", label: "TLSv1" }, { value: "tlsv1.1", label: "TLSv1.1" }, { value: "tlsv1.2", label: "TLSv1.2" }, ], }, required: true, }) tlsVersion!: string; @TaskInput({ title: "启用TLSv3", value: true, component: { name: "a-switch", vModel: "checked", }, required: true, }) enableTLSv3!: boolean; async onInstance() {} async getWafClient(access: AliyunAccess) { const client = new AliyunClient({ logger: this.logger }); await client.init({ accessKeyId: access.accessKeyId, accessKeySecret: access.accessKeySecret, //https://wafopenapi.cn-hangzhou.aliyuncs.com endpoint: `https://wafopenapi.${this.regionId}.aliyuncs.com`, apiVersion: "2021-10-01", }); return client; } async getInstanceId(client: AliyunClient) { const params = { RegionId: "cn-hangzhou", }; const res = await client.request("DescribeInstance", params); this.logger.info("获取实例ID", res.InstanceId); return res.InstanceId; } async execute(): Promise { this.logger.info("开始部署证书到阿里云"); const access = await this.getAccess(this.accessId); let certId: any = this.cert; if (typeof this.cert === "object") { const sslClient = new AliyunSslClient({ access, logger: this.logger, endpoint: this.casEndpoint, }); const cert = this.cert as CertInfo; const casCert = this.cert as CasCertInfo; if (cert.crt) { const certIdRes = await sslClient.uploadCertificate({ name: this.buildCertName(CertReader.getMainDomain(cert.crt)), cert: cert, }); certId = certIdRes.certId as any; } else if (casCert.certId) { certId = casCert.certId; } else { throw new Error("证书格式错误" + JSON.stringify(this.cert)); } } const client = await this.getWafClient(access); const instanceId = await this.getInstanceId(client); for (const siteDomain of this.cnameDomains) { this.logger.info("开始部署", siteDomain); const params = { RegionId: this.regionId, InstanceId: instanceId, Domain: siteDomain, }; const siteDetail = await client.request("DescribeDomainDetail", params); this.logger.info("站点详情", JSON.stringify(siteDetail)); const listen = siteDetail.Listen; if (!listen) { throw new Error(`没有找到${siteDomain}的监听器`); } /** * "HttpsPorts": [ * 443 * ], * "CertId": "14738336-cn-hangzhou", */ const redirect = siteDetail.Redirect; redirect.Backends = redirect.AllBackends; listen.CertId = certId + "-" + this.regionId; if (!listen.HttpsPorts || listen.HttpsPorts.length === 0) { listen.HttpsPorts = [443]; } const updateParams = { InstanceId: instanceId, RegionId: this.regionId, Redirect: JSON.stringify(redirect), Listen: JSON.stringify(listen), Domain: siteDomain, TLSVersion: this.tlsVersion || "tlsv1.2", EnableTLSv3: this.enableTLSv3 ?? true, }; const res = await client.request("ModifyDomain", updateParams); this.logger.info("部署成功", JSON.stringify(res)); } } async onGetCnameList(data: PageSearch) { if (!this.accessId) { throw new Error("请选择Access授权"); } const access = await this.getAccess(this.accessId); const client = await this.getWafClient(access); const pager = new Pager(data); const instanceId = await this.getInstanceId(client); const params: any = { RegionId: this.regionId, InstanceId: instanceId, PageSize: pager.pageSize, PageNumber: pager.pageNo, }; if (data.searchKey) { params.Domain = data.searchKey; } const res = await client.request("DescribeDomains", params); if (!res?.Domains || res?.Domains.length === 0) { throw new Error("没有找到CNAME接入的域名站点"); } const total = res.TotalCount; const options = res.Domains.map((item: any) => { return { label: item.Domain, value: item.Domain, title: item.Domain, domain: item.Domain, }; }); const list = this.ctx.utils.options.buildGroupOptions(options, this.certDomains); // const list = [{value:"1",label:"1"},{value:"2",label:"2"}] // const total = 120 return { list, total: total, pageNo: pager.pageNo, pageSize: pager.pageSize, }; } } new AliyunDeployCertToWaf();