mirror of
https://github.com/certd/certd.git
synced 2026-06-27 22:17:35 +08:00
chore: format
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
+12
-14
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+33
-41
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+49
-54
@@ -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";
|
||||
|
||||
+44
-41
@@ -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();
|
||||
|
||||
+117
-131
@@ -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();
|
||||
|
||||
+78
-84
@@ -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) => {
|
||||
|
||||
+113
-128
@@ -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();
|
||||
|
||||
+86
-91
@@ -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();
|
||||
|
||||
+59
-66
@@ -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";
|
||||
|
||||
+35
-35
@@ -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";
|
||||
|
||||
+28
-29
@@ -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);
|
||||
// 返回证书 ARN(Amazon 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";
|
||||
|
||||
+25
-25
@@ -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";
|
||||
|
||||
+18
-22
@@ -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,
|
||||
|
||||
+21
-23
@@ -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
Reference in New Issue
Block a user