mirror of
https://github.com/certd/certd.git
synced 2026-04-23 19:57:27 +08:00
refactor: 1
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
export class DnspodAccessProvider {
|
||||
static define () {
|
||||
return {
|
||||
name: 'dnspod',
|
||||
title: 'dnspod',
|
||||
desc: '腾讯云的域名解析接口已迁移到dnspod',
|
||||
input: {
|
||||
id: {
|
||||
component: {
|
||||
placeholder: 'dnspod接口账户id'
|
||||
},
|
||||
rules: [{ required: true, message: '该项必填' }]
|
||||
},
|
||||
token: {
|
||||
title: 'token',
|
||||
component: {
|
||||
placeholder: '开放接口token'
|
||||
},
|
||||
rules: [{ required: true, message: '该项必填' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export class TencentAccessProvider {
|
||||
static define () {
|
||||
return {
|
||||
name: 'tencent',
|
||||
title: '腾讯云',
|
||||
input: {
|
||||
secretId: {
|
||||
component: {
|
||||
placeholder: 'secretId'
|
||||
},
|
||||
rules: [{ required: true, message: '该项必填' }]
|
||||
},
|
||||
secretKey: {
|
||||
component: {
|
||||
placeholder: 'secretKey'
|
||||
},
|
||||
rules: [{ required: true, message: '该项必填' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { AbstractAccess, IsAccess } from "@certd/pipeline";
|
||||
|
||||
@IsAccess({
|
||||
name: "tencent",
|
||||
title: "腾讯云",
|
||||
input: {
|
||||
secretId: {
|
||||
title: "secretId",
|
||||
component: {
|
||||
placeholder: "secretId",
|
||||
},
|
||||
rules: [{ required: true, message: "该项必填" }],
|
||||
},
|
||||
secretKey: {
|
||||
title: "secretKey",
|
||||
component: {
|
||||
placeholder: "secretKey",
|
||||
},
|
||||
rules: [{ required: true, message: "该项必填" }],
|
||||
},
|
||||
},
|
||||
})
|
||||
export class TencentAccess extends AbstractAccess {
|
||||
secretId = "";
|
||||
secretKey = "";
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline";
|
||||
import _ from "lodash";
|
||||
import { DnspodAccess } from "../access";
|
||||
|
||||
@IsDnsProvider({
|
||||
name: "dnspod",
|
||||
title: "dnspod(腾讯云)",
|
||||
desc: "腾讯云的域名解析接口已迁移到dnspod",
|
||||
accessType: "dnspod",
|
||||
})
|
||||
export class DnspodDnsProvider extends AbstractDnsProvider implements IDnsProvider {
|
||||
loginToken: any;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
async onInit() {
|
||||
const access: DnspodAccess = this.access as DnspodAccess;
|
||||
this.loginToken = access.id + "," + access.token;
|
||||
}
|
||||
|
||||
async doRequest(options: any, successCodes: string[] = []) {
|
||||
const config: any = {
|
||||
// @ts-ignore
|
||||
method: "post",
|
||||
formData: {
|
||||
login_token: this.loginToken,
|
||||
format: "json",
|
||||
lang: "cn",
|
||||
error_on_empty: "no",
|
||||
},
|
||||
timeout: 5000,
|
||||
};
|
||||
_.merge(config, options);
|
||||
|
||||
const ret: any = await this.http.request(config);
|
||||
if (!ret || !ret.status) {
|
||||
const code = ret.status.code;
|
||||
if (code !== "1" || !successCodes.includes(code)) {
|
||||
throw new Error("请求失败:" + ret.status.message + ",api=" + config.url);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getDomainList() {
|
||||
const ret = await this.doRequest({
|
||||
url: "https://dnsapi.cn/Domain.List",
|
||||
});
|
||||
this.logger.debug("dnspod 域名列表:", ret.domains);
|
||||
return ret.domains;
|
||||
}
|
||||
|
||||
async createRecord(options: CreateRecordOptions): Promise<any> {
|
||||
const { fullRecord, value, type } = options;
|
||||
this.logger.info("添加域名解析:", fullRecord, value);
|
||||
const domainItem = await this.matchDomain(fullRecord);
|
||||
const domain = domainItem.name;
|
||||
const rr = fullRecord.replace("." + domain, "");
|
||||
|
||||
const ret = await this.doRequest(
|
||||
{
|
||||
url: "https://dnsapi.cn/Record.Create",
|
||||
formData: {
|
||||
domain,
|
||||
sub_domain: rr,
|
||||
record_type: type,
|
||||
record_line: "默认",
|
||||
value: value,
|
||||
mx: 1,
|
||||
},
|
||||
},
|
||||
["104"]
|
||||
); // 104错误码为记录已存在,无需再次添加
|
||||
this.logger.info("添加域名解析成功:", fullRecord, value, JSON.stringify(ret.record));
|
||||
return ret.record;
|
||||
}
|
||||
|
||||
async removeRecord(options: RemoveRecordOptions) {
|
||||
const { fullRecord, value, record } = options;
|
||||
const domain = await this.matchDomain(fullRecord);
|
||||
|
||||
const ret = await this.doRequest({
|
||||
url: "https://dnsapi.cn/Record.Remove",
|
||||
formData: {
|
||||
domain,
|
||||
record_id: record.id,
|
||||
},
|
||||
});
|
||||
this.logger.info("删除域名解析成功:", fullRecord, value);
|
||||
return ret.RecordId;
|
||||
}
|
||||
|
||||
async matchDomain(dnsRecord: any) {
|
||||
const list = await this.getDomainList();
|
||||
let domain = null;
|
||||
for (const item of list) {
|
||||
if (_.endsWith(dnsRecord, item.name)) {
|
||||
domain = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!domain) {
|
||||
throw new Error("找不到域名,请检查域名是否正确:" + dnsRecord);
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import "./dnspod-dns-provider";
|
||||
@@ -1,96 +0,0 @@
|
||||
import { AbstractDnsProvider, util } from '@certd/api'
|
||||
import _ from 'lodash'
|
||||
const request = util.request
|
||||
export class DnspodDnsProvider extends AbstractDnsProvider {
|
||||
static define () {
|
||||
return {
|
||||
name: 'dnspod',
|
||||
title: 'dnspod(腾讯云)',
|
||||
desc: '腾讯云的域名解析接口已迁移到dnspod',
|
||||
input: {
|
||||
accessProvider: {
|
||||
title: '授权',
|
||||
helper: '需要dnspod类型的授权',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'dnspod'
|
||||
},
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor (args) {
|
||||
super(args)
|
||||
const { props } = args
|
||||
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||
this.loginToken = accessProvider.id + ',' + accessProvider.token
|
||||
}
|
||||
|
||||
async doRequest (options, successCodes = []) {
|
||||
const config = {
|
||||
method: 'post',
|
||||
formData: {
|
||||
login_token: this.loginToken,
|
||||
format: 'json',
|
||||
lang: 'cn',
|
||||
error_on_empty: 'no'
|
||||
},
|
||||
timeout: 5000
|
||||
}
|
||||
_.merge(config, options)
|
||||
|
||||
const ret = await request(config)
|
||||
if (!ret || !ret.status) {
|
||||
const code = ret.status.code
|
||||
if (code !== '1' || !successCodes.includes(code)) {
|
||||
throw new Error('请求失败:' + ret.status.message + ',api=' + config.url)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
async getDomainList () {
|
||||
const ret = await this.doRequest({
|
||||
url: 'https://dnsapi.cn/Domain.List'
|
||||
})
|
||||
this.logger.debug('dnspod 域名列表:', ret.domains)
|
||||
return ret.domains
|
||||
}
|
||||
|
||||
async createRecord ({ fullRecord, type, value }) {
|
||||
this.logger.info('添加域名解析:', fullRecord, value)
|
||||
const domainItem = await this.matchDomain(fullRecord, 'name')
|
||||
const domain = domainItem.name
|
||||
const rr = fullRecord.replace('.' + domain, '')
|
||||
|
||||
const ret = await this.doRequest({
|
||||
url: 'https://dnsapi.cn/Record.Create',
|
||||
formData: {
|
||||
domain,
|
||||
sub_domain: rr,
|
||||
record_type: type,
|
||||
record_line: '默认',
|
||||
value: value,
|
||||
mx: 1
|
||||
}
|
||||
}, ['104'])// 104错误码为记录已存在,无需再次添加
|
||||
this.logger.info('添加域名解析成功:', fullRecord, value, JSON.stringify(ret.record))
|
||||
return ret.record
|
||||
}
|
||||
|
||||
async removeRecord ({ fullRecord, type, value, record }) {
|
||||
const domain = await this.matchDomain(fullRecord, 'name')
|
||||
|
||||
const ret = await this.doRequest({
|
||||
url: 'https://dnsapi.cn/Record.Remove',
|
||||
formData: {
|
||||
domain,
|
||||
record_id: record.id
|
||||
}
|
||||
})
|
||||
this.logger.info('删除域名解析成功:', fullRecord, value)
|
||||
return ret.RecordId
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import _ from 'lodash'
|
||||
|
||||
import { TencentAccessProvider } from './access-providers/tencent.js'
|
||||
import { DnspodAccessProvider } from './access-providers/dnspod.js'
|
||||
import { DnspodDnsProvider } from './dns-providers/dnspod.js'
|
||||
|
||||
import { UploadCertToTencent } from './plugins/upload-to-tencent/index.js'
|
||||
|
||||
import { DeployCertToTencentCDN } from './plugins/deploy-to-cdn/index.js'
|
||||
|
||||
import { DeployCertToTencentCLB } from './plugins/deploy-to-clb/index.js'
|
||||
|
||||
import { DeployCertToTencentTKEIngress } from './plugins/deploy-to-tke-ingress/index.js'
|
||||
|
||||
import { pluginRegistry, accessProviderRegistry, dnsProviderRegistry } from '@certd/api'
|
||||
|
||||
export const DefaultPlugins = {
|
||||
UploadCertToTencent,
|
||||
DeployCertToTencentTKEIngress,
|
||||
DeployCertToTencentCDN,
|
||||
DeployCertToTencentCLB
|
||||
}
|
||||
export default {
|
||||
install () {
|
||||
_.forEach(DefaultPlugins, item => {
|
||||
pluginRegistry.install(item)
|
||||
})
|
||||
|
||||
accessProviderRegistry.install(TencentAccessProvider)
|
||||
accessProviderRegistry.install(DnspodAccessProvider)
|
||||
|
||||
dnsProviderRegistry.install(DnspodDnsProvider)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin, utils } from "@certd/pipeline";
|
||||
import tencentcloud from "tencentcloud-sdk-nodejs/index";
|
||||
import { TencentAccess } from "../../access";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
@IsTask(() => {
|
||||
return {
|
||||
name: "DeployCertToTencentCLB",
|
||||
title: "部署到腾讯云CLB",
|
||||
desc: "暂时只支持单向认证证书,暂时只支持通用负载均衡",
|
||||
input: {
|
||||
region: {
|
||||
title: "大区",
|
||||
value: "ap-guangzhou",
|
||||
component: {
|
||||
name: "a-select",
|
||||
options: [{ value: "ap-guangzhou" }],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
domain: {
|
||||
title: "域名",
|
||||
required: true,
|
||||
helper: "要更新的支持https的负载均衡的域名",
|
||||
},
|
||||
loadBalancerId: {
|
||||
title: "负载均衡ID",
|
||||
helper: "如果没有配置,则根据域名匹配负载均衡下的监听器(根据域名匹配时暂时只支持前100个)",
|
||||
required: true,
|
||||
},
|
||||
listenerId: {
|
||||
title: "监听器ID",
|
||||
helper: "如果没有配置,则根据域名或负载均衡id匹配监听器",
|
||||
},
|
||||
certName: {
|
||||
title: "证书名称前缀",
|
||||
},
|
||||
accessId: {
|
||||
title: "Access提供者",
|
||||
helper: "access授权",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "tencent",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
cert: {
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
output: {},
|
||||
};
|
||||
})
|
||||
export class DeployToClbPlugin extends AbstractPlugin implements TaskPlugin {
|
||||
async execute(input: TaskInput): Promise<TaskOutput> {
|
||||
const { accessId, region, domain } = input;
|
||||
const accessProvider = (await this.accessService.getById(accessId)) as TencentAccess;
|
||||
const client = this.getClient(accessProvider, region);
|
||||
|
||||
const lastCertId = await this.getCertIdFromProps(client, input);
|
||||
if (!domain) {
|
||||
await this.updateListener(client, input);
|
||||
} else {
|
||||
await this.updateByDomainAttr(client, input);
|
||||
}
|
||||
|
||||
try {
|
||||
await utils.sleep(2000);
|
||||
let newCertId = await this.getCertIdFromProps(client, input);
|
||||
if ((lastCertId && newCertId === lastCertId) || (!lastCertId && !newCertId)) {
|
||||
await utils.sleep(2000);
|
||||
newCertId = await this.getCertIdFromProps(client, input);
|
||||
}
|
||||
if (newCertId === lastCertId) {
|
||||
return {};
|
||||
}
|
||||
this.logger.info("腾讯云证书ID:", newCertId);
|
||||
} catch (e) {
|
||||
this.logger.warn("查询腾讯云证书失败", e);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async getCertIdFromProps(client: any, input: TaskInput) {
|
||||
const listenerRet = await this.getListenerList(client, input.loadBalancerId, [input.listenerId]);
|
||||
return this.getCertIdFromListener(listenerRet[0], input.domain);
|
||||
}
|
||||
|
||||
getCertIdFromListener(listener: any, domain: string) {
|
||||
let certId;
|
||||
if (!domain) {
|
||||
certId = listener.Certificate.CertId;
|
||||
} else {
|
||||
if (listener.Rules && listener.Rules.length > 0) {
|
||||
for (const rule of listener.Rules) {
|
||||
if (rule.Domain === domain) {
|
||||
if (rule.Certificate != null) {
|
||||
certId = rule.Certificate.CertId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return certId;
|
||||
}
|
||||
|
||||
async updateListener(client: any, props: TaskInput) {
|
||||
const params = this.buildProps(props);
|
||||
const ret = await client.ModifyListener(params);
|
||||
this.checkRet(ret);
|
||||
this.logger.info("设置腾讯云CLB证书成功:", ret.RequestId, "->loadBalancerId:", props.loadBalancerId, "listenerId", props.listenerId);
|
||||
return ret;
|
||||
}
|
||||
|
||||
async updateByDomainAttr(client: any, props: TaskInput) {
|
||||
const params: any = this.buildProps(props);
|
||||
params.Domain = props.domain;
|
||||
const ret = await client.ModifyDomainAttributes(params);
|
||||
this.checkRet(ret);
|
||||
this.logger.info(
|
||||
"设置腾讯云CLB证书(sni)成功:",
|
||||
ret.RequestId,
|
||||
"->loadBalancerId:",
|
||||
props.loadBalancerId,
|
||||
"listenerId",
|
||||
props.listenerId,
|
||||
"domain:",
|
||||
props.domain
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
appendTimeSuffix(name: string) {
|
||||
if (name == null) {
|
||||
name = "certd";
|
||||
}
|
||||
return name + "-" + dayjs().format("YYYYMMDD-HHmmss");
|
||||
}
|
||||
buildProps(props: TaskInput) {
|
||||
const { certName, cert } = props;
|
||||
return {
|
||||
Certificate: {
|
||||
SSLMode: "UNIDIRECTIONAL", // 单向认证
|
||||
CertName: this.appendTimeSuffix(certName || cert.domain),
|
||||
CertKey: cert.key,
|
||||
CertContent: cert.crt,
|
||||
},
|
||||
LoadBalancerId: props.loadBalancerId,
|
||||
ListenerId: props.listenerId,
|
||||
};
|
||||
}
|
||||
|
||||
async getCLBList(client: any, props: TaskInput) {
|
||||
const params = {
|
||||
Limit: 100, // 最大暂时只支持100个,暂时没做翻页
|
||||
OrderBy: "CreateTime",
|
||||
OrderType: 0,
|
||||
...props.DescribeLoadBalancers,
|
||||
};
|
||||
const ret = await client.DescribeLoadBalancers(params);
|
||||
this.checkRet(ret);
|
||||
return ret.LoadBalancerSet;
|
||||
}
|
||||
|
||||
async getListenerList(client: any, balancerId: any, listenerIds: any) {
|
||||
// HTTPS
|
||||
const params = {
|
||||
LoadBalancerId: balancerId,
|
||||
Protocol: "HTTPS",
|
||||
ListenerIds: listenerIds,
|
||||
};
|
||||
const ret = await client.DescribeListeners(params);
|
||||
this.checkRet(ret);
|
||||
return ret.Listeners;
|
||||
}
|
||||
|
||||
getClient(accessProvider: TencentAccess, region: string) {
|
||||
const ClbClient = tencentcloud.clb.v20180317.Client;
|
||||
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: accessProvider.secretId,
|
||||
secretKey: accessProvider.secretKey,
|
||||
},
|
||||
region: region,
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: "clb.tencentcloudapi.com",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return new ClbClient(clientConfig);
|
||||
}
|
||||
|
||||
checkRet(ret: any) {
|
||||
if (!ret || ret.Error) {
|
||||
throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin, utils } from "@certd/pipeline";
|
||||
import tencentcloud from "tencentcloud-sdk-nodejs/index";
|
||||
import { K8sClient } from "@certd/plugin-util";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
@IsTask(() => {
|
||||
return {
|
||||
name: "DeployCertToTencentTKEIngress",
|
||||
title: "部署到腾讯云TKE-ingress",
|
||||
desc: "需要【上传到腾讯云】作为前置任务",
|
||||
input: {
|
||||
region: {
|
||||
title: "大区",
|
||||
value: "ap-guangzhou",
|
||||
required: true,
|
||||
},
|
||||
clusterId: {
|
||||
title: "集群ID",
|
||||
required: true,
|
||||
desc: "例如:cls-6lbj1vee",
|
||||
request: true,
|
||||
},
|
||||
namespace: {
|
||||
title: "集群namespace",
|
||||
value: "default",
|
||||
required: true,
|
||||
},
|
||||
secreteName: {
|
||||
title: "证书的secret名称",
|
||||
required: true,
|
||||
},
|
||||
ingressName: {
|
||||
title: "ingress名称",
|
||||
required: true,
|
||||
},
|
||||
ingressClass: {
|
||||
title: "ingress类型",
|
||||
component: {
|
||||
name: "a-select",
|
||||
options: [{ value: "qcloud" }, { value: "nginx" }],
|
||||
},
|
||||
helper: "可选 qcloud / nginx",
|
||||
},
|
||||
clusterIp: {
|
||||
title: "集群内网ip",
|
||||
helper: "如果开启了外网的话,无需设置",
|
||||
},
|
||||
clusterDomain: {
|
||||
title: "集群域名",
|
||||
helper: "可不填,默认为:[clusterId].ccs.tencent-cloud.com",
|
||||
},
|
||||
|
||||
tencentCertId: {
|
||||
title: "腾讯云证书id",
|
||||
helper: "请选择“上传证书到腾讯云”前置任务的输出",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
from: "UploadCertToTencent",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
||||
/**
|
||||
* AccessProvider的key,或者一个包含access的具体的对象
|
||||
*/
|
||||
accessId: {
|
||||
title: "Access授权",
|
||||
helper: "access授权",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "tencent",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
cert: {
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
output: {},
|
||||
};
|
||||
})
|
||||
export class DeployCertToTencentTKEIngressPlugin extends AbstractPlugin implements TaskPlugin {
|
||||
async execute(input: TaskInput): Promise<TaskOutput> {
|
||||
const { accessId, region, clusterId, clusterIp, ingressClass } = input;
|
||||
let { clusterDomain } = input;
|
||||
const accessProvider = this.accessService.getById(accessId);
|
||||
const tkeClient = this.getTkeClient(accessProvider, region);
|
||||
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, clusterId);
|
||||
|
||||
this.logger.info("kubeconfig已成功获取");
|
||||
const k8sClient = new K8sClient(kubeConfigStr);
|
||||
if (clusterIp != null) {
|
||||
if (!clusterDomain) {
|
||||
clusterDomain = `${clusterId}.ccs.tencent-cloud.com`;
|
||||
}
|
||||
// 修改内网解析ip地址
|
||||
k8sClient.setLookup({ [clusterDomain]: { ip: clusterIp } });
|
||||
}
|
||||
const ingressType = ingressClass || "qcloud";
|
||||
if (ingressType === "qcloud") {
|
||||
await this.patchQcloudCertSecret({ k8sClient, input });
|
||||
} else {
|
||||
await this.patchNginxCertSecret({ k8sClient, input });
|
||||
}
|
||||
|
||||
await utils.sleep(2000); // 停留2秒,等待secret部署完成
|
||||
await this.restartIngress({ k8sClient, input });
|
||||
return {};
|
||||
}
|
||||
|
||||
getTkeClient(accessProvider: any, region = "ap-guangzhou") {
|
||||
const TkeClient = tencentcloud.tke.v20180525.Client;
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: accessProvider.secretId,
|
||||
secretKey: accessProvider.secretKey,
|
||||
},
|
||||
region,
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: "tke.tencentcloudapi.com",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return new TkeClient(clientConfig);
|
||||
}
|
||||
|
||||
async getTkeKubeConfig(client: any, clusterId: string) {
|
||||
// Depends on tencentcloud-sdk-nodejs version 4.0.3 or higher
|
||||
const params = {
|
||||
ClusterId: clusterId,
|
||||
};
|
||||
const ret = await client.DescribeClusterKubeconfig(params);
|
||||
this.checkRet(ret);
|
||||
this.logger.info("注意:后续操作需要在【集群->基本信息】中开启外网或内网访问,https://console.cloud.tencent.com/tke2/cluster");
|
||||
return ret.Kubeconfig;
|
||||
}
|
||||
|
||||
appendTimeSuffix(name: string) {
|
||||
if (name == null) {
|
||||
name = "certd";
|
||||
}
|
||||
return name + "-" + dayjs().format("YYYYMMDD-HHmmss");
|
||||
}
|
||||
|
||||
async patchQcloudCertSecret(options: { k8sClient: any; input: TaskInput }) {
|
||||
const { tencentCertId } = options.input;
|
||||
if (tencentCertId == null) {
|
||||
throw new Error("请先将【上传证书到腾讯云】作为前置任务");
|
||||
}
|
||||
this.logger.info("腾讯云证书ID:", tencentCertId);
|
||||
const certIdBase64 = Buffer.from(tencentCertId).toString("base64");
|
||||
|
||||
const { namespace, secretName } = options.input;
|
||||
|
||||
const body = {
|
||||
data: {
|
||||
qcloud_cert_id: certIdBase64,
|
||||
},
|
||||
metadata: {
|
||||
labels: {
|
||||
certd: this.appendTimeSuffix("certd"),
|
||||
},
|
||||
},
|
||||
};
|
||||
let secretNames = secretName;
|
||||
if (typeof secretName === "string") {
|
||||
secretNames = [secretName];
|
||||
}
|
||||
for (const secret of secretNames) {
|
||||
await options.k8sClient.patchSecret({ namespace, secretName: secret, body });
|
||||
this.logger.info(`CertSecret已更新:${secret}`);
|
||||
}
|
||||
}
|
||||
|
||||
async patchNginxCertSecret(options: { k8sClient: any; input: TaskInput }) {
|
||||
const { k8sClient, input } = options;
|
||||
const { cert } = input;
|
||||
const crt = cert.crt;
|
||||
const key = cert.key;
|
||||
const crtBase64 = Buffer.from(crt).toString("base64");
|
||||
const keyBase64 = Buffer.from(key).toString("base64");
|
||||
|
||||
const { namespace, secretName } = input;
|
||||
|
||||
const body = {
|
||||
data: {
|
||||
"tls.crt": crtBase64,
|
||||
"tls.key": keyBase64,
|
||||
},
|
||||
metadata: {
|
||||
labels: {
|
||||
certd: this.appendTimeSuffix("certd"),
|
||||
},
|
||||
},
|
||||
};
|
||||
let secretNames = secretName;
|
||||
if (typeof secretName === "string") {
|
||||
secretNames = [secretName];
|
||||
}
|
||||
for (const secret of secretNames) {
|
||||
await k8sClient.patchSecret({ namespace, secretName: secret, body });
|
||||
this.logger.info(`CertSecret已更新:${secret}`);
|
||||
}
|
||||
}
|
||||
|
||||
async restartIngress(options: { k8sClient: any; input: TaskInput }) {
|
||||
const { k8sClient, input } = options;
|
||||
const { namespace, ingressName } = input;
|
||||
|
||||
const body = {
|
||||
metadata: {
|
||||
labels: {
|
||||
certd: this.appendTimeSuffix("certd"),
|
||||
},
|
||||
},
|
||||
};
|
||||
let ingressNames = ingressName;
|
||||
if (typeof ingressName === "string") {
|
||||
ingressNames = [ingressName];
|
||||
}
|
||||
for (const ingress of ingressNames) {
|
||||
await k8sClient.patchIngress({ namespace, ingressName: ingress, body });
|
||||
this.logger.info(`ingress已重启:${ingress}`);
|
||||
}
|
||||
}
|
||||
checkRet(ret: any) {
|
||||
if (!ret || ret.Error) {
|
||||
throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline";
|
||||
import tencentcloud from "tencentcloud-sdk-nodejs/index";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
@IsTask(() => {
|
||||
return {
|
||||
name: "UploadCertToTencent",
|
||||
title: "上传证书到腾讯云",
|
||||
desc: "上传成功后输出:tencentCertId",
|
||||
input: {
|
||||
name: {
|
||||
title: "证书名称",
|
||||
},
|
||||
accessId: {
|
||||
title: "Access授权",
|
||||
helper: "access授权",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "tencent",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
cert: {
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
tencentCertId: {
|
||||
title: "上传成功后的腾讯云CertId",
|
||||
},
|
||||
},
|
||||
};
|
||||
})
|
||||
export class UploadToTencentPlugin extends AbstractPlugin implements TaskPlugin {
|
||||
async execute(input: TaskInput): Promise<TaskOutput> {
|
||||
const { accessId, name, cert } = input;
|
||||
const accessProvider = this.accessService.getById(accessId);
|
||||
const certName = this.appendTimeSuffix(name || cert.domain);
|
||||
const client = this.getClient(accessProvider);
|
||||
|
||||
const params = {
|
||||
CertificatePublicKey: cert.crt,
|
||||
CertificatePrivateKey: cert.key,
|
||||
Alias: certName,
|
||||
};
|
||||
const ret = await client.UploadCertificate(params);
|
||||
this.checkRet(ret);
|
||||
this.logger.info("证书上传成功:tencentCertId=", ret.CertificateId);
|
||||
return { tencentCertId: ret.CertificateId };
|
||||
}
|
||||
|
||||
appendTimeSuffix(name: string) {
|
||||
if (name == null) {
|
||||
name = "certd";
|
||||
}
|
||||
return name + "-" + dayjs().format("YYYYMMDD-HHmmss");
|
||||
}
|
||||
|
||||
getClient(accessProvider: any) {
|
||||
const SslClient = tencentcloud.ssl.v20191205.Client;
|
||||
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: accessProvider.secretId,
|
||||
secretKey: accessProvider.secretKey,
|
||||
},
|
||||
region: "",
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: "ssl.tencentcloudapi.com",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return new SslClient(clientConfig);
|
||||
}
|
||||
|
||||
// async rollback({ input }) {
|
||||
// const { accessId } = input;
|
||||
// const accessProvider = this.accessService.getById(accessId);
|
||||
// const client = this.getClient(accessProvider);
|
||||
//
|
||||
// const { tencentCertId } = context;
|
||||
// const params = {
|
||||
// CertificateId: tencentCertId,
|
||||
// };
|
||||
// const ret = await client.DeleteCertificate(params);
|
||||
// this.checkRet(ret);
|
||||
// this.logger.info("证书删除成功:DeleteResult=", ret.DeleteResult);
|
||||
// delete context.tencentCertId;
|
||||
// }
|
||||
checkRet(ret: any) {
|
||||
if (!ret || ret.Error) {
|
||||
throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { AbstractPlugin } from '@certd/api'
|
||||
|
||||
export class AbstractTencentPlugin extends AbstractPlugin {
|
||||
checkRet (ret) {
|
||||
if (!ret || ret.Error) {
|
||||
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message)
|
||||
}
|
||||
}
|
||||
|
||||
getSafetyDomain (domain) {
|
||||
return domain.replace(/\*/g, '_')
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
||||
import dayjs from 'dayjs'
|
||||
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
||||
|
||||
export class DeployCertToTencentCDN extends AbstractTencentPlugin {
|
||||
/**
|
||||
* 插件定义
|
||||
* 名称
|
||||
* 入参
|
||||
* 出参
|
||||
*/
|
||||
static define () {
|
||||
return {
|
||||
name: 'deployCertToTencentCDN',
|
||||
title: '部署到腾讯云CDN',
|
||||
input: {
|
||||
domainName: {
|
||||
title: 'cdn加速域名',
|
||||
rules: [{ required: true, message: '该项必填' }]
|
||||
},
|
||||
certName: {
|
||||
title: '证书名称',
|
||||
helper: '证书上传后将以此参数作为名称前缀'
|
||||
},
|
||||
accessProvider: {
|
||||
title: 'Access提供者',
|
||||
helper: 'access 授权',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'tencent'
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
output: {
|
||||
tencentCertId: {
|
||||
type: String,
|
||||
desc: '证书来源选择上传时,将返回此id'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async execute ({ cert, props, context }) {
|
||||
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||
const client = this.getClient(accessProvider)
|
||||
const params = this.buildParams(props, context, cert)
|
||||
await this.doRequest(client, params)
|
||||
}
|
||||
|
||||
async rollback ({ cert, props, context }) {
|
||||
|
||||
}
|
||||
|
||||
getClient (accessProvider) {
|
||||
const CdnClient = tencentcloud.cdn.v20180606.Client
|
||||
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: accessProvider.secretId,
|
||||
secretKey: accessProvider.secretKey
|
||||
},
|
||||
region: '',
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: 'cdn.tencentcloudapi.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new CdnClient(clientConfig)
|
||||
}
|
||||
|
||||
buildParams (props, context, cert) {
|
||||
const { domainName, from } = props
|
||||
const { tencentCertId } = context
|
||||
this.logger.info('部署腾讯云证书ID:', tencentCertId)
|
||||
const params = {
|
||||
Https: {
|
||||
Switch: 'on',
|
||||
CertInfo: {
|
||||
CertId: tencentCertId
|
||||
// Certificate: '1231',
|
||||
// PrivateKey: '1231'
|
||||
}
|
||||
},
|
||||
Domain: domainName
|
||||
}
|
||||
if (from === 'upload' || tencentCertId == null) {
|
||||
params.Https.CertInfo = {
|
||||
Certificate: cert.crt,
|
||||
PrivateKey: cert.key
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
async doRequest (client, params) {
|
||||
const ret = await client.UpdateDomainConfig(params)
|
||||
this.checkRet(ret)
|
||||
this.logger.info('设置腾讯云CDN证书成功:', ret.RequestId)
|
||||
return ret.RequestId
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
||||
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
||||
export class DeployCertToTencentCLB extends AbstractTencentPlugin {
|
||||
/**
|
||||
* 插件定义
|
||||
* 名称
|
||||
* 入参
|
||||
* 出参
|
||||
*/
|
||||
static define () {
|
||||
return {
|
||||
name: 'deployCertToTencentCLB',
|
||||
title: '部署到腾讯云CLB',
|
||||
desc: '暂时只支持单向认证证书,暂时只支持通用负载均衡',
|
||||
input: {
|
||||
region: {
|
||||
title: '大区',
|
||||
value: 'ap-guangzhou',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
options: [{ value: 'ap-guangzhou' }]
|
||||
},
|
||||
required: true
|
||||
},
|
||||
domain: {
|
||||
title: '域名',
|
||||
required: true,
|
||||
helper: '要更新的支持https的负载均衡的域名'
|
||||
},
|
||||
loadBalancerId: {
|
||||
title: '负载均衡ID',
|
||||
helper: '如果没有配置,则根据域名匹配负载均衡下的监听器(根据域名匹配时暂时只支持前100个)',
|
||||
required: true
|
||||
},
|
||||
listenerId: {
|
||||
title: '监听器ID',
|
||||
helper: '如果没有配置,则根据域名或负载均衡id匹配监听器'
|
||||
},
|
||||
certName: {
|
||||
title: '证书名称前缀'
|
||||
},
|
||||
accessProvider: {
|
||||
title: 'Access提供者',
|
||||
helper: 'access授权',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'tencent'
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
output: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async execute ({ cert, props, context }) {
|
||||
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||
const { region } = props
|
||||
const client = this.getClient(accessProvider, region)
|
||||
|
||||
const lastCertId = await this.getCertIdFromProps(client, props)
|
||||
if (!props.domain) {
|
||||
await this.updateListener(client, cert, props, context)
|
||||
} else {
|
||||
await this.updateByDomainAttr(client, cert, props, context)
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sleep(2000)
|
||||
let newCertId = await this.getCertIdFromProps(client, props)
|
||||
if ((lastCertId && newCertId === lastCertId) || (!lastCertId && !newCertId)) {
|
||||
await this.sleep(2000)
|
||||
newCertId = await this.getCertIdFromProps(client, props)
|
||||
}
|
||||
if (newCertId === lastCertId) {
|
||||
return {}
|
||||
}
|
||||
this.logger.info('腾讯云证书ID:', newCertId)
|
||||
if (!context.tencentCertId) {
|
||||
context.tencentCertId = newCertId
|
||||
}
|
||||
return { tencentCertId: newCertId }
|
||||
} catch (e) {
|
||||
this.logger.warn('查询腾讯云证书失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
async getCertIdFromProps (client, props) {
|
||||
const listenerRet = await this.getListenerList(client, props.loadBalancerId, [props.listenerId])
|
||||
return this.getCertIdFromListener(listenerRet[0], props.domain)
|
||||
}
|
||||
|
||||
getCertIdFromListener (listener, domain) {
|
||||
let certId
|
||||
if (!domain) {
|
||||
certId = listener.Certificate.CertId
|
||||
} else {
|
||||
if (listener.Rules && listener.Rules.length > 0) {
|
||||
for (const rule of listener.Rules) {
|
||||
if (rule.Domain === domain) {
|
||||
if (rule.Certificate != null) {
|
||||
certId = rule.Certificate.CertId
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return certId
|
||||
}
|
||||
|
||||
async rollback ({ cert, props, context }) {
|
||||
this.logger.warn('未实现rollback')
|
||||
}
|
||||
|
||||
async updateListener (client, cert, props, context) {
|
||||
const params = this.buildProps(props, context, cert)
|
||||
const ret = await client.ModifyListener(params)
|
||||
this.checkRet(ret)
|
||||
this.logger.info('设置腾讯云CLB证书成功:', ret.RequestId, '->loadBalancerId:', props.loadBalancerId, 'listenerId', props.listenerId)
|
||||
return ret
|
||||
}
|
||||
|
||||
async updateByDomainAttr (client, cert, props, context) {
|
||||
const params = this.buildProps(props, context, cert)
|
||||
params.Domain = props.domain
|
||||
const ret = await client.ModifyDomainAttributes(params)
|
||||
this.checkRet(ret)
|
||||
this.logger.info('设置腾讯云CLB证书(sni)成功:', ret.RequestId, '->loadBalancerId:', props.loadBalancerId, 'listenerId', props.listenerId, 'domain:', props.domain)
|
||||
return ret
|
||||
}
|
||||
|
||||
buildProps (props, context, cert) {
|
||||
const { certName } = props
|
||||
const { tencentCertId } = context
|
||||
this.logger.info('部署腾讯云证书ID:', tencentCertId)
|
||||
const params = {
|
||||
Certificate: {
|
||||
SSLMode: 'UNIDIRECTIONAL', // 单向认证
|
||||
CertId: tencentCertId
|
||||
},
|
||||
LoadBalancerId: props.loadBalancerId,
|
||||
ListenerId: props.listenerId
|
||||
}
|
||||
|
||||
if (tencentCertId == null) {
|
||||
params.Certificate.CertName = this.appendTimeSuffix(certName || cert.domain)
|
||||
params.Certificate.CertKey = cert.key
|
||||
params.Certificate.CertContent = cert.crt
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
async getCLBList (client, props) {
|
||||
const params = {
|
||||
Limit: 100, // 最大暂时只支持100个,暂时没做翻页
|
||||
OrderBy: 'CreateTime',
|
||||
OrderType: 0,
|
||||
...props.DescribeLoadBalancers
|
||||
}
|
||||
const ret = await client.DescribeLoadBalancers(params)
|
||||
this.checkRet(ret)
|
||||
return ret.LoadBalancerSet
|
||||
}
|
||||
|
||||
async getListenerList (client, balancerId, listenerIds) {
|
||||
// HTTPS
|
||||
const params = {
|
||||
LoadBalancerId: balancerId,
|
||||
Protocol: 'HTTPS',
|
||||
ListenerIds: listenerIds
|
||||
}
|
||||
const ret = await client.DescribeListeners(params)
|
||||
this.checkRet(ret)
|
||||
return ret.Listeners
|
||||
}
|
||||
|
||||
getClient (accessProvider, region) {
|
||||
const ClbClient = tencentcloud.clb.v20180317.Client
|
||||
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: accessProvider.secretId,
|
||||
secretKey: accessProvider.secretKey
|
||||
},
|
||||
region: region,
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: 'clb.tencentcloudapi.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ClbClient(clientConfig)
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
||||
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
||||
import { K8sClient } from '@certd/plugin-common'
|
||||
export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
|
||||
/**
|
||||
* 插件定义
|
||||
* 名称
|
||||
* 入参
|
||||
* 出参
|
||||
*/
|
||||
static define () {
|
||||
return {
|
||||
name: 'deployCertToTencentTKEIngress',
|
||||
title: '部署到腾讯云TKE-ingress',
|
||||
desc: '需要【上传到腾讯云】作为前置任务',
|
||||
input: {
|
||||
region: {
|
||||
title: '大区',
|
||||
value: 'ap-guangzhou',
|
||||
required: true
|
||||
},
|
||||
clusterId: {
|
||||
title: '集群ID',
|
||||
required: true,
|
||||
desc: '例如:cls-6lbj1vee',
|
||||
request: true
|
||||
},
|
||||
namespace: {
|
||||
title: '集群namespace',
|
||||
value: 'default',
|
||||
required: true
|
||||
},
|
||||
secreteName: {
|
||||
title: '证书的secret名称',
|
||||
required: true
|
||||
},
|
||||
ingressName: {
|
||||
title: 'ingress名称',
|
||||
required: true
|
||||
},
|
||||
ingressClass: {
|
||||
title: 'ingress类型',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
options: [
|
||||
{ value: 'qcloud' },
|
||||
{ value: 'nginx' }
|
||||
]
|
||||
},
|
||||
helper: '可选 qcloud / nginx'
|
||||
},
|
||||
clusterIp: {
|
||||
title: '集群内网ip',
|
||||
helper: '如果开启了外网的话,无需设置'
|
||||
},
|
||||
clusterDomain: {
|
||||
title: '集群域名',
|
||||
helper: '可不填,默认为:[clusterId].ccs.tencent-cloud.com'
|
||||
},
|
||||
/**
|
||||
* AccessProvider的key,或者一个包含access的具体的对象
|
||||
*/
|
||||
accessProvider: {
|
||||
title: 'Access授权',
|
||||
helper: 'access授权',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'tencent'
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
output: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async execute ({ cert, props, context }) {
|
||||
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||
const tkeClient = this.getTkeClient(accessProvider, props.region)
|
||||
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, props.clusterId)
|
||||
|
||||
this.logger.info('kubeconfig已成功获取')
|
||||
const k8sClient = new K8sClient(kubeConfigStr)
|
||||
if (props.clusterIp != null) {
|
||||
let clusterDomain = props.clusterDomain
|
||||
if (!clusterDomain) {
|
||||
clusterDomain = `${props.clusterId}.ccs.tencent-cloud.com`
|
||||
}
|
||||
// 修改内网解析ip地址
|
||||
k8sClient.setLookup({ [clusterDomain]: { ip: props.clusterIp } })
|
||||
}
|
||||
const ingressType = props.ingressClass || 'qcloud'
|
||||
if (ingressType === 'qcloud') {
|
||||
await this.patchQcloudCertSecret({ k8sClient, props, context })
|
||||
} else {
|
||||
await this.patchNginxCertSecret({ cert, k8sClient, props, context })
|
||||
}
|
||||
|
||||
await this.sleep(2000) // 停留2秒,等待secret部署完成
|
||||
await this.restartIngress({ k8sClient, props })
|
||||
return true
|
||||
}
|
||||
|
||||
getTkeClient (accessProvider, region = 'ap-guangzhou') {
|
||||
const TkeClient = tencentcloud.tke.v20180525.Client
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: accessProvider.secretId,
|
||||
secretKey: accessProvider.secretKey
|
||||
},
|
||||
region,
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: 'tke.tencentcloudapi.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new TkeClient(clientConfig)
|
||||
}
|
||||
|
||||
async getTkeKubeConfig (client, clusterId) {
|
||||
// Depends on tencentcloud-sdk-nodejs version 4.0.3 or higher
|
||||
const params = {
|
||||
ClusterId: clusterId
|
||||
}
|
||||
const ret = await client.DescribeClusterKubeconfig(params)
|
||||
this.checkRet(ret)
|
||||
this.logger.info('注意:后续操作需要在【集群->基本信息】中开启外网或内网访问,https://console.cloud.tencent.com/tke2/cluster')
|
||||
return ret.Kubeconfig
|
||||
}
|
||||
|
||||
async patchQcloudCertSecret ({ k8sClient, props, context }) {
|
||||
const { tencentCertId } = context
|
||||
if (tencentCertId == null) {
|
||||
throw new Error('请先将【上传证书到腾讯云】作为前置任务')
|
||||
}
|
||||
this.logger.info('腾讯云证书ID:', tencentCertId)
|
||||
const certIdBase64 = Buffer.from(tencentCertId).toString('base64')
|
||||
|
||||
const { namespace, secretName } = props
|
||||
|
||||
const body = {
|
||||
data: {
|
||||
qcloud_cert_id: certIdBase64
|
||||
},
|
||||
metadata: {
|
||||
labels: {
|
||||
certd: this.appendTimeSuffix('certd')
|
||||
}
|
||||
}
|
||||
}
|
||||
let secretNames = secretName
|
||||
if (typeof secretName === 'string') {
|
||||
secretNames = [secretName]
|
||||
}
|
||||
for (const secret of secretNames) {
|
||||
await k8sClient.patchSecret({ namespace, secretName: secret, body })
|
||||
this.logger.info(`CertSecret已更新:${secret}`)
|
||||
}
|
||||
}
|
||||
|
||||
async patchNginxCertSecret ({ cert, k8sClient, props, context }) {
|
||||
const crt = cert.crt
|
||||
const key = cert.key
|
||||
const crtBase64 = Buffer.from(crt).toString('base64')
|
||||
const keyBase64 = Buffer.from(key).toString('base64')
|
||||
|
||||
const { namespace, secretName } = props
|
||||
|
||||
const body = {
|
||||
data: {
|
||||
'tls.crt': crtBase64,
|
||||
'tls.key': keyBase64
|
||||
},
|
||||
metadata: {
|
||||
labels: {
|
||||
certd: this.appendTimeSuffix('certd')
|
||||
}
|
||||
}
|
||||
}
|
||||
let secretNames = secretName
|
||||
if (typeof secretName === 'string') {
|
||||
secretNames = [secretName]
|
||||
}
|
||||
for (const secret of secretNames) {
|
||||
await k8sClient.patchSecret({ namespace, secretName: secret, body })
|
||||
this.logger.info(`CertSecret已更新:${secret}`)
|
||||
}
|
||||
}
|
||||
|
||||
async restartIngress ({ k8sClient, props }) {
|
||||
const { namespace, ingressName } = props
|
||||
|
||||
const body = {
|
||||
metadata: {
|
||||
labels: {
|
||||
certd: this.appendTimeSuffix('certd')
|
||||
}
|
||||
}
|
||||
}
|
||||
let ingressNames = ingressName
|
||||
if (typeof ingressName === 'string') {
|
||||
ingressNames = [ingressName]
|
||||
}
|
||||
for (const ingress of ingressNames) {
|
||||
await k8sClient.patchIngress({ namespace, ingressName: ingress, body })
|
||||
this.logger.info(`ingress已重启:${ingress}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import tencentcloud from 'tencentcloud-sdk-nodejs'
|
||||
import { AbstractTencentPlugin } from '../abstract-tencent.js'
|
||||
|
||||
export class UploadCertToTencent extends AbstractTencentPlugin {
|
||||
/**
|
||||
* 插件定义
|
||||
* 名称
|
||||
* 入参
|
||||
* 出参
|
||||
*/
|
||||
static define () {
|
||||
return {
|
||||
name: 'uploadCertToTencent',
|
||||
title: '上传证书到腾讯云',
|
||||
desc: '成功后获取,tencentCertId',
|
||||
input: {
|
||||
name: {
|
||||
title: '证书名称'
|
||||
},
|
||||
accessProvider: {
|
||||
title: 'Access授权',
|
||||
helper: 'access授权',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'tencent'
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
output: {
|
||||
tencentCertId: {
|
||||
type: String,
|
||||
desc: '上传成功后的腾讯云CertId'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getClient (accessProvider) {
|
||||
const SslClient = tencentcloud.ssl.v20191205.Client
|
||||
|
||||
const clientConfig = {
|
||||
credential: {
|
||||
secretId: accessProvider.secretId,
|
||||
secretKey: accessProvider.secretKey
|
||||
},
|
||||
region: '',
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint: 'ssl.tencentcloudapi.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SslClient(clientConfig)
|
||||
}
|
||||
|
||||
async execute ({ cert, props, context, logger }) {
|
||||
const { name, accessProvider } = props
|
||||
const certName = this.appendTimeSuffix(name || cert.domain)
|
||||
|
||||
const provider = this.getAccessProvider(accessProvider)
|
||||
const client = this.getClient(provider)
|
||||
|
||||
const params = {
|
||||
CertificatePublicKey: cert.crt,
|
||||
CertificatePrivateKey: cert.key,
|
||||
Alias: certName
|
||||
}
|
||||
const ret = await client.UploadCertificate(params)
|
||||
this.checkRet(ret)
|
||||
this.logger.info('证书上传成功:tencentCertId=', ret.CertificateId)
|
||||
context.tencentCertId = ret.CertificateId
|
||||
}
|
||||
|
||||
async rollback ({ cert, props, context }) {
|
||||
const { accessProvider } = props
|
||||
const provider = super.getAccessProvider(accessProvider)
|
||||
const client = this.getClient(provider)
|
||||
|
||||
const { tencentCertId } = context
|
||||
const params = {
|
||||
CertificateId: tencentCertId
|
||||
}
|
||||
const ret = await client.DeleteCertificate(params)
|
||||
this.checkRet(ret)
|
||||
this.logger.info('证书删除成功:DeleteResult=', ret.DeleteResult)
|
||||
delete context.tencentCertId
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user