chore: format

This commit is contained in:
xiaojunnuo
2026-05-31 01:41:33 +08:00
parent acd440106b
commit 4b57a0d729
557 changed files with 12530 additions and 14039 deletions
@@ -1,54 +1,53 @@
import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
import { Dns51Client } from './client.js';
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
import { Dns51Client } from "./client.js";
/**
* 这个注解将注册一个授权配置
* 在certd的后台管理系统中,用户可以选择添加此类型的授权
*/
@IsAccess({
name: '51dns',
title: '51dns授权',
icon: 'arcticons:dns-changer-3',
desc: '',
name: "51dns",
title: "51dns授权",
icon: "arcticons:dns-changer-3",
desc: "",
})
export class Dns51Access extends BaseAccess {
/**
* 授权属性配置
*/
@AccessInput({
title: '用户名',
title: "用户名",
component: {
placeholder: '用户名或手机号',
placeholder: "用户名或手机号",
},
required: true,
encrypt: false,
})
username = '';
username = "";
@AccessInput({
title: '登录密码',
title: "登录密码",
component: {
name: "a-input-password",
vModel: "value",
placeholder: '密码',
placeholder: "密码",
},
required: true,
encrypt: true,
})
password = '';
password = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "测试授权是否正确"
helper: "测试授权是否正确",
})
testRequest = true;
async onTestRequest() {
const client = new Dns51Client({
logger: this.ctx.logger,
access: this,
@@ -58,8 +57,6 @@ export class Dns51Access extends BaseAccess {
return "ok";
}
}
new Dns51Access();
@@ -12,42 +12,35 @@ export class Dns51Client {
_token = "";
_cookie = "";
constructor(options: {
logger: ILogger;
access: Dns51Access;
}) {
constructor(options: { logger: ILogger; access: Dns51Access }) {
this.logger = options.logger;
this.access = options.access;
this.http = createAxiosService({
logger: this.logger
logger: this.logger,
});
}
aes(val: string) {
if (!this.cryptoJs) {
throw new Error("crypto-js not init");
}
const CryptoJS = this.cryptoJs;
var k = CryptoJS.enc.Utf8.parse("1234567890abcDEF");
var iv = CryptoJS.enc.Utf8.parse("1234567890abcDEF");
const k = CryptoJS.enc.Utf8.parse("1234567890abcDEF");
const iv = CryptoJS.enc.Utf8.parse("1234567890abcDEF");
return CryptoJS.AES.encrypt(val, k, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
padding: CryptoJS.pad.ZeroPadding,
}).toString();
}
async init() {
if (this.cryptoJs) {
return;
}
const CryptoJSModule = await import("crypto-js");
this.cryptoJs = CryptoJSModule.default;
}
async login() {
@@ -63,77 +56,79 @@ export class Dns51Client {
returnOriginRes: true,
headers: {
// 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36',
'Origin': 'https://www.51dns.com',
'Referer': 'https://www.51dns.com',
Origin: "https://www.51dns.com",
Referer: "https://www.51dns.com",
},
});
let setCookie = res.headers['set-cookie']
let cookie = setCookie.map((item: any) => {
return item.split(';')[0]
}).join(';')
let setCookie = res.headers["set-cookie"];
const cookie = setCookie
.map((item: any) => {
return item.split(";")[0];
})
.join(";");
//提取 var csrfToken = "ieOfM21eDd9nWJv3OZtMJF6ogDsnPKQHJ17dlMck";
const _token = res.data.match(/var csrfToken = "(.*?)"/)[1];
this.logger.info("_token:", _token);
this._token = _token;
var obj = {
"email_or_phone": this.aes(this.access.username),
"password": this.aes(this.access.password),
"type": this.aes("account"),
"redirectTo": "https://www.51dns.com/domain",
"_token": _token
const obj = {
email_or_phone: this.aes(this.access.username),
password: this.aes(this.access.password),
type: this.aes("account"),
redirectTo: "https://www.51dns.com/domain",
_token: _token,
};
const res2 = await this.http.request({
url: "https://www.51dns.com/login",
method: "post",
data: {
...obj
...obj,
},
withCredentials: true,
logRes: false,
returnOriginRes: true,
headers: {
'Origin': 'https://www.51dns.com',
'Referer': 'https://www.51dns.com',
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': cookie,
'X-Requested-With': 'XMLHttpRequest'
}
Origin: "https://www.51dns.com",
Referer: "https://www.51dns.com",
"Content-Type": "application/x-www-form-urlencoded",
Cookie: cookie,
"X-Requested-With": "XMLHttpRequest",
},
});
this.logger.info("return headers:", JSON.stringify(res2.headers))
this.logger.info("return headers:", JSON.stringify(res2.headers));
if (res2.data.code == 0) {
setCookie = res2.headers['set-cookie']
this._cookie = setCookie.map((item: any) => {
return item.split(';')[0]
}).join(';')
this.logger.info("cookie:", this._cookie)
this.logger.info("登录成功")
setCookie = res2.headers["set-cookie"];
this._cookie = setCookie
.map((item: any) => {
return item.split(";")[0];
})
.join(";");
this.logger.info("cookie:", this._cookie);
this.logger.info("登录成功");
} else {
throw new Error("登录失败:", res2.data)
throw new Error("登录失败:", res2.data);
}
const res3 = await this.http.request({
url: 'https://www.51dns.com/domain',
method: 'get',
url: "https://www.51dns.com/domain",
method: "get",
withCredentials: true,
logRes: false,
returnOriginRes: true,
headers: {
// 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36',
'Origin': 'https://www.51dns.com',
'Referer': 'https://www.51dns.com/login.html',
'Cookie': this._cookie,
}
})
Origin: "https://www.51dns.com",
Referer: "https://www.51dns.com/login.html",
Cookie: this._cookie,
},
});
const success2 = res3.data.includes('<span class="nav-title">DNS解析</span>')
const success2 = res3.data.includes('<span class="nav-title">DNS解析</span>');
if (!success2) {
throw new Error("检查登录失败")
throw new Error("检查登录失败");
}
this.logger.info("检查登录成功")
this.logger.info("检查登录成功");
this.isLogined = true;
}
@@ -147,7 +142,7 @@ export class Dns51Client {
withCredentials: true,
logRes: false,
returnOriginRes: true,
headers: this.getRequestHeaders()
headers: this.getRequestHeaders(),
});
// 提取 <a target="_blank" href="https://www.51dns.com/domain/record/193341603"
@@ -158,20 +153,20 @@ export class Dns51Client {
throw new Error(`域名${domain}不存在`);
}
const domainId = matched[1];
this.logger.info(`域名${domain}的id为${domainId}`)
this.logger.info(`域名${domain}的id为${domainId}`);
return parseInt(domainId);
}
private getRequestHeaders() {
return {
'Origin': 'https://www.51dns.com',
'Referer': 'https://www.51dns.com',
'Cookie': this._cookie
Origin: "https://www.51dns.com",
Referer: "https://www.51dns.com",
Cookie: this._cookie,
};
}
async createRecord(param: { domain: string, data: any; domainId: number; host: string; ttl: number; type: string }) {
const {domain, data, host, type} = param;
async createRecord(param: { domain: string; data: any; domainId: number; host: string; ttl: number; type: string }) {
const { domain, data, host, type } = param;
const domainId = await this.getDomainId(domain);
const url = "https://www.51dns.com/domain/storenNewRecord";
const req = {
@@ -181,10 +176,10 @@ export class Dns51Client {
type: type,
value: data,
ttl: 300,
mx:"",
view_id: 0
mx: "",
view_id: 0,
};
this.logger.info("req:", JSON.stringify(req))
this.logger.info("req:", JSON.stringify(req));
const res = await this.http.request({
url,
method: "post",
@@ -192,8 +187,8 @@ export class Dns51Client {
withCredentials: true,
headers: {
...this.getRequestHeaders(),
'Content-Type': 'application/x-www-form-urlencoded'
}
"Content-Type": "application/x-www-form-urlencoded",
},
});
if (res.status !== 200) {
@@ -202,13 +197,12 @@ export class Dns51Client {
const id = res.data.id;
return {
id,
domainId
domainId,
};
}
async deleteRecord(param: { domainId: number; id: number }) {
const url = "https://www.51dns.com/domain/operateRecord"
const url = "https://www.51dns.com/domain/operateRecord";
/*
type: delete
ids[0]: 601019779
@@ -219,8 +213,8 @@ _token: ieOfM21eDd9nWJv3OZtMJF6ogDsnPKQHJ17dlMck
type: "delete",
ids: [param.id],
domain_id: param.domainId,
_token: this._token
}
_token: this._token,
};
const res = await this.http.request({
url,
method: "post",
@@ -228,55 +222,55 @@ _token: ieOfM21eDd9nWJv3OZtMJF6ogDsnPKQHJ17dlMck
withCredentials: true,
headers: {
...this.getRequestHeaders(),
'Content-Type': 'application/x-www-form-urlencoded'
}
"Content-Type": "application/x-www-form-urlencoded",
},
});
if (res.status !== 200) {
throw new Error(`删除域名解析失败:${res.msg}`);
}
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
if (req.pageNo >=2) { //不知道翻页查询的参数是什么
if (req.pageNo >= 2) {
//不知道翻页查询的参数是什么
return {
total: 0,
list: []
}
list: [],
};
}
await this.login();
const query = {
//domain=&id=&status=&perPage=500
perPage: 1000,
}
//domain=&id=&status=&perPage=500
perPage: 1000,
};
const res = await this.http.request({
url: 'https://www.51dns.com/domain?' + qs.stringify(query),
method: 'get',
url: "https://www.51dns.com/domain?" + qs.stringify(query),
method: "get",
withCredentials: true,
logRes: false,
returnOriginRes: true,
headers: this.getRequestHeaders()
headers: this.getRequestHeaders(),
});
//提取记录
const content = res.data || ""
const startIndex = content.indexOf(`<table cellpadding="0" cellspacing="0" class="domiantable">`)
const content = res.data || "";
const startIndex = content.indexOf(`<table cellpadding="0" cellspacing="0" class="domiantable">`);
if (startIndex < 0) {
throw new Error("解析域名列表失败,未找到域名列表")
throw new Error("解析域名列表失败,未找到域名列表");
}
const endIndex = content.indexOf(`</table>`, startIndex)
const tableContent = content.substring(startIndex, endIndex + 8)
const endIndex = content.indexOf(`</table>`, startIndex);
const tableContent = content.substring(startIndex, endIndex + 8);
// <tr class="">
// <a target="_blank" href="https://www.51dns.com/domain/record/199820259"
// class="color47">docmirror.cn</a>
const list: DomainRecord[] = []
const trArr = tableContent.split(`<tr class="">`)
const list: DomainRecord[] = [];
const trArr = tableContent.split(`<tr class="">`);
for (const tr of trArr) {
const lines = tr.trim().split("\n")
const row:any = {}
const lines = tr.trim().split("\n");
const row: any = {};
for (const line of lines) {
if (line.includes(`<a target="_blank" href="https://www.51dns.com/domain/record/`)) {
// 提取id
// 提取id
const domainId = line.match(/record\/(\d+)"/i)[1];
row.id = parseInt(domainId);
}
@@ -287,13 +281,13 @@ _token: ieOfM21eDd9nWJv3OZtMJF6ogDsnPKQHJ17dlMck
}
}
if (row.domain) {
list.push(row)
list.push(row);
}
}
return {
total: list.length,
list
}
list,
};
}
}
@@ -6,23 +6,23 @@ import { Dns51Client } from "./client.js";
export type Dns51Record = {
id: number;
domainId: number,
domainId: number;
};
// 这里通过IsDnsProvider注册一个dnsProvider
@IsDnsProvider({
name: '51dns',
title: '51dns',
desc: '51DNS',
icon: 'arcticons:dns-changer-3',
name: "51dns",
title: "51dns",
desc: "51DNS",
icon: "arcticons:dns-changer-3",
// 这里是对应的 cloudflare的access类型名称
accessType: '51dns',
order:999,
accessType: "51dns",
order: 999,
})
export class Dns51DnsProvider extends AbstractDnsProvider<Dns51Record> {
access!: Dns51Access;
client!:Dns51Client;
client!: Dns51Client;
async onInstance() {
//一些初始化的操作
// 也可以通过ctx成员变量传递context
@@ -43,30 +43,26 @@ export class Dns51DnsProvider extends AbstractDnsProvider<Dns51Record> {
* type: 'TXT',
* domain: 'example.com'
*/
const { fullRecord,hostRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
const { fullRecord, hostRecord, value, type, domain } = options;
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
const domainId = await this.client.getDomainId(domain);
this.logger.info('获取domainId成功:', domainId);
this.logger.info("获取domainId成功:", domainId);
const res = await this.client.createRecord({
domain: domain,
domainId: domainId,
type: 'TXT',
type: "TXT",
host: hostRecord,
data: value,
ttl: 300,
})
});
return {
id: res.id,
domainId: domainId,
};
}
/**
* 删除dns解析记录,清理申请痕迹
* @param options
@@ -74,9 +70,9 @@ export class Dns51DnsProvider extends AbstractDnsProvider<Dns51Record> {
async removeRecord(options: RemoveRecordOptions<Dns51Record>): Promise<void> {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value);
this.logger.info("删除域名解析:", fullRecord, value);
if (!record) {
this.logger.info('record为空,不执行删除');
this.logger.info("record为空,不执行删除");
return;
}
//这里调用删除txt dns解析记录接口
@@ -86,16 +82,16 @@ export class Dns51DnsProvider extends AbstractDnsProvider<Dns51Record> {
* Authorization: Basic {token}
* 请求参数
*/
const {id,domainId} = record
const { id, domainId } = record;
await this.client.deleteRecord({
id,
domainId
})
domainId,
});
this.logger.info(`删除域名解析成功:fullRecord=${fullRecord},id=${id}`);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
return await this.client.getDomainListPage(req)
return await this.client.getDomainListPage(req);
}
}
@@ -1,3 +1,3 @@
export * from './dns-provider.js';
export * from './access.js';
export * from './client.js';
export * from "./dns-provider.js";
export * from "./access.js";
export * from "./client.js";
@@ -1,10 +1,8 @@
import {AccessInput, BaseAccess, IsAccess, Pager, PageSearch} from "@certd/pipeline";
import {HttpRequestConfig} from "@certd/basic";
import { AccessInput, BaseAccess, IsAccess, Pager, PageSearch } from "@certd/pipeline";
import { HttpRequestConfig } from "@certd/basic";
import crypto from "crypto";
import url from "url";
/**
* AcePanel授权
*/
@@ -12,22 +10,21 @@ import url from "url";
name: "acepanel",
title: "AcePanel授权",
desc: "",
icon: "svg:icon-lucky"
icon: "svg:icon-lucky",
})
export class AcePanelAccess extends BaseAccess {
@AccessInput({
title: "AcePanel管理地址",
component: {
placeholder: "http://127.0.0.1:25475/entrance",
},
helper:"请输入AcePanel管理地址,格式为http://127.0.0.1:25475/entrance, 要带安全入口,最后面不要加/",
helper: "请输入AcePanel管理地址,格式为http://127.0.0.1:25475/entrance, 要带安全入口,最后面不要加/",
required: true,
})
endpoint = '';
endpoint = "";
@AccessInput({
title: '访问令牌ID',
title: "访问令牌ID",
component: {
name: "a-input-number",
vModel: "value",
@@ -35,20 +32,20 @@ export class AcePanelAccess extends BaseAccess {
helper: "AcePanel控制台->设置->用户->访问令牌->创建访问令牌",
required: true,
})
tokenId :number;
tokenId: number;
@AccessInput({
title: '访问令牌',
title: "访问令牌",
component: {
placeholder: 'AccessToken',
placeholder: "AccessToken",
},
helper: "创建访问令牌后复制该令牌填到这里",
required: true,
encrypt: true,
})
accessToken = '';
accessToken = "";
@AccessInput({
@AccessInput({
title: "忽略证书校验",
value: true,
component: {
@@ -63,37 +60,38 @@ export class AcePanelAccess extends BaseAccess {
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "点击测试接口是否正常"
helper: "点击测试接口是否正常",
})
testRequest = true;
async onTestRequest() {
await this.testApi();
return "ok"
return "ok";
}
/**
* 计算字符串的SHA256哈希值
*/
sha256Hash(text: string) {
return crypto
.createHash("sha256")
.update(text || "")
.digest("hex");
}
/**
* 使用HMAC-SHA256算法计算签名
*/
hmacSha256(key: string, message: string) {
return crypto.createHmac("sha256", key).update(message).digest("hex");
}
/**
* 计算字符串的SHA256哈希值
*/
sha256Hash(text: string) {
return crypto.createHash('sha256').update(text || '').digest('hex');
}
/**
* 使用HMAC-SHA256算法计算签名
*/
hmacSha256(key: string, message: string) {
return crypto.createHmac('sha256', key).update(message).digest('hex');
}
/**
* 为API请求生成签名
*/
signRequest(method: string, apiUrl: string, body: string, id: number, token: string) {
/**
* 为API请求生成签名
*/
signRequest(method: string, apiUrl: string, body: string, id: number, token: string) {
// 解析URL
const parsedUrl = new url.URL(apiUrl);
const path = parsedUrl.pathname;
@@ -101,61 +99,50 @@ export class AcePanelAccess extends BaseAccess {
// 规范化路径
let canonicalPath = path;
if (!path.startsWith('/api')) {
const apiPos = path.indexOf('/api');
if (apiPos !== -1) {
canonicalPath = path.slice(apiPos);
}
if (!path.startsWith("/api")) {
const apiPos = path.indexOf("/api");
if (apiPos !== -1) {
canonicalPath = path.slice(apiPos);
}
}
// 构造规范化请求
const canonicalRequest = [
method,
canonicalPath,
query,
this.sha256Hash(body || '')
].join('\n');
const canonicalRequest = [method, canonicalPath, query, this.sha256Hash(body || "")].join("\n");
// 获取当前时间戳
const timestamp = Math.floor(Date.now() / 1000);
// 构造待签名字符串
const stringToSign = [
'HMAC-SHA256',
timestamp,
this.sha256Hash(canonicalRequest)
].join('\n');
const stringToSign = ["HMAC-SHA256", timestamp, this.sha256Hash(canonicalRequest)].join("\n");
// 计算签名
const signature = this.hmacSha256(token, stringToSign);
return {
timestamp,
signature,
id
timestamp,
signature,
id,
};
}
}
async doRequest(req: HttpRequestConfig) {
let endpoint = this.endpoint
if (endpoint.endsWith('/')) {
let endpoint = this.endpoint;
if (endpoint.endsWith("/")) {
endpoint = endpoint.slice(0, -1);
}
const fullUrl = endpoint + req.url;
const method = req.method || 'GET';
const body = req.data ? JSON.stringify(req.data) : '';
const method = req.method || "GET";
const body = req.data ? JSON.stringify(req.data) : "";
const token = this.accessToken;
const tokenId = this.tokenId;
const signingData = this.signRequest(method, fullUrl, body, tokenId, token);
// 准备HTTP请求头
const headers = {
'Content-Type': 'application/json',
'X-Timestamp': signingData.timestamp,
'Authorization': `HMAC-SHA256 Credential=${signingData.id}, Signature=${signingData.signature}`
"Content-Type": "application/json",
"X-Timestamp": signingData.timestamp,
Authorization: `HMAC-SHA256 Credential=${signingData.id}, Signature=${signingData.signature}`,
};
// 发送请求
@@ -170,17 +157,15 @@ export class AcePanelAccess extends BaseAccess {
});
return res;
}
async testApi() {
await this.getWebSiteList({
pageNo: 1,
pageSize: 1,
})
});
return "ok"
return "ok";
}
async getWebSiteList(opts: PageSearch) {
@@ -198,8 +183,8 @@ export class AcePanelAccess extends BaseAccess {
method: "POST",
data: {
cert,
key
}
key,
},
};
return await this.doRequest(req);
}
@@ -210,14 +195,13 @@ export class AcePanelAccess extends BaseAccess {
method: "POST",
data: {
id: certId,
website_id: websiteId
}
website_id: websiteId,
},
};
return await this.doRequest(req);
}
async updatePanelCert(cert: string, key: string) {
const oldSettingRes = await this.doRequest({
url: "/api/setting",
method: "GET",
@@ -232,12 +216,11 @@ export class AcePanelAccess extends BaseAccess {
acme: false,
https: true,
cert,
key
}
key,
},
};
return await this.doRequest(req);
}
}
new AcePanelAccess();
@@ -1,2 +1,2 @@
export * from "./plugins/index.js";
export * from "./access.js";
export * from "./access.js";
@@ -1,3 +1,2 @@
export * from "./plugin-deploy-to-website.js";
export * from "./plugin-panel-cert.js";
@@ -13,19 +13,18 @@ import { AcePanelAccess } from "../access.js";
needPlus: true,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class AcePanelDeployToWebsite extends AbstractPlusTaskPlugin {
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
from: [...CertApplyPluginNames],
},
})
cert!: CertInfo;
@@ -36,9 +35,9 @@ export class AcePanelDeployToWebsite extends AbstractPlusTaskPlugin {
title: "ACEPanel授权",
component: {
name: "access-selector",
type: "acepanel"
type: "acepanel",
},
required: true
required: true,
})
accessId!: string;
@@ -48,13 +47,12 @@ export class AcePanelDeployToWebsite extends AbstractPlusTaskPlugin {
helper: "选择需要部署证书的网站",
action: AcePanelDeployToWebsite.prototype.onGetWebsiteList.name,
pager: false,
search: false
search: false,
})
)
websiteList!: number[];
async onInstance() {
}
async onInstance() {}
async onGetWebsiteList(data: PageSearch = {}) {
const access = await this.getAccess<AcePanelAccess>(this.accessId);
@@ -65,9 +63,9 @@ export class AcePanelDeployToWebsite extends AbstractPlusTaskPlugin {
}
const options = items.map((item: any) => {
return {
label: `${item.name} (${item.domains.join(', ')})`,
label: `${item.name} (${item.domains.join(", ")})`,
value: item.id,
domain: item.domains
domain: item.domains,
};
});
return {
@@ -83,7 +81,7 @@ export class AcePanelDeployToWebsite extends AbstractPlusTaskPlugin {
const result = await access.uploadCert(this.cert.crt, this.cert.key);
const certId = result.data.id;
this.logger.info(`证书上传成功,证书ID${certId}`);
this.logger.info(`证书域名:${result.data.domains.join(', ')}`);
this.logger.info(`证书域名:${result.data.domains.join(", ")}`);
// 部署证书到选择的网站
if (this.websiteList && this.websiteList.length > 0) {
@@ -1,6 +1,6 @@
import {IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from "@certd/pipeline";
import {CertApplyPluginNames, CertInfo} from "@certd/plugin-cert";
import {AcePanelAccess} from "../access.js";
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { AcePanelAccess } from "../access.js";
import { AbstractPlusTaskPlugin } from "@certd/plugin-plus";
@IsTaskPlugin({
@@ -12,19 +12,18 @@ import { AbstractPlusTaskPlugin } from "@certd/plugin-plus";
needPlus: true,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class AcePanelPanelCert extends AbstractPlusTaskPlugin {
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
from: [...CertApplyPluginNames],
},
})
cert!: CertInfo;
@@ -32,14 +31,13 @@ export class AcePanelPanelCert extends AbstractPlusTaskPlugin {
title: "ACEPanel授权",
component: {
name: "access-selector",
type: "acepanel"
type: "acepanel",
},
required: true
required: true,
})
accessId!: string;
async onInstance() {
}
async onInstance() {}
async execute(): Promise<void> {
const access = await this.getAccess<AcePanelAccess>(this.accessId);
@@ -50,4 +48,4 @@ export class AcePanelPanelCert extends AbstractPlusTaskPlugin {
}
}
new AcePanelPanelCert();
new AcePanelPanelCert();
@@ -1,4 +1,4 @@
export * from './plugin-restart.js';
export * from './plugin-script.js';
export * from './plugin-db-backup.js';
export * from './plugin-deploy-to-certd.js';
export * from "./plugin-restart.js";
export * from "./plugin-script.js";
export * from "./plugin-db-backup.js";
export * from "./plugin-deploy-to-certd.js";
@@ -5,7 +5,7 @@ import dayjs from "dayjs";
import { AbstractPlusTaskPlugin } from "@certd/plugin-plus";
import JSZip from "jszip";
import * as os from "node:os";
import { OssClientContext, ossClientFactory, OssClientRemoveByOpts} from "../plugin-lib/oss/index.js";
import { OssClientContext, ossClientFactory, OssClientRemoveByOpts } from "../plugin-lib/oss/index.js";
import { SshAccess, SshClient } from "../plugin-lib/ssh/index.js";
import { pipeline } from "stream/promises";
const defaultBackupDir = "certd_backup";
@@ -1,15 +1,15 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { httpsServer } from '../../modules/auto/https/server.js';
import { RestartCertdPlugin } from './plugin-restart.js';
import path from 'path';
import fs from 'fs';
import { CertApplyPluginNames, CertInfo, CertReader } from '@certd/plugin-lib';
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { httpsServer } from "../../modules/auto/https/server.js";
import { RestartCertdPlugin } from "./plugin-restart.js";
import path from "path";
import fs from "fs";
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-lib";
@IsTaskPlugin({
name: 'DeployToCertd',
title: '部署证书到Certd本身',
icon: 'mdi:restart',
desc: '【仅管理员可用】 部署证书到 certd的https服务,用于更新 Certd 的 ssl 证书,建议将此任务放在流水线的最后一步',
name: "DeployToCertd",
title: "部署证书到Certd本身",
icon: "mdi:restart",
desc: "【仅管理员可用】 部署证书到 certd的https服务,用于更新 Certd 的 ssl 证书,建议将此任务放在流水线的最后一步",
group: pluginGroups.admin.key,
onlyAdmin: true,
default: {
@@ -19,18 +19,17 @@ import { CertApplyPluginNames, CertInfo, CertReader } from '@certd/plugin-lib';
},
})
export class DeployToCertdPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: true,
})
cert!: CertInfo;
async onInstance() { }
async onInstance() {}
async execute(): Promise<void> {
this.checkAdmin();
@@ -40,15 +39,15 @@ export class DeployToCertdPlugin extends AbstractTaskPlugin {
const certReader = new CertReader(this.cert);
const dataDir = "./data";
const handle = async ({ tmpCrtPath, tmpKeyPath, }) => {
this.logger.info('复制到目标路径');
const handle = async ({ tmpCrtPath, tmpKeyPath }) => {
this.logger.info("复制到目标路径");
if (crtPath) {
crtPath = crtPath.startsWith('/') ? crtPath : path.join(dataDir, crtPath);
crtPath = crtPath.startsWith("/") ? crtPath : path.join(dataDir, crtPath);
this.copyFile(tmpCrtPath, crtPath);
}
if (keyPath) {
keyPath = keyPath.trim();
keyPath = keyPath.startsWith('/') ? keyPath : path.join(dataDir, keyPath);
keyPath = keyPath.startsWith("/") ? keyPath : path.join(dataDir, keyPath);
this.copyFile(tmpKeyPath, keyPath);
}
};
@@ -56,8 +55,7 @@ export class DeployToCertdPlugin extends AbstractTaskPlugin {
await certReader.readCertFile({ logger: this.logger, handle });
this.logger.info(`证书已部署到 ${crtPath}${keyPath}`);
this.logger.info('Certd https server 将在 30 秒后重启');
this.logger.info("Certd https server 将在 30 秒后重启");
await this.ctx.utils.sleep(30000);
await httpsServer.restart();
}
@@ -1,13 +1,13 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy } from '@certd/pipeline';
import { httpsServer } from '../../modules/auto/https/server.js';
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy } from "@certd/pipeline";
import { httpsServer } from "../../modules/auto/https/server.js";
@IsTaskPlugin({
name: 'RestartCertd',
title: '重启 Certd',
icon: 'mdi:restart',
desc: '【仅管理员可用】 重启 certd的https服务,用于更新 Certd 的 ssl 证书',
name: "RestartCertd",
title: "重启 Certd",
icon: "mdi:restart",
desc: "【仅管理员可用】 重启 certd的https服务,用于更新 Certd 的 ssl 证书",
group: pluginGroups.admin.key,
onlyAdmin:true,
onlyAdmin: true,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
@@ -18,7 +18,7 @@ export class RestartCertdPlugin extends AbstractTaskPlugin {
async onInstance() {}
async execute(): Promise<void> {
this.checkAdmin();
this.logger.info('Certd https server 将在 3 秒后重启');
this.logger.info("Certd https server 将在 3 秒后重启");
await this.ctx.utils.sleep(3000);
await httpsServer.restart();
}
@@ -1,16 +1,16 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskInstanceContext } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import { CertApplyPluginNames} from '@certd/plugin-cert';
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskInstanceContext } from "@certd/pipeline";
import { CertInfo, CertReader } from "@certd/plugin-cert";
import { CertApplyPluginNames } from "@certd/plugin-cert";
export type CustomScriptContext = {
CertReader: typeof CertReader;
self: CustomScriptPlugin;
} & TaskInstanceContext;
@IsTaskPlugin({
name: 'CustomScript',
title: '自定义js脚本',
icon: 'ri:javascript-line',
desc: '【仅管理员】运行自定义js脚本执行',
name: "CustomScript",
title: "自定义js脚本",
icon: "ri:javascript-line",
desc: "【仅管理员】运行自定义js脚本执行",
group: pluginGroups.admin.key,
showRunStrategy: true,
onlyAdmin: true,
@@ -22,23 +22,23 @@ export type CustomScriptContext = {
})
export class CustomScriptPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '脚本',
helper: '自定义js脚本,[脚本编写帮助文档](https://certd.docmirror.cn/guide/use/custom-script/)',
title: "脚本",
helper: "自定义js脚本,[脚本编写帮助文档](https://certd.docmirror.cn/guide/use/custom-script/)",
component: {
name: 'a-textarea',
vModel: 'value',
name: "a-textarea",
vModel: "value",
rows: 10,
style: 'background-color: #000c17;color: #fafafa;',
style: "background-color: #000c17;color: #fafafa;",
},
required: true,
})
script!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: false,
@@ -48,14 +48,14 @@ export class CustomScriptPlugin extends AbstractTaskPlugin {
async onInstance() {}
async execute(): Promise<void> {
this.checkAdmin();
this.logger.info('执行自定义脚本:\n', this.script);
this.logger.info("执行自定义脚本:\n", this.script);
const ctx: CustomScriptContext = {
CertReader,
self: this,
...this.ctx,
};
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
const func = new AsyncFunction('ctx', this.script);
const func = new AsyncFunction("ctx", this.script);
return await func(ctx);
}
}
@@ -1,27 +1,23 @@
import { PageRes, PageSearch } from '@certd/pipeline';
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { AliesaAccess } from '../../plugin-lib/aliyun/index.js';
import { AliyunClientV2 } from '../../plugin-lib/aliyun/lib/aliyun-client-v2.js';
import { PageRes, PageSearch } from "@certd/pipeline";
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { AliesaAccess } from "../../plugin-lib/aliyun/index.js";
import { AliyunClientV2 } from "../../plugin-lib/aliyun/lib/aliyun-client-v2.js";
@IsDnsProvider({
name: 'aliesa',
title: '阿里ESA',
desc: '阿里ESA DNS解析',
accessType: 'aliesa',
icon: 'svg:icon-aliyun',
name: "aliesa",
title: "阿里ESA",
desc: "阿里ESA DNS解析",
accessType: "aliesa",
icon: "svg:icon-aliyun",
order: 0,
})
export class AliesaDnsProvider extends AbstractDnsProvider {
client: AliyunClientV2
client: AliyunClientV2;
async onInstance() {
const access : AliesaAccess = this.ctx.access as AliesaAccess
this.client = await access.getEsaClient()
const access: AliesaAccess = this.ctx.access as AliesaAccess;
this.client = await access.getEsaClient();
}
async getSiteItem(domain: string) {
const ret = await this.client.doRequest({
// 接口名称
@@ -39,26 +35,23 @@ export class AliesaDnsProvider extends AbstractDnsProvider {
SiteName: domain,
// ["SiteSearchType"] = "exact";
SiteSearchType: "exact",
AccessType: "NS"
}
}
})
const list = ret.Sites
AccessType: "NS",
},
},
});
const list = ret.Sites;
if (list?.length === 0) {
throw new Error(`阿里云ESA中不存在此域名站点:${domain},请确认域名已添加到ESA中,且为NS接入方式`);
}
return list[0]
return list[0];
}
async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, domain);
const siteItem = await this.getSiteItem(domain)
const siteId = siteItem.SiteId
this.logger.info("添加域名解析:", fullRecord, value, domain);
const siteItem = await this.getSiteItem(domain);
const siteId = siteItem.SiteId;
const res = await this.client.doRequest({
action: "CreateRecord",
@@ -72,16 +65,15 @@ export class AliesaDnsProvider extends AbstractDnsProvider {
// queries["Ttl"] = 1231311;
Ttl: 100,
Data: JSON.stringify({ Value: value }),
}
}
})
},
},
});
this.logger.info('添加域名解析成功:', fullRecord, value, res.RecordId);
this.logger.info("添加域名解析成功:", fullRecord, value, res.RecordId);
return {
RecordId: res.RecordId,
SiteId: siteId,
}
};
}
async removeRecord(options: RemoveRecordOptions<any>): Promise<any> {
@@ -91,16 +83,16 @@ export class AliesaDnsProvider extends AbstractDnsProvider {
action: "DeleteRecord",
version: "2024-09-10",
data: {
query: {
RecordId: record.RecordId,
}
}
})
this.logger.info('删除域名解析成功:', record.RecordId);
query: {
RecordId: record.RecordId,
},
},
});
this.logger.info("删除域名解析成功:", record.RecordId);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
return await this.ctx.access.getDomainListPage(req)
return await this.ctx.access.getDomainListPage(req);
}
}
@@ -1,29 +1,27 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { AliyunAccess } from '../../plugin-lib/aliyun/access/aliyun-access.js';
import { AliyunClient } from '../../plugin-lib/aliyun/index.js';
import { Pager, PageRes, PageSearch } from '@certd/pipeline';
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { AliyunAccess } from "../../plugin-lib/aliyun/access/aliyun-access.js";
import { AliyunClient } from "../../plugin-lib/aliyun/index.js";
import { Pager, PageRes, PageSearch } from "@certd/pipeline";
@IsDnsProvider({
name: 'aliyun',
title: '阿里云',
desc: '阿里云DNS解析提供商',
accessType: 'aliyun',
icon: 'svg:icon-aliyun',
order:0,
name: "aliyun",
title: "阿里云",
desc: "阿里云DNS解析提供商",
accessType: "aliyun",
icon: "svg:icon-aliyun",
order: 0,
})
export class AliyunDnsProvider extends AbstractDnsProvider {
client: any;
async onInstance() {
const access: AliyunAccess = this.ctx.access as AliyunAccess
const access: AliyunAccess = this.ctx.access as AliyunAccess;
this.client = new AliyunClient({ logger: this.logger });
await this.client.init({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
endpoint: 'https://alidns.aliyuncs.com',
apiVersion: '2015-01-09',
endpoint: "https://alidns.aliyuncs.com",
apiVersion: "2015-01-09",
});
}
//
@@ -88,11 +86,11 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
// }
async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord,hostRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, domain);
const { fullRecord, hostRecord, value, type, domain } = options;
this.logger.info("添加域名解析:", fullRecord, value, domain);
// const domain = await this.matchDomain(fullRecord);
const params = {
RegionId: 'cn-hangzhou',
RegionId: "cn-hangzhou",
DomainName: domain,
RR: hostRecord,
Type: type,
@@ -101,31 +99,31 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
};
const requestOption = {
method: 'POST',
method: "POST",
};
try {
const ret = await this.client.request('AddDomainRecord', params, requestOption);
this.logger.info('添加域名解析成功:', JSON.stringify(options), ret.RecordId);
const ret = await this.client.request("AddDomainRecord", params, requestOption);
this.logger.info("添加域名解析成功:", JSON.stringify(options), ret.RecordId);
return ret.RecordId;
} catch (e: any) {
if (e.code === 'DomainRecordDuplicate') {
if (e.code === "DomainRecordDuplicate") {
return;
}
if(e.code === "LastOperationNotFinished"){
this.logger.info('上一个操作还未完成,5s后重试')
await this.ctx.utils.sleep(5000)
return this.createRecord(options)
if (e.code === "LastOperationNotFinished") {
this.logger.info("上一个操作还未完成,5s后重试");
await this.ctx.utils.sleep(5000);
return this.createRecord(options);
}
if (e.code === 'SignatureDoesNotMatch') {
this.logger.error('阿里云账号的AccessKeyId或AccessKeySecret错误,请检查AccessKey是否被删除、过期、或者选择了错误的授权记录');
if (e.code === "SignatureDoesNotMatch") {
this.logger.error("阿里云账号的AccessKeyId或AccessKeySecret错误,请检查AccessKey是否被删除、过期、或者选择了错误的授权记录");
}
this.logger.info('添加域名解析出错', e);
this.logger.info("添加域名解析出错", e);
this.resolveError(e, options);
}
}
resolveError(e: any, req: CreateRecordOptions) {
if (e.message?.indexOf('The specified domain name does not exist') > -1) {
if (e.message?.indexOf("The specified domain name does not exist") > -1) {
throw new Error(`阿里云账号中不存在此域名:${req.domain}`);
}
throw e;
@@ -134,53 +132,50 @@ export class AliyunDnsProvider extends AbstractDnsProvider {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
const params = {
RegionId: 'cn-hangzhou',
RegionId: "cn-hangzhou",
RecordId: record,
};
const requestOption = {
method: 'POST',
method: "POST",
};
try{
const ret = await this.client.request('DeleteDomainRecord', params, requestOption);
this.logger.info('删除域名解析成功:', fullRecord, value, ret.RecordId);
try {
const ret = await this.client.request("DeleteDomainRecord", params, requestOption);
this.logger.info("删除域名解析成功:", fullRecord, value, ret.RecordId);
return ret.RecordId;
}catch (e) {
if(e.code === "LastOperationNotFinished"){
this.logger.info('上一个操作还未完成,5s后重试')
await this.ctx.utils.sleep(5000)
return this.removeRecord(options)
} catch (e) {
if (e.code === "LastOperationNotFinished") {
this.logger.info("上一个操作还未完成,5s后重试");
await this.ctx.utils.sleep(5000);
return this.removeRecord(options);
}
throw e
throw e;
}
}
async getDomainListPage(req: PageSearch) :Promise<PageRes<DomainRecord>> {
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const pager = new Pager(req);
const params = {
RegionId: 'cn-hangzhou',
RegionId: "cn-hangzhou",
PageSize: pager.pageSize,
PageNumber: pager.pageNo,
};
const requestOption = {
method: 'POST',
method: "POST",
};
const ret = await this.client.request(
'DescribeDomains',
params,
requestOption
);
const list = ret.Domains?.Domain?.map(item => ({
id: item.DomainId,
domain: item.DomainName,
})) || []
const ret = await this.client.request("DescribeDomains", params, requestOption);
const list =
ret.Domains?.Domain?.map(item => ({
id: item.DomainId,
domain: item.DomainName,
})) || [];
return {
list,
total: ret.TotalCount,
}
};
}
}
@@ -1,2 +1,2 @@
import './aliyun-dns-provider.js';
import './aliesa-dns-provider.js';
import "./aliyun-dns-provider.js";
import "./aliesa-dns-provider.js";
@@ -1,2 +1,2 @@
export * from './dns-provider/index.js';
export * from './plugin/index.js';
export * from "./dns-provider/index.js";
export * from "./plugin/index.js";
@@ -1,15 +1,15 @@
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { AbstractPlusTaskPlugin } from "@certd/plugin-plus";
import dayjs from 'dayjs';
import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js';
import { AliyunSslClient } from '../../../plugin-lib/aliyun/lib/index.js';
import dayjs from "dayjs";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunSslClient } from "../../../plugin-lib/aliyun/lib/index.js";
@IsTaskPlugin({
name: 'AliyunDeleteExpiringCert',
title: '阿里云-删除即将过期证书',
icon: 'ant-design:aliyun-outlined',
name: "AliyunDeleteExpiringCert",
title: "阿里云-删除即将过期证书",
icon: "ant-design:aliyun-outlined",
group: pluginGroups.aliyun.key,
desc: '仅删除未使用的证书',
desc: "仅删除未使用的证书",
default: {
strategy: {
runStrategy: RunStrategy.AlwaysRun,
@@ -19,28 +19,28 @@ import { AliyunSslClient } from '../../../plugin-lib/aliyun/lib/index.js';
})
export class AliyunDeleteExpiringCert extends AbstractPlusTaskPlugin {
@TaskInput({
title: 'Access提供者',
helper: 'access 授权',
title: "Access提供者",
helper: "access 授权",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@TaskInput({
title: '地域',
helper: '阿里云CAS证书服务地域',
title: "地域",
helper: "阿里云CAS证书服务地域",
component: {
name: 'a-select',
name: "a-select",
options: [
{ value: 'cas.aliyuncs.com', label: '中国大陆' },
{ value: 'cas.ap-southeast-1.aliyuncs.com', label: '新加坡' },
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
],
},
required: true,
value: 'cas.aliyuncs.com',
value: "cas.aliyuncs.com",
})
endpoint!: string;
@@ -55,36 +55,36 @@ export class AliyunDeleteExpiringCert extends AbstractPlusTaskPlugin {
// searchKey!: string;
@TaskInput({
title: '最大删除数量',
helper: '单次运行最大删除数量',
title: "最大删除数量",
helper: "单次运行最大删除数量",
value: 100,
component: {
name: 'a-input-number',
vModel: 'value',
name: "a-input-number",
vModel: "value",
},
required: true,
})
maxCount!: number;
@TaskInput({
title: '即将过期天数',
helper: '仅删除有效期小于此天数的证书,0表示完全过期时才删除',
title: "即将过期天数",
helper: "仅删除有效期小于此天数的证书,0表示完全过期时才删除",
value: 0,
component: {
name: 'a-input-number',
vModel: 'value',
name: "a-input-number",
vModel: "value",
},
required: true,
})
expiringDays!: number;
@TaskInput({
title: '检查超时时间',
helper: '检查删除任务结果超时时间,单位分钟',
title: "检查超时时间",
helper: "检查删除任务结果超时时间,单位分钟",
value: 10,
component: {
name: 'a-input-number',
vModel: 'value',
name: "a-input-number",
vModel: "value",
},
required: true,
})
@@ -106,17 +106,17 @@ export class AliyunDeleteExpiringCert extends AbstractPlusTaskPlugin {
// Keyword: this.searchKey,
};
const certificates: any[] = [];
while(true){
const res = await sslClient.doRequest('ListCertificates', params, {
method: 'POST',
});
while (true) {
const res = await sslClient.doRequest("ListCertificates", params, {
method: "POST",
});
let list = res?.CertificateList;
if (!list || list.length === 0) {
break;
}
this.logger.info(`查询第${params.CurrentPage}页,每页${params.ShowSize}个证书,当前页共${list.length}个证书`);
const lastDay = dayjs().add(this.expiringDays, 'day');
const lastDay = dayjs().add(this.expiringDays, "day");
list = list.filter((item: any) => {
const notAfter = item.NotAfter;
const usingProducts = item.UsingProductList;
@@ -131,7 +131,7 @@ export class AliyunDeleteExpiringCert extends AbstractPlusTaskPlugin {
this.logger.info(`即将过期的证书数量:${certificates.length}`);
if (certificates.length === 0) {
this.logger.info('没有即将过期的证书, 无需删除');
this.logger.info("没有即将过期的证书, 无需删除");
return;
}
this.logger.info(`开始删除证书,共${certificates.length}个证书`);
@@ -140,12 +140,15 @@ export class AliyunDeleteExpiringCert extends AbstractPlusTaskPlugin {
for (const certificate of certificates) {
try {
const deleteRes = await sslClient.doRequest('DeleteUserCertificate', {
CertId: certificate.CertificateId,
}, { method: 'POST' });
const deleteRes = await sslClient.doRequest(
"DeleteUserCertificate",
{
CertId: certificate.CertificateId,
},
{ method: "POST" }
);
this.logger.info(`删除证书成功,证书ID:${certificate.CertificateId}, 名称:${certificate.CertificateName}, requestId:${deleteRes?.RequestId}`);
successCount++;
} catch (error: any) {
this.logger.error(`删除证书失败,证书ID:${certificate.CertificateId}, 名称:${certificate.CertificateName}, 错误:${error.message}`);
failedCount++;
@@ -156,4 +159,4 @@ export class AliyunDeleteExpiringCert extends AbstractPlusTaskPlugin {
}
}
new AliyunDeleteExpiringCert();
new AliyunDeleteExpiringCert();
@@ -1,9 +1,6 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
import {
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from "@certd/plugin-lib";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
import { AliyunClientV2 } from "../../../plugin-lib/aliyun/lib/aliyun-client-v2.js";
@@ -17,9 +14,9 @@ import { AliyunClientV2 } from "../../../plugin-lib/aliyun/lib/aliyun-client-v2.
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class AliyunDeployCertToALB extends AbstractTaskPlugin {
@TaskInput({
@@ -27,9 +24,9 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
helper: "请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量",
component: {
name: "output-selector",
from: [...CertApplyPluginNames, "uploadCertToAliyun"]
from: [...CertApplyPluginNames, "uploadCertToAliyun"],
},
required: true
required: true,
})
cert!: CertInfo | CasCertId | number;
@@ -45,10 +42,10 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
options: [
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" }
]
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" },
],
},
required: true
required: true,
})
casEndpoint!: string;
@@ -57,9 +54,9 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "access-selector",
type: "aliyun"
type: "aliyun",
},
required: true
required: true,
})
accessId!: string;
@@ -69,7 +66,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
typeName: "AliyunDeployCertToALB",
single: true,
action: AliyunDeployCertToALB.prototype.onGetRegionList.name,
watches: ["accessId"]
watches: ["accessId"],
})
)
regionId: string;
@@ -80,7 +77,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
helper: "要部署证书的负载均衡ID",
typeName: "AliyunDeployCertToALB",
action: AliyunDeployCertToALB.prototype.onGetLoadBalanceList.name,
watches: ["regionId"]
watches: ["regionId"],
})
)
loadBalancers!: string[];
@@ -91,12 +88,11 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
helper: "要部署证书的监听器列表",
typeName: "AliyunDeployCertToALB",
action: AliyunDeployCertToALB.prototype.onGetListenerList.name,
watches: ["loadBalancers"]
watches: ["loadBalancers"],
})
)
listeners!: string[];
@TaskInput({
title: "部署证书类型",
value: "default",
@@ -106,18 +102,17 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
options: [
{
label: "默认证书",
value: "default"
value: "default",
},
{
label: "扩展证书",
value: "extension"
}
]
value: "extension",
},
],
},
required: true
}
)
deployType: string = "default";
required: true,
})
deployType = "default";
@TaskInput({
title: "是否清理过期证书",
@@ -126,14 +121,11 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
name: "a-switch",
vModel: "checked",
},
required: true
}
)
required: true,
})
clearExpiredCert: boolean;
async onInstance() {
}
async onInstance() {}
async getLBClient(access: AliyunAccess, region: string) {
const client = new AliyunClient({ logger: this.logger });
@@ -144,7 +136,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
accessKeySecret: access.accessKeySecret,
//https://wafopenapi.cn-hangzhou.aliyuncs.com
endpoint: `https://alb.${region}.aliyuncs.com`,
apiVersion: version
apiVersion: version,
});
return client;
}
@@ -166,9 +158,9 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
const client = await this.getLBClient(access, this.regionId);
await this.deployDefaultCert(certId, client);
}
if (this.clearExpiredCert!==false) {
if (this.clearExpiredCert !== false) {
this.logger.info(`准备开始清理过期证书`);
await this.ctx.utils.sleep(30000)
await this.ctx.utils.sleep(30000);
for (const listener of this.listeners) {
try {
await this.clearInvalidCert(albClientV2, listener);
@@ -188,9 +180,9 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
ListenerId: listener,
Certificates: [
{
CertificateId: certId
}
]
CertificateId: certId,
},
],
};
const res = await client.request("UpdateListenerAttribute", params);
@@ -212,11 +204,11 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
ListenerId: listenerId,
Certificates: [
{
CertificateId: certId
}
]
}
}
CertificateId: certId,
},
],
},
},
});
this.logger.info(`部署监听器${listenerId}的扩展证书成功`);
@@ -232,24 +224,23 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
version: "2020-06-16",
data: {
query: {
ListenerId: listener
}
}
ListenerId: listener,
},
},
};
const res = await client.doRequest(req);
const list = res.Certificates;
if (list.length === 0) {
this.logger.info(`监听器${listener}没有绑定证书`);
return
return;
}
const sslClient = new AliyunSslClient({
access: client.access,
logger: this.logger,
endpoint: this.casEndpoint
endpoint: this.casEndpoint,
});
const certIds = [];
for (const item of list) {
this.logger.info(`监听器${listener}绑定的证书${item.CertificateId},status:${item.Status},IsDefault:${item.IsDefault}`);
@@ -273,7 +264,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
}
if (invalidCertIds.length === 0) {
this.logger.info(`监听器${listener}没有过期的证书`);
return
return;
}
this.logger.info(`开始解绑过期的证书:${invalidCertIds}listener:${listener}`);
await client.doRequest({
@@ -284,13 +275,13 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
data: {
query: {
ListenerId: listener,
Certificates: invalidCertIds.map((item) => {
Certificates: invalidCertIds.map(item => {
return {
CertificateId: item
}
})
}
}
CertificateId: item,
};
}),
},
},
});
this.logger.info(`解绑过期证书成功`);
}
@@ -298,13 +289,12 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
async getAliyunCertId(access: AliyunAccess) {
let certId: any = this.cert;
if (typeof this.cert === "object") {
const certInfo = this.cert as CertInfo;
const casCert = this.cert as CasCertId;
const casCert = this.cert as CasCertId;
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: this.casEndpoint
endpoint: this.casEndpoint,
});
if (certInfo.crt) {
@@ -313,11 +303,11 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
name: certName,
cert: certInfo,
});
certId = certIdRes.certId as any;
}else if (casCert.certId){
certId = certIdRes.certId as any;
} else if (casCert.certId) {
certId = casCert.certId;
}else{
throw new Error('证书格式错误'+JSON.stringify(this.cert));
} else {
throw new Error("证书格式错误" + JSON.stringify(this.cert));
}
}
@@ -341,7 +331,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
return {
label: item.LocalName,
value: item.RegionId,
endpoint: item.RegionEndpoint
endpoint: item.RegionEndpoint,
};
});
}
@@ -357,7 +347,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
const client = await this.getLBClient(access, this.regionId);
const params = {
MaxResults: 100
MaxResults: 100,
};
const res = await client.request("ListLoadBalancers", params);
this.checkRet(res);
@@ -369,7 +359,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
const label = `${item.LoadBalancerId}<${item.LoadBalancerName}}>`;
return {
label: label,
value: item.LoadBalancerId
value: item.LoadBalancerId,
};
});
}
@@ -385,7 +375,7 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
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;
@@ -401,19 +391,16 @@ 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();
@@ -1,19 +1,16 @@
import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
import {
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from "@certd/plugin-lib";
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/ssl-client.js";
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
import {optionsUtils} from "@certd/basic";
import { optionsUtils } from "@certd/basic";
@IsTaskPlugin({
name: 'DeployCertToAliyunApig',
title: '阿里云-部署至云原生API网关/AI网关',
icon: 'svg:icon-aliyun',
name: "DeployCertToAliyunApig",
title: "阿里云-部署至云原生API网关/AI网关",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '自动部署域名证书至云原生API网关、AI网关',
desc: "自动部署域名证书至云原生API网关、AI网关",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
@@ -22,27 +19,25 @@ import {optionsUtils} from "@certd/basic";
})
export class DeployCertToAliyunApig extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'uploadCertToAliyun'],
name: "output-selector",
from: [...CertApplyPluginNames, "uploadCertToAliyun"],
},
required: true,
})
cert!: CertInfo | CasCertId |number;
cert!: CertInfo | CasCertId | number;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '阿里云授权',
title: "Access授权",
helper: "阿里云授权",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
@@ -50,88 +45,83 @@ export class DeployCertToAliyunApig extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: '区域',
helper: '请选择区域',
title: "区域",
helper: "请选择区域",
action: DeployCertToAliyunApig.prototype.onGetRegionList.name,
watches: ['certDomains', 'accessId'],
watches: ["certDomains", "accessId"],
required: true,
component:{
name:"remote-auto-complete"
}
component: {
name: "remote-auto-complete",
},
})
)
regionEndpoint!: string;
@TaskInput({
title: "网关类型",
component: {
name: "a-select",
vModel:"value",
options:[
{value:"AI",label:"AI"},
{value:"API",label:"API"},
]
vModel: "value",
options: [
{ value: "AI", label: "AI" },
{ value: "API", label: "API" },
],
},
required: true //必填
required: true, //必填
})
gatewayType!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '绑定域名',
helper: '请选择域名',
title: "绑定域名",
helper: "请选择域名",
action: DeployCertToAliyunApig.prototype.onGetDomainList.name,
watches: ['region', 'accessId','gatewayType'],
watches: ["region", "accessId", "gatewayType"],
required: true,
})
)
domainList!: string[];
@TaskInput({
title: "强制HTTPS",
component: {
name: "a-select",
vModel:"value",
options:[
{value:true,label:"强制HTTPS"},
{value:false,label:"不强制HTTPS"},
]
vModel: "value",
options: [
{ value: true, label: "强制HTTPS" },
{ value: false, label: "不强制HTTPS" },
],
},
required: true //必填
required: true, //必填
})
forceHttps!: boolean;
@TaskInput({
title: '证书服务接入点',
helper: '不会选就按默认',
value: 'cn-hangzhou',
title: "证书服务接入点",
helper: "不会选就按默认",
value: "cn-hangzhou",
component: {
name: 'a-select',
name: "a-select",
options: [
{ value: 'cn-hangzhou', label: '中国大陆' },
{ value: 'ap-southeast-1', label: '新加坡' },
{ value: "cn-hangzhou", label: "中国大陆" },
{ value: "ap-southeast-1", label: "新加坡" },
],
},
required: true,
})
casRegion!: string;
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始部署证书到云原生Api网关');
if(!this.domainList){
throw new Error('您还未选择域名');
this.logger.info("开始部署证书到云原生Api网关");
if (!this.domainList) {
throw new Error("您还未选择域名");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint)
const client = access.getClient(this.regionEndpoint);
let certId: any = this.cert;
if (typeof this.cert === 'object') {
if (typeof this.cert === "object") {
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
@@ -146,25 +136,23 @@ export class DeployCertToAliyunApig extends AbstractTaskPlugin {
name: this.buildCertName(CertReader.getMainDomain(certInfo.crt)),
cert: certInfo,
});
}else{
throw new Error('证书格式错误'+JSON.stringify(this.cert));
} else {
throw new Error("证书格式错误" + JSON.stringify(this.cert));
}
}
const certIdentify = `${certId}-${this.casRegion}`
const certIdentify = `${certId}-${this.casRegion}`;
for (const domainId of this.domainList ) {
this.logger.info(`[${domainId}]开始部署`)
await this.updateCert(client, domainId,certIdentify);
this.logger.info(`[${domainId}]部署成功`)
for (const domainId of this.domainList) {
this.logger.info(`[${domainId}]开始部署`);
await this.updateCert(client, domainId, certIdentify);
this.logger.info(`[${domainId}]部署成功`);
}
this.logger.info('部署完成');
this.logger.info("部署完成");
}
async updateCert(client: any, domainId: string,certIdentify:string) {
async updateCert(client: any, domainId: string, certIdentify: string) {
const domainInfoRes = await client.doRequest({
action: "GetDomain",
version: "2024-03-27",
@@ -175,57 +163,56 @@ export class DeployCertToAliyunApig extends AbstractTaskPlugin {
pathname: `/v1/domains/${domainId}`,
});
const tlsCipherSuitesConfig = domainInfoRes.data?.tlsCipherSuitesConfig
const tlsCipherSuitesConfig = domainInfoRes.data?.tlsCipherSuitesConfig;
const ret = await client.doRequest({
action: "UpdateDomain",
version: "2024-03-27",
method: "PUT",
style: "ROA",
pathname: `/v1/domains/${domainId}`,
data:{
body:{
const ret = await client.doRequest({
action: "UpdateDomain",
version: "2024-03-27",
method: "PUT",
style: "ROA",
pathname: `/v1/domains/${domainId}`,
data: {
body: {
certIdentifier: certIdentify,
protocol: "HTTPS",
forceHttps:this.forceHttps,
tlsCipherSuitesConfig
}
}
})
forceHttps: this.forceHttps,
tlsCipherSuitesConfig,
},
},
});
this.logger.info(`设置${domainId}证书成功:`, ret.requestId);
}
async onGetDomainList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
if (!this.regionEndpoint) {
throw new Error('请选择区域');
throw new Error("请选择区域");
}
if (!this.gatewayType) {
throw new Error('请选择网关类型');
throw new Error("请选择网关类型");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint)
const client = access.getClient(this.regionEndpoint);
const res =await client.doRequest({
action: "ListDomains",
version: "2024-03-27",
method: "GET",
style: "ROA",
pathname: `/v1/domains`,
data:{
query:{
pageSize: 100,
gatewayType: this.gatewayType ,
}
}
})
const res = await client.doRequest({
action: "ListDomains",
version: "2024-03-27",
method: "GET",
style: "ROA",
pathname: `/v1/domains`,
data: {
query: {
pageSize: 100,
gatewayType: this.gatewayType,
},
},
});
const list = res?.data?.items;
if (!list || list.length === 0) {
return []
return [];
}
const options = list.map((item: any) => {
return {
@@ -237,43 +224,42 @@ export class DeployCertToAliyunApig extends AbstractTaskPlugin {
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
async onGetRegionList(data: any) {
const list = [
{value:"cn-qingdao",label:"华北1(青岛)",endpoint:"apig.cn-qingdao.aliyuncs.com"},
{value:"cn-beijing",label:"华北2(北京)",endpoint:"apig.cn-beijing.aliyuncs.com"},
{value:"cn-zhangjiakou",label:"华北3(张家口)",endpoint:"apig.cn-zhangjiakou.aliyuncs.com"},
{value:"cn-wulanchabu",label:"华北6(乌兰察布)",endpoint:"apig.cn-wulanchabu.aliyuncs.com"},
{value:"cn-hangzhou",label:"华东1(杭州)",endpoint:"apig.cn-hangzhou.aliyuncs.com"},
{value:"cn-shanghai",label:"华东2(上海)",endpoint:"apig.cn-shanghai.aliyuncs.com"},
{value:"cn-shenzhen",label:"华南1(深圳)",endpoint:"apig.cn-shenzhen.aliyuncs.com"},
{value:"cn-heyuan",label:"华南2(河源)",endpoint:"apig.cn-heyuan.aliyuncs.com"},
{value:"cn-guangzhou",label:"华南3(广州)",endpoint:"apig.cn-guangzhou.aliyuncs.com"},
{value:"ap-southeast-2",label:"澳大利亚(悉尼)已关停",endpoint:"apig.ap-southeast-2.aliyuncs.com"},
{value:"ap-southeast-6",label:"菲律宾(马尼拉)",endpoint:"apig.ap-southeast-6.aliyuncs.com"},
{value:"ap-northeast-2",label:"韩国(首尔)",endpoint:"apig.ap-northeast-2.aliyuncs.com"},
{value:"ap-southeast-3",label:"马来西亚(吉隆坡)",endpoint:"apig.ap-southeast-3.aliyuncs.com"},
{value:"ap-northeast-1",label:"日本(东京)",endpoint:"apig.ap-northeast-1.aliyuncs.com"},
{value:"ap-southeast-7",label:"泰国(曼谷)",endpoint:"apig.ap-southeast-7.aliyuncs.com"},
{value:"cn-chengdu",label:"西南1(成都)",endpoint:"apig.cn-chengdu.aliyuncs.com"},
{value:"ap-southeast-1",label:"新加坡",endpoint:"apig.ap-southeast-1.aliyuncs.com"},
{value:"ap-southeast-5",label:"印度尼西亚(雅加达)",endpoint:"apig.ap-southeast-5.aliyuncs.com"},
{value:"cn-hongkong",label:"中国香港",endpoint:"apig.cn-hongkong.aliyuncs.com"},
{value:"eu-central-1",label:"德国(法兰克福)",endpoint:"apig.eu-central-1.aliyuncs.com"},
{value:"us-east-1",label:"美国(弗吉尼亚)",endpoint:"apig.us-east-1.aliyuncs.com"},
{value:"us-west-1",label:"美国(硅谷)",endpoint:"apig.us-west-1.aliyuncs.com"},
{value:"eu-west-1",label:"英国(伦敦)",endpoint:"apig.eu-west-1.aliyuncs.com"},
{value:"me-east-1",label:"阿联酋(迪拜)",endpoint:"apig.me-east-1.aliyuncs.com"},
{value:"me-central-1",label:"沙特(利雅得)",endpoint:"apig.me-central-1.aliyuncs.com"},
]
{ value: "cn-qingdao", label: "华北1(青岛)", endpoint: "apig.cn-qingdao.aliyuncs.com" },
{ value: "cn-beijing", label: "华北2(北京)", endpoint: "apig.cn-beijing.aliyuncs.com" },
{ value: "cn-zhangjiakou", label: "华北3(张家口)", endpoint: "apig.cn-zhangjiakou.aliyuncs.com" },
{ value: "cn-wulanchabu", label: "华北6(乌兰察布)", endpoint: "apig.cn-wulanchabu.aliyuncs.com" },
{ value: "cn-hangzhou", label: "华东1(杭州)", endpoint: "apig.cn-hangzhou.aliyuncs.com" },
{ value: "cn-shanghai", label: "华东2(上海)", endpoint: "apig.cn-shanghai.aliyuncs.com" },
{ value: "cn-shenzhen", label: "华南1(深圳)", endpoint: "apig.cn-shenzhen.aliyuncs.com" },
{ value: "cn-heyuan", label: "华南2(河源)", endpoint: "apig.cn-heyuan.aliyuncs.com" },
{ value: "cn-guangzhou", label: "华南3(广州)", endpoint: "apig.cn-guangzhou.aliyuncs.com" },
{ value: "ap-southeast-2", label: "澳大利亚(悉尼)已关停", endpoint: "apig.ap-southeast-2.aliyuncs.com" },
{ value: "ap-southeast-6", label: "菲律宾(马尼拉)", endpoint: "apig.ap-southeast-6.aliyuncs.com" },
{ value: "ap-northeast-2", label: "韩国(首尔)", endpoint: "apig.ap-northeast-2.aliyuncs.com" },
{ value: "ap-southeast-3", label: "马来西亚(吉隆坡)", endpoint: "apig.ap-southeast-3.aliyuncs.com" },
{ value: "ap-northeast-1", label: "日本(东京)", endpoint: "apig.ap-northeast-1.aliyuncs.com" },
{ value: "ap-southeast-7", label: "泰国(曼谷)", endpoint: "apig.ap-southeast-7.aliyuncs.com" },
{ value: "cn-chengdu", label: "西南1(成都)", endpoint: "apig.cn-chengdu.aliyuncs.com" },
{ value: "ap-southeast-1", label: "新加坡", endpoint: "apig.ap-southeast-1.aliyuncs.com" },
{ value: "ap-southeast-5", label: "印度尼西亚(雅加达)", endpoint: "apig.ap-southeast-5.aliyuncs.com" },
{ value: "cn-hongkong", label: "中国香港", endpoint: "apig.cn-hongkong.aliyuncs.com" },
{ value: "eu-central-1", label: "德国(法兰克福)", endpoint: "apig.eu-central-1.aliyuncs.com" },
{ value: "us-east-1", label: "美国(弗吉尼亚)", endpoint: "apig.us-east-1.aliyuncs.com" },
{ value: "us-west-1", label: "美国(硅谷)", endpoint: "apig.us-west-1.aliyuncs.com" },
{ value: "eu-west-1", label: "英国(伦敦)", endpoint: "apig.eu-west-1.aliyuncs.com" },
{ value: "me-east-1", label: "阿联酋(迪拜)", endpoint: "apig.me-east-1.aliyuncs.com" },
{ value: "me-central-1", label: "沙特(利雅得)", endpoint: "apig.me-central-1.aliyuncs.com" },
];
return list.map((item: any) => {
return {
value: item.endpoint,
label: item.label,
endpoint: item.endpoint,
regionId : item.value
regionId: item.value,
};
})
});
}
}
new DeployCertToAliyunApig();
@@ -1,15 +1,15 @@
import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
import {CertApplyPluginNames, CertInfo} from '@certd/plugin-cert';
import {optionsUtils} from "@certd/basic";
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { optionsUtils } from "@certd/basic";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
@IsTaskPlugin({
name: 'DeployCertToAliyunApiGateway',
title: '阿里云-部署证书至API网关',
icon: 'svg:icon-aliyun',
name: "DeployCertToAliyunApiGateway",
title: "阿里云-部署证书至API网关",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '自动部署域名证书至阿里云API网关(APIGateway',
desc: "自动部署域名证书至阿里云API网关(APIGateway",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
@@ -18,10 +18,10 @@ import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
})
export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: true,
@@ -32,122 +32,117 @@ export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@TaskInput({
title: '证书名称',
helper: '上传后将以此名称作为前缀备注',
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
})
certName!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '区域',
helper: '请选择区域',
title: "区域",
helper: "请选择区域",
action: DeployCertToAliyunApiGateway.prototype.onGetRegionList.name,
watches: ['certDomains', 'accessId'],
watches: ["certDomains", "accessId"],
required: true,
component:{
name:"remote-auto-complete"
}
component: {
name: "remote-auto-complete",
},
})
)
regionEndpoint!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: 'API分组',
helper: '请选择API分组',
title: "API分组",
helper: "请选择API分组",
action: DeployCertToAliyunApiGateway.prototype.onGetGroupList.name,
watches: ['regionEndpoint', 'accessId'],
watches: ["regionEndpoint", "accessId"],
required: true,
component:{
name:"remote-auto-complete"
}
component: {
name: "remote-auto-complete",
},
})
)
groupId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '绑定域名',
helper: '在API分组上配置的绑定域名',
title: "绑定域名",
helper: "在API分组上配置的绑定域名",
action: DeployCertToAliyunApiGateway.prototype.onGetDomainList.name,
watches: ['groupId','regionEndpoint', 'accessId'],
watches: ["groupId", "regionEndpoint", "accessId"],
required: true,
})
)
customDomains!: string[];
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云Api网关');
if(!this.customDomains){
throw new Error('您还未选择域名');
this.logger.info("开始部署证书到阿里云Api网关");
if (!this.customDomains) {
throw new Error("您还未选择域名");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint)
const client = access.getClient(this.regionEndpoint);
for (const domainName of this.customDomains ) {
this.logger.info(`[${domainName}]开始部署`)
for (const domainName of this.customDomains) {
this.logger.info(`[${domainName}]开始部署`);
await this.updateCert(client, domainName);
this.logger.info(`[${domainName}]部署成功`)
this.logger.info(`[${domainName}]部署成功`);
}
this.logger.info('部署完成');
this.logger.info("部署完成");
}
async updateCert(client: any, domainName: string) {
const ret = await client.doRequest({
const ret = await client.doRequest({
// 接口名称
action: "SetDomainCertificate",
// 接口版本
version: "2016-07-14",
data:{
query:{
data: {
query: {
GroupId: this.groupId,
DomainName: domainName,
CertificateName: this.buildCertName(domainName),
CertificateBody: this.cert.crt,
CertificatePrivateKey: this.cert.key
}
}
})
CertificatePrivateKey: this.cert.key,
},
},
});
this.logger.info(`设置${domainName}证书成功:`, ret.RequestId);
}
async onGetGroupList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
if (!this.regionEndpoint) {
throw new Error('请选择区域');
throw new Error("请选择区域");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint)
const res =await client.doRequest({
const client = access.getClient(this.regionEndpoint);
const res = await client.doRequest({
// 接口名称
action: "DescribeApiGroups",
// 接口版本
version: "2016-07-14",
data:{}
})
data: {},
});
const list = res?.ApiGroupAttributes?.ApiGroupAttribute;
if (!list || list.length === 0) {
throw new Error('没有数据,您可以手动输入API网关ID');
throw new Error("没有数据,您可以手动输入API网关ID");
}
return list.map((item: any) => {
return {
@@ -159,32 +154,32 @@ export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
async onGetDomainList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
if (!this.regionEndpoint) {
throw new Error('请选择区域');
throw new Error("请选择区域");
}
if (!this.groupId) {
throw new Error('请选择分组');
throw new Error("请选择分组");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient(this.regionEndpoint)
const client = access.getClient(this.regionEndpoint);
const res =await client.doRequest({
// 接口名称
action: "DescribeApiGroup",
// 接口版本
version: "2016-07-14",
data:{
query:{
GroupId: this.groupId
}
}
})
const res = await client.doRequest({
// 接口名称
action: "DescribeApiGroup",
// 接口版本
version: "2016-07-14",
data: {
query: {
GroupId: this.groupId,
},
},
});
const list = res?.CustomDomains?.DomainItem;
if (!list || list.length === 0) {
throw new Error('没有数据,您可以手动输入');
throw new Error("没有数据,您可以手动输入");
}
const options = list.map((item: any) => {
return {
@@ -196,32 +191,31 @@ export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
async onGetRegionList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = access.getClient("apigateway.cn-hangzhou.aliyuncs.com")
const client = access.getClient("apigateway.cn-hangzhou.aliyuncs.com");
const res =await client.doRequest({
const res = await client.doRequest({
// 接口名称
action: "DescribeRegions",
// 接口版本
version: "2016-07-14",
data:{}
})
const list = res.Regions.Region ;
data: {},
});
const list = res.Regions.Region;
if (!list || list.length === 0) {
throw new Error('没有数据,您可以手动输入');
throw new Error("没有数据,您可以手动输入");
}
return list.map((item: any) => {
return {
value: item.RegionEndpoint,
label: item.LocalName,
endpoint: item.RegionEndpoint,
regionId: item.RegionId
regionId: item.RegionId,
};
});
}
@@ -1,15 +1,15 @@
import { optionsUtils } from '@certd/basic';
import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import { optionsUtils } from "@certd/basic";
import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
@IsTaskPlugin({
name: 'DeployCertToAliyunCDN',
title: '阿里云-部署证书至CDN',
icon: 'svg:icon-aliyun',
name: "DeployCertToAliyunCDN",
title: "阿里云-部署证书至CDN",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '自动部署域名证书至阿里云CDN',
desc: "自动部署域名证书至阿里云CDN",
runStrategy: RunStrategy.AlwaysRun,
// default: {
// strategy: {
@@ -19,15 +19,15 @@ import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/al
})
export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
@TaskInput({
title: '证书服务接入点',
helper: '不会选就按默认',
value: 'cas.aliyuncs.com',
title: "证书服务接入点",
helper: "不会选就按默认",
value: "cas.aliyuncs.com",
component: {
name: 'a-select',
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: '德国(法兰克福)' },
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" },
],
},
required: true,
@@ -35,11 +35,11 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
endpoint!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'uploadCertToAliyun'],
name: "output-selector",
from: [...CertApplyPluginNames, "uploadCertToAliyun"],
},
template: false,
required: true,
@@ -50,62 +50,61 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@TaskInput({
title: '证书所在地域',
helper: 'cn-hangzhou和ap-southeast-1,默认cn-hangzhou。国际站用户建议使用ap-southeast-1。',
title: "证书所在地域",
helper: "cn-hangzhou和ap-southeast-1,默认cn-hangzhou。国际站用户建议使用ap-southeast-1。",
value: "cn-hangzhou",
component: {
name: 'a-select',
name: "a-select",
options: [
{ value: 'cn-hangzhou', label: '中国大陆' },
{ value: 'ap-southeast-1', label: '新加坡' }
]
{ value: "cn-hangzhou", label: "中国大陆" },
{ value: "ap-southeast-1", label: "新加坡" },
],
},
required: true,
})
certRegion: string
certRegion: string;
@TaskInput({
title: '证书名称',
helper: '上传后将以此名称作为前缀备注',
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
})
certName!: string;
@TaskInput({
title: '域名匹配模式',
helper: '根据证书匹配:根据证书域名自动匹配DCDN加速域名自动部署,新增加速域名自动感知,自动新增部署',
title: "域名匹配模式",
helper: "根据证书匹配:根据证书域名自动匹配DCDN加速域名自动部署,新增加速域名自动感知,自动新增部署",
component: {
name: 'a-select',
name: "a-select",
options: [
{ label: '手动选择', value: 'manual' },
{ label: '根据证书匹配', value: 'auto' },
{ label: "手动选择", value: "manual" },
{ label: "根据证书匹配", value: "auto" },
],
},
value: 'manual',
value: "manual",
})
domainMatchMode!: 'manual' | 'auto';
domainMatchMode!: "manual" | "auto";
@TaskInput(
createRemoteSelectInputDefine({
title: 'CDN加速域名',
helper: '你在阿里云上配置的CDN加速域名,比如:certd.docmirror.cn',
typeName: 'DeployCertToAliyunCDN',
title: "CDN加速域名",
helper: "你在阿里云上配置的CDN加速域名,比如:certd.docmirror.cn",
typeName: "DeployCertToAliyunCDN",
action: DeployCertToAliyunCDN.prototype.onGetDomainList.name,
watches: ['certDomains', 'accessId'],
watches: ["certDomains", "accessId"],
required: true,
pageSize: 100,
search:true,
search: true,
mergeScript: `
return {
show: ctx.compute(({form})=>{
@@ -113,72 +112,70 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
})
}
`,
pager:true,
pager: true,
})
)
domainName!: string | string[];
@TaskOutput({
title: '已部署过的DCDN加速域名',
title: "已部署过的DCDN加速域名",
})
deployedList!: string[];
async onInstance() { }
async onInstance() {}
async execute(): Promise<any> {
this.logger.info('开始部署证书到阿里云cdn');
this.logger.info("开始部署证书到阿里云cdn");
const access = await this.getAccess<AliyunAccess>(this.accessId);
if (this.cert == null) {
throw new Error('域名证书参数为空,请检查前置任务')
throw new Error("域名证书参数为空,请检查前置任务");
}
const client = await this.getClient(access);
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: this.endpoint || 'cas.aliyuncs.com',
endpoint: this.endpoint || "cas.aliyuncs.com",
});
if (this.domainMatchMode === 'auto') {
if (this.domainMatchMode === "auto") {
const { result, deployedList } = await this.autoMatchedDeploy({
targetName: 'DCDN加速域名',
targetName: "DCDN加速域名",
uploadCert: async () => {
return await sslClient.uploadCertOrGet(this.cert);
},
deployOne: async (req: { target: CertTargetItem, cert: any }) => {
deployOne: async (req: { target: CertTargetItem; cert: any }) => {
return await this.deployOne(client, req.target.value, req.cert);
},
getCertDomains:async ()=>{
return sslClient.getCertDomains(this.cert);
getCertDomains: async () => {
return sslClient.getCertDomains(this.cert);
},
getDeployTargetList: this.onGetDomainList.bind(this)
getDeployTargetList: this.onGetDomainList.bind(this),
});
this.deployedList = deployedList;
return result;
} else {
if (this.isNotChanged()) {
this.logger.info('输入参数未变更,跳过');
this.logger.info("输入参数未变更,跳过");
return "skip";
}
const certId = await this.getOrUploadCasCert(sslClient);
if (typeof this.domainName === 'string') {
if (typeof this.domainName === "string") {
this.domainName = [this.domainName];
}
for (const domain of this.domainName) {
await this.deployOne(client, domain, certId );
await this.deployOne(client, domain, certId);
}
}
this.logger.info('部署完成');
this.logger.info("部署完成");
}
async getOrUploadCasCert(sslClient: AliyunSslClient) {
let certId: any = this.cert;
let certName = this.appendTimeSuffix(this.certName);
if (typeof this.cert === 'object') {
if (typeof this.cert === "object") {
const certInfo = this.cert as CertInfo;
const casCert = this.cert as CasCertId;
if (casCert.certId) {
@@ -191,23 +188,23 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
});
certId = certIdRes.certId as any;
} else {
throw new Error('证书格式错误' + JSON.stringify(this.cert));
throw new Error("证书格式错误" + JSON.stringify(this.cert));
}
}
return {
certId,
certName,
}
};
}
async deployOne(client: any, domain: string, cert: any ) {
async deployOne(client: any, domain: string, cert: any) {
const { certId, certName } = cert;
await this.SetCdnDomainSSLCertificate(client, {
CertId: certId,
DomainName: domain,
CertName: certName,
CertRegion: this.certRegion || 'cn-hangzhou',
CertRegion: this.certRegion || "cn-hangzhou",
});
}
@@ -216,23 +213,23 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
await client.init({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
endpoint: 'https://cdn.aliyuncs.com',
apiVersion: '2018-05-10',
endpoint: "https://cdn.aliyuncs.com",
apiVersion: "2018-05-10",
});
return client;
}
async SetCdnDomainSSLCertificate(client: any, params: { CertId: number; DomainName: string, CertName: string, CertRegion: string }) {
this.logger.info('设置CDN: ', JSON.stringify(params));
async SetCdnDomainSSLCertificate(client: any, params: { CertId: number; DomainName: string; CertName: string; CertRegion: string }) {
this.logger.info("设置CDN: ", JSON.stringify(params));
const requestOption = {
method: 'POST',
method: "POST",
formatParams: false,
};
const ret: any = await client.request(
'SetCdnDomainSSLCertificate',
"SetCdnDomainSSLCertificate",
{
SSLProtocol: 'on',
SSLProtocol: "on",
CertType: "cas",
...params,
},
@@ -244,41 +241,41 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
checkRet(ret: any) {
if (ret.Code != null) {
throw new Error('执行失败:' + ret.Message);
throw new Error("执行失败:" + ret.Message);
}
}
async onGetDomainList(data: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const pager = new Pager(data)
const pager = new Pager(data);
const params = {
DomainName: data.searchKey,
PageSize: pager.pageSize || 100,
PageNumber: pager.pageNo || 1,
DomainSearchType: "fuzzy_match"
DomainSearchType: "fuzzy_match",
};
const requestOption = {
method: 'POST',
method: "POST",
formatParams: false,
};
const res = await client.request('DescribeUserDomains', params, requestOption);
const res = await client.request("DescribeUserDomains", params, requestOption);
this.checkRet(res);
const pageData = res?.Domains?.PageData;
if (!pageData || pageData.length === 0) {
return {
list: [],
total: 0,
};
return {
list: [],
total: 0,
};
}
const total = res?.TotalCount || 0;
const total = res?.TotalCount || 0;
const options = pageData.map((item: any) => {
return {
value: item.DomainName,
@@ -1,20 +1,17 @@
import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import {
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from "@certd/plugin-lib";
import dayjs from 'dayjs';
import { AbstractTaskPlugin, CertTargetItem, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import dayjs from "dayjs";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { optionsUtils } from "@certd/basic";
import { CertApplyPluginNames, CertInfo } from '@certd/plugin-cert';
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
@IsTaskPlugin({
name: 'DeployCertToAliyunDCDN',
title: '阿里云-部署证书至DCDN',
icon: 'svg:icon-aliyun',
name: "DeployCertToAliyunDCDN",
title: "阿里云-部署证书至DCDN",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '依赖证书申请前置任务,自动部署域名证书至阿里云DCDN',
desc: "依赖证书申请前置任务,自动部署域名证书至阿里云DCDN",
runStrategy: RunStrategy.AlwaysRun,
// default: {
// strategy: {
@@ -24,11 +21,11 @@ import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/al
})
export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'uploadCertToAliyun'],
name: "output-selector",
from: [...CertApplyPluginNames, "uploadCertToAliyun"],
},
required: true,
})
@@ -38,47 +35,46 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@TaskInput({
title: '证书名称',
helper: '上传后将以此名称作为前缀备注',
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
})
certName!: string;
@TaskInput({
title: '域名匹配模式',
helper: '根据证书匹配:根据证书域名自动匹配DCDN加速域名自动部署,新增加速域名自动感知,自动新增部署',
title: "域名匹配模式",
helper: "根据证书匹配:根据证书域名自动匹配DCDN加速域名自动部署,新增加速域名自动感知,自动新增部署",
component: {
name: 'a-select',
name: "a-select",
options: [
{ label: '手动选择', value: 'manual' },
{ label: '根据证书匹配', value: 'auto' },
{ label: "手动选择", value: "manual" },
{ label: "根据证书匹配", value: "auto" },
],
},
value: 'manual',
value: "manual",
})
domainMatchMode!: 'manual' | 'auto';
domainMatchMode!: "manual" | "auto";
@TaskInput(
createRemoteSelectInputDefine({
title: 'DCDN加速域名',
helper: '你在阿里云上配置的DCDN加速域名,比如:certd.docmirror.cn',
title: "DCDN加速域名",
helper: "你在阿里云上配置的DCDN加速域名,比如:certd.docmirror.cn",
action: DeployCertToAliyunDCDN.prototype.onGetDomainList.name,
watches: ['certDomains', 'accessId'],
watches: ["certDomains", "accessId"],
required: true,
pageSize: 100,
search:true,
pager:true,
search: true,
pager: true,
mergeScript: `
return {
show: ctx.compute(({form})=>{
@@ -91,63 +87,59 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
domainName!: string | string[];
@TaskOutput({
title: '已部署过的DCDN加速域名',
title: "已部署过的DCDN加速域名",
})
deployedList!: string[];
async onInstance() { }
async onInstance() {}
async execute(): Promise<any> {
this.logger.info('开始部署证书到阿里云DCDN');
this.logger.info("开始部署证书到阿里云DCDN");
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
const client = await this.getClient(access);
const sslClient = new AliyunSslClient({ access, logger: this.logger });
if (this.domainMatchMode === 'auto') {
if (this.domainMatchMode === "auto") {
const { result, deployedList } = await this.autoMatchedDeploy({
targetName: 'CDN加速域名',
targetName: "CDN加速域名",
uploadCert: async () => {
return await sslClient.uploadCertOrGet(this.cert);
},
deployOne: async (req: { target: CertTargetItem, cert: any }) => {
deployOne: async (req: { target: CertTargetItem; cert: any }) => {
return await this.deployOne(client, req.target.value, req.cert);
},
getCertDomains: async ()=>{
getCertDomains: async () => {
return sslClient.getCertDomains(this.cert);
},
getDeployTargetList: this.onGetDomainList.bind(this)
getDeployTargetList: this.onGetDomainList.bind(this),
});
this.deployedList = deployedList;
return result;
} else {
if (this.isNotChanged()) {
this.logger.info('输入参数未变更,跳过');
this.logger.info("输入参数未变更,跳过");
return "skip";
}
if (!this.domainName) {
throw new Error('您还未选择DCDN域名');
throw new Error("您还未选择DCDN域名");
}
let domains: string[] = [];
domains = typeof this.domainName === 'string' ? [this.domainName] : this.domainName;
domains = typeof this.domainName === "string" ? [this.domainName] : this.domainName;
const aliCrtId = await sslClient.uploadCertOrGet(this.cert);
for (const domainName of domains) {
await this.deployOne(client, domainName, aliCrtId);
}
}
this.logger.info('部署完成');
this.logger.info("部署完成");
}
async deployOne(client: any, domainName: string, aliCrtId: CasCertId) {
this.logger.info(`[${domainName}]开始部署`)
this.logger.info(`[${domainName}]开始部署`);
const params = await this.buildParams(domainName, aliCrtId);
await this.doRequest(client, params);
await this.ctx.utils.sleep(1000);
this.logger.info(`[${domainName}]部署成功`)
this.logger.info(`[${domainName}]部署成功`);
}
async getClient(access: AliyunAccess) {
@@ -155,20 +147,20 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
await client.init({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
endpoint: 'https://dcdn.aliyuncs.com',
apiVersion: '2018-01-15',
endpoint: "https://dcdn.aliyuncs.com",
apiVersion: "2018-01-15",
});
return client;
}
async buildParams(domainName: string, aliCrtId: CasCertId) {
const CertName = (this.certName ?? 'certd') + '-' + dayjs().format('YYYYMMDDHHmmss');
const CertName = (this.certName ?? "certd") + "-" + dayjs().format("YYYYMMDDHHmmss");
const certId = aliCrtId.certId;
this.logger.info('使用已上传的证书:', certId);
this.logger.info("使用已上传的证书:", certId);
return {
DomainName: domainName,
SSLProtocol: 'on',
CertType: 'cas',
SSLProtocol: "on",
CertType: "cas",
CertName: CertName,
CertId: certId,
};
@@ -176,42 +168,41 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
async doRequest(client: any, params: any) {
const requestOption = {
method: 'POST',
method: "POST",
formatParams: false,
};
const ret: any = await client.request('SetDcdnDomainSSLCertificate', params, requestOption);
const ret: any = await client.request("SetDcdnDomainSSLCertificate", params, requestOption);
this.checkRet(ret);
this.logger.info('设置Dcdn证书成功:', ret.RequestId);
this.logger.info("设置Dcdn证书成功:", ret.RequestId);
}
checkRet(ret: any) {
if (ret.Code != null) {
throw new Error('执行失败:' + ret.Message);
throw new Error("执行失败:" + ret.Message);
}
}
async onGetDomainList(data: PageSearch): Promise<{ list: CertTargetItem[], total: number }> {
async onGetDomainList(data: PageSearch): Promise<{ list: CertTargetItem[]; total: number }> {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const pager = new Pager(data)
const pager = new Pager(data);
const params = {
DomainName: data.searchKey,
PageSize: pager.pageSize || 200,
PageNumber: pager.pageNo || 1,
DomainSearchType: "fuzzy_match"
DomainSearchType: "fuzzy_match",
};
const requestOption = {
method: 'POST',
method: "POST",
formatParams: false,
};
const res = await client.request('DescribeDcdnUserDomains', params, requestOption);
const res = await client.request("DescribeDcdnUserDomains", params, requestOption);
this.checkRet(res);
const pageData = res?.Domains?.PageData || [];
const total = res?.TotalCount || 0;
@@ -1,9 +1,6 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
import {
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from "@certd/plugin-lib";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/ssl-client.js";
import { AliyunClientV2 } from "../../../plugin-lib/aliyun/lib/aliyun-client-v2.js";
@@ -18,9 +15,9 @@ import dayjs from "dayjs";
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class AliyunDeployCertToESA extends AbstractTaskPlugin {
@TaskInput({
@@ -28,9 +25,9 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
helper: "请选择证书申请任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames, 'uploadCertToAliyun']
from: [...CertApplyPluginNames, "uploadCertToAliyun"],
},
required: true
required: true,
})
cert!: CertInfo | CasCertId | number;
@@ -45,10 +42,10 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
vModel: "value",
options: [
{ value: "cn-hangzhou", label: "华东1(杭州)" },
{ value: "ap-southeast-1", label: "新加坡" }
]
{ value: "ap-southeast-1", label: "新加坡" },
],
},
required: true
required: true,
})
regionId!: string;
@@ -60,23 +57,22 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
name: "a-select",
options: [
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.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"
type: "aliyun",
},
required: true
required: true,
})
accessId!: string;
@@ -85,7 +81,7 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
title: "站点",
helper: "请选择要部署证书的站点",
action: AliyunDeployCertToESA.prototype.onGetSiteList.name,
watches: ["accessId", "regionId"]
watches: ["accessId", "regionId"],
})
)
siteIds!: string[];
@@ -95,15 +91,14 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
value: 2,
component: {
name: "a-input-number",
vModel: "value"
vModel: "value",
},
helper: "将检查证书数量限制,如果超限将删除最旧的那张证书",
required: true
required: true,
})
certLimit: number = 2;
certLimit = 2;
async onInstance() {
}
async onInstance() {}
async getAliyunCertId(access: AliyunAccess) {
let certId: any = this.cert;
@@ -112,7 +107,7 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: this.casEndpoint
endpoint: this.casEndpoint,
});
const certInfo = this.cert as CertInfo;
@@ -121,21 +116,21 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
certId = casCert.certId;
certName = casCert.certName;
} else if (certInfo.crt) {
certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt),"certd");
certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt), "certd");
const certIdRes = await sslClient.uploadCertificate({
name: certName,
cert: certInfo
cert: certInfo,
});
certId = certIdRes.certId as any;
this.logger.info("上传证书成功", certId, certName);
}else{
throw new Error('证书格式错误'+JSON.stringify(this.cert));
} else {
throw new Error("证书格式错误" + JSON.stringify(this.cert));
}
}
return {
certId,
certName
certName,
};
}
@@ -160,12 +155,11 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
SiteId: siteId,
CasId: certId,
Type: "cas",
Name: certName
}
}
Name: certName,
},
},
});
this.logger.info(`部署站点[${siteId}]证书成功:${JSON.stringify(res)}`);
} catch (e) {
if (e.message.includes("Certificate.Duplicated")) {
this.logger.info(`站点[${siteId}]证书已存在,无需重复部署`);
@@ -176,13 +170,12 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
try {
await this.clearSiteExpiredCert(client, siteId);
} catch (e) {
this.logger.error(`清理站点[${siteId}]过期证书失败`, e)
this.logger.error(`清理站点[${siteId}]过期证书失败`, e);
}
}
}
}
async getClient(access: AliyunAccess) {
const endpoint = `esa.${this.regionId}.aliyuncs.com`;
return access.getClient(endpoint);
@@ -199,7 +192,7 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
action: "ListSites",
version: "2024-09-10",
method: "GET",
data: {}
data: {},
});
const list = res?.Sites;
@@ -211,7 +204,7 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
return {
label: item.SiteName,
value: item.SiteId,
domain: item.SiteName
domain: item.SiteName,
};
});
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
@@ -226,9 +219,9 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
data: {
query: {
SiteId: siteId,
PageSize: 100
}
}
PageSize: 100,
},
},
});
const list = certListRes.Result;
@@ -245,21 +238,19 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
data: {
query: {
SiteId: siteId,
Id: item.id
}
}
Id: item.id,
},
},
});
this.logger.info(`证书${item.Name}已删除`);
} catch (e) {
this.logger.error(`过期证书${item.Name}删除失败:`, e.message)
this.logger.error(`过期证书${item.Name}删除失败:`, e.message);
}
}
}
}
async clearSiteLimitCert(client: AliyunClientV2, siteId: string) {
//删除最旧的证书
const certLimit = this.certLimit || 2;
this.logger.info(`站点[${siteId}]证书数量检查,当前限制${certLimit}`);
@@ -270,31 +261,31 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
data: {
query: {
SiteId: siteId,
PageSize: 100
}
}
PageSize: 100,
},
},
});
let list = certListRes.Result || [];
list = list.filter((item: any) => item.Type === "cas");
if (!list || list.length === 0) {
this.logger.info(`站点[${siteId}]没有CAS证书, 无需删除`);
return
return;
}
if (list.length < certLimit) {
this.logger.info(`站点[${siteId}]证书数量(${list.length})未超限制, 无需删除`);
return;
}
this.logger.info(`站点[${siteId}]证书数量(${list.length})已超限制, 开始删除最旧的证书`);
let oldly:any = null;
let oldly: any = null;
for (const item of list) {
if (!oldly) {
oldly = item;
continue;
}
if (dayjs(item.CreateTime).valueOf() < (dayjs(oldly.CreateTime)).valueOf()){
if (dayjs(item.CreateTime).valueOf() < dayjs(oldly.CreateTime).valueOf()) {
oldly = item;
}
}
@@ -306,9 +297,9 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
data: {
query: {
SiteId: siteId,
Id: oldly.Id
}
}
Id: oldly.Id,
},
},
});
this.logger.info(`最旧证书${oldly.Name}已删除`);
}
@@ -1,6 +1,6 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import fs from "fs";
import path from "path";
import { tmpdir } from "node:os";
@@ -8,11 +8,11 @@ import { sp } from "@certd/basic";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
@IsTaskPlugin({
name: 'AliyunDeployCertToFC',
title: '阿里云-部署至阿里云FC(3.0)',
icon: 'svg:icon-aliyun',
name: "AliyunDeployCertToFC",
title: "阿里云-部署至阿里云FC(3.0)",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '部署证书到阿里云函数计算(FC3.0',
desc: "部署证书到阿里云函数计算(FC3.0",
needPlus: false,
default: {
strategy: {
@@ -22,10 +22,10 @@ import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
})
export class AliyunDeployCertToFC extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择证书申请任务输出的域名证书',
title: "域名证书",
helper: "请选择证书申请任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: true,
@@ -36,56 +36,55 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: 'FC大区',
value: 'cn-hangzhou',
title: "FC大区",
value: "cn-hangzhou",
component: {
name: 'a-auto-complete',
vModel: 'value',
name: "a-auto-complete",
vModel: "value",
options: [
{ value: 'cn-qingdao', label: '华北1(青岛)' },
{ value: 'cn-beijing', label: '华北2(北京)' },
{ value: 'cn-zhangjiakou', label: '华北 3(张家口)' },
{ value: 'cn-huhehaote', label: '华北5(呼和浩特)' },
{ value: 'cn-hangzhou', label: '华东1(杭州)' },
{ value: 'cn-shanghai', label: '华东2(上海)' },
{ value: 'cn-shenzhen', label: '华南1(深圳)' },
{ value: 'ap-southeast-2', label: '澳大利亚(悉尼)' },
{ value: 'eu-central-1', label: '德国(法兰克福)' },
{ value: 'ap-southeast-3', label: '马来西亚(吉隆坡)' },
{ value: 'us-east-1', label: '美国(弗吉尼亚)' },
{ value: 'us-west-1', label: '美国(硅谷)' },
{ value: 'ap-northeast-1', label: '日本(东京)' },
{ value: 'ap-southeast-7', label: '泰国(曼谷)' },
{ value: 'cn-chengdu', label: '西南1(成都)' },
{ value: 'ap-southeast-1', label: '新加坡' },
{ value: 'ap-south-1', label: '印度(孟买)' },
{ value: 'ap-southeast-5', label: '印度尼西亚(雅加达)' },
{ value: 'eu-west-1', label: '英国(伦敦)' },
{ value: 'cn-hongkong', label: '中国香港' },
{ value: "cn-qingdao", label: "华北1(青岛)" },
{ value: "cn-beijing", label: "华北2(北京)" },
{ value: "cn-zhangjiakou", label: "华北 3(张家口)" },
{ value: "cn-huhehaote", label: "华北5(呼和浩特)" },
{ value: "cn-hangzhou", label: "华东1(杭州)" },
{ value: "cn-shanghai", label: "华东2(上海)" },
{ value: "cn-shenzhen", label: "华南1(深圳)" },
{ value: "ap-southeast-2", label: "澳大利亚(悉尼)" },
{ value: "eu-central-1", label: "德国(法兰克福)" },
{ value: "ap-southeast-3", label: "马来西亚(吉隆坡)" },
{ value: "us-east-1", label: "美国(弗吉尼亚)" },
{ value: "us-west-1", label: "美国(硅谷)" },
{ value: "ap-northeast-1", label: "日本(东京)" },
{ value: "ap-southeast-7", label: "泰国(曼谷)" },
{ value: "cn-chengdu", label: "西南1(成都)" },
{ value: "ap-southeast-1", label: "新加坡" },
{ value: "ap-south-1", label: "印度(孟买)" },
{ value: "ap-southeast-5", label: "印度尼西亚(雅加达)" },
{ value: "eu-west-1", label: "英国(伦敦)" },
{ value: "cn-hongkong", label: "中国香港" },
],
},
required: true,
})
regionId!: string;
@TaskInput({
title: '阿里云账号id',
helper: '阿里云主账号ID,右上角头像下方获取',
title: "阿里云账号id",
helper: "阿里云主账号ID,右上角头像下方获取",
component: {
name: 'a-input',
vModel:"value"
name: "a-input",
vModel: "value",
},
required: true,
})
accountId!: string;
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
@@ -93,33 +92,33 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: 'FC域名',
title: "FC域名",
helper: "请选择要部署证书的域名",
typeName: 'AliyunDeployCertToFC',
typeName: "AliyunDeployCertToFC",
action: AliyunDeployCertToFC.prototype.onGetDomainList.name,
watches: ['accessId', 'regionId'],
watches: ["accessId", "regionId"],
})
)
fcDomains!: string[];
@TaskInput({
title: '域名支持的协议类型',
value: '',
title: "域名支持的协议类型",
value: "",
component: {
name: 'a-select',
vModel:"value",
name: "a-select",
vModel: "value",
options: [
{ value: '', label: '保持原样(适用于原来已经开启了HTTPS)' },
{ value: 'HTTPS', label: '仅HTTPS' },
{ value: 'HTTP,HTTPS', label: 'HTTP与HTTPS同时支持' },
{ value: "", label: "保持原样(适用于原来已经开启了HTTPS)" },
{ value: "HTTPS", label: "仅HTTPS" },
{ value: "HTTP,HTTPS", label: "HTTP与HTTPS同时支持" },
],
},
})
protocol!: string;
@TaskInput({
title: '证书名称',
helper: '上传后将以此名称作为前缀备注',
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
})
certName!: string;
@@ -133,17 +132,16 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
});
}
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云');
this.logger.info("开始部署证书到阿里云");
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const $Util = await import('@alicloud/tea-util');
const $OpenApi = await import('@alicloud/openapi-client');
const $Util = await import("@alicloud/tea-util");
const $OpenApi = await import("@alicloud/openapi-client");
let privateKey = this.cert.key
try{
let privateKey = this.cert.key;
try {
// openssl rsa -in private_key.pem -out private_key_pkcs1.pem
const tempDir = path.join(tmpdir(), "certd");
if (!fs.existsSync(tempDir)) {
@@ -151,7 +149,7 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
}
const keyFileName = this.ctx.utils.id.randomNumber(10);
const tempPem = `${tempDir}/${keyFileName}.pem`;
const tempPkcs1Pem =`${tempDir}/${keyFileName}_pkcs1.pem`;
const tempPkcs1Pem = `${tempDir}/${keyFileName}_pkcs1.pem`;
fs.writeFileSync(tempPem, this.cert.key);
const oldPfxCmd = `openssl rsa -in ${tempPem} -traditional -out ${tempPkcs1Pem}`;
await this.exec(oldPfxCmd);
@@ -159,34 +157,31 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
privateKey = fileBuffer.toString();
fs.unlinkSync(tempPem);
fs.unlinkSync(tempPkcs1Pem);
}catch (e) {
this.logger.warn("私钥转换为PKCS#1格式失败",e);
} catch (e) {
this.logger.warn("私钥转换为PKCS#1格式失败", e);
}
for (const domainName of this.fcDomains) {
const params = new $OpenApi.Params({
// 接口名称
action: 'UpdateCustomDomain',
action: "UpdateCustomDomain",
// 接口版本
version: '2023-03-30',
version: "2023-03-30",
// 接口协议
protocol: 'HTTPS',
protocol: "HTTPS",
// 接口 HTTP 方法
method: 'PUT',
authType: 'AK',
style: 'FC',
method: "PUT",
authType: "AK",
style: "FC",
// 接口 PATH
pathname: `/2023-03-30/custom-domains/${domainName}`,
// 接口请求体内容格式
reqBodyType: 'json',
reqBodyType: "json",
// 接口响应体内容格式
bodyType: 'json',
bodyType: "json",
});
// body params
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt),this.certName??"")
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt), this.certName ?? "");
const body: { [key: string]: any } = {
certConfig: {
@@ -209,7 +204,7 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
}
async getClient(access: AliyunAccess) {
const $OpenApi = await import('@alicloud/openapi-client');
const $OpenApi = await import("@alicloud/openapi-client");
const config = new $OpenApi.Config({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
@@ -221,30 +216,30 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
async onGetDomainList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const $OpenApi = await import('@alicloud/openapi-client');
const $Util = await import('@alicloud/tea-util');
const $OpenApi = await import("@alicloud/openapi-client");
const $Util = await import("@alicloud/tea-util");
const params = new $OpenApi.Params({
// 接口名称
action: 'ListCustomDomains',
action: "ListCustomDomains",
// 接口版本
version: '2023-03-30',
version: "2023-03-30",
// 接口协议
protocol: 'HTTPS',
protocol: "HTTPS",
// 接口 HTTP 方法
method: 'GET',
authType: 'AK',
style: 'FC',
method: "GET",
authType: "AK",
style: "FC",
// 接口 PATH
pathname: `/2023-03-30/custom-domains`,
// 接口请求体内容格式
reqBodyType: 'json',
reqBodyType: "json",
// 接口响应体内容格式
bodyType: 'json',
bodyType: "json",
});
const runtime = new $Util.RuntimeOptions({});
@@ -255,7 +250,7 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
const list = res?.body?.customDomains;
if (!list || list.length === 0) {
throw new Error('没有找到FC域名,请先创建FC域名');
throw new Error("没有找到FC域名,请先创建FC域名");
}
const options = list.map((item: any) => {
@@ -1,9 +1,6 @@
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 { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/ssl-client.js";
@@ -16,9 +13,9 @@ import { AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/ssl-c
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class AliyunDeployCertToGA extends AbstractTaskPlugin {
@TaskInput({
@@ -26,11 +23,11 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
helper: "请选择证书申请任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames, 'uploadCertToAliyun']
from: [...CertApplyPluginNames, "uploadCertToAliyun"],
},
required: true
required: true,
})
cert!: CertInfo|number | CasCertId;
cert!: CertInfo | number | CasCertId;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@@ -43,10 +40,10 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
name: "a-select",
options: [
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" }
]
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
],
},
required: true
required: true,
})
casEndpoint!: string;
@@ -55,9 +52,9 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "access-selector",
type: "aliyun"
type: "aliyun",
},
required: true
required: true,
})
accessId!: string;
@@ -77,7 +74,7 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
title: "监听",
helper: "请选择要部署证书的监听",
action: AliyunDeployCertToGA.prototype.onGetListenerList.name,
watches: ["accessId", "acceleratorId"]
watches: ["accessId", "acceleratorId"],
})
)
listenerIds!: string[];
@@ -90,8 +87,8 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
name: "a-select",
options: [
{ value: "default", label: "默认证书" },
{ value: "additional", label: "扩展证书" }
]
{ value: "additional", label: "扩展证书" },
],
},
required: true,
})
@@ -109,21 +106,20 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
return form.certType === "additional";
})
}
`
`,
})
)
additionalDomains!: string[];
async onInstance() {
}
async onInstance() {}
async getAliyunCertId(access: AliyunAccess) {
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: this.casEndpoint
endpoint: this.casEndpoint,
});
return await sslClient.uploadCertOrGet(this.cert as any)
return await sslClient.uploadCertOrGet(this.cert as any);
}
async execute(): Promise<void> {
@@ -133,7 +129,7 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
const client = await this.getClient(access);
const { certIdentifier } = await this.getAliyunCertId(access);
for (const listenerId of this.listenerIds) {
if (this.certType === "default") {
// 更新默认证书
@@ -146,11 +142,9 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId,
ListenerId: listenerId,
Certificates: [
{ Id: certIdentifier},
]
}
}
Certificates: [{ Id: certIdentifier }],
},
},
});
this.logger.info(`部署默认证书到实例[${this.acceleratorId}]监听[${listenerId}]成功:${JSON.stringify(res)}`);
} else if (this.certType === "additional") {
@@ -166,14 +160,12 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
query: {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId,
ListenerId: listenerId
}
}
ListenerId: listenerId,
},
},
});
const domainExists = existingCerts.Certificates?.some((cert: any) =>
cert.Domain === domain
);
const domainExists = existingCerts.Certificates?.some((cert: any) => cert.Domain === domain);
if (domainExists) {
// 更新扩展证书
@@ -187,9 +179,9 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
AcceleratorId: this.acceleratorId,
ListenerId: listenerId,
Domain: domain,
CertificateId: certIdentifier
}
}
CertificateId: certIdentifier,
},
},
});
this.logger.info(`更新扩展证书到实例[${this.acceleratorId}]监听[${listenerId}]域名[${domain}]成功:${JSON.stringify(res)}`);
} else {
@@ -203,12 +195,14 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId,
ListenerId: listenerId,
Certificates: [{
Id: certIdentifier,
Domain: domain
}]
}
}
Certificates: [
{
Id: certIdentifier,
Domain: domain,
},
],
},
},
});
this.logger.info(`新增扩展证书绑定到实例[${this.acceleratorId}]监听[${listenerId}]域名[${domain}]成功:${JSON.stringify(res)}`);
}
@@ -228,8 +222,8 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
throw new Error("请选择Access授权");
}
const pager = new Pager(data)
pager.pageSize = 50
const pager = new Pager(data);
pager.pageSize = 50;
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
@@ -242,9 +236,9 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
RegionId: "cn-hangzhou",
PageNumber: pager.pageNo,
PageSize: pager.pageSize,
State: "active"
}
}
State: "active",
},
},
});
const list = res?.Accelerators;
@@ -253,7 +247,7 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
}
const options = list.map((item: any) => {
const label = `${item.Name} (${item.AcceleratorId})`
const label = `${item.Name} (${item.AcceleratorId})`;
return {
label: label,
value: item.AcceleratorId,
@@ -279,9 +273,9 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
data: {
query: {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId
}
}
AcceleratorId: this.acceleratorId,
},
},
});
const listeners = res?.Listeners;
@@ -327,4 +321,4 @@ export class AliyunDeployCertToGA extends AbstractTaskPlugin {
}
}
new AliyunDeployCertToGA();
new AliyunDeployCertToGA();
@@ -1,15 +1,15 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertApplyPluginNames } from '@certd/plugin-cert';
import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js';
import { AliyunSslClient, CasCertId } from '../../../plugin-lib/aliyun/lib/index.js';
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames } from "@certd/plugin-cert";
import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
@IsTaskPlugin({
name: 'DeployCertToAliyunLive',
title: '阿里云-部署至直播(Live',
icon: 'svg:icon-aliyun',
name: "DeployCertToAliyunLive",
title: "阿里云-部署至直播(Live",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '部署证书到阿里云视频直播(Live)域名',
desc: "部署证书到阿里云视频直播(Live)域名",
needPlus: false,
default: {
strategy: {
@@ -18,14 +18,12 @@ import { AliyunSslClient, CasCertId } from '../../../plugin-lib/aliyun/lib/index
},
})
export class DeployCertToAliyunLive extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'uploadCertToAliyun'],
name: "output-selector",
from: [...CertApplyPluginNames, "uploadCertToAliyun"],
},
template: false,
required: true,
@@ -35,28 +33,27 @@ export class DeployCertToAliyunLive extends AbstractTaskPlugin {
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@TaskInput({
title: '证书服务接入点',
helper: '不会选就按默认',
value: 'cas.aliyuncs.com',
title: "证书服务接入点",
helper: "不会选就按默认",
value: "cas.aliyuncs.com",
component: {
name: 'a-select',
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: '德国(法兰克福)' },
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" },
],
},
required: true,
@@ -65,11 +62,11 @@ export class DeployCertToAliyunLive extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: '直播域名',
helper: '请选择要部署证书的直播域名',
typeName: 'DeployCertToAliyunLive',
title: "直播域名",
helper: "请选择要部署证书的直播域名",
typeName: "DeployCertToAliyunLive",
action: DeployCertToAliyunLive.prototype.onGetDomainList.name,
watches: ['certDomains', 'accessId'],
watches: ["certDomains", "accessId"],
pager: true,
search: true,
})
@@ -79,18 +76,18 @@ export class DeployCertToAliyunLive extends AbstractTaskPlugin {
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云直播');
this.logger.info("开始部署证书到阿里云直播");
const access = await this.getAccess<AliyunAccess>(this.accessId);
if (this.cert == null) {
throw new Error('域名证书参数为空,请检查前置任务');
throw new Error("域名证书参数为空,请检查前置任务");
}
const client = await this.getClient(access);
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: this.endpoint || 'cas.aliyuncs.com',
endpoint: this.endpoint || "cas.aliyuncs.com",
});
// 确保证书已上传到 CAS,统一使用 cas 方式部署
@@ -98,39 +95,39 @@ export class DeployCertToAliyunLive extends AbstractTaskPlugin {
// const certName = this.appendTimeSuffix(this.certName || casCert.certName);
for (const domain of this.domainList) {
const res = await client.doRequest({
action: 'SetLiveDomainCertificate',
version: '2016-11-01',
protocol: 'HTTPS',
action: "SetLiveDomainCertificate",
version: "2016-11-01",
protocol: "HTTPS",
data: {
query: {
DomainName: domain,
CertName: casCert.certName,
CertType: 'cas',
SSLProtocol: 'on',
CertType: "cas",
SSLProtocol: "on",
CertId: casCert.certId,
},
},
});
this.logger.info('部署直播域名[' + domain + ']证书成功:' + JSON.stringify(res));
this.logger.info("部署直播域名[" + domain + "]证书成功:" + JSON.stringify(res));
}
}
async getClient(access: AliyunAccess) {
const endpoint = 'live.aliyuncs.com';
const endpoint = "live.aliyuncs.com";
return access.getClient(endpoint);
}
async onGetDomainList(data: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const res = await client.doRequest({
action: 'DescribeLiveUserDomains',
version: '2016-11-01',
protocol: 'HTTPS',
action: "DescribeLiveUserDomains",
version: "2016-11-01",
protocol: "HTTPS",
data: {
query: {
DomainName: data.searchKey || undefined,
@@ -142,7 +139,7 @@ export class DeployCertToAliyunLive extends AbstractTaskPlugin {
const list = res?.Domains?.PageData;
if (!list || list.length === 0) {
throw new Error('没有找到直播域名,请先在阿里云添加直播域名');
throw new Error("没有找到直播域名,请先在阿里云添加直播域名");
}
const options = list.map((item: any) => {
@@ -1,19 +1,16 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertInfo, CertReader } from "@certd/plugin-cert";
import {
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from "@certd/plugin-lib";
import { CertApplyPluginNames} from '@certd/plugin-cert';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { CertApplyPluginNames } from "@certd/plugin-cert";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunClient, AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
import { AliyunClientV2 } from '../../../plugin-lib/aliyun/lib/aliyun-client-v2.js';
import { AliyunClientV2 } from "../../../plugin-lib/aliyun/lib/aliyun-client-v2.js";
@IsTaskPlugin({
name: 'AliyunDeployCertToNLB',
title: '阿里云-部署至NLB(网络负载均衡)',
icon: 'svg:icon-aliyun',
name: "AliyunDeployCertToNLB",
title: "阿里云-部署至NLB(网络负载均衡)",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: 'NLB,网络负载均衡,更新监听器的默认证书',
desc: "NLB,网络负载均衡,更新监听器的默认证书",
needPlus: false,
default: {
strategy: {
@@ -23,11 +20,11 @@ import { AliyunClientV2 } from '../../../plugin-lib/aliyun/lib/aliyun-client-v2.
})
export class AliyunDeployCertToNLB 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,
})
@@ -36,17 +33,16 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: '证书接入点',
helper: '不会选就保持默认即可',
value: 'cas.aliyuncs.com',
title: "证书接入点",
helper: "不会选就保持默认即可",
value: "cas.aliyuncs.com",
component: {
name: 'a-select',
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: '德国(法兰克福)' },
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" },
],
},
required: true,
@@ -54,11 +50,11 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
casEndpoint!: string;
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
@@ -66,68 +62,64 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: 'NLB所在地区',
typeName: 'AliyunDeployCertToNLB',
title: "NLB所在地区",
typeName: "AliyunDeployCertToNLB",
single: true,
action: AliyunDeployCertToNLB.prototype.onGetRegionList.name,
watches: ['accessId'],
watches: ["accessId"],
})
)
regionId: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '负载均衡列表',
helper: '要部署证书的负载均衡ID',
typeName: 'AliyunDeployCertToNLB',
title: "负载均衡列表",
helper: "要部署证书的负载均衡ID",
typeName: "AliyunDeployCertToNLB",
action: AliyunDeployCertToNLB.prototype.onGetLoadBalanceList.name,
watches: ['regionId'],
watches: ["regionId"],
})
)
loadBalancers!: string[];
@TaskInput(
createRemoteSelectInputDefine({
title: '监听器列表',
helper: '要部署证书的监听器列表',
typeName: 'AliyunDeployCertToNLB',
title: "监听器列表",
helper: "要部署证书的监听器列表",
typeName: "AliyunDeployCertToNLB",
action: AliyunDeployCertToNLB.prototype.onGetListenerList.name,
watches: ['loadBalancers'],
watches: ["loadBalancers"],
})
)
listeners!: string[];
@TaskInput({
title: "部署证书类型",
value: "default",
component: {
name: "a-select",
vModel: "value",
options: [
{
label: "默认证书",
value: "default"
},
{
label: "扩展证书",
value: "extension"
}
]
},
required: true
}
)
deployType: string = "default";
title: "部署证书类型",
value: "default",
component: {
name: "a-select",
vModel: "value",
options: [
{
label: "默认证书",
value: "default",
},
{
label: "扩展证书",
value: "extension",
},
],
},
required: true,
})
deployType = "default";
async onInstance() {}
async getLBClient(access: AliyunAccess, region: string) {
const client = new AliyunClient({ logger: this.logger });
const version = '2022-04-30';
const version = "2022-04-30";
await client.init({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
@@ -152,16 +144,16 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
}
this.logger.info(`准备开始清理过期证书`);
await this.ctx.utils.sleep(30000)
await this.ctx.utils.sleep(30000);
for (const listener of this.listeners) {
try{
try {
await this.clearInvalidCert(nlbClientV2, listener);
}catch(e){
} catch (e) {
this.logger.error(`清理监听器${listener}的过期证书失败`, e);
}
}
this.logger.info('执行完成');
this.logger.info("执行完成");
}
async deployExtensionCert(client: AliyunClientV2, certId: any) {
@@ -176,32 +168,30 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
body: {
ListenerId: listenerId,
RegionId: this.regionId,
"AdditionalCertificateIds.1": certId
}
}
"AdditionalCertificateIds.1": certId,
},
},
});
this.logger.info(`部署监听器${listenerId}的扩展证书成功`);
}
}
async deployDefaultCert(certId: any, client: AliyunClient) {
for (const listener of this.listeners) {
//查询原来的证书
const params: any = {
RegionId: this.regionId,
ListenerId: listener,
CertificateIds:[certId], //旧sdk
CertificateIds: [certId], //旧sdk
};
const res = await client.request('UpdateListenerAttribute', params);
const res = await client.request("UpdateListenerAttribute", params);
this.checkRet(res);
this.logger.info(`部署${listener}监听器证书成功`, JSON.stringify(res));
}
}
getNLBClientV2(access: AliyunAccess) {
return access.getClient(`nlb.${this.regionId}.aliyuncs.com`);
}
@@ -216,24 +206,23 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
data: {
body: {
ListenerId: listener,
RegionId: this.regionId
}
}
RegionId: this.regionId,
},
},
};
const res = await client.doRequest(req);
const list = res.Certificates;
if (list.length === 0) {
this.logger.info(`监听器${listener}没有绑定证书`);
return
return;
}
const sslClient = new AliyunSslClient({
access: client.access,
logger: this.logger,
endpoint: this.casEndpoint
endpoint: this.casEndpoint,
});
const certIds = [];
for (const item of list) {
this.logger.info(`监听器${listener}绑定的证书${item.CertificateId},status:${item.Status},IsDefault:${item.IsDefault}`);
@@ -243,7 +232,7 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
if (item.IsDefault) {
continue;
}
certIds.push( parseInt(item.CertificateId));
certIds.push(parseInt(item.CertificateId));
}
this.logger.info(`监听器${listener}绑定的证书${certIds}`);
//检查是否过期,过期则删除
@@ -257,14 +246,14 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
}
if (invalidCertIds.length === 0) {
this.logger.info(`监听器${listener}没有过期的证书`);
return
return;
}
this.logger.info(`开始解绑过期的证书:${invalidCertIds},listener:${listener}`);
const ids:any = {}
let i = 0
const ids: any = {};
let i = 0;
for (const certId of invalidCertIds) {
i++
i++;
ids[`AdditionalCertificateIds.${i}`] = certId;
}
await client.doRequest({
@@ -276,17 +265,16 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
body: {
ListenerId: listener,
RegionId: this.regionId,
...ids
}
}
...ids,
},
},
});
this.logger.info(`解绑过期证书成功`);
}
async getAliyunCertId(access: AliyunAccess) {
let certId: any = this.cert;
if (typeof this.cert === 'object') {
if (typeof this.cert === "object") {
const casCert = this.cert as CasCertId;
if (casCert.certId) {
return casCert.certId;
@@ -300,7 +288,7 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
endpoint: this.casEndpoint,
});
const certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt))
const certName = this.buildCertName(CertReader.getMainDomain(certInfo.crt));
const certIdRes = await sslClient.uploadCertificate({
name: certName,
@@ -313,15 +301,15 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
async onGetRegionList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(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) => {
@@ -335,10 +323,10 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
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<AliyunAccess>(this.accessId);
const client = await this.getLBClient(access, this.regionId);
@@ -346,10 +334,10 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
const params = {
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) => {
@@ -363,10 +351,10 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
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<AliyunAccess>(this.accessId);
const client = await this.getLBClient(access, this.regionId);
@@ -377,10 +365,10 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
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('没有找到TCPSSL监听器');
throw new Error("没有找到TCPSSL监听器");
}
return res.Listeners.map((item: any) => {
@@ -1,19 +1,16 @@
import { optionsUtils } from "@certd/basic";
import { AbstractTaskPlugin, IsTaskPlugin, Pager, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertApplyPluginNames, CertInfo } from '@certd/plugin-cert';
import {
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from '@certd/plugin-lib';
import { AbstractTaskPlugin, IsTaskPlugin, Pager, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { isArray } from "lodash-es";
import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js';
import { CasCertId } from '../../../plugin-lib/aliyun/lib/index.js';
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
@IsTaskPlugin({
name: 'DeployCertToAliyunOSS',
title: '阿里云-部署证书至OSS',
icon: 'svg:icon-aliyun',
name: "DeployCertToAliyunOSS",
title: "阿里云-部署证书至OSS",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '部署域名证书至阿里云OSS自定义域名,不是上传到阿里云oss',
desc: "部署域名证书至阿里云OSS自定义域名,不是上传到阿里云oss",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
@@ -21,13 +18,12 @@ import { CasCertId } from '../../../plugin-lib/aliyun/lib/index.js';
},
})
export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames,"uploadCertToAliyun"],
name: "output-selector",
from: [...CertApplyPluginNames, "uploadCertToAliyun"],
},
required: true,
})
@@ -36,42 +32,41 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: '大区',
title: "大区",
component: {
name: 'a-auto-complete',
vModel: 'value',
name: "a-auto-complete",
vModel: "value",
options: [
{ value: 'oss-cn-hangzhou', label: '华东1(杭州)' },
{ value: 'oss-cn-shanghai', label: '华东2(上海)' },
{ value: 'oss-cn-nanjing', label: '华东5(南京-本地地域)' },
{ value: 'oss-cn-fuzhou', label: '华东6(福州-本地地域)' },
{ value: 'oss-cn-wuhan-lr', label: '华中1(武汉-本地地域)' },
{ value: 'oss-cn-qingdao', label: '华北1(青岛)' },
{ value: 'oss-cn-beijing', label: '华北2(北京)' },
{ value: 'oss-cn-zhangjiakou', label: '华北 3(张家口)' },
{ value: 'oss-cn-huhehaote', label: '华北5(呼和浩特)' },
{ value: 'oss-cn-wulanchabu', label: '华北6(乌兰察布)' },
{ value: 'oss-cn-shenzhen', label: '华南1(深圳)' },
{ value: 'oss-cn-heyuan', label: '华南2(河源)' },
{ value: 'oss-cn-guangzhou', label: '华南3(广州)' },
{ value: 'oss-cn-chengdu', label: '西南1(成都)' },
{ value: 'oss-cn-hongkong', label: '中国香港' },
{ value: 'oss-us-west-1', label: '美国(硅谷)①' },
{ value: 'oss-us-east-1', label: '美国(弗吉尼亚)①' },
{ value: 'oss-ap-northeast-1', label: '日本(东京)①' },
{ value: 'oss-ap-northeast-2', label: '韩国(首尔)' },
{ value: 'oss-ap-southeast-1', label: '新加坡①' },
{ value: 'oss-ap-southeast-2', label: '澳大利亚(悉尼)①' },
{ value: 'oss-ap-southeast-3', label: '马来西亚(吉隆坡)①' },
{ value: 'oss-ap-southeast-5', label: '印度尼西亚(雅加达)①' },
{ value: 'oss-ap-southeast-6', label: '菲律宾(马尼拉)' },
{ value: 'oss-ap-southeast-7', label: '泰国(曼谷)' },
{ value: 'oss-eu-central-1', label: '德国(法兰克福)①' },
{ value: 'oss-eu-west-1', label: '英国(伦敦)' },
{ value: 'oss-me-east-1', label: '阿联酋(迪拜)①' },
{ value: 'oss-rg-china-mainland', label: '无地域属性(中国内地)' },
{ value: "oss-cn-hangzhou", label: "华东1(杭州)" },
{ value: "oss-cn-shanghai", label: "华东2(上海)" },
{ value: "oss-cn-nanjing", label: "华东5(南京-本地地域)" },
{ value: "oss-cn-fuzhou", label: "华东6(福州-本地地域)" },
{ value: "oss-cn-wuhan-lr", label: "华中1(武汉-本地地域)" },
{ value: "oss-cn-qingdao", label: "华北1(青岛)" },
{ value: "oss-cn-beijing", label: "华北2(北京)" },
{ value: "oss-cn-zhangjiakou", label: "华北 3(张家口)" },
{ value: "oss-cn-huhehaote", label: "华北5(呼和浩特)" },
{ value: "oss-cn-wulanchabu", label: "华北6(乌兰察布)" },
{ value: "oss-cn-shenzhen", label: "华南1(深圳)" },
{ value: "oss-cn-heyuan", label: "华南2(河源)" },
{ value: "oss-cn-guangzhou", label: "华南3(广州)" },
{ value: "oss-cn-chengdu", label: "西南1(成都)" },
{ value: "oss-cn-hongkong", label: "中国香港" },
{ value: "oss-us-west-1", label: "美国(硅谷)①" },
{ value: "oss-us-east-1", label: "美国(弗吉尼亚)①" },
{ value: "oss-ap-northeast-1", label: "日本(东京)①" },
{ value: "oss-ap-northeast-2", label: "韩国(首尔)" },
{ value: "oss-ap-southeast-1", label: "新加坡①" },
{ value: "oss-ap-southeast-2", label: "澳大利亚(悉尼)①" },
{ value: "oss-ap-southeast-3", label: "马来西亚(吉隆坡)①" },
{ value: "oss-ap-southeast-5", label: "印度尼西亚(雅加达)①" },
{ value: "oss-ap-southeast-6", label: "菲律宾(马尼拉)" },
{ value: "oss-ap-southeast-7", label: "泰国(曼谷)" },
{ value: "oss-eu-central-1", label: "德国(法兰克福)①" },
{ value: "oss-eu-west-1", label: "英国(伦敦)" },
{ value: "oss-me-east-1", label: "阿联酋(迪拜)①" },
{ value: "oss-rg-china-mainland", label: "无地域属性(中国内地)" },
],
},
required: true,
@@ -79,49 +74,48 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
region!: string;
@TaskInput({
title: 'Bucket',
helper: '存储桶名称',
title: "Bucket",
helper: "存储桶名称",
component: {
name: 'remote-auto-complete',
vModel: 'value',
type: 'plugin',
action: 'onGetBucketList',
name: "remote-auto-complete",
vModel: "value",
type: "plugin",
action: "onGetBucketList",
search: false,
pager: false,
watches: ['accessId', 'region']
watches: ["accessId", "region"],
},
required: true,
})
bucket!: string;
@TaskInput(createRemoteSelectInputDefine({
title: '绑定的域名',
helper: '你在阿里云OSS上绑定的域名,比如:certd.docmirror.cn',
required: true,
action: DeployCertToAliyunOSS.prototype.onGetDomainList.name,
watches: ['certDomains', 'accessId','bucket'],
}))
@TaskInput(
createRemoteSelectInputDefine({
title: "绑定的域名",
helper: "你在阿里云OSS上绑定的域名,比如:certd.docmirror.cn",
required: true,
action: DeployCertToAliyunOSS.prototype.onGetDomainList.name,
watches: ["certDomains", "accessId", "bucket"],
})
)
domainName!: string | string[];
@TaskInput({
title: '证书名称',
helper: '上传后将以此名称作为前缀备注',
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
})
certName!: string;
@TaskInput({
title: '证书服务接入点',
helper: '不会选就按默认',
value: 'cn-hangzhou',
title: "证书服务接入点",
helper: "不会选就按默认",
value: "cn-hangzhou",
component: {
name: 'a-select',
name: "a-select",
options: [
{ value: 'cn-hangzhou', label: '中国大陆' },
{ value: 'ap-southeast-1', label: '新加坡' },
{ value: 'eu-central-1', label: '德国(法兰克福)' },
{ value: "cn-hangzhou", label: "中国大陆" },
{ value: "ap-southeast-1", label: "新加坡" },
{ value: "eu-central-1", label: "德国(法兰克福)" },
],
},
required: true,
@@ -130,11 +124,11 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
casRegion!: string;
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
order: -98,
@@ -143,44 +137,43 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云OSS');
this.logger.info("开始部署证书到阿里云OSS");
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
this.logger.info(`bucket: ${this.bucket}, region: ${this.region}, domainName: ${this.domainName}`);
const client = await this.getClient(access);
if (typeof this.domainName === "string"){
if (typeof this.domainName === "string") {
this.domainName = [this.domainName];
}
for (const domainName of this.domainName) {
this.logger.info("开始部署证书到阿里云oss自定义域名:", domainName)
await this.updateCert(domainName,client, {});
this.logger.info("开始部署证书到阿里云oss自定义域名:", domainName);
await this.updateCert(domainName, client, {});
}
this.logger.info('部署完成');
this.logger.info("部署完成");
}
async updateCert(domainName:string,client: any, params: any) {
params = client._bucketRequestParams('POST', this.bucket, {
cname: '',
comp: 'add',
async updateCert(domainName: string, client: any, params: any) {
params = client._bucketRequestParams("POST", this.bucket, {
cname: "",
comp: "add",
});
let certStr = ""
let certStr = "";
if (typeof this.cert === "object" ){
if (typeof this.cert === "object") {
const certInfo = this.cert as CertInfo;
if (certInfo.crt){
if (certInfo.crt) {
certStr = `
<PrivateKey>${certInfo.key}</PrivateKey>
<Certificate>${certInfo.crt}</Certificate>
`
}else {
`;
} else {
const casCert = this.cert as CasCertId;
certStr = `<CertId>${casCert.certIdentifier}</CertId>`
certStr = `<CertId>${casCert.certIdentifier}</CertId>`;
}
}else {
certStr = `<CertId>${this.cert}-${this.casRegion}</CertId>`
} else {
certStr = `<CertId>${this.cert}-${this.casRegion}</CertId>`;
}
const xml = `
@@ -194,17 +187,16 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
</Cname>
</BucketCnameConfiguration>`;
params.content = xml;
params.mime = 'xml';
params.mime = "xml";
params.successStatuses = [200];
const res = await client.request(params);
this.checkRet(res);
return res;
}
async getClient(access: AliyunAccess) {
// @ts-ignore
const OSS = await import('ali-oss');
const OSS = await import("ali-oss");
return new OSS.default({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
@@ -218,34 +210,30 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
}
async onGetBucketList(data: Pager) {
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
const client = await this.getClient(access);
let res;
const buckets = []
do{
const requestData = {'marker': res?.nextMarker || null, 'max-keys': 1000};
res = await client.listBuckets(requestData)
buckets.push(...(res?.buckets || []))
} while (!!res?.nextMarker)
return buckets
.filter(bucket => bucket?.region === this.region)
.map(bucket => ({label: `${bucket.name}<${bucket.region}>`, value: bucket.name}));
const buckets = [];
do {
const requestData = { marker: res?.nextMarker || null, "max-keys": 1000 };
res = await client.listBuckets(requestData);
buckets.push(...(res?.buckets || []));
} while (!!res?.nextMarker);
return buckets.filter(bucket => bucket?.region === this.region).map(bucket => ({ label: `${bucket.name}<${bucket.region}>`, value: bucket.name }));
}
async onGetDomainList(data: any) {
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
const client = await this.getClient(access);
const res = await this.doListCnameRequest(client,this.bucket)
let domains = res.data?.Cname
if (domains == null || domains.length === 0){
return []
const res = await this.doListCnameRequest(client, this.bucket);
let domains = res.data?.Cname;
if (domains == null || domains.length === 0) {
return [];
}
if (!isArray(domains)){
domains = [domains]
if (!isArray(domains)) {
domains = [domains];
}
const options = domains.map((item: any) => {
@@ -258,13 +246,12 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
async doListCnameRequest(client: any,bucket:string) {
const params = client._bucketRequestParams('GET', this.bucket, {
cname: '',
bucket
async doListCnameRequest(client: any, bucket: string) {
const params = client._bucketRequestParams("GET", this.bucket, {
cname: "",
bucket,
});
params.mime = 'xml';
params.mime = "xml";
params.successStatuses = [200];
params.xmlResponse = true;
const res = await client.request(params);
@@ -272,11 +259,9 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
return res;
}
checkRet(ret: any) {
if (ret.Code != null || ret.status!==200) {
throw new Error('执行失败:' + ret.Message || ret.data);
if (ret.Code != null || ret.status !== 200) {
throw new Error("执行失败:" + ret.Message || ret.data);
}
}
}
@@ -1,19 +1,16 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo } from '@certd/plugin-cert';
import {
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from '@certd/plugin-lib';
import { CertApplyPluginNames } from '@certd/plugin-cert';
import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js';
import { AliyunClient, AliyunSslClient, CasCertInfo } from '../../../plugin-lib/aliyun/lib/index.js';
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertInfo } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { CertApplyPluginNames } from "@certd/plugin-cert";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunClient, AliyunSslClient, CasCertInfo } from "../../../plugin-lib/aliyun/lib/index.js";
@IsTaskPlugin({
name: 'AliyunDeployCertToSLB',
title: '阿里云-部署至CLB(传统负载均衡)',
icon: 'svg:icon-aliyun',
name: "AliyunDeployCertToSLB",
title: "阿里云-部署至CLB(传统负载均衡)",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '部署证书到阿里云CLB(传统负载均衡)',
desc: "部署证书到阿里云CLB(传统负载均衡)",
needPlus: false,
default: {
strategy: {
@@ -23,11 +20,11 @@ import { AliyunClient, AliyunSslClient, CasCertInfo } from '../../../plugin-lib/
})
export class AliyunDeployCertToSLB 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,
})
@@ -36,77 +33,71 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: '证书接入点',
helper: '不会选就保持默认即可',
value: 'cas.aliyuncs.com',
title: "证书接入点",
helper: "不会选就保持默认即可",
value: "cas.aliyuncs.com",
component: {
name: 'a-select',
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: '德国(法兰克福)' },
{ 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',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: 'LB所在地区',
title: "LB所在地区",
single: true,
action: AliyunDeployCertToSLB.prototype.onGetRegionList.name,
watches: ['accessId'],
watches: ["accessId"],
})
)
regionId: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '负载均衡列表',
helper: '要部署证书的负载均衡ID',
title: "负载均衡列表",
helper: "要部署证书的负载均衡ID",
action: AliyunDeployCertToSLB.prototype.onGetLoadBalanceList.name,
watches: ['regionId'],
watches: ["regionId"],
})
)
loadBalancers!: string[];
@TaskInput(
createRemoteSelectInputDefine({
title: '监听器列表',
helper: '要部署证书的监听器列表',
title: "监听器列表",
helper: "要部署证书的监听器列表",
action: AliyunDeployCertToSLB.prototype.onGetListenerList.name,
watches: ['loadBalancers'],
watches: ["loadBalancers"],
})
)
listeners!: string[];
@TaskInput({
title: "部署默认证书",
value: true,
component: {
name: "a-switch",
vModel: "checked"
}
}
)
vModel: "checked",
},
})
deployDefault!: boolean;
@TaskInput({
@@ -114,37 +105,33 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
value: false,
component: {
name: "a-switch",
vModel: "checked"
}
}
)
vModel: "checked",
},
})
deployExtension!: boolean;
@TaskInput(
createRemoteSelectInputDefine({
title: '扩展域名列表',
helper: '要部署扩展域名列表',
title: "扩展域名列表",
helper: "要部署扩展域名列表",
action: AliyunDeployCertToSLB.prototype.onGetExtensionDomainList.name,
watches: ['listeners', 'deployExtension'],
watches: ["listeners", "deployExtension"],
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.deployExtension;
})
}
`
`,
})
)
extensionDomains!: string[];
async onInstance() {
}
async onInstance() {}
async getLBClient(access: AliyunAccess, region: string) {
const client = new AliyunClient({ logger: this.logger });
const version = '2014-05-15';
const version = "2014-05-15";
await client.init({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
@@ -164,9 +151,9 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
const slbServerCertId = await this.uploadServerCert(client, aliyunCert);
if (this.deployDefault !== false) {
this.logger.info("部署监听器默认证书")
this.logger.info("部署监听器默认证书");
for (const listener of this.listeners) {
const { port, loadBalanceId } = this.resolveListenerKey(listener)
const { port, loadBalanceId } = this.resolveListenerKey(listener);
const params = {
RegionId: this.regionId,
LoadBalancerId: loadBalanceId,
@@ -174,18 +161,18 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
ServerCertificateId: slbServerCertId,
};
const res = await client.request('SetLoadBalancerHTTPSListenerAttribute', params);
const res = await client.request("SetLoadBalancerHTTPSListenerAttribute", params);
this.checkRet(res);
this.logger.info(`部署${listener}监听器证书成功`, JSON.stringify(res));
}
}
if (this.deployExtension) {
this.logger.info("部署监听器扩展域名证书")
this.logger.info("部署监听器扩展域名证书");
const clientV2 = this.getCLBClientV2(access);
for (const domainStr of this.extensionDomains) {
const { extensionDomainId } = this.resolveListenerKey(domainStr)
const { extensionDomainId } = this.resolveListenerKey(domainStr);
const res = await clientV2.doRequest({
action: "SetDomainExtensionAttribute",
// 接口版本
@@ -194,22 +181,22 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
query: {
RegionId: this.regionId,
DomainExtensionId: extensionDomainId,
ServerCertificateId: slbServerCertId
}
}
})
this.logger.info(`部署扩展域名${extensionDomainId}证书成功`, JSON.stringify(res))
ServerCertificateId: slbServerCertId,
},
},
});
this.logger.info(`部署扩展域名${extensionDomainId}证书成功`, JSON.stringify(res));
}
}
this.logger.info('执行完成');
this.logger.info("执行完成");
}
getCLBClientV2(access: AliyunAccess) {
return access.getClient(`slb.${this.regionId}.aliyuncs.com`)
return access.getClient(`slb.${this.regionId}.aliyuncs.com`);
}
resolveListenerKey(listener: string) {
const arr = listener.split('_');
const arr = listener.split("_");
const loadBalanceId = arr[0];
const protocol = arr[1];
const port = arr[2];
@@ -222,8 +209,8 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
loadBalanceId,
port: parseInt(port),
extensionDomainId: extensionDomainId,
protocol: protocol
}
protocol: protocol,
};
}
async uploadServerCert(client: any, aliyunCert: CasCertInfo) {
@@ -231,12 +218,12 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
RegionId: this.regionId,
AliCloudCertificateId: aliyunCert.certId,
AliCloudCertificateName: aliyunCert.certName,
AliCloudCertificateRegionId: aliyunCert.casRegion
AliCloudCertificateRegionId: aliyunCert.casRegion,
};
const res = await client.request('UploadServerCertificate', params);
const res = await client.request("UploadServerCertificate", params);
this.checkRet(res);
this.logger.info('SLBServerCertificate创建成功', res.ServerCertificateId);
this.logger.info("SLBServerCertificate创建成功", res.ServerCertificateId);
return res.ServerCertificateId;
}
@@ -249,8 +236,8 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
endpoint: this.casEndpoint,
});
if (typeof this.cert === 'object') {
const name = this.appendTimeSuffix('certd');
if (typeof this.cert === "object") {
const name = this.appendTimeSuffix("certd");
const casCert = this.cert as CasCertInfo;
if (casCert.certIdentifier) {
@@ -263,8 +250,6 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
});
certId = certIdRes.certId as any;
}
}
return await sslClient.getCertInfo(certId);
@@ -272,15 +257,15 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
async onGetRegionList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(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?.Region || res?.Regions?.Region.length === 0) {
throw new Error('没有找到Regions列表');
throw new Error("没有找到Regions列表");
}
return res.Regions.Region.map((item: any) => {
@@ -294,10 +279,10 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
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<AliyunAccess>(this.accessId);
const client = await this.getLBClient(access, this.regionId);
@@ -306,10 +291,10 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
RegionId: this.regionId,
MaxResults: 100,
};
const res = await client.request('DescribeLoadBalancers', params);
const res = await client.request("DescribeLoadBalancers", params);
this.checkRet(res);
if (!res?.LoadBalancers?.LoadBalancer || res?.LoadBalancers.LoadBalancer.length === 0) {
throw new Error('没有找到LoadBalancers');
throw new Error("没有找到LoadBalancers");
}
return res.LoadBalancers.LoadBalancer.map((item: any) => {
@@ -323,10 +308,10 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
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<AliyunAccess>(this.accessId);
const client = await this.getLBClient(access, this.regionId);
@@ -334,15 +319,15 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
const params: any = {
MaxResults: 100,
RegionId: this.regionId,
ListenerProtocol: 'HTTPS',
ListenerProtocol: "HTTPS",
};
if (this.loadBalancers && this.loadBalancers.length > 0) {
params.LoadBalancerId = this.loadBalancers;
}
const res = await client.request('DescribeLoadBalancerListeners', params);
const res = await client.request("DescribeLoadBalancerListeners", 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) => {
@@ -357,46 +342,39 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
async onGetExtensionDomainList(data: PageSearch) {
if (!this.accessId) {
throw new Error('请先选择Access授权');
throw new Error("请先选择Access授权");
}
if (!this.regionId) {
throw new Error('请先选择地区');
throw new Error("请先选择地区");
}
if (!this.listeners && this.listeners.length == 0) {
throw new Error('请先选择监听器');
throw new Error("请先选择监听器");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const allDomains: any[] = []
const allDomains: any[] = [];
for (const ls of this.listeners) {
const { port, loadBalanceId, protocol } = this.resolveListenerKey(ls)
const { port, loadBalanceId, protocol } = this.resolveListenerKey(ls);
const domains = await this.doGetExtensionDomainList({
access,
loadBalancerId: loadBalanceId,
listenerPort: port,
listenerProtocol: protocol,
});
allDomains.push(...domains)
allDomains.push(...domains);
}
return this.ctx.utils.options.buildGroupOptions(allDomains, this.certDomains)
return this.ctx.utils.options.buildGroupOptions(allDomains, this.certDomains);
}
async doGetExtensionDomainList(data: {
loadBalancerId: string,
listenerPort: number,
listenerProtocol: string,
access: AliyunAccess
}) {
async doGetExtensionDomainList(data: { loadBalancerId: string; listenerPort: number; listenerProtocol: string; access: AliyunAccess }) {
const { loadBalancerId, listenerPort, listenerProtocol, access } = data;
const client = access.getClient(`slb.${this.regionId}.aliyuncs.com`)
const client = access.getClient(`slb.${this.regionId}.aliyuncs.com`);
let queries = {
const queries = {
RegionId: this.regionId,
LoadBalancerId: loadBalancerId,
ListenerPort: listenerPort
ListenerPort: listenerPort,
};
const res = await client.doRequest({
@@ -406,13 +384,13 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
version: "2014-05-15",
data: {
query: queries,
}
})
},
});
this.checkRet(res);
const list = res?.DomainExtensions.DomainExtension;
if (!list || list.length === 0) {
return []
return [];
}
return list.map((i: any) => {
@@ -421,12 +399,11 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
return {
value: value,
label: label,
domain: i.Domain
domain: i.Domain,
};
});
}
checkRet(ret: any) {
if (ret.Code != null) {
throw new Error(ret.Message);
@@ -12,9 +12,9 @@ import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class AliyunDeployCertToVod extends AbstractTaskPlugin {
@TaskInput({
@@ -22,9 +22,9 @@ export class AliyunDeployCertToVod extends AbstractTaskPlugin {
helper: "请选择证书申请任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
from: [...CertApplyPluginNames],
},
required: true
required: true,
})
cert!: CertInfo;
@@ -55,22 +55,21 @@ export class AliyunDeployCertToVod extends AbstractTaskPlugin {
options: [
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
{ value: "cas.eu-central-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"
type: "aliyun",
},
required: true
required: true,
})
accessId!: string;
@@ -81,15 +80,12 @@ export class AliyunDeployCertToVod extends AbstractTaskPlugin {
action: AliyunDeployCertToVod.prototype.onGetDomainList.name,
watches: ["accessId"],
pager: true,
search: true
search: true,
})
)
domainList!: string[];
async onInstance() {
}
async onInstance() {}
async execute(): Promise<void> {
this.logger.info("开始部署证书到阿里云VOD");
@@ -97,7 +93,6 @@ export class AliyunDeployCertToVod extends AbstractTaskPlugin {
const client = await this.getClient(access);
for (const siteId of this.domainList) {
/**
* let queries : {[key: string ]: any} = { };
@@ -122,9 +117,9 @@ export class AliyunDeployCertToVod extends AbstractTaskPlugin {
CertName: this.appendTimeSuffix("certd"),
SSLProtocol: "on",
SSLPub: this.cert.crt,
SSLPri: this.cert.key
}
}
SSLPri: this.cert.key,
},
},
});
this.logger.info(`部署站点[${siteId}]证书成功:${JSON.stringify(res)}`);
}
@@ -161,9 +156,9 @@ export class AliyunDeployCertToVod extends AbstractTaskPlugin {
query: {
DomainName: data.searchKey,
PageNumber: data.pageNo,
PageSize: data.pageSize
}
}
PageSize: data.pageSize,
},
},
});
const list = res?.Domains.PageData;
@@ -175,12 +170,11 @@ export class AliyunDeployCertToVod extends AbstractTaskPlugin {
return {
label: item.DomainName,
value: item.DomainName,
domain: item.DomainName
domain: item.DomainName,
};
});
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
}
}
new AliyunDeployCertToVod();
@@ -1,18 +1,15 @@
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 { 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: 'AliyunDeployCertToWafCloud',
title: '阿里云-部署至阿里云WAF(云产品接入)',
icon: 'svg:icon-aliyun',
name: "AliyunDeployCertToWafCloud",
title: "阿里云-部署至阿里云WAF(云产品接入)",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '部署证书到阿里云WAF(云产品接入),CNAME方式接入的请选择另外一个waf插件',
desc: "部署证书到阿里云WAF(云产品接入),CNAME方式接入的请选择另外一个waf插件",
needPlus: false,
default: {
strategy: {
@@ -22,11 +19,11 @@ import { AliyunClient, AliyunSslClient, CasCertInfo } from "../../../plugin-lib/
})
export class AliyunDeployCertToWafCloud 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,
})
@@ -36,14 +33,14 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: 'WAF接入点',
helper: '不会选就按默认',
value: 'cn-hangzhou',
title: "WAF接入点",
helper: "不会选就按默认",
value: "cn-hangzhou",
component: {
name: 'a-select',
name: "a-select",
options: [
{ value: 'cn-hangzhou', label: '中国内地' },
{ value: 'ap-southeast-1', label: '非中国内地' },
{ value: "cn-hangzhou", label: "中国内地" },
{ value: "ap-southeast-1", label: "非中国内地" },
],
},
required: true,
@@ -51,15 +48,15 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
regionId!: string;
@TaskInput({
title: '证书接入点',
helper: '跟上面保持一致即可',
value: 'cas.aliyuncs.com',
title: "证书接入点",
helper: "跟上面保持一致即可",
value: "cas.aliyuncs.com",
component: {
name: 'a-select',
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: '德国(法兰克福)' },
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" },
],
},
required: true,
@@ -67,11 +64,11 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
casEndpoint!: string;
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
@@ -79,33 +76,31 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: '云产品资源',
helper: '请选择要部署证书的云产品资源',
title: "云产品资源",
helper: "请选择要部署证书的云产品资源",
action: AliyunDeployCertToWafCloud.prototype.onGetCloudResourceList.name,
watches: ['accessId', 'regionId'],
watches: ["accessId", "regionId"],
pager: true,
search: true,
})
)
cloudResources!: string[];
@TaskInput({
title: '证书部署类型',
value: 'default',
title: "证书部署类型",
value: "default",
component: {
name: 'a-select',
name: "a-select",
options: [
{ value: 'default', label: '默认证书' },
{ value: 'extension', label: '扩展证书' },
{ value: "default", label: "默认证书" },
{ value: "extension", label: "扩展证书" },
],
},
required: true,
})
certType!: string;
async onInstance() { }
async onInstance() {}
async getWafClient(access: AliyunAccess) {
const client = new AliyunClient({ logger: this.logger });
@@ -113,7 +108,7 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
endpoint: `https://wafopenapi.${this.regionId}.aliyuncs.com`,
apiVersion: '2021-10-01',
apiVersion: "2021-10-01",
});
return client;
}
@@ -122,17 +117,17 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
const params = {
RegionId: this.regionId,
};
this.logger.info('调用DescribeInstance API', JSON.stringify(params));
const res = await client.request('DescribeInstance', params);
this.logger.info('获取实例ID', res.InstanceId);
this.logger.info("调用DescribeInstance API", JSON.stringify(params));
const res = await client.request("DescribeInstance", params);
this.logger.info("获取实例ID", res.InstanceId);
return res.InstanceId;
}
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云WAF(云产品接入)');
this.logger.info("开始部署证书到阿里云WAF(云产品接入)");
const access = await this.getAccess<AliyunAccess>(this.accessId);
let certId: any = this.cert;
if (typeof this.cert === 'object') {
if (typeof this.cert === "object") {
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
@@ -153,16 +148,16 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
} else if (casCert.certId) {
certId = casCert.certId;
} else {
throw new Error('证书格式错误'+JSON.stringify(this.cert));
throw new Error("证书格式错误" + JSON.stringify(this.cert));
}
}
const client = await this.getWafClient(access);
const instanceId = await this.getInstanceId(client);
for (const cloudResourceId of this.cloudResources) {
this.logger.info('开始部署', cloudResourceId);
if (this.certType === 'default') {
this.logger.info("开始部署", cloudResourceId);
if (this.certType === "default") {
// 部署默认证书
const params = {
RegionId: this.regionId,
@@ -170,10 +165,10 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
CloudResourceId: cloudResourceId,
CertId: certId,
};
this.logger.info('调用ModifyCloudResourceDefaultCert API', JSON.stringify(params));
const res = await client.request('ModifyCloudResourceDefaultCert', params);
this.logger.info('部署默认证书成功', JSON.stringify(res));
} else if (this.certType === 'extension') {
this.logger.info("调用ModifyCloudResourceDefaultCert API", JSON.stringify(params));
const res = await client.request("ModifyCloudResourceDefaultCert", params);
this.logger.info("部署默认证书成功", JSON.stringify(res));
} else if (this.certType === "extension") {
// 部署扩展证书
const addCertParams = {
RegionId: this.regionId,
@@ -181,10 +176,10 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
CloudResourceId: cloudResourceId,
CertId: certId,
};
this.logger.info('调用CreateCloudResourceExtensionCert API', JSON.stringify(addCertParams));
const addCertRes = await client.request('CreateCloudResourceExtensionCert', addCertParams);
this.logger.info('部署扩展证书成功', JSON.stringify(addCertRes));
this.logger.info("调用CreateCloudResourceExtensionCert API", JSON.stringify(addCertParams));
const addCertRes = await client.request("CreateCloudResourceExtensionCert", addCertParams);
this.logger.info("部署扩展证书成功", JSON.stringify(addCertRes));
// 清理过期扩展证书
await this.cleanupExpiredExtensionCerts(client, instanceId, cloudResourceId, certId);
}
@@ -193,20 +188,20 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
async cleanupExpiredExtensionCerts(client: AliyunClient, instanceId: string, cloudResourceId: string, currentCertId: string) {
try {
this.logger.info('开始清理过期扩展证书, cloudResourceId: ' + cloudResourceId);
this.logger.info("开始清理过期扩展证书, cloudResourceId: " + cloudResourceId);
// 解析CloudResourceId获取ResourceInstanceId
// CloudResourceId格式:{ResourceInstanceId}-{Port}-{ResourceProduct}
const resourceInfo = cloudResourceId.split('-');
const resourceInfo = cloudResourceId.split("-");
if (resourceInfo.length < 3) {
this.logger.warn('CloudResourceId格式不正确: ' + cloudResourceId);
this.logger.warn("CloudResourceId格式不正确: " + cloudResourceId);
return;
}
// 从后往前解析,因为ResourceInstanceId可能包含"-"
const product = resourceInfo.pop();
const port = resourceInfo.pop();
const resourceInstanceId = resourceInfo.join('-');
this.logger.info('ResourceInstanceId: ' + resourceInstanceId, 'Port: ' + port, 'Product: ' + product);
const resourceInstanceId = resourceInfo.join("-");
this.logger.info("ResourceInstanceId: " + resourceInstanceId, "Port: " + port, "Product: " + product);
// 查询云产品实例的证书列表
const certsParams = {
InstanceId: instanceId,
@@ -214,22 +209,22 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
ResourceInstanceId: resourceInstanceId,
PageSize: 100,
};
this.logger.info('调用DescribeResourceInstanceCerts API: ' + JSON.stringify(certsParams));
const certsRes = await client.request('DescribeResourceInstanceCerts', certsParams);
this.logger.info("调用DescribeResourceInstanceCerts API: " + JSON.stringify(certsParams));
const certsRes = await client.request("DescribeResourceInstanceCerts", certsParams);
if (!certsRes || !certsRes.Certs || certsRes.Certs.length === 0) {
this.logger.info('没有找到证书, cloudResourceId: ' + cloudResourceId);
this.logger.info("没有找到证书, cloudResourceId: " + cloudResourceId);
return;
}
this.logger.info('查询到的证书数量: ' + certsRes.Certs.length);
this.logger.info("查询到的证书数量: " + certsRes.Certs.length);
const now = Date.now();
const expiredCerts = certsRes.Certs.filter((cert: any) => {
// 检查证书是否有必要的属性
if (!cert || !cert.AfterDate || !cert.CertIdentifier) {
this.logger.warn('证书格式不正确: ' + JSON.stringify(cert));
this.logger.warn("证书格式不正确: " + JSON.stringify(cert));
return false;
}
// 检查是否为当前部署的证书
if (cert.CertIdentifier === currentCertId) {
return false;
@@ -239,13 +234,13 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
// 检查是否过期
return afterDate < now;
});
if (expiredCerts.length === 0) {
this.logger.info('没有过期的扩展证书, cloudResourceId: ' + cloudResourceId);
this.logger.info("没有过期的扩展证书, cloudResourceId: " + cloudResourceId);
return;
}
this.logger.info('过期的扩展证书数量: ' + expiredCerts.length);
this.logger.info("过期的扩展证书数量: " + expiredCerts.length);
// 删除过期的扩展证书
for (const expiredCert of expiredCerts) {
const deleteParams = {
@@ -254,26 +249,26 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
CloudResourceId: cloudResourceId,
CertId: expiredCert.CertIdentifier,
};
this.logger.info('调用DeleteCloudResourceExtensionCert API: ' + JSON.stringify(deleteParams));
const deleteRes = await client.request('DeleteCloudResourceExtensionCert', deleteParams);
this.logger.info('删除过期扩展证书成功, certId: ' + expiredCert.CertIdentifier + ', response: ' + JSON.stringify(deleteRes));
this.logger.info("调用DeleteCloudResourceExtensionCert API: " + JSON.stringify(deleteParams));
const deleteRes = await client.request("DeleteCloudResourceExtensionCert", deleteParams);
this.logger.info("删除过期扩展证书成功, certId: " + expiredCert.CertIdentifier + ", response: " + JSON.stringify(deleteRes));
}
this.logger.info('清理过期扩展证书完成, cloudResourceId: ' + cloudResourceId + ', deletedCount: ' + expiredCerts.length);
this.logger.info("清理过期扩展证书完成, cloudResourceId: " + cloudResourceId + ", deletedCount: " + expiredCerts.length);
} catch (error) {
this.logger.error('清理过期扩展证书失败, cloudResourceId: ' + cloudResourceId + ', error: ' + JSON.stringify(error));
this.logger.error("清理过期扩展证书失败, cloudResourceId: " + cloudResourceId + ", error: " + JSON.stringify(error));
// 清理失败不影响主流程
}
}
async onGetCloudResourceList(data: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getWafClient(access);
const pager = new Pager(data)
const pager = new Pager(data);
const instanceId = await this.getInstanceId(client);
const params: any = {
@@ -286,17 +281,17 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
params.ResourceInstanceId = data.searchKey;
}
this.logger.info('调用DescribeCloudResourceList API', JSON.stringify(params));
const res = await client.request('DescribeCloudResourceList', params);
this.logger.info('DescribeCloudResourceList API返回', JSON.stringify(res));
this.logger.info("调用DescribeCloudResourceList API", JSON.stringify(params));
const res = await client.request("DescribeCloudResourceList", params);
this.logger.info("DescribeCloudResourceList API返回", JSON.stringify(res));
if (!res || !res.CloudResourceList || res.CloudResourceList.length === 0) {
this.logger.warn('没有找到云产品接入的资源');
this.logger.warn("没有找到云产品接入的资源");
return {
list: [],
total: 0,
pageNo: pager.pageNo,
pageSize: pager.pageSize
pageSize: pager.pageSize,
};
}
const total = res.TotalCount || 0;
@@ -315,9 +310,9 @@ export class AliyunDeployCertToWafCloud extends AbstractTaskPlugin {
list,
total: total,
pageNo: pager.pageNo,
pageSize: pager.pageSize
pageSize: pager.pageSize,
};
}
}
new AliyunDeployCertToWafCloud();
new AliyunDeployCertToWafCloud();
@@ -1,18 +1,15 @@
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 { 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',
name: "AliyunDeployCertToWaf",
title: "阿里云-部署至阿里云WAF(cname接入)",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '部署证书到阿里云WAF(cname接入),云资源的请选择另外一个waf插件',
desc: "部署证书到阿里云WAF(cname接入),云资源的请选择另外一个waf插件",
needPlus: false,
default: {
strategy: {
@@ -22,11 +19,11 @@ import { AliyunClient, AliyunSslClient, CasCertInfo } from "../../../plugin-lib/
})
export class AliyunDeployCertToWaf 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,
})
@@ -36,14 +33,14 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: 'WAF接入点',
helper: '不会选就按默认',
value: 'cn-hangzhou',
title: "WAF接入点",
helper: "不会选就按默认",
value: "cn-hangzhou",
component: {
name: 'a-select',
name: "a-select",
options: [
{ value: 'cn-hangzhou', label: '中国大陆-华东1(杭州)' },
{ value: 'ap-southeast-1', label: '新加坡' },
{ value: "cn-hangzhou", label: "中国大陆-华东1(杭州)" },
{ value: "ap-southeast-1", label: "新加坡" },
],
},
required: true,
@@ -51,15 +48,15 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
regionId!: string;
@TaskInput({
title: '证书接入点',
helper: '跟上面保持一致即可',
value: 'cas.aliyuncs.com',
title: "证书接入点",
helper: "跟上面保持一致即可",
value: "cas.aliyuncs.com",
component: {
name: 'a-select',
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: '德国(法兰克福)' },
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" },
],
},
required: true,
@@ -67,11 +64,11 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
casEndpoint!: string;
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
@@ -79,26 +76,25 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: 'CNAME站点',
helper: '请选择要部署证书的CNAME站点',
title: "CNAME站点",
helper: "请选择要部署证书的CNAME站点",
action: AliyunDeployCertToWaf.prototype.onGetCnameList.name,
watches: ['accessId', 'regionId'],
watches: ["accessId", "regionId"],
pager: true,
search: true,
})
)
cnameDomains!: string[];
@TaskInput({
title: 'TLS版本',
value: 'tlsv1.2',
title: "TLS版本",
value: "tlsv1.2",
component: {
name: 'a-select',
name: "a-select",
options: [
{ value: 'tlsv1', label: 'TLSv1' },
{ value: 'tlsv1.1', label: 'TLSv1.1' },
{ value: 'tlsv1.2', label: 'TLSv1.2' },
{ value: "tlsv1", label: "TLSv1" },
{ value: "tlsv1.1", label: "TLSv1.1" },
{ value: "tlsv1.2", label: "TLSv1.2" },
],
},
required: true,
@@ -106,19 +102,17 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
tlsVersion!: string;
@TaskInput({
title: '启用TLSv3',
title: "启用TLSv3",
value: true,
component: {
name: 'a-switch',
vModel: 'checked',
name: "a-switch",
vModel: "checked",
},
required: true,
})
enableTLSv3!: boolean;
async onInstance() { }
async onInstance() {}
async getWafClient(access: AliyunAccess) {
const client = new AliyunClient({ logger: this.logger });
@@ -127,25 +121,25 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
accessKeySecret: access.accessKeySecret,
//https://wafopenapi.cn-hangzhou.aliyuncs.com
endpoint: `https://wafopenapi.${this.regionId}.aliyuncs.com`,
apiVersion: '2021-10-01',
apiVersion: "2021-10-01",
});
return client;
}
async getInstanceId(client: AliyunClient) {
const params = {
RegionId: 'cn-hangzhou',
RegionId: "cn-hangzhou",
};
const res = await client.request('DescribeInstance', params);
this.logger.info('获取实例ID', res.InstanceId);
const res = await client.request("DescribeInstance", params);
this.logger.info("获取实例ID", res.InstanceId);
return res.InstanceId;
}
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云');
this.logger.info("开始部署证书到阿里云");
const access = await this.getAccess<AliyunAccess>(this.accessId);
let certId: any = this.cert;
if (typeof this.cert === 'object') {
if (typeof this.cert === "object") {
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
@@ -163,21 +157,21 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
} else if (casCert.certId) {
certId = casCert.certId;
} else {
throw new Error('证书格式错误'+JSON.stringify(this.cert));
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);
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 siteDetail = await client.request("DescribeDomainDetail", params);
this.logger.info("站点详情", JSON.stringify(siteDetail));
const listen = siteDetail.Listen;
if (!listen) {
throw new Error(`没有找到${siteDomain}的监听器`);
@@ -190,7 +184,7 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
*/
const redirect = siteDetail.Redirect;
redirect.Backends = redirect.AllBackends;
listen.CertId = certId + '-' + this.regionId;
listen.CertId = certId + "-" + this.regionId;
if (!listen.HttpsPorts || listen.HttpsPorts.length === 0) {
listen.HttpsPorts = [443];
}
@@ -200,23 +194,22 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
Redirect: JSON.stringify(redirect),
Listen: JSON.stringify(listen),
Domain: siteDomain,
TLSVersion: this.tlsVersion || 'tlsv1.2',
TLSVersion: this.tlsVersion || "tlsv1.2",
EnableTLSv3: this.enableTLSv3 ?? true,
};
const res = await client.request('ModifyDomain', updateParams);
this.logger.info('部署成功', JSON.stringify(res));
const res = await client.request("ModifyDomain", updateParams);
this.logger.info("部署成功", JSON.stringify(res));
}
}
async onGetCnameList(data: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getWafClient(access);
const pager = new Pager(data)
const pager = new Pager(data);
const instanceId = await this.getInstanceId(client);
const params: any = {
@@ -226,12 +219,12 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
PageNumber: pager.pageNo,
};
if (data.searchKey) {
params.Domain = data.searchKey
params.Domain = data.searchKey;
}
const res = await client.request('DescribeDomains', params);
const res = await client.request("DescribeDomains", params);
if (!res?.Domains || res?.Domains.length === 0) {
throw new Error('没有找到CNAME接入的域名站点');
throw new Error("没有找到CNAME接入的域名站点");
}
const total = res.TotalCount;
@@ -251,7 +244,7 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
list,
total: total,
pageNo: pager.pageNo,
pageSize: pager.pageSize
pageSize: pager.pageSize,
};
}
}
@@ -1,2 +1,2 @@
export * from './deploy-to-waf-cname.js';
export * from './deploy-to-waf-cloud.js';
export * from "./deploy-to-waf-cname.js";
export * from "./deploy-to-waf-cloud.js";
@@ -1,18 +1,18 @@
export * from './deploy-to-cdn/index.js';
export * from './deploy-to-dcdn/index.js';
export * from './deploy-to-oss/index.js';
export * from './upload-to-aliyun/index.js';
export * from './deploy-to-waf/index.js';
export * from './deploy-to-alb/index.js';
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-live/index.js';
export * from './deploy-to-apigateway/index.js';
export * from './deploy-to-apig/index.js';
export * from './deploy-to-ack/index.js';
export * from './deploy-to-all/index.js';
export * from './delete-expiring-cert/index.js';
export * from "./deploy-to-cdn/index.js";
export * from "./deploy-to-dcdn/index.js";
export * from "./deploy-to-oss/index.js";
export * from "./upload-to-aliyun/index.js";
export * from "./deploy-to-waf/index.js";
export * from "./deploy-to-alb/index.js";
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-live/index.js";
export * from "./deploy-to-apigateway/index.js";
export * from "./deploy-to-apig/index.js";
export * from "./deploy-to-ack/index.js";
export * from "./deploy-to-all/index.js";
export * from "./delete-expiring-cert/index.js";
@@ -1,7 +1,7 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
import { CertApplyPluginNames, CertReader } from "@certd/plugin-cert";
import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js';
import { AliyunSslClient, CasCertId } from '../../../plugin-lib/aliyun/lib/index.js';
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunSslClient, CasCertId } from "../../../plugin-lib/aliyun/lib/index.js";
/**
* 华东1(杭州) cn-hangzhou cas.aliyuncs.com cas-vpc.cn-hangzhou.aliyuncs.com
* 马来西亚(吉隆坡) ap-southeast-3 cas.ap-southeast-3.aliyuncs.com cas-vpc.ap-southeast-3.aliyuncs.com
@@ -13,21 +13,21 @@ import { AliyunSslClient, CasCertId } from '../../../plugin-lib/aliyun/lib/index
* 德国(法兰克福) eu-central-1 cas.eu-central-1.aliyuncs.com
*/
const regionDict = [
{ value: 'cn-hangzhou', endpoint: 'cas.aliyuncs.com', label: 'cn-hangzhou-中国大陆' },
{ value: 'ap-southeast-1', endpoint: 'cas.ap-southeast-1.aliyuncs.com', label: 'ap-southeast-1-新加坡(国际版选这个)' },
{ value: 'private-', endpoint: '', disabled:true, label: '以下是私有证书区域' },
{ value: 'eu-central-1', endpoint: 'cas.eu-central-1.aliyuncs.com', label: 'eu-central-1-德国(法兰克福)' },
{ value: 'ap-southeast-3', endpoint: 'cas.ap-southeast-3.aliyuncs.com', label: 'ap-southeast-3-马来西亚(吉隆坡)' },
{ value: 'ap-southeast-5', endpoint: 'cas.ap-southeast-5.aliyuncs.com', label: 'ap-southeast-5-印度尼西亚(雅加达)' },
{ value: 'cn-hongkong', endpoint: 'cas.cn-hongkong.aliyuncs.com', label: 'cn-hongkong-中国香港' },
{ value: "cn-hangzhou", endpoint: "cas.aliyuncs.com", label: "cn-hangzhou-中国大陆" },
{ value: "ap-southeast-1", endpoint: "cas.ap-southeast-1.aliyuncs.com", label: "ap-southeast-1-新加坡(国际版选这个)" },
{ value: "private-", endpoint: "", disabled: true, label: "以下是私有证书区域" },
{ value: "eu-central-1", endpoint: "cas.eu-central-1.aliyuncs.com", label: "eu-central-1-德国(法兰克福)" },
{ value: "ap-southeast-3", endpoint: "cas.ap-southeast-3.aliyuncs.com", label: "ap-southeast-3-马来西亚(吉隆坡)" },
{ value: "ap-southeast-5", endpoint: "cas.ap-southeast-5.aliyuncs.com", label: "ap-southeast-5-印度尼西亚(雅加达)" },
{ value: "cn-hongkong", endpoint: "cas.cn-hongkong.aliyuncs.com", label: "cn-hongkong-中国香港" },
];
@IsTaskPlugin({
name: 'uploadCertToAliyun',
title: '阿里云-上传证书到CAS',
icon: 'svg:icon-aliyun',
name: "uploadCertToAliyun",
title: "阿里云-上传证书到CAS",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: '上传证书到阿里云证书管理服务(CAS),如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出',
desc: "上传证书到阿里云证书管理服务(CAS),如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
@@ -36,17 +36,17 @@ const regionDict = [
})
export class UploadCertToAliyun extends AbstractTaskPlugin {
@TaskInput({
title: '证书名称',
helper: '证书上传后将以此参数作为名称前缀',
title: "证书名称",
helper: "证书上传后将以此参数作为名称前缀",
})
name!: string;
@TaskInput({
title: '大区',
value: 'cn-hangzhou',
title: "大区",
value: "cn-hangzhou",
component: {
name: 'a-auto-complete',
vModel: 'value',
name: "a-auto-complete",
vModel: "value",
options: regionDict,
},
required: true,
@@ -54,10 +54,10 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
regionId!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: true,
@@ -65,28 +65,28 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
cert!: any;
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: 'access-selector',
type: 'aliyun',
name: "access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@TaskOutput({
title: '上传成功后的阿里云CertId',
title: "上传成功后的阿里云CertId",
})
aliyunCertId!: CasCertId;
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始上传证书到阿里云证书管理CAS');
this.logger.info("开始上传证书到阿里云证书管理CAS");
const access: AliyunAccess = await this.getAccess(this.accessId);
let endpoint = '';
let endpoint = "";
for (const region of regionDict) {
if (region.value === this.regionId) {
endpoint = region.endpoint;
@@ -98,12 +98,12 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
logger: this.logger,
endpoint,
});
let certName = ""
let certName = "";
const certReader = new CertReader(this.cert);
if (this.name){
certName = this.appendTimeSuffix(this.name)
}else {
certName = this.buildCertName(certReader.getMainDomain())
if (this.name) {
certName = this.appendTimeSuffix(this.name);
} else {
certName = this.buildCertName(certReader.getMainDomain());
}
const certIdRes = await client.uploadCertificate({
@@ -1,6 +1,6 @@
import {AccessInput, BaseAccess, IsAccess} from "@certd/pipeline";
import {HttpRequestConfig} from "@certd/basic";
import {CertInfo, CertReader} from "@certd/plugin-cert";
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
import { HttpRequestConfig } from "@certd/basic";
import { CertInfo, CertReader } from "@certd/plugin-cert";
/**
*/
@@ -8,10 +8,9 @@ import {CertInfo, CertReader} from "@certd/plugin-cert";
name: "apisix",
title: "APISIX授权",
desc: "",
icon: "svg:icon-lucky"
icon: "svg:icon-lucky",
})
export class ApisixAccess extends BaseAccess {
@AccessInput({
title: "Apisix管理地址",
component: {
@@ -19,23 +18,23 @@ export class ApisixAccess extends BaseAccess {
},
required: true,
})
endpoint = '';
endpoint = "";
@AccessInput({
title: 'ApiKey',
title: "ApiKey",
component: {
placeholder: 'ApiKey',
placeholder: "ApiKey",
},
helper: "[参考文档](https://apisix.apache.org/docs/apisix/admin-api/#using-environment-variables)在config中配置admin apiKey",
required: true,
encrypt: true,
})
apiKey = '';
apiKey = "";
@AccessInput({
title: '版本',
title: "版本",
component: {
name:"a-select",
name: "a-select",
options: [
{
label: "v3.x",
@@ -45,78 +44,77 @@ export class ApisixAccess extends BaseAccess {
label: "v2.x",
value: "2",
},
]
],
},
helper: "apisix系统的版本",
value:"3",
value: "3",
required: true,
})
version = '3';
version = "3";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "点击测试接口是否正常"
helper: "点击测试接口是否正常",
})
testRequest = true;
async onTestRequest() {
await this.getCertList();
return "ok"
return "ok";
}
async getCertList(){
async getCertList() {
const sslPath = this.getSslPath();
const req = {
url :`/apisix/admin/${sslPath}`,
url: `/apisix/admin/${sslPath}`,
method: "get",
}
};
return await this.doRequest(req);
}
getSslPath(){
const sslPath = this.version === '3' ? 'ssls' : 'ssl';
getSslPath() {
const sslPath = this.version === "3" ? "ssls" : "ssl";
return sslPath;
}
async createCert(opts:{cert:CertInfo}){
const certReader = new CertReader(opts.cert)
async createCert(opts: { cert: CertInfo }) {
const certReader = new CertReader(opts.cert);
const sslPath = this.getSslPath();
const req = {
url :`/apisix/admin/${sslPath}`,
url: `/apisix/admin/${sslPath}`,
method: "post",
data:{
data: {
cert: opts.cert.crt,
key: opts.cert.key,
snis: certReader.getAllDomains()
}
}
snis: certReader.getAllDomains(),
},
};
return await this.doRequest(req);
}
async updateCert (opts:{cert:CertInfo,id:string}){
const certReader = new CertReader(opts.cert)
async updateCert(opts: { cert: CertInfo; id: string }) {
const certReader = new CertReader(opts.cert);
const sslPath = this.getSslPath();
const req = {
url :`/apisix/admin/${sslPath}/${opts.id}`,
url: `/apisix/admin/${sslPath}/${opts.id}`,
method: "put",
data:{
data: {
cert: opts.cert.crt,
key: opts.cert.key,
snis: certReader.getAllDomains()
}
}
snis: certReader.getAllDomains(),
},
};
return await this.doRequest(req);
}
async doRequest(req: HttpRequestConfig){
async doRequest(req: HttpRequestConfig) {
const headers = {
"X-API-KEY": this.apiKey,
...req.headers
...req.headers,
};
return await this.ctx.http.request({
headers,
@@ -125,9 +123,6 @@ export class ApisixAccess extends BaseAccess {
logRes: false,
});
}
}
new ApisixAccess();
@@ -1 +1 @@
import "./plugin-refresh-cert.js"
import "./plugin-refresh-cert.js";
@@ -1,7 +1,7 @@
import {IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput} from "@certd/pipeline";
import {CertApplyPluginNames, CertInfo} from "@certd/plugin-cert";
import {createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
import {ApisixAccess} from "../access.js";
import { IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { ApisixAccess } from "../access.js";
import { AbstractPlusTaskPlugin } from "@certd/plugin-plus";
@IsTaskPlugin({
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
@@ -15,9 +15,9 @@ import { AbstractPlusTaskPlugin } from "@certd/plugin-plus";
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
//类名规范,跟上面插件名称(name)一致
export class ApisixRefreshCDNCert extends AbstractPlusTaskPlugin {
@@ -27,8 +27,8 @@ export class ApisixRefreshCDNCert extends AbstractPlusTaskPlugin {
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
from: [...CertApplyPluginNames],
},
// required: true, // 必填
})
cert!: CertInfo;
@@ -41,9 +41,9 @@ export class ApisixRefreshCDNCert extends AbstractPlusTaskPlugin {
title: "Apisix授权",
component: {
name: "access-selector",
type: "apisix" //固定授权类型
type: "apisix", //固定授权类型
},
required: true //必填
required: true, //必填
})
accessId!: string;
//
@@ -54,14 +54,13 @@ export class ApisixRefreshCDNCert extends AbstractPlusTaskPlugin {
helper: "要更新的证书id,如果这里没有,请先给手动绑定一次证书",
action: ApisixRefreshCDNCert.prototype.onGetCertList.name,
pager: false,
search: false
search: false,
})
)
certList!: string[];
//插件实例化时执行的方法
async onInstance() {
}
async onInstance() {}
//插件执行方法
async execute(): Promise<void> {
@@ -74,7 +73,7 @@ export class ApisixRefreshCDNCert extends AbstractPlusTaskPlugin {
await access.updateCert({
id: certId,
cert: this.cert
cert: this.cert,
});
this.logger.info(`----------- 更新证书${certId}成功`);
}
@@ -85,13 +84,12 @@ export class ApisixRefreshCDNCert extends AbstractPlusTaskPlugin {
async onGetCertList(data: PageSearch = {}) {
const access = await this.getAccess<ApisixAccess>(this.accessId);
const res = await access.getCertList()
const list = res.list
const res = await access.getCertList();
const list = res.list;
if (!list || list.length === 0) {
throw new Error("没有找到证书,你可以直接手动输入id,如果id不存在将自动创建");
}
/**
* certificate-id
* name
@@ -101,7 +99,7 @@ export class ApisixRefreshCDNCert extends AbstractPlusTaskPlugin {
return {
label: `${item.value.snis[0]}<${item.value.id}>`,
value: item.value.id,
domain: item.value.snis
domain: item.value.snis,
};
});
return {
@@ -1,33 +1,32 @@
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
@IsAccess({
name: 'aws-cn',
title: '亚马逊云科技(国区)授权',
desc: '',
icon: 'svg:icon-aws',
name: "aws-cn",
title: "亚马逊云科技(国区)授权",
desc: "",
icon: "svg:icon-aws",
})
export class AwsCNAccess extends BaseAccess {
@AccessInput({
title: 'accessKeyId',
title: "accessKeyId",
component: {
placeholder: 'accessKeyId',
placeholder: "accessKeyId",
},
helper:
'右上角->安全凭证->访问密钥,[点击前往](https://cn-north-1.console.amazonaws.cn/iam/home?region=cn-north-1#/security_credentials/access-key-wizard#)',
helper: "右上角->安全凭证->访问密钥,[点击前往](https://cn-north-1.console.amazonaws.cn/iam/home?region=cn-north-1#/security_credentials/access-key-wizard#)",
required: true,
})
accessKeyId = '';
accessKeyId = "";
@AccessInput({
title: 'secretAccessKey',
title: "secretAccessKey",
component: {
placeholder: 'secretAccessKey',
placeholder: "secretAccessKey",
},
required: true,
encrypt: true,
helper: '请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。',
helper: "请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。",
})
secretAccessKey = '';
secretAccessKey = "";
}
new AwsCNAccess();
@@ -1,4 +1,4 @@
export const AwsCNRegions = [
{ label: 'cn-north-1', value: 'cn-north-1' },
{ label: 'cn-northwest-1', value: 'cn-northwest-1' },
];
{ label: "cn-north-1", value: "cn-north-1" },
{ label: "cn-northwest-1", value: "cn-northwest-1" },
];
@@ -1,3 +1,3 @@
export * from './plugins/index.js';
export * from './access.js';
export * from './constants.js';
export * from "./plugins/index.js";
export * from "./access.js";
export * from "./constants.js";
@@ -1,6 +1,6 @@
// 导入所需的 SDK 模块
import { AwsCNAccess } from '../access.js';
import { CertInfo } from '@certd/plugin-cert';
import { AwsCNAccess } from "../access.js";
import { CertInfo } from "@certd/plugin-cert";
type AwsIAMClientOptions = { access: AwsCNAccess; region: string };
@@ -15,7 +15,7 @@ export class AwsIAMClient {
}
async importCertificate(certInfo: CertInfo, certName: string) {
// 创建 IAM 客户端
const { IAMClient, UploadServerCertificateCommand } = await import('@aws-sdk/client-iam');
const { IAMClient, UploadServerCertificateCommand } = await import("@aws-sdk/client-iam");
const iamClient = new IAMClient({
region: this.region, // 替换为您的 AWS 区域
credentials: {
@@ -24,18 +24,18 @@ export class AwsIAMClient {
},
});
const cert = certInfo.crt.split('-----END CERTIFICATE-----')[0] + '-----END CERTIFICATE-----';
const chain = certInfo.crt.split('-----END CERTIFICATE-----\n')[1];
const cert = certInfo.crt.split("-----END CERTIFICATE-----")[0] + "-----END CERTIFICATE-----";
const chain = certInfo.crt.split("-----END CERTIFICATE-----\n")[1];
// 构建上传参数
const command = new UploadServerCertificateCommand({
Path: '/cloudfront/',
Path: "/cloudfront/",
ServerCertificateName: certName,
CertificateBody: cert,
PrivateKey: certInfo.key,
CertificateChain: chain
})
CertificateChain: chain,
});
const data = await iamClient.send(command);
console.log('Upload successful:', data);
console.log("Upload successful:", data);
// 返回证书 ID
return data.ServerCertificateMetadata.ServerCertificateId;
}
@@ -1 +1 @@
export * from './plugin-deploy-to-cloudfront.js';
export * from "./plugin-deploy-to-cloudfront.js";
@@ -3,14 +3,13 @@ import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { AwsCNAccess } from "../access.js";
import { AwsIAMClient } from "../libs/aws-iam-client.js";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { AwsCNRegions } from '../constants.js';
import { AwsCNRegions } from "../constants.js";
@IsTaskPlugin({
name: 'AwsCNDeployToCloudFront',
title: 'AWS(国区)-部署证书到CloudFront',
desc: '部署证书到 AWS CloudFront',
icon: 'svg:icon-aws',
name: "AwsCNDeployToCloudFront",
title: "AWS(国区)-部署证书到CloudFront",
desc: "部署证书到 AWS CloudFront",
icon: "svg:icon-aws",
group: pluginGroups.aws.key,
needPlus: false,
default: {
@@ -21,11 +20,11 @@ import { AwsCNRegions } from '../constants.js';
})
export class AwsCNDeployToCloudFront extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'AwsUploadToACM'],
name: "output-selector",
from: [...CertApplyPluginNames, "AwsUploadToACM"],
},
required: true,
})
@@ -35,11 +34,11 @@ export class AwsCNDeployToCloudFront extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: '区域',
helper: '证书上传区域',
title: "区域",
helper: "证书上传区域",
component: {
name: 'a-auto-complete',
vModel: 'value',
name: "a-auto-complete",
vModel: "value",
options: AwsCNRegions,
},
required: true,
@@ -47,26 +46,26 @@ export class AwsCNDeployToCloudFront extends AbstractTaskPlugin {
region!: string;
@TaskInput({
title: 'Access授权',
helper: 'aws的授权',
title: "Access授权",
helper: "aws的授权",
component: {
name: 'access-selector',
type: 'aws-cn',
name: "access-selector",
type: "aws-cn",
},
required: true,
})
accessId!: string;
@TaskInput({
title: '证书名称',
helper: '上传后将以此名称作为前缀备注',
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
})
certName!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '分配ID',
helper: '请选择distributions id',
title: "分配ID",
helper: "请选择distributions id",
action: AwsCNDeployToCloudFront.prototype.onGetDistributions.name,
required: true,
})
@@ -79,13 +78,13 @@ export class AwsCNDeployToCloudFront extends AbstractTaskPlugin {
const access = await this.getAccess<AwsCNAccess>(this.accessId);
let certId = this.cert as string;
if (typeof this.cert !== 'string') {
if (typeof this.cert !== "string") {
//先上传
certId = await this.uploadToIAM(access, this.cert);
}
//部署到CloudFront
const { CloudFrontClient, UpdateDistributionCommand, GetDistributionConfigCommand } = await import('@aws-sdk/client-cloudfront');
const { CloudFrontClient, UpdateDistributionCommand, GetDistributionConfigCommand } = await import("@aws-sdk/client-cloudfront");
const cloudFrontClient = new CloudFrontClient({
region: this.region,
credentials: {
@@ -116,7 +115,7 @@ export class AwsCNDeployToCloudFront extends AbstractTaskPlugin {
await cloudFrontClient.send(updateDistributionCommand);
this.logger.info(`部署${distributionId}完成:`);
}
this.logger.info('部署完成');
this.logger.info("部署完成");
}
private async uploadToIAM(access: AwsCNAccess, cert: CertInfo) {
@@ -125,18 +124,18 @@ export class AwsCNDeployToCloudFront extends AbstractTaskPlugin {
region: this.region,
});
const awsCertID = await acmClient.importCertificate(cert, this.appendTimeSuffix(this.certName));
this.logger.info('证书上传成功,id=', awsCertID);
this.logger.info("证书上传成功,id=", awsCertID);
return awsCertID;
}
//查找分配ID列表选项
async onGetDistributions() {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AwsCNAccess>(this.accessId);
const { CloudFrontClient, ListDistributionsCommand } = await import('@aws-sdk/client-cloudfront');
const { CloudFrontClient, ListDistributionsCommand } = await import("@aws-sdk/client-cloudfront");
const cloudFrontClient = new CloudFrontClient({
region: this.region,
credentials: {
@@ -149,7 +148,7 @@ export class AwsCNDeployToCloudFront extends AbstractTaskPlugin {
const data = await cloudFrontClient.send(listDistributionsCommand);
const distributions = data.DistributionList?.Items;
if (!distributions || distributions.length === 0) {
throw new Error('找不到CloudFront分配ID,您可以手动输入');
throw new Error("找不到CloudFront分配ID,您可以手动输入");
}
const options = distributions.map((item: any) => {
@@ -1,66 +1,62 @@
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
import { AwsRegions } from './constants.js';
import { AwsClient } from './libs/aws-client.js';
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
import { AwsRegions } from "./constants.js";
import { AwsClient } from "./libs/aws-client.js";
@IsAccess({
name: 'aws',
title: '亚马逊云aws授权',
desc: '',
icon: 'svg:icon-aws',
name: "aws",
title: "亚马逊云aws授权",
desc: "",
icon: "svg:icon-aws",
})
export class AwsAccess extends BaseAccess {
@AccessInput({
title: 'accessKeyId',
title: "accessKeyId",
component: {
placeholder: 'accessKeyId',
placeholder: "accessKeyId",
},
helper:
'右上角->安全凭证->访问密钥,[点击前往](https://us-east-1.console.aws.amazon.com/iam/home?region=ap-east-1#/security_credentials/access-key-wizard)',
helper: "右上角->安全凭证->访问密钥,[点击前往](https://us-east-1.console.aws.amazon.com/iam/home?region=ap-east-1#/security_credentials/access-key-wizard)",
required: true,
})
accessKeyId = '';
accessKeyId = "";
@AccessInput({
title: 'secretAccessKey',
title: "secretAccessKey",
component: {
placeholder: 'secretAccessKey',
placeholder: "secretAccessKey",
},
required: true,
encrypt: true,
helper: '请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。',
helper: "请妥善保管您的安全访问密钥。您可以在AWS管理控制台的IAM中创建新的访问密钥。",
})
secretAccessKey = '';
secretAccessKey = "";
@AccessInput({
title: 'region',
title: "region",
component: {
name: "a-select",
options: AwsRegions,
},
required: true,
helper: '请选择您的默认AWS区域,主要区分中国区还是海外区即可',
helper: "请选择您的默认AWS区域,主要区分中国区还是海外区即可",
options: AwsRegions,
})
region = '';
region = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "测试授权是否正确"
helper: "测试授权是否正确",
})
testRequest = true;
async onTestRequest() {
const client = new AwsClient({ access: this, logger: this.ctx.logger, region: this.region || 'us-east-1' });
const client = new AwsClient({ access: this, logger: this.ctx.logger, region: this.region || "us-east-1" });
await client.getCallerIdentity();
return "ok";
}
}
new AwsAccess();
@@ -1,61 +1,58 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { AwsClient } from './libs/aws-client.js';
import { AwsAccess } from './access.js';
import { PageRes, PageSearch } from '@certd/pipeline';
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { AwsClient } from "./libs/aws-client.js";
import { AwsAccess } from "./access.js";
import { PageRes, PageSearch } from "@certd/pipeline";
@IsDnsProvider({
name: 'aws-route53',
title: 'AWS Route53',
desc: 'AWS Route53 DNS解析提供商',
accessType: 'aws',
icon: 'svg:icon-aws',
order:0,
name: "aws-route53",
title: "AWS Route53",
desc: "AWS Route53 DNS解析提供商",
accessType: "aws",
icon: "svg:icon-aws",
order: 0,
})
export class AwsRoute53Provider extends AbstractDnsProvider {
client: AwsClient;
async onInstance() {
const access: AwsAccess = this.ctx.access as AwsAccess
this.client = new AwsClient({ access: access, logger: this.logger, region:access.region || 'us-east-1' });
const access: AwsAccess = this.ctx.access as AwsAccess;
this.client = new AwsClient({ access: access, logger: this.logger, region: access.region || "us-east-1" });
}
async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, domain);
this.logger.info("添加域名解析:", fullRecord, value, domain);
// const domain = await this.matchDomain(fullRecord);
const {ZoneId,ZoneName} = await this.client.route53GetHostedZoneId(domain);
const { ZoneId, ZoneName } = await this.client.route53GetHostedZoneId(domain);
this.logger.info(`获取到hostedZoneId:${ZoneId},name:${ZoneName},domain:${domain}`);
await this.client.route53ChangeRecord({
hostedZoneId: ZoneId,
fullRecord: fullRecord,
type: type,
value: value,
action: 'UPSERT',
action: "UPSERT",
});
return {
hostedZoneId: ZoneId,
}
};
}
async removeRecord(options: RemoveRecordOptions<any>): Promise<any> {
const { fullRecord, value,type } = options.recordReq;
const { fullRecord, value, type } = options.recordReq;
const record = options.recordRes;
const hostedZoneId = record.hostedZoneId;
try{
try {
await this.client.route53ChangeRecord({
hostedZoneId: hostedZoneId,
fullRecord: fullRecord,
type: type,
value: value,
action: 'DELETE',
action: "DELETE",
});
}catch(e){
this.logger.warn(`删除域名解析失败:${e.message} : ${hostedZoneId} ${fullRecord} ${value} ${type} `, );
} catch (e) {
this.logger.warn(`删除域名解析失败:${e.message} : ${hostedZoneId} ${fullRecord} ${value} ${type} `);
}
}
@@ -1,37 +1,36 @@
export const AwsRegions = [
{ label: '------中国区------', value: 'cn',disabled: true },
{ label: '北京', value: 'cn-north-1' },
{ label: '宁夏', value: 'cn-northwest-1' },
{ label: '------海外-----', value: 'out',disabled: true },
{ label: 'us-east-1', value: 'us-east-1' },
{ label: 'us-east-2', value: 'us-east-2' },
{ label: 'us-west-1', value: 'us-west-1' },
{ label: 'us-west-2', value: 'us-west-2' },
{ label: 'af-south-1', value: 'af-south-1' },
{ label: 'ap-east-1', value: 'ap-east-1' },
{ label: 'ap-northeast-1', value: 'ap-northeast-1' },
{ label: 'ap-northeast-2', value: 'ap-northeast-2' },
{ label: 'ap-northeast-3', value: 'ap-northeast-3' },
{ label: 'ap-south-1', value: 'ap-south-1' },
{ label: 'ap-south-2', value: 'ap-south-2' },
{ label: 'ap-southeast-1', value: 'ap-southeast-1' },
{ label: 'ap-southeast-2', value: 'ap-southeast-2' },
{ label: 'ap-southeast-3', value: 'ap-southeast-3' },
{ label: 'ap-southeast-4', value: 'ap-southeast-4' },
{ label: 'ap-southeast-5', value: 'ap-southeast-5' },
{ label: 'ca-central-1', value: 'ca-central-1' },
{ label: 'ca-west-1', value: 'ca-west-1' },
{ label: 'eu-central-1', value: 'eu-central-1' },
{ label: 'eu-central-2', value: 'eu-central-2' },
{ label: 'eu-north-1', value: 'eu-north-1' },
{ label: 'eu-south-1', value: 'eu-south-1' },
{ label: 'eu-south-2', value: 'eu-south-2' },
{ label: 'eu-west-1', value: 'eu-west-1' },
{ label: 'eu-west-2', value: 'eu-west-2' },
{ label: 'eu-west-3', value: 'eu-west-3' },
{ label: 'il-central-1', value: 'il-central-1' },
{ label: 'me-central-1', value: 'me-central-1' },
{ label: 'me-south-1', value: 'me-south-1' },
{ label: 'sa-east-1', value: 'sa-east-1' },
];
{ label: "------中国区------", value: "cn", disabled: true },
{ label: "北京", value: "cn-north-1" },
{ label: "宁夏", value: "cn-northwest-1" },
{ label: "------海外-----", value: "out", disabled: true },
{ label: "us-east-1", value: "us-east-1" },
{ label: "us-east-2", value: "us-east-2" },
{ label: "us-west-1", value: "us-west-1" },
{ label: "us-west-2", value: "us-west-2" },
{ label: "af-south-1", value: "af-south-1" },
{ label: "ap-east-1", value: "ap-east-1" },
{ label: "ap-northeast-1", value: "ap-northeast-1" },
{ label: "ap-northeast-2", value: "ap-northeast-2" },
{ label: "ap-northeast-3", value: "ap-northeast-3" },
{ label: "ap-south-1", value: "ap-south-1" },
{ label: "ap-south-2", value: "ap-south-2" },
{ label: "ap-southeast-1", value: "ap-southeast-1" },
{ label: "ap-southeast-2", value: "ap-southeast-2" },
{ label: "ap-southeast-3", value: "ap-southeast-3" },
{ label: "ap-southeast-4", value: "ap-southeast-4" },
{ label: "ap-southeast-5", value: "ap-southeast-5" },
{ label: "ca-central-1", value: "ca-central-1" },
{ label: "ca-west-1", value: "ca-west-1" },
{ label: "eu-central-1", value: "eu-central-1" },
{ label: "eu-central-2", value: "eu-central-2" },
{ label: "eu-north-1", value: "eu-north-1" },
{ label: "eu-south-1", value: "eu-south-1" },
{ label: "eu-south-2", value: "eu-south-2" },
{ label: "eu-west-1", value: "eu-west-1" },
{ label: "eu-west-2", value: "eu-west-2" },
{ label: "eu-west-3", value: "eu-west-3" },
{ label: "il-central-1", value: "il-central-1" },
{ label: "me-central-1", value: "me-central-1" },
{ label: "me-south-1", value: "me-south-1" },
{ label: "sa-east-1", value: "sa-east-1" },
];
@@ -1,4 +1,4 @@
export * from './plugins/index.js';
export * from './access.js';
export * from './aws-route53-provider.js';
export * from './constants.js';
export * from "./plugins/index.js";
export * from "./access.js";
export * from "./aws-route53-provider.js";
export * from "./constants.js";
@@ -1,9 +1,9 @@
// 导入所需的 SDK 模块
import { AwsAccess } from '../access.js';
import { CertInfo, DomainRecord } from '@certd/plugin-cert';
import { ILogger, utils } from '@certd/basic';
import { PageRes, PageSearch } from '@certd/pipeline';
type AwsClientOptions = { access: AwsAccess; region: string, logger: ILogger };
import { AwsAccess } from "../access.js";
import { CertInfo, DomainRecord } from "@certd/plugin-cert";
import { ILogger, utils } from "@certd/basic";
import { PageRes, PageSearch } from "@certd/pipeline";
type AwsClientOptions = { access: AwsAccess; region: string; logger: ILogger };
/**
* https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/route-53-domains/
@@ -21,7 +21,7 @@ export class AwsClient {
}
async importCertificate(certInfo: CertInfo) {
// 创建 ACM 客户端
const { ACMClient, ImportCertificateCommand } = await import('@aws-sdk/client-acm');
const { ACMClient, ImportCertificateCommand } = await import("@aws-sdk/client-acm");
const acmClient = new ACMClient({
region: this.region, // 替换为您的 AWS 区域
credentials: {
@@ -30,7 +30,7 @@ export class AwsClient {
},
});
const cert = certInfo.crt.split('-----END CERTIFICATE-----')[0] + '-----END CERTIFICATE-----';
const cert = certInfo.crt.split("-----END CERTIFICATE-----")[0] + "-----END CERTIFICATE-----";
// 构建上传参数
const data = await acmClient.send(
new ImportCertificateCommand({
@@ -39,17 +39,16 @@ export class AwsClient {
// CertificateChain: certificateChain, // 可选
})
);
console.log('Upload successful:', data);
console.log("Upload successful:", data);
// 返回证书 ARNAmazon Resource Name
return data.CertificateArn;
}
async getCallerIdentity() {
const { STSClient, GetCallerIdentityCommand } = await import ("@aws-sdk/client-sts");
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
const client = new STSClient({
region: this.access.region || 'us-east-1',
region: this.access.region || "us-east-1",
credentials: {
accessKeyId: this.access.accessKeyId, // 从环境变量中读取
secretAccessKey: this.access.secretAccessKey,
@@ -64,9 +63,8 @@ export class AwsClient {
return response;
}
async route53ClientGet() {
const { Route53Client } = await import('@aws-sdk/client-route-53');
const { Route53Client } = await import("@aws-sdk/client-route-53");
return new Route53Client({
region: this.region,
credentials: {
@@ -76,20 +74,21 @@ export class AwsClient {
});
}
async route53GetHostedZoneId(name: string): Promise<{ ZoneId: string, ZoneName: string }> {
async route53GetHostedZoneId(name: string): Promise<{ ZoneId: string; ZoneName: string }> {
const hostedZones = await this.route53ListHostedZones(name);
const zoneId = hostedZones[0].Id.replace('/hostedzone/', '');
const zoneId = hostedZones[0].Id.replace("/hostedzone/", "");
this.logger.info(`获取到hostedZoneId:${zoneId},name:${hostedZones[0].Name}`);
return {
ZoneId: zoneId,
ZoneName: hostedZones[0].Name,
};
}
async route53ListHostedZones(name: string): Promise<{ Id: string, Name: string }[]> {
async route53ListHostedZones(name: string): Promise<{ Id: string; Name: string }[]> {
const { ListHostedZonesByNameCommand } = await import("@aws-sdk/client-route-53"); // ES Modules import
const client = await this.route53ClientGet();
const input = { // ListHostedZonesByNameRequest
const input = {
// ListHostedZonesByNameRequest
DNSName: name,
};
const command = new ListHostedZonesByNameCommand(input);
@@ -105,7 +104,8 @@ export class AwsClient {
const { ListHostedZonesByNameCommand } = await import("@aws-sdk/client-route-53"); // ES Modules import
const client = await this.route53ClientGet();
const input: any = { // ListHostedZonesByNameRequest
const input: any = {
// ListHostedZonesByNameRequest
MaxItems: req.pageSize,
};
if (req.searchKey) {
@@ -115,7 +115,7 @@ export class AwsClient {
const response = await this.doRequest(() => client.send(command));
let list: any[] = response.HostedZones || [];
list = list.map((item: any) => ({
id: item.Id.replace('/hostedzone/', ''),
id: item.Id.replace("/hostedzone/", ""),
domain: item.Name,
}));
return {
@@ -124,24 +124,29 @@ export class AwsClient {
};
}
async route53ChangeRecord(req: {
hostedZoneId: string, fullRecord: string, type: string, value: string, action: "UPSERT" | "DELETE"
}) {
async route53ChangeRecord(req: { hostedZoneId: string; fullRecord: string; type: string; value: string; action: "UPSERT" | "DELETE" }) {
const { ChangeResourceRecordSetsCommand } = await import("@aws-sdk/client-route-53"); // ES Modules import
// const { Route53Client, ChangeResourceRecordSetsCommand } = require("@aws-sdk/client-route-53"); // CommonJS import
// import type { Route53ClientConfig } from "@aws-sdk/client-route-53";
const client = await this.route53ClientGet();
const input = { // ChangeResourceRecordSetsRequest
const input = {
// ChangeResourceRecordSetsRequest
HostedZoneId: req.hostedZoneId, // required
ChangeBatch: { // ChangeBatch
Changes: [ // Changes // required
{ // Change
ChangeBatch: {
// ChangeBatch
Changes: [
// Changes // required
{
// Change
Action: req.action as any, // required
ResourceRecordSet: { // ResourceRecordSet
ResourceRecordSet: {
// ResourceRecordSet
Name: req.fullRecord, // required
Type: req.type.toUpperCase() as any,
ResourceRecords: [ // ResourceRecords
{ // ResourceRecord
ResourceRecords: [
// ResourceRecords
{
// ResourceRecord
Value: `"${req.value}"`, // required
},
],
@@ -154,7 +159,7 @@ export class AwsClient {
this.logger.info(`设置域名解析参数:${JSON.stringify(input)}`);
const command = new ChangeResourceRecordSetsCommand(input);
const response = await this.doRequest(() => client.send(command));
console.log('Add record successful:', JSON.stringify(response));
console.log("Add record successful:", JSON.stringify(response));
await utils.sleep(3000);
return response;
/*
@@ -1,2 +1,2 @@
export * from './plugin-deploy-to-cloudfront.js';
export * from './plugin-upload-to-acm.js'
export * from "./plugin-deploy-to-cloudfront.js";
export * from "./plugin-upload-to-acm.js";
@@ -7,10 +7,10 @@ import { optionsUtils } from "@certd/basic";
import { AwsRegions } from "../constants.js";
@IsTaskPlugin({
name: 'AwsDeployToCloudFront',
title: 'AWS-部署证书到CloudFront',
desc: '部署证书到 AWS CloudFront',
icon: 'svg:icon-aws',
name: "AwsDeployToCloudFront",
title: "AWS-部署证书到CloudFront",
desc: "部署证书到 AWS CloudFront",
icon: "svg:icon-aws",
group: pluginGroups.aws.key,
needPlus: false,
default: {
@@ -21,11 +21,11 @@ import { AwsRegions } from "../constants.js";
})
export class AwsDeployToCloudFront extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'AwsUploadToACM'],
name: "output-selector",
from: [...CertApplyPluginNames, "AwsUploadToACM"],
},
required: true,
})
@@ -35,11 +35,11 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: '区域',
helper: '证书上传区域',
title: "区域",
helper: "证书上传区域",
component: {
name: 'a-auto-complete',
vModel: 'value',
name: "a-auto-complete",
vModel: "value",
options: AwsRegions,
},
required: true,
@@ -47,11 +47,11 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
region!: string;
@TaskInput({
title: 'Access授权',
helper: 'aws的授权',
title: "Access授权",
helper: "aws的授权",
component: {
name: 'access-selector',
type: 'aws',
name: "access-selector",
type: "aws",
},
required: true,
})
@@ -59,8 +59,8 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: '分配ID',
helper: '请选择distributions id',
title: "分配ID",
helper: "请选择distributions id",
action: AwsDeployToCloudFront.prototype.onGetDistributions.name,
required: true,
})
@@ -73,13 +73,13 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
const access = await this.getAccess<AwsAccess>(this.accessId);
let certId = this.cert as string;
if (typeof this.cert !== 'string') {
if (typeof this.cert !== "string") {
//先上传
certId = await this.uploadToACM(access, this.cert);
}
//部署到CloudFront
const { CloudFrontClient, UpdateDistributionCommand, GetDistributionConfigCommand } = await import('@aws-sdk/client-cloudfront');
const { CloudFrontClient, UpdateDistributionCommand, GetDistributionConfigCommand } = await import("@aws-sdk/client-cloudfront");
const cloudFrontClient = new CloudFrontClient({
region: this.region,
credentials: {
@@ -112,7 +112,7 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
await cloudFrontClient.send(updateDistributionCommand);
this.logger.info(`部署${distributionId}完成:`);
}
this.logger.info('部署完成');
this.logger.info("部署完成");
}
private async uploadToACM(access: AwsAccess, cert: CertInfo) {
@@ -122,18 +122,18 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
logger: this.logger,
});
const awsCertARN = await acmClient.importCertificate(cert);
this.logger.info('证书上传成功,id=', awsCertARN);
this.logger.info("证书上传成功,id=", awsCertARN);
return awsCertARN;
}
//查找分配ID列表选项
async onGetDistributions() {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AwsAccess>(this.accessId);
const { CloudFrontClient, ListDistributionsCommand } = await import('@aws-sdk/client-cloudfront');
const { CloudFrontClient, ListDistributionsCommand } = await import("@aws-sdk/client-cloudfront");
const cloudFrontClient = new CloudFrontClient({
region: this.region,
credentials: {
@@ -146,7 +146,7 @@ export class AwsDeployToCloudFront extends AbstractTaskPlugin {
const data = await cloudFrontClient.send(listDistributionsCommand);
const distributions = data.DistributionList?.Items;
if (!distributions || distributions.length === 0) {
throw new Error('找不到CloudFront分配ID,您可以手动输入');
throw new Error("找不到CloudFront分配ID,您可以手动输入");
}
const options = distributions.map((item: any) => {
@@ -1,14 +1,14 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import { CertInfo } from '@certd/plugin-cert';
import { AwsAccess } from '../access.js';
import { AwsClient } from '../libs/aws-client.js';
import { CertApplyPluginNames} from '@certd/plugin-cert';
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
import { CertInfo } from "@certd/plugin-cert";
import { AwsAccess } from "../access.js";
import { AwsClient } from "../libs/aws-client.js";
import { CertApplyPluginNames } from "@certd/plugin-cert";
import { AwsRegions } from "../constants.js";
@IsTaskPlugin({
name: 'AwsUploadToACM',
title: 'AWS-上传证书到ACM',
desc: '上传证书 AWS ACM',
icon: 'svg:icon-aws',
name: "AwsUploadToACM",
title: "AWS-上传证书到ACM",
desc: "上传证书 AWS ACM",
icon: "svg:icon-aws",
group: pluginGroups.aws.key,
default: {
strategy: {
@@ -18,10 +18,10 @@ import { AwsRegions } from "../constants.js";
})
export class AwsUploadToACM extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: true,
@@ -29,21 +29,21 @@ export class AwsUploadToACM extends AbstractTaskPlugin {
cert!: CertInfo;
@TaskInput({
title: 'Access授权',
helper: 'aws的授权',
title: "Access授权",
helper: "aws的授权",
component: {
name: 'access-selector',
type: 'aws',
name: "access-selector",
type: "aws",
},
required: true,
})
accessId!: string;
@TaskInput({
title: '区域',
helper: '证书上传区域',
title: "区域",
helper: "证书上传区域",
component: {
name: 'a-auto-complete',
vModel: 'value',
name: "a-auto-complete",
vModel: "value",
options: AwsRegions,
},
required: true,
@@ -51,9 +51,9 @@ export class AwsUploadToACM extends AbstractTaskPlugin {
region!: string;
@TaskOutput({
title: '证书ARN',
title: "证书ARN",
})
awsCertARN = '';
awsCertARN = "";
async onInstance() {}
@@ -66,7 +66,7 @@ export class AwsUploadToACM extends AbstractTaskPlugin {
logger: this.logger,
});
this.awsCertARN = await acmClient.importCertificate(cert);
this.logger.info('证书上传成功,id=', this.awsCertARN);
this.logger.info("证书上传成功,id=", this.awsCertARN);
}
}
@@ -1,108 +1,97 @@
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-cert';
import { utils } from '@certd/basic';
import { PageRes, PageSearch } from '@certd/pipeline';
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
import { DomainRecord } from "@certd/plugin-cert";
import { utils } from "@certd/basic";
import { PageRes, PageSearch } from "@certd/pipeline";
@IsAccess({
name: 'azure',
title: '微软云Azure授权',
desc: '',
icon: 'simple-icons:microsoftazure',
name: "azure",
title: "微软云Azure授权",
desc: "",
icon: "simple-icons:microsoftazure",
})
export class AzureAccess extends BaseAccess {
@AccessInput({
title: '订阅 ID',
title: "订阅 ID",
component: {
placeholder: 'subscriptionId',
placeholder: "subscriptionId",
},
helper: 'Azure 订阅 ID',
helper: "Azure 订阅 ID",
required: true,
})
subscriptionId = '';
subscriptionId = "";
@AccessInput({
title: '资源组',
title: "资源组",
component: {
placeholder: 'resourceGroupName',
placeholder: "resourceGroupName",
},
helper: 'DNS 区域所在的资源组名称',
helper: "DNS 区域所在的资源组名称",
required: true,
})
resourceGroupName = '';
resourceGroupName = "";
@AccessInput({
title: '目录(租户) ID',
title: "目录(租户) ID",
component: {
placeholder: 'tenantId',
placeholder: "tenantId",
},
helper: '目录(租户) ID',
helper: "目录(租户) ID",
required: true,
})
tenantId = '';
tenantId = "";
@AccessInput({
title: '应用程序ID',
title: "应用程序ID",
component: {
placeholder: 'clientId',
placeholder: "clientId",
},
helper: '应用程序(客户端) ID',
helper: "应用程序(客户端) ID",
required: true,
})
clientId = '';
clientId = "";
@AccessInput({
title: '客户端凭据',
title: "客户端凭据",
component: {
placeholder: 'clientSecret',
placeholder: "clientSecret",
},
required: true,
encrypt: true,
helper: '客户端凭据(机密)->客户端密码->新客户端密码->时间选长一点的->复制值',
helper: "客户端凭据(机密)->客户端密码->新客户端密码->时间选长一点的->复制值",
})
clientSecret = '';
clientSecret = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "测试授权是否正确"
helper: "测试授权是否正确",
})
testRequest = true;
async onTestRequest() {
this.ctx.logger.info('开始测试 Azure 认证...');
this.ctx.logger.info("开始测试 Azure 认证...");
// 1. 先测试身份认证,获取访问令牌
const { ClientSecretCredential } = await import('@azure/identity');
const credential = new ClientSecretCredential(
this.tenantId,
this.clientId,
this.clientSecret
);
const { ClientSecretCredential } = await import("@azure/identity");
const credential = new ClientSecretCredential(this.tenantId, this.clientId, this.clientSecret);
// 获取 Azure 管理 API 的访问令牌来验证凭据
this.ctx.logger.info('验证身份凭据...');
const token = await credential.getToken('https://management.azure.com/.default');
this.ctx.logger.info('身份认证成功!', token);
this.ctx.logger.info("验证身份凭据...");
const token = await credential.getToken("https://management.azure.com/.default");
this.ctx.logger.info("身份认证成功!", token);
return "ok";
}
async getDnsManagementClient() {
const { DnsManagementClient } = await import('@azure/arm-dns');
const { ClientSecretCredential } = await import('@azure/identity');
const { DnsManagementClient } = await import("@azure/arm-dns");
const { ClientSecretCredential } = await import("@azure/identity");
const credential = new ClientSecretCredential(
this.tenantId,
this.clientId,
this.clientSecret
);
const credential = new ClientSecretCredential(this.tenantId, this.clientId, this.clientSecret);
return new DnsManagementClient(credential, this.subscriptionId);
}
@@ -120,10 +109,10 @@ export class AzureAccess extends BaseAccess {
async getZoneId(domain: string): Promise<{ id: string; name: string }> {
const zones = await this.listZones();
const domainSuffix = domain.endsWith('.') ? domain : domain + '.';
const domainSuffix = domain.endsWith(".") ? domain : domain + ".";
const matchingZone = zones.find((zone: any) => {
const zoneName = zone.name.endsWith('.') ? zone.name : zone.name + '.';
const zoneName = zone.name.endsWith(".") ? zone.name : zone.name + ".";
return domainSuffix.endsWith(zoneName) || domainSuffix === zoneName;
});
@@ -133,7 +122,7 @@ export class AzureAccess extends BaseAccess {
this.ctx.logger.info(`找到 DNS 区域: ${matchingZone.name}, ID: ${matchingZone.id}`);
return {
id: matchingZone.id.split('/').pop()!,
id: matchingZone.id.split("/").pop()!,
name: matchingZone.name,
};
}
@@ -147,7 +136,7 @@ export class AzureAccess extends BaseAccess {
}
list = list.map((item: any) => ({
id: item.id.split('/').pop()!,
id: item.id.split("/").pop()!,
domain: item.name,
}));
@@ -157,7 +146,6 @@ export class AzureAccess extends BaseAccess {
};
}
async createOrUpdateRecordSet(zoneName: string, recordType: string, relativeRecordSetName: string, value: string) {
const client = await this.getDnsManagementClient();
@@ -166,12 +154,7 @@ export class AzureAccess extends BaseAccess {
// 获取现有记录集
let existingRecordSet: any = null;
try {
existingRecordSet = await client.recordSets.get(
this.resourceGroupName,
zoneName,
relativeRecordSetName,
recordType as any
);
existingRecordSet = await client.recordSets.get(this.resourceGroupName, zoneName, relativeRecordSetName, recordType as any);
} catch (e) {
// 记录集不存在,这是正常的
}
@@ -189,19 +172,13 @@ export class AzureAccess extends BaseAccess {
// 添加新记录
txtRecords.push({
value: [value]
value: [value],
});
const recordSet = await client.recordSets.createOrUpdate(
this.resourceGroupName,
zoneName,
relativeRecordSetName,
recordType as any,
{
ttl: 60,
txtRecords: txtRecords
}
);
const recordSet = await client.recordSets.createOrUpdate(this.resourceGroupName, zoneName, relativeRecordSetName, recordType as any, {
ttl: 60,
txtRecords: txtRecords,
});
await utils.sleep(3000);
return recordSet;
@@ -214,15 +191,10 @@ export class AzureAccess extends BaseAccess {
try {
// 获取现有记录集
const existingRecordSet = await client.recordSets.get(
this.resourceGroupName,
zoneName,
relativeRecordSetName,
recordType as any
);
const existingRecordSet = await client.recordSets.get(this.resourceGroupName, zoneName, relativeRecordSetName, recordType as any);
if (!existingRecordSet.txtRecords || existingRecordSet.txtRecords.length === 0) {
this.ctx.logger.info('记录集不存在或已为空,无需删除');
this.ctx.logger.info("记录集不存在或已为空,无需删除");
return;
}
@@ -238,26 +210,15 @@ export class AzureAccess extends BaseAccess {
if (filteredTxtRecords.length === 0) {
// 如果没有记录了,就删除整个记录集
this.ctx.logger.info('删除空记录集');
await client.recordSets.delete(
this.resourceGroupName,
zoneName,
relativeRecordSetName,
recordType as any
);
this.ctx.logger.info("删除空记录集");
await client.recordSets.delete(this.resourceGroupName, zoneName, relativeRecordSetName, recordType as any);
} else {
// 还有其他记录,只更新记录集,移除我们的值
this.ctx.logger.info(`更新记录集,移除指定值,剩余 ${filteredTxtRecords.length} 条记录`);
await client.recordSets.createOrUpdate(
this.resourceGroupName,
zoneName,
relativeRecordSetName,
recordType as any,
{
ttl: existingRecordSet.ttl,
txtRecords: filteredTxtRecords
}
);
await client.recordSets.createOrUpdate(this.resourceGroupName, zoneName, relativeRecordSetName, recordType as any, {
ttl: existingRecordSet.ttl,
txtRecords: filteredTxtRecords,
});
}
} catch (e: any) {
this.ctx.logger.warn(`删除记录时出错: ${e.message}`);
@@ -1,33 +1,33 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { AzureAccess } from './access.js';
import { PageRes, PageSearch } from '@certd/pipeline';
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { AzureAccess } from "./access.js";
import { PageRes, PageSearch } from "@certd/pipeline";
@IsDnsProvider({
name: 'azure-dns',
title: 'Azure DNS',
desc: 'Azure DNS 解析提供商',
accessType: 'azure',
icon: 'simple-icons:microsoftazure',
name: "azure-dns",
title: "Azure DNS",
desc: "Azure DNS 解析提供商",
accessType: "azure",
icon: "simple-icons:microsoftazure",
order: 1,
})
export class AzureDnsProvider extends AbstractDnsProvider {
access: AzureAccess;
async onInstance() {
this.access = this.ctx.access as AzureAccess;
}
async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
const zone = await this.access.getZoneId(domain);
this.logger.info(`获取到 DNS 区域: ${zone.name}, ID: ${zone.id}`);
const relativeRecordSetName = fullRecord.replace(`.${zone.name}`, '').replace(`.${zone.name.replace(/\.$/, '')}`, '');
const relativeRecordSetName = fullRecord.replace(`.${zone.name}`, "").replace(`.${zone.name.replace(/\.$/, "")}`, "");
await this.access.createOrUpdateRecordSet(zone.name, type, relativeRecordSetName, value);
return {
zoneId: zone.id,
zoneName: zone.name,
@@ -40,9 +40,9 @@ export class AzureDnsProvider extends AbstractDnsProvider {
async removeRecord(options: RemoveRecordOptions<any>): Promise<void> {
const { fullRecord, value: reqValue, type } = options.recordReq;
const record = options.recordRes;
if (!record) {
this.logger.warn('记录信息为空,不执行删除');
this.logger.warn("记录信息为空,不执行删除");
return;
}
@@ -51,7 +51,7 @@ export class AzureDnsProvider extends AbstractDnsProvider {
await this.access.deleteRecordSet(record.zoneName, record.recordType, record.relativeRecordSetName, value);
this.logger.info(`删除域名解析成功:${fullRecord} ${value} ${type}`);
} catch (e: any) {
this.logger.warn(`删除域名解析失败:${e.message}`, );
this.logger.warn(`删除域名解析失败:${e.message}`);
}
}
@@ -1,2 +1,2 @@
export * from './access.js';
export * from './azure-dns-provider.js';
export * from "./access.js";
export * from "./azure-dns-provider.js";
@@ -13,7 +13,7 @@ export class Bind9Access extends BaseAccess {
component: {
name: "access-selector",
type: "ssh",
vModel:"modelValue"
vModel: "modelValue",
},
required: true,
})
@@ -37,7 +37,7 @@ export class Bind9Access extends BaseAccess {
placeholder: "53",
},
})
dnsPort: number = 53;
dnsPort = 53;
@AccessInput({
title: "测试",
@@ -1,6 +1,6 @@
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { Bind9Access } from './access.js';
import { SshClient } from '../plugin-lib/ssh/index.js';
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { Bind9Access } from "./access.js";
import { SshClient } from "../plugin-lib/ssh/index.js";
export type Bind9Record = {
fullRecord: string;
@@ -10,11 +10,11 @@ export type Bind9Record = {
};
@IsDnsProvider({
name: 'bind9',
title: 'BIND9 DNS',
desc: '通过 SSH 连接到 BIND9 服务器,使用 nsupdate 命令管理 DNS 记录',
icon: 'clarity:host-line',
accessType: 'bind9',
name: "bind9",
title: "BIND9 DNS",
desc: "通过 SSH 连接到 BIND9 服务器,使用 nsupdate 命令管理 DNS 记录",
icon: "clarity:host-line",
accessType: "bind9",
})
export class Bind9DnsProvider extends AbstractDnsProvider<Bind9Record> {
access!: Bind9Access;
@@ -32,7 +32,7 @@ export class Bind9DnsProvider extends AbstractDnsProvider<Bind9Record> {
// 从 accessService 获取 SSH 授权配置
const sshAccess = await this.ctx.accessGetter.getById(this.access.sshAccessId);
if (!sshAccess) {
throw new Error('SSH 授权不存在');
throw new Error("SSH 授权不存在");
}
return sshAccess;
}
@@ -42,12 +42,8 @@ export class Bind9DnsProvider extends AbstractDnsProvider<Bind9Record> {
*/
private buildNsupdateCommand(commands: string[]): string {
const { dnsServer, dnsPort } = this.access;
const nsupdateScript = [
`server ${dnsServer} ${dnsPort}`,
...commands,
"send",
].join("\n");
const nsupdateScript = [`server ${dnsServer} ${dnsPort}`, ...commands, "send"].join("\n");
// 使用 heredoc 方式执行 nsupdate
return `nsupdate << 'EOF'
${nsupdateScript}
@@ -59,15 +55,15 @@ EOF`;
*/
async createRecord(options: CreateRecordOptions): Promise<Bind9Record> {
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
// 构建 nsupdate 命令
// 格式: update add <name> <ttl> <type> <value>
const updateCommand = `update add ${fullRecord} 60 ${type} "${value}"`;
const nsupdateCmd = this.buildNsupdateCommand([updateCommand]);
this.logger.info('执行 nsupdate 命令添加记录');
this.logger.info("执行 nsupdate 命令添加记录");
try {
const sshAccess = await this.getSshAccess();
await this.sshClient.exec({
@@ -75,10 +71,10 @@ EOF`;
script: nsupdateCmd,
throwOnStdErr: true,
});
this.logger.info(`添加域名解析成功: fullRecord=${fullRecord}, value=${value}`);
} catch (error: any) {
this.logger.error('添加域名解析失败:', error.message);
this.logger.error("添加域名解析失败:", error.message);
throw new Error(`添加 DNS 记录失败: ${error.message}`);
}
@@ -89,7 +85,7 @@ EOF`;
type,
domain,
};
return record;
}
@@ -98,15 +94,15 @@ EOF`;
*/
async removeRecord(options: RemoveRecordOptions<Bind9Record>): Promise<void> {
const { fullRecord, value, type, domain } = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value, type, domain);
this.logger.info("删除域名解析:", fullRecord, value, type, domain);
// 构建 nsupdate 命令
// 格式: update delete <name> <type> <value>
const updateCommand = `update delete ${fullRecord} ${type} "${value}"`;
const nsupdateCmd = this.buildNsupdateCommand([updateCommand]);
this.logger.info('执行 nsupdate 命令删除记录');
this.logger.info("执行 nsupdate 命令删除记录");
try {
const sshAccess = await this.getSshAccess();
await this.sshClient.exec({
@@ -114,11 +110,11 @@ EOF`;
script: nsupdateCmd,
throwOnStdErr: false, // 删除时忽略错误(记录可能已不存在)
});
this.logger.info(`删除域名解析成功: fullRecord=${fullRecord}, value=${value}`);
} catch (error: any) {
// 删除失败只记录警告,不抛出异常(清理操作不应影响主流程)
this.logger.warn('删除域名解析时出现警告:', error.message);
this.logger.warn("删除域名解析时出现警告:", error.message);
}
}
}
@@ -1,2 +1,2 @@
export * from './dns-provider.js';
export * from './access.js';
export * from "./dns-provider.js";
export * from "./access.js";
@@ -1,49 +1,45 @@
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
@IsAccess({
name: 'CacheFly',
title: 'CacheFly',
desc: 'CacheFly',
icon: 'clarity:plugin-line',
name: "CacheFly",
title: "CacheFly",
desc: "CacheFly",
icon: "clarity:plugin-line",
})
export class CacheflyAccess extends BaseAccess {
@AccessInput({
title: 'username',
title: "username",
component: {
placeholder: 'username',
placeholder: "username",
},
required: true,
})
username = '';
username = "";
@AccessInput({
title: 'password',
title: "password",
component: {
placeholder: 'password',
placeholder: "password",
},
required: true,
encrypt: true,
})
password = '';
password = "";
@AccessInput({
title: 'totp key',
title: "totp key",
component: {
placeholder: 'totp key',
placeholder: "totp key",
},
encrypt: true,
})
otpkey = '';
otpkey = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "测试授权是否正确"
helper: "测试授权是否正确",
})
testRequest = true;
@@ -52,16 +48,15 @@ export class CacheflyAccess extends BaseAccess {
return "ok";
}
async login(){
async login() {
let otp = null;
if (this.otpkey) {
const response = await this.ctx.http.request<any, any>({
url: `https://cn-api.my-api.cn/api/totp/?key=${this.otpkey}`,
method: 'get',
method: "get",
});
otp = response;
this.ctx.logger.info('获取到otp:', otp);
this.ctx.logger.info("获取到otp:", otp);
}
const loginResponse = await this.doRequestApi(`/api/2.6/auth/login`, {
username: this.username,
@@ -69,17 +64,16 @@ export class CacheflyAccess extends BaseAccess {
...(otp && { otp }),
});
const token = loginResponse.token;
this.ctx.logger.info('Token 获取成功');
this.ctx.logger.info("Token 获取成功");
return token;
}
async doRequestApi(url: string, data: any = null, method = 'post', token: string | null = null) {
const baseApi = 'https://api.cachefly.com';
async doRequestApi(url: string, data: any = null, method = "post", token: string | null = null) {
const baseApi = "https://api.cachefly.com";
const headers = {
'Content-Type': 'application/json',
...(token ? { 'x-cf-authorization': `Bearer ${token}` } : {}),
"Content-Type": "application/json",
...(token ? { "x-cf-authorization": `Bearer ${token}` } : {}),
};
const res = await this.ctx.http.request<any, any>({
url,
@@ -1,2 +1,2 @@
export * from './plugins/index.js';
export * from './access.js';
export * from "./plugins/index.js";
export * from "./access.js";
@@ -1 +1 @@
export * from './plugin-deploy-to-cdn.js';
export * from "./plugin-deploy-to-cdn.js";
@@ -1,12 +1,12 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo } from '@certd/plugin-cert';
import { CacheflyAccess } from '../access.js';
import { CertApplyPluginNames} from '@certd/plugin-cert';
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertInfo } from "@certd/plugin-cert";
import { CacheflyAccess } from "../access.js";
import { CertApplyPluginNames } from "@certd/plugin-cert";
@IsTaskPlugin({
name: 'CacheFly',
title: 'CacheFly-部署证书到CacheFly',
desc: '部署证书到 CacheFly',
icon: 'clarity:plugin-line',
name: "CacheFly",
title: "CacheFly-部署证书到CacheFly",
desc: "部署证书到 CacheFly",
icon: "clarity:plugin-line",
group: pluginGroups.cdn.key,
default: {
strategy: {
@@ -16,36 +16,32 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
})
export class CacheFlyPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: true,
})
cert!: CertInfo;
@TaskInput({
title: 'Access授权',
helper: 'CacheFly 的授权',
title: "Access授权",
helper: "CacheFly 的授权",
component: {
name: 'access-selector',
type: 'CacheFly',
name: "access-selector",
type: "CacheFly",
},
required: true,
})
accessId!: string;
async onInstance() {}
async execute(): Promise<void> {
const { cert, accessId } = this;
const access = (await this.getAccess(accessId)) as CacheflyAccess;
const token = await access.login();
// 更新证书
await access.doRequestApi(
@@ -54,10 +50,10 @@ export class CacheFlyPlugin extends AbstractTaskPlugin {
certificate: cert.crt,
certificateKey: cert.key,
},
'post',
"post",
token
);
this.logger.info('证书更新成功');
this.logger.info("证书更新成功");
}
}
@@ -1,7 +1,7 @@
export type CaptchaRequest = {
remoteIp: string,
}
export interface ICaptchaAddon{
onValidate(data?:any,req?:CaptchaRequest):Promise<any>;
getCaptcha():Promise<any>;
remoteIp: string;
};
export interface ICaptchaAddon {
onValidate(data?: any, req?: CaptchaRequest): Promise<any>;
getCaptcha(): Promise<any>;
}
@@ -9,7 +9,6 @@ import { CaptchaRequest, ICaptchaAddon } from "../api.js";
showTest: false,
})
export class CfTurnstileCaptcha extends BaseAddon implements ICaptchaAddon {
@AddonInput({
title: "站点密钥",
component: {
@@ -37,30 +36,28 @@ export class CfTurnstileCaptcha extends BaseAddon implements ICaptchaAddon {
const { remoteIp } = req;
const formData = new FormData();
formData.append('secret', this.secretKey);
formData.append('response', token);
formData.append('remoteip', remoteIp);
formData.append("secret", this.secretKey);
formData.append("response", token);
formData.append("remoteip", remoteIp);
const res = await this.http.request({
url: 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
data: formData
})
if (res.success) {
// Token is valid - process the form
return true;
} else {
// Token is invalid - reject the submission
const errorMessage = 'Cloudflare Turnstile 校验失败:' + res['error-codes'].join(', ')
this.logger.error(errorMessage);
throw new Error(errorMessage);
}
const res = await this.http.request({
url: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
},
data: formData,
});
if (res.success) {
// Token is valid - process the form
return true;
} else {
// Token is invalid - reject the submission
const errorMessage = "Cloudflare Turnstile 校验失败:" + res["error-codes"].join(", ");
this.logger.error(errorMessage);
throw new Error(errorMessage);
}
}
async getCaptcha(): Promise<any> {
@@ -3,53 +3,50 @@ import crypto from "crypto";
import { ICaptchaAddon } from "../api.js";
@IsAddon({
addonType:"captcha",
name: 'geetest',
title: '极验验证码v4',
desc: '',
showTest:false,
addonType: "captcha",
name: "geetest",
title: "极验验证码v4",
desc: "",
showTest: false,
})
export class GeeTestCaptcha extends BaseAddon implements ICaptchaAddon{
export class GeeTestCaptcha extends BaseAddon implements ICaptchaAddon {
@AddonInput({
title: '验证ID',
title: "验证ID",
component: {
placeholder: 'captchaId',
placeholder: "captchaId",
},
helper:"[极验验证码v4](https://console.geetest.com/sensbot/management) -> 创建业务模块 -> 新增业务场景",
helper: "[极验验证码v4](https://console.geetest.com/sensbot/management) -> 创建业务模块 -> 新增业务场景",
required: true,
})
captchaId = '';
captchaId = "";
@AddonInput({
title: '验证Key',
title: "验证Key",
component: {
placeholder: 'captchaKey',
placeholder: "captchaKey",
},
required: true,
})
captchaKey = '';
captchaKey = "";
async onValidate(data?:any) {
async onValidate(data?: any) {
if (!data) {
return false
return false;
}
// geetest 服务地址
// geetest server url
// geetest server url
const API_SERVER = "http://gcaptcha4.geetest.com";
// geetest 验证接口
// geetest server interface
// geetest 验证接口
// geetest server interface
const API_URL = API_SERVER + "/validate" + "?captcha_id=" + this.captchaId;
// 前端参数
// web parameter
var lot_number = data['lot_number'];
var captcha_output = data['captcha_output'];
var pass_token = data['pass_token'];
var gen_time = data['gen_time'];
const lot_number = data["lot_number"];
const captcha_output = data["captcha_output"];
const pass_token = data["pass_token"];
const gen_time = data["gen_time"];
if (!lot_number || !captcha_output || !pass_token || !gen_time) {
return false;
}
@@ -57,23 +54,23 @@ export class GeeTestCaptcha extends BaseAddon implements ICaptchaAddon{
// 生成签名, 使用标准的hmac算法,使用用户当前完成验证的流水号lot_number作为原始消息message,使用客户验证私钥作为key
// 采用sha256散列算法将message和key进行单向散列生成最终的 “sign_token” 签名
// use lot_number + CAPTCHA_KEY, generate the signature
var sign_token = this.hmac_sha256_encode(lot_number, this.captchaKey);
const sign_token = this.hmac_sha256_encode(lot_number, this.captchaKey);
// 向极验转发前端数据 + “sign_token” 签名
// send web parameter and “sign_token” to geetest server
var datas = {
'lot_number': lot_number,
'captcha_output': captcha_output,
'pass_token': pass_token,
'gen_time': gen_time,
'sign_token': sign_token
const datas = {
lot_number: lot_number,
captcha_output: captcha_output,
pass_token: pass_token,
gen_time: gen_time,
sign_token: sign_token,
};
// post request
// 根据极验返回的用户验证状态, 网站主进行自己的业务逻辑
// According to the user authentication status returned by the geetest, the website owner carries out his own business logic
try{
const res = await this.doRequest(datas, API_URL)
try {
const res = await this.doRequest(datas, API_URL);
if (res.result == "success") {
// 验证成功
// verification successful
@@ -81,42 +78,38 @@ export class GeeTestCaptcha extends BaseAddon implements ICaptchaAddon{
} else {
// 验证失败
// verification failed
this.logger.error("极验验证不通过 ",res.reason)
this.logger.error("极验验证不通过 ", res.reason);
return false;
}
}catch (e) {
this.ctx.logger.error("极验验证服务异常",e)
return true
} catch (e) {
this.ctx.logger.error("极验验证服务异常", e);
return true;
}
}
// 生成签名
// Generate signature
hmac_sha256_encode(value, key){
var hash = crypto.createHmac("sha256", key)
.update(value, 'utf8')
.digest('hex');
// Generate signature
hmac_sha256_encode(value, key) {
const hash = crypto.createHmac("sha256", key).update(value, "utf8").digest("hex");
return hash;
}
// 发送post请求, 响应json数据如:{"result": "success", "reason": "", "captcha_args": {}}
// Send a post request and respond to JSON data, such as: {result ":" success "," reason ":" "," captcha_args ": {}}
async doRequest(datas, url){
var options = {
// 发送post请求, 响应json数据如:{"result": "success", "reason": "", "captcha_args": {}}
// Send a post request and respond to JSON data, such as: {result ":" success "," reason ":" "," captcha_args ": {}}
async doRequest(datas, url) {
const options = {
url: url,
method: "POST",
params: datas,
timeout: 5000
timeout: 5000,
};
const result = await this.ctx.http.request(options);
return result;
}
async getCaptcha(): Promise<any> {
async getCaptcha(): Promise<any> {
return {
captchaId: this.captchaId,
}
};
}
}
@@ -4,53 +4,52 @@ import { cache } from "@certd/basic";
import { nanoid } from "nanoid";
@IsAddon({
addonType:"captcha",
name: 'image',
title: '图片验证码',
desc: '',
showTest:false,
addonType: "captcha",
name: "image",
title: "图片验证码",
desc: "",
showTest: false,
})
export class ImageCaptcha extends BaseAddon implements ICaptchaAddon{
async onValidate(data?:any) {
export class ImageCaptcha extends BaseAddon implements ICaptchaAddon {
async onValidate(data?: any) {
if (!data) {
return false;
}
return await this.checkCaptcha(data.randomStr, data.imageCode)
return await this.checkCaptcha(data.randomStr, data.imageCode);
}
async getCaptchaText(randomStr:string) {
return cache.get('imgCode:' + randomStr);
async getCaptchaText(randomStr: string) {
return cache.get("imgCode:" + randomStr);
}
async removeCaptcha(randomStr:string) {
cache.delete('imgCode:' + randomStr);
async removeCaptcha(randomStr: string) {
cache.delete("imgCode:" + randomStr);
}
async checkCaptcha(randomStr: string, userCaptcha: string) {
const code = await this.getCaptchaText(randomStr);
if (code == null) {
throw new Error('验证码已过期');
throw new Error("验证码已过期");
}
if (code.toLowerCase() !== userCaptcha?.toLowerCase()) {
throw new Error('验证码不正确');
throw new Error("验证码不正确");
}
await this.removeCaptcha(randomStr);
return true;
}
async getCaptcha(): Promise<any> {
const svgCaptcha = await import('svg-captcha');
const c = svgCaptcha.create();
//{data: '<svg.../svg>', text: 'abcd'}
const imgCode = c.text; // = RandomUtil.randomStr(4, true);
const randomStr = nanoid(10)
cache.set('imgCode:' + randomStr, imgCode, {
ttl: 2 * 60 * 1000, //过期时间 2分钟
})
return {
randomStr: randomStr,
imageData: c.data,
}
}
async getCaptcha(): Promise<any> {
const svgCaptcha = await import("svg-captcha");
const c = svgCaptcha.create();
//{data: '<svg.../svg>', text: 'abcd'}
const imgCode = c.text; // = RandomUtil.randomStr(4, true);
const randomStr = nanoid(10);
cache.set("imgCode:" + randomStr, imgCode, {
ttl: 2 * 60 * 1000, //过期时间 2分钟
});
return {
randomStr: randomStr,
imageData: c.data,
};
}
}
@@ -1,4 +1,4 @@
export * from './geetest/index.js';
export * from './image/index.js';
export * from './tencent/index.js';
export * from './cf-turnstile/index.js';
export * from "./geetest/index.js";
export * from "./image/index.js";
export * from "./tencent/index.js";
export * from "./cf-turnstile/index.js";
@@ -94,7 +94,7 @@ export class TencentCaptcha extends BaseAddon implements ICaptchaAddon {
this.logger.error("腾讯云验证码账户欠费,临时放行:", err.message);
return true;
}
throw err
throw err;
}
}
@@ -335,7 +335,7 @@ export class AcmeService {
try {
const recordRes = await dnsProvider.createRecord(recordReq);
this.logger.info("添加 TXT 解析记录成功", JSON.stringify(recordRes));
return {
return {
recordReq,
recordRes,
dnsProvider,
@@ -345,9 +345,8 @@ export class AcmeService {
} catch (e: any) {
//@ts-ignore
e.message = `[${dnsProvider?.constructor?.name}错误] ${e.message}`;
throw e
throw e;
}
};
const doDnsPersistVerify = async (challenge: any, plan: DnsPersistVerifyPlan) => {
@@ -1,10 +1,9 @@
import { AbstractTaskPlugin, FileItem, IContext, Step, TaskInput, TaskOutput } from "@certd/pipeline";
import dayjs from "dayjs";
import type { CertInfo } from "./acme.js";
import { CertReader,CertConverter, EVENT_CERT_APPLY_SUCCESS } from "@certd/plugin-lib";
import { CertReader, CertConverter, EVENT_CERT_APPLY_SUCCESS } from "@certd/plugin-lib";
import JSZip from "jszip";
export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
@TaskInput({
title: "证书域名",
@@ -16,7 +15,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
placeholder: "请输入证书域名/IP,比如:foo.com , *.foo.com , *.sub.foo.com , *.bar.com , 123.123.123.123",
tokenSeparators: [",", " ", "", "、", "|"],
search: true,
pager:true,
pager: true,
},
rules: [{ type: "domains" }],
required: true,
@@ -20,7 +20,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
@TaskInput({
title: "更新天数",
value:20,
value: 20,
component: {
name: "a-input-number",
vModel: "value",
@@ -140,25 +140,25 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
*/
isWillExpire(cert: CertReader, maxDays = 15) {
const expires = cert.expires;
if (expires == null) {
if (expires == null) {
throw new Error("过期时间不能为空");
}
const begin = dayjs(cert.detail?.notBefore )
const begin = dayjs(cert.detail?.notBefore);
//证书总天数
const totalDays = Math.floor((expires - begin.valueOf()) / (1000 * 60 * 60 * 24));
// 检查有效期
// 检查有效期
const leftDays = Math.floor((expires - dayjs().valueOf()) / (1000 * 60 * 60 * 24));
this.logger.info(`证书有效期剩余天数:${leftDays}`);
if(totalDays < maxDays){
this.logger.info(`证书有效期剩余天数:${leftDays}`);
if (totalDays < maxDays) {
this.logger.warn(`当前更新天数为${maxDays},证书总天数${totalDays},总天数小于更新天数(更新天数是指到期前多少天更新证书,您可以在任务配置中调整该值)`);
maxDays = Math.floor(totalDays/2);
if(maxDays < 2){
maxDays = Math.floor(totalDays / 2);
if (maxDays < 2) {
maxDays = 2;
}
this.logger.warn(`为避免每次运行都更新证书,更新天数自动减半(即证书最大时长${totalDays}天减半),调整为${maxDays}`);
}
return {
isWillExpire: leftDays <= maxDays,
leftDays,
@@ -28,27 +28,25 @@ export class CertApplyGetFormAliyunPlugin extends CertApplyBasePlugin {
})
accessId!: string;
@TaskInput(
{
title: "证书API 版本",
value: "v1",
component: {
name: "a-select",
vModel: "value",
options: [
{
label: "API 1.0 (旧版)",
value: "v1",
},
{
label: "API 2.0 (新版)",
value: "v2",
},
],
},
helper: "选择阿里云证书 API 版本",
}
)
@TaskInput({
title: "证书API 版本",
value: "v1",
component: {
name: "a-select",
vModel: "value",
options: [
{
label: "API 1.0 (旧版)",
value: "v1",
},
{
label: "API 2.0 (新版)",
value: "v2",
},
],
},
helper: "选择阿里云证书 API 版本",
})
apiVersion!: string;
@TaskInput(
@@ -67,7 +65,7 @@ export class CertApplyGetFormAliyunPlugin extends CertApplyBasePlugin {
)
orderId!: string;
async onInit(): Promise<void> { }
async onInit(): Promise<void> {}
async doCertApply(): Promise<CertReader> {
const access = await this.getAccess<AliyunAccess>(this.accessId);
@@ -102,7 +100,7 @@ export class CertApplyGetFormAliyunPlugin extends CertApplyBasePlugin {
throw new Error("请先输入证书实例 ID");
}
if (Array.isArray(this.orderId) && this.orderId.length > 0) {
this.orderId = this.orderId[0]
this.orderId = this.orderId[0];
}
const certificateId = await this.getOrderDetailV2(client, this.orderId);
this.logger.info(`获取到证书 ID:${certificateId}`);
@@ -1,3 +1,2 @@
export * from "./base.js";
export * from "./apply.js";
@@ -7,7 +7,6 @@ import path from "path";
import JSZip from "jszip";
import { PrivateKeyType } from "./dns.js";
@IsTaskPlugin({
name: "CertApplyLego",
icon: "ph:certificate",
@@ -1,50 +1,48 @@
import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
/**
*
* certd的后台管理系统中
*/
@IsAccess({
name: 'cloudflare',
title: 'cloudflare授权',
icon: 'simple-icons:cloudflare',
desc: '',
name: "cloudflare",
title: "cloudflare授权",
icon: "simple-icons:cloudflare",
desc: "",
})
export class CloudflareAccess extends BaseAccess {
/**
*
*/
@AccessInput({
title: 'API Token',
title: "API Token",
component: {
placeholder: 'api token,用户 API 令牌',
placeholder: "api token,用户 API 令牌",
},
helper:
'前往 [获取API令牌](https://dash.cloudflare.com/profile/api-tokens),注意是令牌,不是密钥。\n token权限必须包含:[Zone区域-Zone区域-Edit编辑], [Zone区域-DNS-Edit编辑]',
helper: "前往 [获取API令牌](https://dash.cloudflare.com/profile/api-tokens),注意是令牌,不是密钥。\n token权限必须包含:[Zone区域-Zone区域-Edit编辑], [Zone区域-DNS-Edit编辑]",
required: true,
encrypt: true,
})
apiToken = '';
apiToken = "";
@AccessInput({
title: 'HTTP代理',
title: "HTTP代理",
component: {
placeholder: 'http://xxxx.xxx.xx:10811',
placeholder: "http://xxxx.xxx.xx:10811",
},
helper:
'是否使用http代理',
helper: "是否使用http代理",
required: false,
encrypt: false,
})
proxy = '';
proxy = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "测试授权是否正确"
helper: "测试授权是否正确",
})
testRequest = true;
@@ -53,20 +51,19 @@ export class CloudflareAccess extends BaseAccess {
return "ok";
}
async getZoneList() {
const url = `https://api.cloudflare.com/client/v4/zones`;
const res = await this.doRequestApi(url, null, 'get');
return res.result
const res = await this.doRequestApi(url, null, "get");
return res.result;
}
async doRequestApi(url: string, data: any = null, method = 'post') {
async doRequestApi(url: string, data: any = null, method = "post") {
try {
const res = await this.ctx.http.request<any, any>({
url,
method,
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiToken}`,
},
data,
@@ -81,14 +78,13 @@ export class CloudflareAccess extends BaseAccess {
const data = e.response?.data;
if (data && data.success === false && data.errors && data.errors.length > 0) {
if (data.errors[0].code === 81058) {
this.ctx.logger.info('dns解析记录重复');
this.ctx.logger.info("dns解析记录重复");
return null;
}
}
throw e;
}
}
}
new CloudflareAccess();
@@ -1,7 +1,7 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { CloudflareAccess } from './access.js';
import { Pager, PageRes, PageSearch } from '@certd/pipeline';
import { CloudflareAccess } from "./access.js";
import { Pager, PageRes, PageSearch } from "@certd/pipeline";
export type CloudflareRecord = {
id: string;
@@ -18,12 +18,12 @@ export type CloudflareRecord = {
// 这里通过IsDnsProvider注册一个dnsProvider
@IsDnsProvider({
name: 'cloudflare',
title: 'cloudflare',
desc: 'cloudflare dns provider',
icon: 'simple-icons:cloudflare',
name: "cloudflare",
title: "cloudflare",
desc: "cloudflare dns provider",
icon: "simple-icons:cloudflare",
// 这里是对应的 cloudflare的access类型名称
accessType: 'cloudflare',
accessType: "cloudflare",
})
export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord> {
access!: CloudflareAccess;
@@ -39,17 +39,15 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
}
async getZoneId(domain: string) {
this.logger.info('获取zoneId:', domain);
this.logger.info("获取zoneId:", domain);
const url = `https://api.cloudflare.com/client/v4/zones?name=${domain}`;
const res = await this.access.doRequestApi(url, null, 'get');
const res = await this.access.doRequestApi(url, null, "get");
if (res.result.length === 0) {
throw new Error(`未找到域名${domain}的zoneId`);
}
return res.result[0].id;
}
/**
* dns解析记录
*/
@@ -61,10 +59,10 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
* domain: 'example.com'
*/
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
const zoneId = await this.getZoneId(domain);
this.logger.info('获取zoneId成功:', zoneId);
this.logger.info("获取zoneId成功:", zoneId);
// 给domain下创建txt类型的dns解析记录,fullRecord
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`;
@@ -92,7 +90,7 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
async findRecord(zoneId: string, options: CreateRecordOptions): Promise<CloudflareRecord | null> {
const { fullRecord, value } = options;
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?type=TXT&name=${fullRecord}&content=${value}`;
const res = await this.access.doRequestApi(url, null, 'get');
const res = await this.access.doRequestApi(url, null, "get");
if (res.result.length === 0) {
return null;
}
@@ -106,29 +104,29 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
async removeRecord(options: RemoveRecordOptions<CloudflareRecord>): Promise<void> {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value);
this.logger.info("删除域名解析:", fullRecord, value);
if (!record) {
this.logger.info('record为空,不执行删除');
this.logger.info("record为空,不执行删除");
return;
}
//这里调用删除txt dns解析记录接口
const zoneId = record.zone_id;
const recordId = record.id;
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${recordId}`;
await this.access.doRequestApi(url, null, 'delete');
await this.access.doRequestApi(url, null, "delete");
this.logger.info(`删除域名解析成功:fullRecord=${fullRecord},value=${value}`);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const pager = new Pager(req);
let url = `https://api.cloudflare.com/client/v4/zones?page=${pager.pageNo}&per_page=${pager.pageSize}`;
if (req.searchKey) {
url += `&name=${req.searchKey}`;
}
const ret = await this.access.doRequestApi(url, null, 'get');
const ret = await this.access.doRequestApi(url, null, "get");
let list = ret.result || []
let list = ret.result || [];
list = list.map((item: any) => ({
id: item.id,
domain: item.name,
@@ -1,2 +1,2 @@
export * from './dns-provider.js';
export * from './access.js';
export * from "./dns-provider.js";
export * from "./access.js";
@@ -11,45 +11,42 @@ import { CmccClient } from "./cmcc-client.js";
name: "cmcc",
title: "中国移动CND授权",
desc: "",
icon: "svg:cmcc"
icon: "svg:cmcc",
})
export class CmccAccess extends BaseAccess {
@AccessInput({
title: 'TenantID',
title: "TenantID",
component: {
placeholder: 'TenantID',
placeholder: "TenantID",
},
required: true,
})
tenantId = '';
tenantId = "";
@AccessInput({
title: 'TenantKey',
title: "TenantKey",
component: {
placeholder: 'TenantKey',
placeholder: "TenantKey",
},
required: true,
encrypt: true,
})
tenantKey = '';
tenantKey = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "点击测试接口是否正常"
helper: "点击测试接口是否正常",
})
testRequest = true;
async onTestRequest() {
const client = await this.getCmccClient()
await client.getDomainList({})
return "ok"
const client = await this.getCmccClient();
await client.getDomainList({});
return "ok";
}
async getCmccClient() {
@@ -58,9 +55,8 @@ export class CmccAccess extends BaseAccess {
tenantKey: this.tenantKey,
http: this.ctx.http,
logger: this.ctx.logger,
})
});
}
}
new CmccAccess();
@@ -1,6 +1,6 @@
import { HttpClient, ILogger } from '@certd/basic';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import * as crypto from 'crypto';
import { HttpClient, ILogger } from "@certd/basic";
import { CertInfo, CertReader } from "@certd/plugin-cert";
import * as crypto from "crypto";
export interface CmcdnConfig {
tenantId: string;
tenantKey: string;
@@ -25,18 +25,18 @@ export class CmccClient {
*/
constructor(config: CmcdnConfig) {
this.config = {
endpoint: 'https://p.cdn.10086.cn/',
endpoint: "https://p.cdn.10086.cn/",
...config,
};
this.http = config.http
this.http = config.http;
this.logger = config.logger;
if (!this.config.tenantId) {
throw new Error('tenantId is required');
throw new Error("tenantId is required");
}
if (!this.config.tenantKey) {
throw new Error('tenantKey is required');
throw new Error("tenantKey is required");
}
}
@@ -46,7 +46,7 @@ export class CmccClient {
* @returns SHA256哈希值
*/
private sha256Hex(data: string): string {
return crypto.createHash('sha256').update(data).digest('hex');
return crypto.createHash("sha256").update(data).digest("hex");
}
/**
@@ -74,7 +74,7 @@ export class CmccClient {
* @returns
*/
private generateApiSign(body: any, token: string): string {
const bodyStr = body ? JSON.stringify(body) : '';
const bodyStr = body ? JSON.stringify(body) : "";
return this.sha256Hex(bodyStr + token);
}
@@ -89,8 +89,6 @@ export class CmccClient {
return Date.now() < this.tokenExpiresAt;
}
/**
* token
* @returns token
@@ -114,17 +112,17 @@ export class CmccClient {
const response = await this.http.request({
baseURL: this.config.endpoint,
url: '/api/authentication',
method: 'POST',
url: "/api/authentication",
method: "POST",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
"Content-Type": "application/json",
Accept: "application/json",
},
data: authRequest,
skipSslVerify: true,
logParams: false,
logRes: false,
logData: false
logData: false,
});
this.token = response.token;
@@ -144,18 +142,18 @@ export class CmccClient {
// 设置默认headers
const defaultHeaders: Record<string, string> = {
'Content-Type': 'application/json',
'Accept': 'application/vnd.cmcdn+json',
'CMCDN-Auth-Token': token,
"Content-Type": "application/json",
Accept: "application/vnd.cmcdn+json",
"CMCDN-Auth-Token": token,
};
// 生成签名
if (req.method === 'POST' || req.method === 'PUT') {
if (req.method === "POST" || req.method === "PUT") {
const signature = this.generateApiSign(req.data, token);
defaultHeaders['HTTP-X-CMCDN-Signature'] = signature;
defaultHeaders["HTTP-X-CMCDN-Signature"] = signature;
} else {
const signature = this.sha256Hex(token);
defaultHeaders['HTTP-X-CMCDN-Signature'] = signature;
defaultHeaders["HTTP-X-CMCDN-Signature"] = signature;
}
// 合并自定义headers
@@ -172,7 +170,7 @@ export class CmccClient {
skipSslVerify: true,
logParams: false,
logRes: false,
logData: false
logData: false,
});
if (response.error_code != 0) {
this.logger.error(`接口请求失败,${JSON.stringify(response)}`);
@@ -283,16 +281,15 @@ export class CmccClient {
]
}
*/
async getDomainList(req: { domainName?: string, domainStatus?: string }) {
async getDomainList(req: { domainName?: string; domainStatus?: string }) {
const res = await this.doRequest({
url: "/api/domain_list",
method: "GET",
params: {
domainName: req.domainName,
domainStatus: req.domainStatus,
}
})
},
});
this.logger.info("getDomainList", res);
@@ -364,7 +361,6 @@ export class CmccClient {
12.1.4.2
*/
async uploadCert(req: { cert: CertInfo }) {
const certReader = new CertReader(req.cert);
const res = await this.doRequest({
url: "/api/config/action?commandType=saveCrt&version=1",
@@ -373,8 +369,8 @@ export class CmccClient {
certificate: req.cert.crt,
private_key: req.cert.key,
crt_name: certReader.buildCertName(),
}
})
},
});
this.logger.info("uploadCert", res);
@@ -382,10 +378,10 @@ export class CmccClient {
}
/**
*
* @param req
*
* @param req
*/
async deployCertToCdn(req: { domainNames: string[], certId: string }) {
async deployCertToCdn(req: { domainNames: string[]; certId: string }) {
// /api/config/action?commandType = manageDomainBaseConfig&version = 1
const res = await this.doRequest({
url: "/api/config/action?commandType=manageDomainBaseConfig&version=1",
@@ -395,11 +391,10 @@ export class CmccClient {
domains: req.domainNames,
https_enable: true,
unique_id: req.certId,
}
})
},
});
this.logger.info("deployCertToCdn", res);
return res.data;
}
}
@@ -1,2 +1,2 @@
export * from './access.js'
export * from './plugin-deploy-to-cdn.js'
export * from "./access.js";
export * from "./plugin-deploy-to-cdn.js";
@@ -1,10 +1,4 @@
import {
IsTaskPlugin,
PageSearch,
pluginGroups,
RunStrategy,
TaskInput
} from "@certd/pipeline";
import { IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { AbstractPlusTaskPlugin } from "@certd/plugin-plus";
@@ -22,9 +16,9 @@ import { CmccAccess } from "./access.js";
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
//类名规范,跟上面插件名称(name)一致
export class CmccDeployCertToCdn extends AbstractPlusTaskPlugin {
@@ -34,8 +28,8 @@ export class CmccDeployCertToCdn extends AbstractPlusTaskPlugin {
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
from: [...CertApplyPluginNames],
},
// required: true, // 必填
})
cert!: CertInfo;
@@ -48,9 +42,9 @@ export class CmccDeployCertToCdn extends AbstractPlusTaskPlugin {
title: "中国移动-授权",
component: {
name: "access-selector",
type: "cmcc" //固定授权类型
type: "cmcc", //固定授权类型
},
required: true //必填
required: true, //必填
})
accessId!: string;
//
@@ -61,14 +55,13 @@ export class CmccDeployCertToCdn extends AbstractPlusTaskPlugin {
helper: "要更新的中国移动CDN域名",
action: CmccDeployCertToCdn.prototype.onGetDomainList.name,
pager: false,
search: false
search: false,
})
)
domainList!: string[];
//插件实例化时执行的方法
async onInstance() {
}
async onInstance() {}
//插件执行方法
async execute(): Promise<void> {
@@ -77,17 +70,16 @@ export class CmccDeployCertToCdn extends AbstractPlusTaskPlugin {
const client = await access.getCmccClient();
this.logger.info(`----------- 开始更新证书:${this.domainList}`);
const newCert = await client.uploadCert({
cert: this.cert
})
cert: this.cert,
});
const certId = newCert.unique_id
const certId = newCert.unique_id;
this.logger.info(`----------- 上传证书成功,证书ID:${certId}`);
await client.deployCertToCdn({
certId: certId,
domainNames: this.domainList
domainNames: this.domainList,
});
this.logger.info(`----------- 更新证书${this.domainList}成功,等待10s`);
await this.ctx.utils.sleep(10000);
@@ -96,14 +88,13 @@ export class CmccDeployCertToCdn extends AbstractPlusTaskPlugin {
async onGetDomainList(data: PageSearch = {}) {
const access = await this.getAccess<CmccAccess>(this.accessId);
const client= await access.getCmccClient();
const res = await client.getDomainList({})
const list = res || []
const client = await access.getCmccClient();
const res = await client.getDomainList({});
const list = res || [];
if (!list || list.length === 0) {
throw new Error("没有找到加速域名");
}
/**
* certificate-id
* name
@@ -113,7 +104,7 @@ export class CmccDeployCertToCdn extends AbstractPlusTaskPlugin {
return {
label: `${item.domainName}`,
value: item.domainName,
domain: item.domainName
domain: item.domainName,
};
});
return {
@@ -1,66 +1,65 @@
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from "@certd/pipeline";
import { DomainRecord } from "@certd/plugin-lib";
/**
*
* certd的后台管理系统中
*/
@IsAccess({
name: 'demo',
title: '授权插件示例',
icon: 'clarity:plugin-line', //插件图标
desc: '这是一个示例授权插件,用于演示如何实现一个授权插件',
name: "demo",
title: "授权插件示例",
icon: "clarity:plugin-line", //插件图标
desc: "这是一个示例授权插件,用于演示如何实现一个授权插件",
})
export class DemoAccess extends BaseAccess {
/**
/**
*
*/
@AccessInput({
title: '授权方式',
value: 'apiKey', //默认值
title: "授权方式",
value: "apiKey", //默认值
component: {
name: "a-select", //基于antdv的输入组件
vModel: "value", // v-model绑定的属性名
options: [ //组件参数
options: [
//组件参数
{ label: "API密钥(推荐)", value: "apiKey" },
{ label: "账号密码", value: "account" },
],
placeholder: 'demoKeyId',
placeholder: "demoKeyId",
},
required: true,
})
apiType = '';
apiType = "";
/**
*
*/
@AccessInput({
title: '密钥Id',
title: "密钥Id",
component: {
name:"a-input",
name: "a-input",
allowClear: true,
placeholder: 'demoKeyId',
placeholder: "demoKeyId",
},
required: true,
})
demoKeyId = '';
demoKeyId = "";
@AccessInput({
title: '密钥',//标题
required: true, //text组件可以省略
title: "密钥", //标题
required: true, //text组件可以省略
encrypt: true, //该属性是否需要加密
})
demoKeySecret = '';
demoKeySecret = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "点击测试接口是否正常"
helper: "点击测试接口是否正常",
})
testRequest = true;
@@ -69,7 +68,7 @@ export class DemoAccess extends BaseAccess {
*/
async onTestRequest() {
await this.GetDomainList({});
return "ok"
return "ok";
}
/**
@@ -85,17 +84,17 @@ export class DemoAccess extends BaseAccess {
domain: req.searchKey,
offset: pager.getOffset(),
limit: pager.pageSize,
}
},
});
const total = resp?.TotalCount || 0;
let list = resp?.DomainList?.map((item) => {
const list = resp?.DomainList?.map(item => {
item.domain = item.Domain;
item.id = item.DomainId;
return item;
})
});
return {
total,
list
list,
};
}
@@ -104,7 +103,7 @@ export class DemoAccess extends BaseAccess {
/**
* api调用方法, API文档
*/
async doRequest(req: { action: string, data?: any }) {
async doRequest(req: { action: string; data?: any }) {
/**
this.ctx中包含很多有用的工具类
type AccessContext = {
@@ -119,12 +118,12 @@ export class DemoAccess extends BaseAccess {
method: "POST",
data: {
Action: req.action,
Body: req.data
}
Body: req.data,
},
});
if (res.Code !== 0) {
//异常处理
//异常处理
throw new Error(res.Message || "请求失败");
}
return res.Resp;
@@ -1,8 +1,8 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { PageRes, PageSearch } from '@certd/pipeline';
import { isDev } from '../../utils/env.js';
import { DemoAccess } from './access.js';
import { PageRes, PageSearch } from "@certd/pipeline";
import { isDev } from "../../utils/env.js";
import { DemoAccess } from "./access.js";
type DemoRecord = {
// 这里定义Record记录的数据结构,跟对应云平台接口返回值一样即可,一般是拿到id就行,用于删除txt解析记录,清理申请痕迹
@@ -11,21 +11,21 @@ type DemoRecord = {
// 这里通过IsDnsProvider注册一个dnsProvider
@IsDnsProvider({
name: 'demo',
title: 'Dns提供商Demo',
desc: 'dns provider示例',
icon: 'clarity:plugin-line',
name: "demo",
title: "Dns提供商Demo",
desc: "dns provider示例",
icon: "clarity:plugin-line",
// 这里是对应的云平台的access类型名称
accessType: 'demo',
accessType: "demo",
order: 99,
})
export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
access!: DemoAccess;
async onInstance() {
this.access = this.ctx.access as DemoAccess
this.access = this.ctx.access as DemoAccess;
// 也可以通过ctx成员变量传递context
this.logger.debug('access', this.access);
this.logger.debug("access", this.access);
//初始化的操作
//...
}
@@ -42,7 +42,7 @@ export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
* domain: 'example.com'
*/
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
// 调用创建dns解析记录的对应的云端接口,创建txt类型的dns解析记录
// 请根据实际接口情况调用,例如:
@@ -62,7 +62,7 @@ export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
async removeRecord(options: RemoveRecordOptions<DemoRecord>): Promise<void> {
const { fullRecord, value, domain } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', domain, fullRecord, value, record);
this.logger.info("删除域名解析:", domain, fullRecord, value, record);
//这里调用删除txt dns解析记录接口
//请根据实际接口情况调用,例如:
@@ -73,29 +73,28 @@ export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
// })
//
this.logger.info('删除域名解析成功:', fullRecord, value);
this.logger.info("删除域名解析成功:", fullRecord, value);
}
/**
*
*
* @param req
* @returns
* @returns
*/
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const res = await this.http.request({
// 请求接口获取域名列表
})
const list = []
});
const list = [];
// const list = res.Domains?.map(item => ({
// id: item.Id,
// domain: item.DomainName,
// })) || []
return {
list,
total: res.Total,
}
};
}
}
@@ -1,3 +1,3 @@
export * from './dns-provider.js';
export * from './plugins/index.js';
export * from './access.js';
export * from "./dns-provider.js";
export * from "./plugins/index.js";
export * from "./access.js";
@@ -1 +1 @@
export * from './plugin-test.js';
export * from "./plugin-test.js";
@@ -1,13 +1,13 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { optionsUtils } from '@certd/basic';
import { CertApplyPluginNames} from '@certd/plugin-cert';
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertInfo, CertReader } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { optionsUtils } from "@certd/basic";
import { CertApplyPluginNames } from "@certd/plugin-cert";
@IsTaskPlugin({
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
name: 'DemoTest',
title: 'Demo-测试插件',
icon: 'clarity:plugin-line',
name: "DemoTest",
title: "Demo-测试插件",
icon: "clarity:plugin-line",
//插件分组
group: pluginGroups.other.key,
default: {
@@ -21,37 +21,37 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
export class DemoTest extends AbstractTaskPlugin {
//测试参数
@TaskInput({
title: '属性示例',
value: '默认值',
title: "属性示例",
value: "默认值",
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/input-cn
name: 'a-input',
vModel: 'value', //双向绑定组件的props名称
name: "a-input",
vModel: "value", //双向绑定组件的props名称
},
helper: '帮助说明,[链接](https://certd.docmirror.cn)',
helper: "帮助说明,[链接](https://certd.docmirror.cn)",
required: false, //是否必填
})
text!: string;
//测试参数
@TaskInput({
title: '选择框',
title: "选择框",
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
name: 'a-auto-complete',
vModel: 'value',
name: "a-auto-complete",
vModel: "value",
options: [
//选项列表
{ value: 'show', label: '动态显' },
{ value: 'hide', label: '动态隐' },
{ value: "show", label: "动态显" },
{ value: "hide", label: "动态隐" },
],
},
})
select!: string;
@TaskInput({
title: '动态显隐',
helper: '我会根据选择框的值进行显隐',
title: "动态显隐",
helper: "我会根据选择框的值进行显隐",
show: true, //动态计算的值会覆盖它
//动态计算脚本, mergeScript返回的对象会合并当前配置,此处演示 show的值会被动态计算结果覆盖,show的值根据用户选择的select的值决定
mergeScript: `
@@ -66,16 +66,16 @@ export class DemoTest extends AbstractTaskPlugin {
//测试参数
@TaskInput({
title: '多选框',
title: "多选框",
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
name: 'a-select',
vModel: 'value',
mode: 'tags',
name: "a-select",
vModel: "value",
mode: "tags",
multiple: true,
options: [
{ value: '1', label: '选项1' },
{ value: '2', label: '选项2' },
{ value: "1", label: "选项1" },
{ value: "2", label: "选项2" },
],
},
})
@@ -83,20 +83,20 @@ export class DemoTest extends AbstractTaskPlugin {
//测试参数
@TaskInput({
title: 'switch',
title: "switch",
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/switch-cn
name: 'a-switch',
vModel: 'checked',
name: "a-switch",
vModel: "checked",
},
})
switch!: boolean;
//证书选择,此项必须要有
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
// required: true, // 必填
@@ -109,11 +109,11 @@ export class DemoTest extends AbstractTaskPlugin {
//授权选择框
@TaskInput({
title: 'demo授权',
helper: 'demoAccess授权',
title: "demo授权",
helper: "demoAccess授权",
component: {
name: 'access-selector',
type: 'demo', //固定授权类型
name: "access-selector",
type: "demo", //固定授权类型
},
// rules: [{ required: true, message: '此项必填' }],
// required: true, //必填
@@ -122,11 +122,11 @@ export class DemoTest extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: '从后端获取选项',
helper: '选择时可以从后端获取选项',
title: "从后端获取选项",
helper: "选择时可以从后端获取选项",
action: DemoTest.prototype.onGetSiteList.name,
//当以下参数变化时,触发获取选项
watches: ['certDomains', 'accessId'],
watches: ["certDomains", "accessId"],
required: true,
single: false,
})
@@ -142,23 +142,23 @@ export class DemoTest extends AbstractTaskPlugin {
try {
const access = await this.getAccess(accessId);
this.logger.debug('access', access);
this.logger.debug("access", access);
} catch (e) {
this.logger.error('获取授权失败', e);
this.logger.error("获取授权失败", e);
}
try {
const certReader = new CertReader(cert);
this.logger.debug('certReader', certReader);
this.logger.debug("certReader", certReader);
} catch (e) {
this.logger.error('读取crt失败', e);
this.logger.error("读取crt失败", e);
}
this.logger.info('DemoTestPlugin execute');
this.logger.info('text:', text);
this.logger.info('select:', select);
this.logger.info('switch:', this.switch);
this.logger.info('授权id:', accessId);
this.logger.info("DemoTestPlugin execute");
this.logger.info("text:", text);
this.logger.info("select:", select);
this.logger.info("switch:", this.switch);
this.logger.info("授权id:", accessId);
// const res = await this.http.request({
// url: 'https://api.demo.com',
@@ -174,7 +174,7 @@ export class DemoTest extends AbstractTaskPlugin {
//此方法演示,如何让前端在添加插件时可以从后端获取选项,这里是后端返回选项的方法
async onGetSiteList(req: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
// @ts-ignore
@@ -183,9 +183,9 @@ export class DemoTest extends AbstractTaskPlugin {
// const siteRes = await access.GetDomainList(req);
//以下是模拟数据
const siteRes = [
{ id: 1, siteName: 'site1.com' },
{ id: 2, siteName: 'site2.com' },
{ id: 3, siteName: 'site2.com' },
{ id: 1, siteName: "site1.com" },
{ id: 2, siteName: "site2.com" },
{ id: 3, siteName: "site2.com" },
];
//转换为前端所需要的格式
const options = siteRes.map((item: any) => {
@@ -1,50 +1,49 @@
import { IsAccess, AccessInput, BaseAccess, PageSearch, PageRes, Pager } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';
import { IsAccess, AccessInput, BaseAccess, PageSearch, PageRes, Pager } from "@certd/pipeline";
import { DomainRecord } from "@certd/plugin-lib";
/**
*
* certd的后台管理系统中
*/
@IsAccess({
name: 'dnsla',
title: 'dns.la授权',
icon: 'arcticons:dns-changer-3',
desc: '',
name: "dnsla",
title: "dns.la授权",
icon: "arcticons:dns-changer-3",
desc: "",
})
export class DnslaAccess extends BaseAccess {
/**
*
*/
@AccessInput({
title: 'APIID',
title: "APIID",
component: {
placeholder: 'APIID',
placeholder: "APIID",
},
helper: "从我的账户->API密钥中获取 APIID APISecret",
required: true,
encrypt: false,
})
apiId = '';
apiId = "";
@AccessInput({
title: 'APISecret',
title: "APISecret",
component: {
placeholder: '',
placeholder: "",
},
helper:
'',
helper: "",
required: false,
encrypt: true,
})
apiSecret = '';
apiSecret = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "测试授权是否正确"
helper: "测试授权是否正确",
})
testRequest = true;
@@ -56,13 +55,12 @@ export class DnslaAccess extends BaseAccess {
return "ok";
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const pager = new Pager(req);
const url = `/api/domainList?pageIndex=${pager.pageNo}&pageSize=${pager.pageSize}`;
const ret = await this.doRequestApi(url, null, 'get');
const ret = await this.doRequestApi(url, null, "get");
let list = ret.data.results || []
let list = ret.data.results || [];
list = list.map((item: any) => ({
id: item.id,
domain: item.domain,
@@ -74,9 +72,7 @@ export class DnslaAccess extends BaseAccess {
};
}
async doRequestApi(url: string, data: any = null, method = 'post') {
async doRequestApi(url: string, data: any = null, method = "post") {
/**
* Basic
* API APIID APISecret
@@ -96,12 +92,12 @@ export class DnslaAccess extends BaseAccess {
* "data":{}
* }
*/
const token = Buffer.from(`${this.apiId}:${this.apiSecret}`).toString('base64');
const token = Buffer.from(`${this.apiId}:${this.apiSecret}`).toString("base64");
const res = await this.ctx.http.request<any, any>({
url: "https://api.dns.la" + url,
method,
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
Authorization: `Basic ${token}`,
},
data,
@@ -111,9 +107,7 @@ export class DnslaAccess extends BaseAccess {
throw new Error(res.msg);
}
return res;
}
}
new DnslaAccess();
@@ -9,12 +9,12 @@ export type DnslaRecord = {
// 这里通过IsDnsProvider注册一个dnsProvider
@IsDnsProvider({
name: 'dnsla',
title: 'dns.la',
desc: 'dns.la',
icon: 'arcticons:dns-changer-3',
name: "dnsla",
title: "dns.la",
desc: "dns.la",
icon: "arcticons:dns-changer-3",
// 这里是对应的 cloudflare的access类型名称
accessType: 'dnsla',
accessType: "dnsla",
})
export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
access!: DnslaAccess;
@@ -24,9 +24,7 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
this.access = this.ctx.access as DnslaAccess;
}
async getDomainDetail(domain:string){
async getDomainDetail(domain: string) {
/**
*
* GET /api/domain?id=85371689655342080&domain=test.com HTTP/1.1
@@ -51,8 +49,8 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
*/
const url = `/api/domain?domain=${domain}`;
const res = await this.access.doRequestApi(url, null, 'get');
return res.data
const res = await this.access.doRequestApi(url, null, "get");
return res.data;
}
/**
@@ -66,12 +64,11 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
* domain: 'example.com'
*/
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
const domainDetail = await this.getDomainDetail(domain);
const domainId = domainDetail.id;
this.logger.info('获取domainId成功:', domainId);
this.logger.info("获取domainId成功:", domainId);
// 给domain下创建txt类型的dns解析记录,fullRecord
/**
@@ -107,7 +104,7 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
const res = await this.access.doRequestApi(url, {
domainId: domainId,
type: 16,
host: fullRecord.replace(`.${domain}`, ''),
host: fullRecord.replace(`.${domain}`, ""),
data: value,
ttl: 60,
});
@@ -115,7 +112,6 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
return res.data;
}
/**
* dns解析记录,
* @param options
@@ -123,9 +119,9 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
async removeRecord(options: RemoveRecordOptions<DnslaRecord>): Promise<void> {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value);
this.logger.info("删除域名解析:", fullRecord, value);
if (!record) {
this.logger.info('record为空,不执行删除');
this.logger.info("record为空,不执行删除");
return;
}
//这里调用删除txt dns解析记录接口
@@ -137,14 +133,13 @@ export class DnslaDnsProvider extends AbstractDnsProvider<DnslaRecord> {
*/
const recordId = record.id;
const url = `/api/record?id=${recordId}`;
await this.access.doRequestApi(url, null, 'delete');
await this.access.doRequestApi(url, null, "delete");
this.logger.info(`删除域名解析成功:fullRecord=${fullRecord},value=${value}`);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
return await this.access.getDomainListPage(req);
}
}
//实例化这个provider,将其自动注册到系统中
@@ -1,2 +1,2 @@
export * from './dns-provider.js';
export * from './access.js';
export * from "./dns-provider.js";
export * from "./access.js";
@@ -1,49 +1,49 @@
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from "@certd/pipeline";
import { DomainRecord } from "@certd/plugin-lib";
@IsAccess({
name: 'dnsmgr',
title: '彩虹DNS',
icon: 'clarity:plugin-line',
desc: '彩虹DNS管理系统授权',
name: "dnsmgr",
title: "彩虹DNS",
icon: "clarity:plugin-line",
desc: "彩虹DNS管理系统授权",
})
export class DnsmgrAccess extends BaseAccess {
@AccessInput({
title: '系统地址',
title: "系统地址",
component: {
name: "a-input",
allowClear: true,
placeholder: 'https://dnsmgr.example.com',
placeholder: "https://dnsmgr.example.com",
},
required: true,
})
endpoint = '';
endpoint = "";
@AccessInput({
title: '用户ID',
title: "用户ID",
component: {
name: "a-input",
allowClear: true,
placeholder: '123456',
placeholder: "123456",
},
required: true,
})
uid = '';
uid = "";
@AccessInput({
title: 'API密钥',
title: "API密钥",
required: true,
encrypt: true,
})
key = '';
key = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "点击测试接口是否正常"
helper: "点击测试接口是否正常",
})
testRequest = true;
@@ -56,7 +56,7 @@ export class DnsmgrAccess extends BaseAccess {
this.ctx.logger.info(`获取域名列表,req:${JSON.stringify(req)}`);
const pager = new Pager(req);
const resp = await this.doRequest({
url: '/api/domain',
url: "/api/domain",
data: {
offset: pager.getOffset(),
limit: pager.pageSize,
@@ -64,7 +64,7 @@ export class DnsmgrAccess extends BaseAccess {
},
});
const total = resp?.total || 0;
let list = resp?.rows?.map((item: any) => {
const list = resp?.rows?.map((item: any) => {
return {
domain: item.name,
...item,
@@ -76,17 +76,15 @@ export class DnsmgrAccess extends BaseAccess {
};
}
async createDnsRecord(domainId: string, record: string, value: string, type: string, domain: string) {
this.ctx.logger.info(`创建DNS记录:${record} ${type} ${value}`);
const resp = await this.doRequest({
url: `/api/record/add/${domainId}`,
data: {
name: record.replace(`.${domain}`, ''),
name: record.replace(`.${domain}`, ""),
type,
value,
line: 'default',
line: "default",
ttl: 600,
},
});
@@ -121,12 +119,12 @@ export class DnsmgrAccess extends BaseAccess {
const timestamp = Math.floor(Date.now() / 1000);
const sign = this.ctx.utils.hash.md5(`${this.uid}${timestamp}${this.key}`);
const url = `${this.endpoint}${req.url}`;
const res = await this.ctx.http.request({
url,
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
"Content-Type": "application/x-www-form-urlencoded",
},
data: {
uid: this.uid,
@@ -137,7 +135,7 @@ export class DnsmgrAccess extends BaseAccess {
});
if (res.code !== undefined && res.code !== 0) {
throw new Error(res.msg || '请求失败');
throw new Error(res.msg || "请求失败");
}
return res;
}
@@ -1,6 +1,6 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DnsmgrAccess } from './access.js';
import { PageRes, PageSearch } from '@certd/pipeline';
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { DnsmgrAccess } from "./access.js";
import { PageRes, PageSearch } from "@certd/pipeline";
type DnsmgrRecord = {
domainId: string;
@@ -9,11 +9,11 @@ type DnsmgrRecord = {
};
@IsDnsProvider({
name: 'dnsmgr',
title: '彩虹DNS',
desc: '彩虹DNS管理系统',
icon: 'clarity:plugin-line',
accessType: 'dnsmgr',
name: "dnsmgr",
title: "彩虹DNS",
desc: "彩虹DNS管理系统",
icon: "clarity:plugin-line",
accessType: "dnsmgr",
order: 99,
})
export class DnsmgrDnsProvider extends AbstractDnsProvider<DnsmgrRecord> {
@@ -21,39 +21,39 @@ export class DnsmgrDnsProvider extends AbstractDnsProvider<DnsmgrRecord> {
async onInstance() {
this.access = this.ctx.access as DnsmgrAccess;
this.logger.debug('access', this.access);
this.logger.debug("access", this.access);
}
async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
const domainList = await this.access.GetDomainList({ searchKey: domain });
const domainInfo = domainList.list?.find((item: any) => item.name === domain);
if (!domainInfo) {
throw new Error(`未找到域名:${domain}`);
}
const name = fullRecord.replace(`.${domain}`, '');
const name = fullRecord.replace(`.${domain}`, "");
const res = await this.access.createDnsRecord(domainInfo.id, fullRecord, value, type, domain);
return { domainId: domainInfo.id, name, value,res };
return { domainId: domainInfo.id, name, value, res };
}
async removeRecord(options: RemoveRecordOptions<DnsmgrRecord>): Promise<void> {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value, record);
this.logger.info("删除域名解析:", fullRecord, value, record);
if (record && record.domainId) {
const records = await this.access.getDnsRecords(record.domainId, 'TXT', record.name, record.value);
const records = await this.access.getDnsRecords(record.domainId, "TXT", record.name, record.value);
if (records && records.rows && records.rows.length > 0) {
const recordToDelete = records.rows[0];
await this.access.deleteDnsRecord(record.domainId, recordToDelete.RecordId);
}
}
this.logger.info('删除域名解析成功:', fullRecord, value);
this.logger.info("删除域名解析成功:", fullRecord, value);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
@@ -1,2 +1,2 @@
export * from './access.js';
export * from './dns-provider.js';
export * from "./access.js";
export * from "./dns-provider.js";
@@ -1,58 +1,55 @@
import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
import { DogeClient } from './lib/index.js';
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
import { DogeClient } from "./lib/index.js";
/**
*
* certd的后台管理系统中
*/
@IsAccess({
name: 'dogecloud',
title: '多吉云',
desc: '',
icon: 'svg:icon-dogecloud',
name: "dogecloud",
title: "多吉云",
desc: "",
icon: "svg:icon-dogecloud",
})
export class DogeCloudAccess extends BaseAccess {
/**
*
*/
@AccessInput({
title: 'AccessKey',
title: "AccessKey",
component: {
placeholder: 'AccessKey',
placeholder: "AccessKey",
},
helper: '请前往[多吉云-密钥管理](https://console.dogecloud.com/user/keys)获取',
helper: "请前往[多吉云-密钥管理](https://console.dogecloud.com/user/keys)获取",
required: true,
encrypt: false,
})
accessKey = '';
accessKey = "";
@AccessInput({
title: 'SecretKey',
title: "SecretKey",
component: {
placeholder: 'SecretKey',
placeholder: "SecretKey",
},
helper: '请前往[多吉云-密钥管理](https://console.dogecloud.com/user/keys)获取',
helper: "请前往[多吉云-密钥管理](https://console.dogecloud.com/user/keys)获取",
required: true,
encrypt: true,
})
secretKey = '';
secretKey = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "测试授权是否正确"
helper: "测试授权是否正确",
})
testRequest = true;
async onTestRequest() {
const dogeClient = new DogeClient(this, this.ctx.http, this.ctx.logger);
await dogeClient.request(
'/cdn/domain/list.json',
{},
);
await dogeClient.request("/cdn/domain/list.json", {});
return "ok";
}
}
@@ -1,3 +1,3 @@
export * from './access.js';
export * from './lib/index.js';
export * from './plugins/index.js';
export * from "./access.js";
export * from "./lib/index.js";
export * from "./plugins/index.js";
@@ -1,14 +1,14 @@
import crypto from 'crypto';
import querystring from 'querystring';
import { DogeCloudAccess } from '../access.js';
import { HttpClient, ILogger } from '@certd/basic';
import crypto from "crypto";
import querystring from "querystring";
import { DogeCloudAccess } from "../access.js";
import { HttpClient, ILogger } from "@certd/basic";
export class DogeClient {
accessKey: string;
secretKey: string;
http: HttpClient;
logger: ILogger;
constructor(access: DogeCloudAccess, http: HttpClient,logger: ILogger) {
constructor(access: DogeCloudAccess, http: HttpClient, logger: ILogger) {
this.accessKey = access.accessKey;
this.secretKey = access.secretKey;
this.http = http;
@@ -21,26 +21,26 @@ export class DogeClient {
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data);
const sign = crypto
.createHmac('sha1', this.secretKey)
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
.digest('hex');
const authorization = 'TOKEN ' + this.accessKey + ':' + sign;
.createHmac("sha1", this.secretKey)
.update(Buffer.from(apiPath + "\n" + body, "utf8"))
.digest("hex");
const authorization = "TOKEN " + this.accessKey + ":" + sign;
const res: any = await this.http.request({
url: 'https://api.dogecloud.com' + apiPath,
method: 'POST',
url: "https://api.dogecloud.com" + apiPath,
method: "POST",
data: body,
responseType: 'json',
responseType: "json",
headers: {
'Content-Type': jsonMode ? 'application/json' : 'application/x-www-form-urlencoded',
"Content-Type": jsonMode ? "application/json" : "application/x-www-form-urlencoded",
Authorization: authorization,
},
});
if (res.code == null && ignoreResNullCode) {
//ignore
this.logger.warn('执行出错:', res);
this.logger.warn("执行出错:", res);
} else if (res.code !== 200) {
throw new Error('API Error: ' + res.msg);
throw new Error("API Error: " + res.msg);
}
return res.data;
}
@@ -1,13 +1,13 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo } from '@certd/plugin-cert';
import { DogeClient } from '../../lib/index.js';
import dayjs from 'dayjs';
import { CertApplyPluginNames } from '@certd/plugin-cert';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertInfo } from "@certd/plugin-cert";
import { DogeClient } from "../../lib/index.js";
import dayjs from "dayjs";
import { CertApplyPluginNames } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
@IsTaskPlugin({
name: 'DogeCloudDeployToCDN',
title: '多吉云-部署到多吉云CDN',
icon: 'svg:icon-dogecloud',
name: "DogeCloudDeployToCDN",
title: "多吉云-部署到多吉云CDN",
icon: "svg:icon-dogecloud",
group: pluginGroups.cdn.key,
default: {
strategy: {
@@ -18,10 +18,10 @@ import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from
export class DogeCloudDeployToCDNPlugin extends AbstractTaskPlugin {
//证书选择,此项必须要有
@TaskInput({
title: '证书',
helper: '请选择前置任务输出的域名证书',
title: "证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: true,
@@ -33,33 +33,35 @@ export class DogeCloudDeployToCDNPlugin extends AbstractTaskPlugin {
//授权选择框
@TaskInput({
title: '多吉云授权',
helper: '多吉云AccessKey',
title: "多吉云授权",
helper: "多吉云AccessKey",
component: {
name: 'access-selector',
type: 'dogecloud',
name: "access-selector",
type: "dogecloud",
},
rules: [{ required: true, message: '此项必填' }],
rules: [{ required: true, message: "此项必填" }],
})
accessId!: string;
@TaskInput(createRemoteSelectInputDefine({
title: 'CDN域名',
helper: '请选择CDN域名,可以选择多个,一次性部署',
required: true,
action: DogeCloudDeployToCDNPlugin.prototype.onGetDomainList.name,
pager: false,
search: false
}))
@TaskInput(
createRemoteSelectInputDefine({
title: "CDN域名",
helper: "请选择CDN域名,可以选择多个,一次性部署",
required: true,
action: DogeCloudDeployToCDNPlugin.prototype.onGetDomainList.name,
pager: false,
search: false,
})
)
domain!: string | string[];
@TaskInput({
title: '忽略部署接口报错',
helper: '当该域名部署后报错,但是实际上已经部署成功时,可以勾选',
title: "忽略部署接口报错",
helper: "当该域名部署后报错,但是实际上已经部署成功时,可以勾选",
value: false,
component: {
name: 'a-switch',
type: 'checked',
name: "a-switch",
type: "checked",
},
})
ignoreDeployNullCode = false;
@@ -73,13 +75,13 @@ export class DogeCloudDeployToCDNPlugin extends AbstractTaskPlugin {
async execute(): Promise<void> {
const certId: number = await this.updateCert();
let domains = this.domain
if (typeof domains === 'string'){
domains = [domains]
let domains = this.domain;
if (typeof domains === "string") {
domains = [domains];
}
for (const domain of domains) {
this.ctx.logger.info(`绑定证书${certId}到域名${domain}`);
await this.bindCert(certId,domain);
await this.bindCert(certId, domain);
}
this.logger.info("执行完成,3秒后删除过期证书");
@@ -88,17 +90,17 @@ export class DogeCloudDeployToCDNPlugin extends AbstractTaskPlugin {
}
async updateCert() {
const data = await this.dogeClient.request('/cdn/cert/upload.json', {
note: 'certd-' + dayjs().format('YYYYMMDDHHmmss'),
const data = await this.dogeClient.request("/cdn/cert/upload.json", {
note: "certd-" + dayjs().format("YYYYMMDDHHmmss"),
cert: this.cert.crt,
private: this.cert.key,
});
return data.id;
}
async bindCert(certId: number,domain: string) {
async bindCert(certId: number, domain: string) {
await this.dogeClient.request(
'/cdn/cert/bind.json',
"/cdn/cert/bind.json",
{
id: certId,
domain: domain,
@@ -108,34 +110,24 @@ export class DogeCloudDeployToCDNPlugin extends AbstractTaskPlugin {
}
async clearExpiredCert() {
const res = await this.dogeClient.request(
'/cdn/cert/list.json',
{},
);
const list = res.certs?.filter((item: any) => item.expire < dayjs().unix() && item.domainCount === 0) || [];
const res = await this.dogeClient.request("/cdn/cert/list.json", {});
const list = res.certs?.filter((item: any) => item.expire < dayjs().unix() && item.domainCount === 0) || [];
for (const item of list) {
this.ctx.logger.info(`删除过期证书${item.id}->${item.domain}`);
try{
await this.dogeClient.request(
'/cdn/cert/delete.json',
{
id: item.id,
},
);
}catch(err){
try {
await this.dogeClient.request("/cdn/cert/delete.json", {
id: item.id,
});
} catch (err) {
this.ctx.logger.warn(`删除过期证书${item.id}->${item.domain}失败`, err);
}
}
}
async onGetDomainList(data: PageSearch = {}) {
const res = await this.dogeClient.request(
'/cdn/domain/list.json',
{},
);
const res = await this.dogeClient.request("/cdn/domain/list.json", {});
const list = res.domains
const list = res.domains;
if (!list || list.length === 0) {
throw new Error("没有找到CDN域名");
}
@@ -144,7 +136,7 @@ export class DogeCloudDeployToCDNPlugin extends AbstractTaskPlugin {
return {
label: `${item.name}`,
value: item.name,
domain: item.name
domain: item.name,
};
});
return {
@@ -1 +1 @@
export * from './deploy-to-cdn/index.js';
export * from "./deploy-to-cdn/index.js";
@@ -8,10 +8,9 @@ import { CertInfo } from "@certd/plugin-cert";
name: "dokploy",
title: "Dokploy授权",
desc: "",
icon: "svg:icon-lucky"
icon: "svg:icon-lucky",
})
export class DokployAccess extends BaseAccess {
@AccessInput({
title: "Dokploy地址",
component: {
@@ -19,12 +18,12 @@ export class DokployAccess extends BaseAccess {
},
required: true,
})
endpoint = '';
endpoint = "";
@AccessInput({
title: 'ApiKey',
title: "ApiKey",
component: {
placeholder: 'ApiKey',
placeholder: "ApiKey",
},
// naAyXbZmxtsfrDfneOCeirbQNIICmBgfBiYXQwryPIUOdzPkXkfnaKjeAdbOQdwp
//tlyvdNzojaFkNfGScALLmyuFHkHcYWaxoYjiDzWFHcnZAWdjOquMSqBwHLvGDGZK
@@ -32,73 +31,71 @@ export class DokployAccess extends BaseAccess {
required: true,
encrypt: true,
})
apiKey = '';
apiKey = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
action: "TestRequest",
},
helper: "点击测试接口是否正常"
helper: "点击测试接口是否正常",
})
testRequest = true;
async onTestRequest() {
await this.getCertList();
return "ok"
return "ok";
}
async getServerList(){
async getServerList() {
const req = {
url :"/api/server.all",
url: "/api/server.all",
method: "get",
}
};
return await this.doRequest(req);
}
async getCertList(){
async getCertList() {
const req = {
url :"/api/certificates.all",
url: "/api/certificates.all",
method: "get",
}
};
return await this.doRequest(req);
}
async createCert(opts:{cert:CertInfo,serverId:string,name:string}){
async createCert(opts: { cert: CertInfo; serverId: string; name: string }) {
const req = {
url :"/api/certificates.create",
url: "/api/certificates.create",
method: "post",
data:{
data: {
// certificateId:opts.certificateId,
"name": opts.name,
"certificateData": opts.cert.crt,
"privateKey": opts.cert.key,
"serverId": opts.serverId,
name: opts.name,
certificateData: opts.cert.crt,
privateKey: opts.cert.key,
serverId: opts.serverId,
autoRenew: false,
organizationId : ""
}
}
organizationId: "",
},
};
return await this.doRequest(req);
}
async removeCert (opts:{id:string}){
async removeCert(opts: { id: string }) {
const req = {
url :"/api/certificates.remove",
url: "/api/certificates.remove",
method: "post",
data:{
certificateId:opts.id,
}
}
data: {
certificateId: opts.id,
},
};
return await this.doRequest(req);
}
async doRequest(req: HttpRequestConfig){
async doRequest(req: HttpRequestConfig) {
const headers = {
"x-api-key": this.apiKey,
...req.headers
...req.headers,
};
return await this.ctx.http.request({
headers,
@@ -107,9 +104,6 @@ export class DokployAccess extends BaseAccess {
logRes: false,
});
}
}
new DokployAccess();

Some files were not shown because too many files have changed in this diff Show More