feat: 腾讯云证书clb支持与删除

This commit is contained in:
xiaojunnuo
2020-12-28 00:22:12 +08:00
parent 25dae3d1ec
commit 43e90503ca
10 changed files with 365 additions and 49 deletions

View File

@@ -1,17 +1,26 @@
import fs from 'fs'
import logger from '../utils/util.log.js'
import dayjs from 'dayjs'
import Sleep from '../utils/util.sleep.js'
export class AbstractPlugin {
constructor () {
this.logger = logger
}
appendTimeSuffix (name) {
if (name == null) {
name = 'certd'
}
return name + '-' + dayjs().format('YYYYMMDD-HHmmss')
}
async executeFromContextFile (options = {}) {
const { contextPath } = options
const contextJson = fs.readFileSync(contextPath)
const context = JSON.parse(contextJson)
options.context = context
const newContext = await this.execute(options)
fs.writeFileSync(JSON.stringify(newContext || context))
await this.doExecute(options)
fs.writeFileSync(JSON.stringify(context))
}
async doExecute (options) {
@@ -55,4 +64,8 @@ export class AbstractPlugin {
}
return accessProvider
}
async sleep (time) {
await Sleep(time)
}
}

View File

@@ -9,7 +9,7 @@ export class AbstractTencentPlugin extends AbstractPlugin {
}
checkRet (ret) {
if (ret && ret.Error) {
if (!ret || ret.Error) {
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message)
}
}

View File

@@ -57,7 +57,6 @@ export class DeployCertToTencentCDN extends AbstractTencentPlugin {
const client = this.getClient(accessProvider)
const params = this.buildParams(props, context, cert)
await this.doRequest(client, params)
return context
}
async rollback ({ accessProviders, cert, props, context }) {
@@ -98,7 +97,7 @@ export class DeployCertToTencentCDN extends AbstractTencentPlugin {
},
Domain: domainName
}
if (from === 'upload') {
if (from === 'upload' || tencentCertId == null) {
params.Https.CertInfo = {
Certificate: this.format(cert.crt.toString()),
PrivateKey: this.format(cert.key.toString())

View File

@@ -0,0 +1,191 @@
import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js'
import tencentcloud from 'tencentcloud-sdk-nodejs'
export class DeployCertToTencentCLB extends AbstractTencentPlugin {
/**
* 插件定义
* 名称
* 入参
* 出参
*/
static define () {
return {
name: 'deployCertToTencentCLB',
label: '部署到腾讯云CLB',
desc: '暂时只支持单向认证证书,暂时只支持通用负载均衡',
input: {
region: {
label: '大区',
value: 'ap-guangzhou'
},
domain: {
label: '域名',
type: [String, Array],
desc: '要更新的支持https的负载均衡的域名'
},
loadBalancerId: {
label: '负载均衡ID',
desc: '如果没有配置则根据域名匹配负载均衡下的监听器根据域名匹配时暂时只支持前100个'
},
listenerId: {
label: '监听器ID',
desc: '如果没有配置则根据域名或负载均衡id匹配监听器'
},
certName: {
label: '证书名称',
desc: '如无uploadCertToTencent作为前置则此项需要设置默认为域名'
},
accessProvider: {
label: 'Access提供者',
type: [String, Object],
desc: 'AccessProviders的key 或 一个包含accessKeyId与accessKeySecret的对象',
options: 'accessProviders[type=tencent]',
required: true
}
},
output: {
}
}
}
async execute ({ accessProviders, cert, props, context }) {
const accessProvider = this.getAccessProvider(props.accessProvider, accessProviders)
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 ({ accessProviders, 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
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 = this.format(cert.key.toString())
params.Certificate.CertContent = this.format(cert.crt.toString())
}
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)
}
}

View File

@@ -54,7 +54,7 @@ export class UploadCertToTencent extends AbstractTencentPlugin {
async execute ({ accessProviders, cert, props, context, logger }) {
const { name, accessProvider } = props
const certName = name + '-' + dayjs().format('YYYYMMDD-HHmmss')
const certName = this.appendTimeSuffix(name)
const provider = super.getAccessProvider(accessProvider, accessProviders)
const client = this.getClient(provider)
@@ -70,7 +70,7 @@ export class UploadCertToTencent extends AbstractTencentPlugin {
context.tencentCertId = ret.CertificateId
}
async rollback ({ accessProviders, cert, props, context, logger }) {
async rollback ({ accessProviders, cert, props, context }) {
const { accessProvider } = props
const provider = super.getAccessProvider(accessProvider, accessProviders)
const client = this.getClient(provider)

View File

@@ -0,0 +1,7 @@
export default function (timeout) {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, timeout)
})
}

View File

@@ -37,13 +37,13 @@ describe('DeployToTencentCDN', function () {
const options = createOptions()
options.args.test = false
const plugin = new DeployCertToTencentCDN()
const certd = new Certd()
const certd = new Certd(options)
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
const context = {}
const deployOpts = {
accessProviders: options.accessProviders,
cert,
props: { domainName: 'tentcent-certd.docmirror.cn', from: 'upload', accessProvider: 'tencent' },
props: { domainName: 'tentcent-certd.docmirror.cn', accessProvider: 'tencent' },
context
}
const ret = await plugin.doExecute(deployOpts)

View File

@@ -0,0 +1,106 @@
import pkg from 'chai'
import { DeployCertToTencentCLB } from '../../src/tencent/deploy-to-clb/index.js'
import { Certd } from '@certd/certd'
// eslint-disable-next-line no-unused-vars
import { createOptions } from '../../../../test/options.js'
import { UploadCertToTencent } from '../../src/tencent/upload-to-tencent/index.js'
const { expect } = pkg
describe('DeployToTencentCLB', function () {
it('#execute-getClbList', async function () {
const options = createOptions()
options.args.test = false
options.cert.dnsProvider = 'tencent-yonsz'
const deployPlugin = new DeployCertToTencentCLB()
const props = {
region: 'ap-guangzhou',
domain: 'certd-test-no-sni.base.yonsz.net',
accessProvider: 'tencent-yonsz'
}
const accessProviders = options.accessProviders
const accessProvider = deployPlugin.getAccessProvider(props.accessProvider, accessProviders)
const { region } = props
const client = deployPlugin.getClient(accessProvider, region)
const ret = await deployPlugin.getCLBList(client, props)
expect(ret.length > 0).ok
console.log('clb count:', ret.length)
})
it('#execute-getListenerList', async function () {
const options = createOptions()
options.args.test = false
options.cert.dnsProvider = 'tencent-yonsz'
const deployPlugin = new DeployCertToTencentCLB()
const props = {
region: 'ap-guangzhou',
domain: 'certd-test-no-sni.base.yonsz.net',
accessProvider: 'tencent-yonsz',
loadBalancerId: 'lb-59yhe5xo'
}
const accessProviders = options.accessProviders
const accessProvider = deployPlugin.getAccessProvider(props.accessProvider, accessProviders)
const { region } = props
const client = deployPlugin.getClient(accessProvider, region)
const ret = await deployPlugin.getListenerList(client, props.loadBalancerId, props)
expect(ret.length > 0).ok
console.log('clb count:', ret.length, ret)
})
it('#execute-no-sni-listenerId', async function () {
this.timeout(10000)
const options = createOptions()
options.args.test = false
options.cert.dnsProvider = 'tencent-yonsz'
const certd = new Certd(options)
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
const deployPlugin = new DeployCertToTencentCLB()
const context = {}
const deployOpts = {
accessProviders: options.accessProviders,
cert,
props: {
region: 'ap-guangzhou',
loadBalancerId: 'lb-59yhe5xo',
listenerId: 'lbl-1vfwx8dq',
accessProvider: 'tencent-yonsz'
},
context
}
const ret = await deployPlugin.doExecute(deployOpts)
expect(ret).ok
console.log('ret:', ret)
// 删除测试证书
const uploadPlugin = new UploadCertToTencent()
await uploadPlugin.doRollback(deployOpts)
})
it('#execute-sni-listenerId', async function () {
this.timeout(10000)
const options = createOptions()
options.args.test = false
options.cert.dnsProvider = 'tencent-yonsz'
const certd = new Certd(options)
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
const deployPlugin = new DeployCertToTencentCLB()
const context = {}
const deployOpts = {
accessProviders: options.accessProviders,
cert,
props: {
region: 'ap-guangzhou',
loadBalancerId: 'lb-59yhe5xo',
listenerId: 'lbl-akbyf5ac',
domain: 'certd-test-sni.base.yonsz.net',
accessProvider: 'tencent-yonsz'
},
context
}
const ret = await deployPlugin.doExecute(deployOpts)
expect(ret).ok
console.log('ret:', ret)
// 删除测试证书
const uploadPlugin = new UploadCertToTencent()
await uploadPlugin.doRollback(deployOpts)
})
})