2025-05-22 23:21:32 +08:00
|
|
|
|
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
|
|
|
|
|
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
|
|
|
|
|
|
import {
|
|
|
|
|
|
AliyunAccess, AliyunClientV2,
|
|
|
|
|
|
AliyunSslClient,
|
|
|
|
|
|
createCertDomainGetterInputDefine,
|
|
|
|
|
|
createRemoteSelectInputDefine
|
|
|
|
|
|
} from "@certd/plugin-lib";
|
|
|
|
|
|
|
|
|
|
|
|
@IsTaskPlugin({
|
|
|
|
|
|
name: "AliyunDeployCertToESA",
|
|
|
|
|
|
title: "阿里云-部署至ESA",
|
|
|
|
|
|
icon: "svg:icon-aliyun",
|
|
|
|
|
|
group: pluginGroups.aliyun.key,
|
|
|
|
|
|
desc: "部署证书到阿里云ESA(边缘安全加速)",
|
|
|
|
|
|
needPlus: false,
|
|
|
|
|
|
default: {
|
|
|
|
|
|
strategy: {
|
|
|
|
|
|
runStrategy: RunStrategy.SkipWhenSucceed
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
|
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "域名证书",
|
|
|
|
|
|
helper: "请选择证书申请任务输出的域名证书",
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "output-selector",
|
2025-07-23 15:45:40 +08:00
|
|
|
|
from: [...CertApplyPluginNames, 'uploadCertToAliyun']
|
2025-05-22 23:21:32 +08:00
|
|
|
|
},
|
|
|
|
|
|
required: true
|
|
|
|
|
|
})
|
|
|
|
|
|
cert!: CertInfo;
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
|
|
|
|
|
certDomains!: string[];
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "大区",
|
|
|
|
|
|
value: "cn-hangzhou",
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-auto-complete",
|
|
|
|
|
|
vModel: "value",
|
|
|
|
|
|
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: "中国大陆" },
|
2025-07-23 17:55:08 +08:00
|
|
|
|
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" }
|
|
|
|
|
|
// { value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" }
|
2025-05-22 23:21:32 +08:00
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
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: AliyunDeployCertToESA.prototype.onGetSiteList.name,
|
|
|
|
|
|
watches: ["accessId", "regionId"]
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
siteIds!: string[];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async onInstance() {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getAliyunCertId(access: AliyunAccess) {
|
|
|
|
|
|
let certId: any = this.cert;
|
|
|
|
|
|
let certName: any = this.appendTimeSuffix("certd");
|
|
|
|
|
|
if (typeof this.cert === "object") {
|
|
|
|
|
|
const sslClient = new AliyunSslClient({
|
|
|
|
|
|
access,
|
|
|
|
|
|
logger: this.logger,
|
|
|
|
|
|
endpoint: this.casEndpoint
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt));
|
|
|
|
|
|
|
|
|
|
|
|
certId = await sslClient.uploadCert({
|
|
|
|
|
|
name: certName,
|
|
|
|
|
|
cert: this.cert
|
|
|
|
|
|
});
|
|
|
|
|
|
this.logger.info("上传证书成功", certId, certName);
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
certId,
|
|
|
|
|
|
certName
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async execute(): Promise<void> {
|
|
|
|
|
|
this.logger.info("开始部署证书到阿里云");
|
|
|
|
|
|
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
|
|
|
|
|
|
|
|
|
|
|
const client = await this.getClient(access);
|
|
|
|
|
|
|
|
|
|
|
|
const { certId, certName } = await this.getAliyunCertId(access);
|
|
|
|
|
|
|
|
|
|
|
|
for (const siteId of this.siteIds) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await client.doRequest({
|
|
|
|
|
|
// 接口名称
|
|
|
|
|
|
action: "SetCertificate",
|
|
|
|
|
|
// 接口版本
|
|
|
|
|
|
version: "2024-09-10",
|
|
|
|
|
|
data: {
|
2025-05-28 16:06:38 +08:00
|
|
|
|
body:{
|
|
|
|
|
|
SiteId: siteId,
|
|
|
|
|
|
CasId: certId,
|
|
|
|
|
|
Type: "cas",
|
|
|
|
|
|
Name: certName
|
|
|
|
|
|
}
|
2025-05-22 23:21:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
this.logger.info(`部署站点[${siteId}]证书成功:${JSON.stringify(res)}`);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if (e.message.includes("Certificate.Duplicated")) {
|
|
|
|
|
|
this.logger.info(`站点[${siteId}]证书已存在,无需重复部署`);
|
|
|
|
|
|
}else{
|
|
|
|
|
|
throw e;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try{
|
|
|
|
|
|
await this.clearSiteCert(client,siteId);
|
|
|
|
|
|
}catch (e) {
|
|
|
|
|
|
this.logger.error("清理站点[${siteId}]证书失败",e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getClient(access: AliyunAccess) {
|
|
|
|
|
|
const endpoint = `esa.${this.regionId}.aliyuncs.com`;
|
|
|
|
|
|
return access.getClient(endpoint);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async onGetSiteList(data: any) {
|
|
|
|
|
|
if (!this.accessId) {
|
|
|
|
|
|
throw new Error("请选择Access授权");
|
|
|
|
|
|
}
|
|
|
|
|
|
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
|
|
|
|
|
|
|
|
|
|
|
const client = await this.getClient(access);
|
|
|
|
|
|
const res = await client.doRequest({
|
|
|
|
|
|
action: "ListSites",
|
|
|
|
|
|
version: "2024-09-10",
|
|
|
|
|
|
method: "GET",
|
|
|
|
|
|
data: {}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const list = res?.Sites;
|
|
|
|
|
|
if (!list || list.length === 0) {
|
|
|
|
|
|
throw new Error("没有找到站点,请先创建站点");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const options = list.map((item: any) => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
label: item.SiteName,
|
|
|
|
|
|
value: item.SiteId,
|
|
|
|
|
|
domain: item.SiteName
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async clearSiteCert(client: AliyunClientV2, siteId: string) {
|
|
|
|
|
|
this.logger.info(`开始清理站点[${siteId}]过期证书`);
|
|
|
|
|
|
const certListRes = await client.doRequest({
|
|
|
|
|
|
action: "ListCertificates",
|
|
|
|
|
|
version: "2024-09-10",
|
|
|
|
|
|
method: "GET",
|
2025-05-28 16:06:38 +08:00
|
|
|
|
data:{
|
|
|
|
|
|
query: {
|
|
|
|
|
|
SiteId: siteId
|
|
|
|
|
|
}
|
2025-05-22 23:21:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const list = certListRes.Result;
|
|
|
|
|
|
for (const item of list) {
|
|
|
|
|
|
this.logger.info(`证书${item.Name}状态:${item.Status}`);
|
|
|
|
|
|
if (item.Status === "Expired") {
|
|
|
|
|
|
this.logger.info(`证书${item.Name}已过期,执行删除`);
|
|
|
|
|
|
await client.doRequest({
|
|
|
|
|
|
action: "DeleteCertificate",
|
|
|
|
|
|
version: "2024-09-10",
|
|
|
|
|
|
// 接口 HTTP 方法
|
|
|
|
|
|
method: "GET",
|
2025-05-28 16:06:38 +08:00
|
|
|
|
data:{
|
|
|
|
|
|
query: {
|
|
|
|
|
|
SiteId: siteId,
|
|
|
|
|
|
Id: item.id
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-22 23:21:32 +08:00
|
|
|
|
});
|
|
|
|
|
|
this.logger.info(`证书${item.Name}已删除`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
new AliyunDeployCertToESA();
|