Compare commits

...
60 Commits
Author SHA1 Message Date
xiaojunnuo 7145aa60ca v0.2.2 2021-12-04 18:43:58 +08:00
xiaojunnuo b1181c0f9a refactor: 1 2021-12-04 18:39:05 +08:00
xiaojunnuo bace2a7c25 refactor: 1 2021-12-04 18:34:52 +08:00
xiaojunnuo a70b4373de v0.2.1 2021-12-04 17:11:07 +08:00
xiaojunnuo b7c12e6d91 perf: 支持阿里云 ack ingress 2021-12-04 16:57:12 +08:00
xiaojunnuo 6a88dd476e refactor: 0.2.0 2021-12-02 17:07:10 +08:00
xiaojunnuo 5fbd774266 v0.2.0 2021-12-02 17:01:24 +08:00
xiaojunnuo bdec010d2e refactor: 1 2021-12-02 17:00:24 +08:00
xiaojunnuo 05a00b7b78 feat: 私钥升级为PKCS8 2021-12-02 16:47:46 +08:00
xiaojunnuo eaf23c3034 v0.1.21 2021-11-04 18:02:14 +08:00
xiaojunnuo 276a8b35e5 feat: 支持腾讯云nginx-ingress 2021-11-04 18:00:30 +08:00
xiaojunnuo 466d659f6e fix: 修改ssh privateKey参数名 2021-06-09 17:50:18 +08:00
xiaojunnuo 84e26381b5 v0.1.20 2021-06-02 09:15:30 +08:00
xiaojunnuo 469b5a5f69 fix: fix 任务成功后不需要重新运行 2021-06-02 09:14:10 +08:00
xiaojunnuo ad77ebd2f9 refactor: 1 2021-03-17 18:06:06 +08:00
xiaojunnuo b75543c3bc v0.1.19 2021-03-16 19:14:31 +08:00
xiaojunnuo 0677275742 refactor: 1 2021-03-16 18:40:16 +08:00
xiaojunnuo 0c3724e0ad refactor: 1 2021-03-16 18:27:24 +08:00
xiaojunnuo 803083d23c refactor: 1 2021-03-16 18:25:11 +08:00
xiaojunnuo f4f8067a12 refactor: new client 2021-03-15 19:04:46 +08:00
xiaojunnuo caa9f084d6 v0.1.18 2021-03-15 11:54:09 +08:00
xiaojunnuo 81407b65d1 refactor: dir with md5 2021-02-26 15:27:34 +08:00
xiaojunnuo 8a24293fd7 refactor: dir with md5 2021-02-26 15:25:04 +08:00
xiaojunnuo 8f1886a585 refactor: dir with suffix 2021-02-26 15:23:53 +08:00
xiaojunnuo 0a64e5fa67 refactor: md5 dir 2021-02-26 15:22:21 +08:00
xiaojunnuo 7a70603971 refactor: doc 2021-02-09 21:23:58 +08:00
xiaojunnuo 0d5e00e744 refactor: doc 2021-02-09 21:22:07 +08:00
xiaojunnuo 91ba1433af refactor: doc 2021-02-09 21:20:41 +08:00
xiaojunnuo 12e56d14f2 refactor: icons 2021-02-09 21:13:19 +08:00
xiaojunnuo 7326119f52 refactor: export 2021-02-09 21:07:19 +08:00
xiaojunnuo 136983cf14 refactor: export 2021-02-09 19:01:15 +08:00
xiaojunnuo 105a1b80ae refactor: export 2021-02-09 18:40:39 +08:00
xiaojunnuo b8000ca533 refactor: export 2021-02-09 18:40:29 +08:00
xiaojunnuo c3e374e6e5 refactor: export 2021-02-09 18:05:01 +08:00
xiaojunnuo a9b6e87249 refactor: 按需加载 2021-02-09 17:19:00 +08:00
xiaojunnuo 61de5422bf refactor: logo 2021-02-09 16:30:59 +08:00
xiaojunnuo f96697f619 refactor: logo 2021-02-09 15:42:10 +08:00
xiaojunnuo b4560d6370 refactor: docker 2021-02-09 11:03:11 +08:00
xiaojunnuo a7bcde8d82 refactor: host 2021-02-09 10:57:08 +08:00
xiaojunnuo 34bb4d54c2 refactor: deploy image 2021-02-08 23:01:07 +08:00
xiaojunnuo e0116a1a03 refactor: deploy image 2021-02-08 22:37:54 +08:00
xiaojunnuo 12fec7939d refactor: deploy 2021-02-08 21:16:56 +08:00
xiaojunnuo ff8e02cceb v0.1.17 2021-02-08 18:19:45 +08:00
xiaojunnuo 8122bed97f refactor: host 2021-02-08 18:18:23 +08:00
xiaojunnuo 991c3dbb76 v0.1.16 2021-02-08 15:46:27 +08:00
xiaojunnuo 399c23623d refactor: host 2021-02-08 15:46:03 +08:00
xiaojunnuo 2232f21b48 refactor: transfer 2021-02-08 15:12:35 +08:00
xiaojunnuo e41c084381 refactor: transfer 2021-02-08 15:00:04 +08:00
xiaojunnuo 520b27e0dc refactor: ui 2021-02-08 14:31:12 +08:00
xiaojunnuo ace7e0247a v0.1.15 2021-02-08 14:18:14 +08:00
xiaojunnuo 9ae414b1c6 refactor: 重构 2021-02-08 14:00:28 +08:00
xiaojunnuo cb8c8186f1 refactor: 重构 2021-02-08 13:40:28 +08:00
xiaojunnuo 82f86d9556 refactor: move 2021-02-08 00:21:36 +08:00
xiaojunnuo cfb1034450 refactor: host 2021-02-07 23:17:44 +08:00
xiaojunnuo 2a07442a85 refactor: ui 2021-02-07 18:32:38 +08:00
xiaojunnuo 68c1eff81d refactor: fix 2021-02-07 13:53:30 +08:00
xiaojunnuo baec15dfc6 refactor: fix 2021-02-07 13:47:09 +08:00
xiaojunnuo 6eb9817296 refactor: fix 2021-02-07 10:54:55 +08:00
xiaojunnuo b9d5d33aaa refactor: delete task 2021-02-05 18:20:33 +08:00
xiaojunnuo 560519894c refactor: fix bug 2021-02-05 18:13:24 +08:00
188 changed files with 1670 additions and 76272 deletions
+15 -4
View File
@@ -1,16 +1,27 @@
# IntelliJ project files
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/
.idea
*.iml
out
gen
node_modules/
/test/*.private.*
/*.log
/ui/*/.idea
/packages/ui/*/.idea
/ui/*/node_modules
/packages/ui/*/node_modules
/packages/*/node_modules
/ui/certd-server/tmp/
/packages/ui/certd-server/tmp/
/packages/ui/certd-ui/dist/
/other
/dev-sidecar-test
/packages/core/certd/yarn.lock
+22 -79
View File
@@ -26,88 +26,31 @@ CertD 是一个帮助你全自动申请和部署SSL证书的工具。
## 快速开始
本案例演示,如何配置自动申请证书,并部署到阿里云CDN,然后快要到期前自动更新证书并重新部署
1. 环境准备
安装[nodejs](https://nodejs.org/zh-cn/)
2. 创建任务项目
2. 生成node项目
通过ui生成: https://certd.docmirror.cn/
开始生成证书,先填写域名,支持将多个域名打到一个证书上
![](./doc/step1.png)
配置证书详细信息
![](./doc/step2.png)
配置证书部署流程
![](./doc/step3.png)
配置好之后,点击导出按钮,导出一个node项目包
4. 运行
将导出的压缩包解压,然后执行如下命令,即可开始申请证书并部署
```
mkdir certd-run # 项目名称可以任意命名
cd certd-run -y
npm install @certd/executor -s --production
```
3. 创建index.js
参数配置分几个部分
args: 运行时参数
accessProviders: 授权提供者,提供dns验证与部署任务的授权
cert: 证书申请的配置
deploy: 证书部署流程
```js
import { Executor } from '@certd/executor'
const options = {
args: { // 运行时参数
forceDeploy: true,
},
accessProviders: { //授权提供者
aliyun: { // 阿里云accessKey,用于dns验证和上传证书到阿里云,并部署到cdn
providerType: 'aliyun',
accessKeyId: 'Your accessKeyId',
accessKeySecret: 'Your accessKeySecret'
},
},
cert: { //免费证书申请配置
domains: [ //可以在一张证书上绑定多个域名(前提是他们的验证方式要一样,目前仅支持dns验证)
'*.yourdomain.com',
'*.test.yourdomain.com',
'yourdomain.com'
],
email: 'Your email',
dnsProvider: 'aliyun', //上方accessProviders里面配置的
csrInfo: { //证书csr信息
country: 'CN',
state: 'GuangDong',
locality: 'ShengZhen',
organization: 'Your company Org.',
organizationUnit: 'IT Department',
emailAddress: 'Your email'
}
},
deploy: [ //部署流程配置,数组,可以配置多条流程
{
deployName: '流程1-部署到阿里云CDN',
tasks: [ //流程任务,一个流程下可以包含多个部署任务,并且将按顺序执行
{ //任务1
taskName: '上传到阿里云', //任务名称
type: 'uploadCertToAliyun', //任务插件名称
props: { //任务插件参数
accessProvider: 'aliyun'
}
},
{ // 任务2
taskName: '部署证书到CDN',
type: 'deployCertToAliyunCDN', //任务插件名称
props:{
domainName: 'your cdn domain 全称', //cdn域名全称
certName: 'certd自动部署',//证书名称前缀
accessProvider: 'aliyun'
}
}
]
}
]
}
const executor = new Executor()
await executor.run(options)
```
4. 运行
```
node index.js
npm install
npm run certd
```
5. 执行效果
生成的证书默认会存储在 `${home}/.certd/${email}/certs/${domain}/current`目录下
@@ -129,7 +72,7 @@ node index.js
所以当你临时需要将证书部署到其他地方时,直接追加部署任务,然后再次运行即可
## CI/DI集成与自动续期重新部署
集成前,将以上代码提交到内网git仓库,或者私有git仓库(由于包含敏感信息,不要提交到公开git仓库)
集成前,将以上导出的node项目提交到内网git仓库,或者私有git仓库(由于包含敏感信息,不要提交到公开git仓库)
### jenkins任务
1. 创建任务
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+2 -3
View File
@@ -1,7 +1,6 @@
{
"packages": [
"packages/*",
"ui/*"
"packages/*/*"
],
"version": "0.1.14"
"version": "0.2.2"
}
-22168
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@
},
"scripts": {
"start": "lerna bootstrap --hoist",
"i-all": "lerna exec npm install && lerna link "
"i-all": "lerna link && lerna exec npm install "
},
"license": "MIT",
"dependencies": {
File diff suppressed because it is too large Load Diff
-24
View File
@@ -1,24 +0,0 @@
{
"name": "@certd/access-providers",
"version": "0.1.13",
"description": "",
"main": "./src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"type": "module",
"author": "Greper",
"license": "MIT",
"dependencies": {
"@certd/api": "^0.1.13",
"lodash-es": "^4.17.20"
},
"devDependencies": {
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1"
},
"gitHead": "4a421d5b142d453203c68ce6d1036e168ea2455b"
}
-18
View File
@@ -1,18 +0,0 @@
import _ from 'lodash-es'
import { AliyunAccessProvider } from './providers/aliyun.js'
import { DnspodAccessProvider } from './providers/dnspod.js'
import { TencentAccessProvider } from './providers/tencent.js'
import { accessProviderRegistry } from '@certd/api'
export const DefaultAccessProviders = {
AliyunAccessProvider,
DnspodAccessProvider,
TencentAccessProvider,
}
export default {
install () {
_.forEach(DefaultAccessProviders, item => {
accessProviderRegistry.install(item)
})
}
}
-2468
View File
File diff suppressed because it is too large Load Diff
-2476
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/
@@ -1,8 +1,8 @@
{
"name": "@certd/api",
"version": "0.1.13",
"version": "0.2.1",
"description": "",
"main": "./src/index.js",
"main": "src/index.js",
"type": "module",
"author": "Greper",
"license": "MIT",
@@ -22,5 +22,5 @@
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
},
"gitHead": "4a421d5b142d453203c68ce6d1036e168ea2455b"
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
}
@@ -1,9 +1,10 @@
import _ from 'lodash-es'
import logger from '../utils/util.log.js'
import commonUtil from '../utils/util.common.js'
export class AbstractDnsProvider {
constructor ({ accessProviders }) {
this.logger = logger
this.accessProviders = accessProviders
this.accessProviders = commonUtil.arrayToMap(accessProviders)
}
async createRecord ({ fullRecord, type, value }) {
@@ -34,9 +35,13 @@ export class AbstractDnsProvider {
}
getAccessProvider (accessProvider, accessProviders = this.accessProviders) {
let access = accessProvider
if (typeof accessProvider === 'string' && accessProviders) {
accessProvider = accessProviders[accessProvider]
access = accessProviders[accessProvider]
}
return accessProvider
if (access == null) {
throw new Error(`accessProvider :${accessProvider}不存在`)
}
return access
}
}
@@ -3,3 +3,4 @@ export * from './plugin/index.js'
export * from './access-provider/index.js'
export { Store } from './store/store.js'
export { util } from './utils/index.js'
// module.createRequireFromPath()
@@ -2,11 +2,15 @@ import fs from 'fs'
import logger from '../utils/util.log.js'
import dayjs from 'dayjs'
import Sleep from '../utils/util.sleep.js'
import commonUtil from '../utils/util.common.js'
export class AbstractPlugin {
constructor ({ accessProviders }) {
constructor (options) {
if (options == null) {
throw new Error('插件安装失败:参数不允许为空')
}
const { accessProviders } = options
this.logger = logger
this.accessProviders = accessProviders
this.accessProviders = commonUtil.arrayToMap(accessProviders)
}
appendTimeSuffix (name) {
@@ -61,10 +65,14 @@ export class AbstractPlugin {
}
getAccessProvider (accessProvider, accessProviders = this.accessProviders) {
let access = accessProvider
if (typeof accessProvider === 'string' && accessProviders) {
accessProvider = accessProviders[accessProvider]
access = accessProviders[accessProvider]
}
return accessProvider
if (access == null) {
throw new Error(`accessProvider :${accessProvider}不存在`)
}
return access
}
async sleep (time) {
@@ -26,10 +26,21 @@ export class Registry {
}
get (name) {
if (name) {
return this.collection[name]
if (!name) {
throw new Error('插件名称不能为空')
}
throw new Error(`${name} not found`)
if (!this.collection) {
this.collection = {}
}
const plugin = this.collection[name]
if (!plugin) {
throw new Error(`插件${name}还未注册`)
}
return plugin
}
getCollection () {
return this.collection
}
}
@@ -2,6 +2,7 @@ import logger from './util.log.js'
import path from './util.path.js'
import { request } from './util.request.js'
import sleep from './util.sleep.js'
import common from './util.common.js'
export const util = {
logger, path, request, sleep
logger, path, request, sleep, common
}
@@ -0,0 +1,33 @@
import _ from 'lodash-es'
export default {
arrayToMap (array) {
if (!array) {
return {}
}
if (!_.isArray(array)) {
return array
}
const map = {}
for (const item of array) {
if (item.key) {
map[item.key] = item
}
}
return map
},
mapToArray (map) {
if (!map) {
return []
}
if (_.isArray(map)) {
return map
}
const array = []
for (const key in map) {
const item = map[key]
item.key = key
array.push(item)
}
return array
}
}
+7
View File
@@ -0,0 +1,7 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/
@@ -1,18 +1,17 @@
{
"name": "@certd/certd",
"version": "0.1.13",
"description": "",
"main": "./src/index.js",
"version": "0.2.1",
"description": "a ssl cert keeper",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \\\"Error: no test specified\\\" && exit 1"
},
"type": "module",
"author": "Greper",
"license": "MIT",
"dependencies": {
"@certd/acme-client": "^0.1.6",
"@certd/api": "^0.1.13",
"@certd/dns-providers": "^0.1.13",
"@certd/acme-client": "^0.2.0",
"@certd/api": "^0.2.1",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20",
"node-forge": "^0.10.0"
@@ -26,5 +25,5 @@
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
},
"gitHead": "4a421d5b142d453203c68ce6d1036e168ea2455b"
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
}
@@ -4,11 +4,8 @@ import { FileStore } from './store/file-store.js'
import { CertStore } from './store/cert-store.js'
import dayjs from 'dayjs'
import forge from 'node-forge'
import DefaultDnsProviders from '@certd/dns-providers'
const logger = util.logger
DefaultDnsProviders.install()
export class Certd {
constructor (options) {
this.options = options
@@ -125,11 +122,10 @@ export class Certd {
createProviderByType (props, accessProviders) {
const { type } = props
try {
const Provider = dnsProviderRegistry.get(type)
return new Provider({ accessProviders, props })
} catch (e) {
throw new Error('暂不支持此dnsProvider,请先注册该provider:' + type, e)
const Provider = dnsProviderRegistry.get(type)
if (Provider == null) {
throw new Error('暂不支持此dnsProvider,请先注册该provider:' + type)
}
return new Provider({ accessProviders, props })
}
}
@@ -11,8 +11,8 @@ export class CertStore {
this.domains = domains
this.domain = this.getMainDomain(this.domains)
this.safetyDomain = this.getSafetyDomain(this.domain)
// this.domainDir = this.safetyDomain + '-' + md5(this.getDomainStr(this.domains))
this.domainDir = this.safetyDomain
this.domainDir = this.safetyDomain + '-' + md5(this.getDomainStr(this.domains))
// this.domainDir = this.safetyDomain
this.certsRootPath = this.store.buildKey(this.email, 'certs')
this.currentMarkPath = this.store.buildKey(this.certsRootPath, this.domainDir, 'current.json')
@@ -1,6 +1,6 @@
import chai from 'chai'
import { Certd } from '../src/index.js'
import { createOptions } from '../../../test/options.js'
import { createOptions } from '../../../../test/options.js'
const { expect } = chai
const fakeCrt = `-----BEGIN CERTIFICATE-----
MIIFSTCCBDGgAwIBAgITAPoZZk/LhVIyXoic2NnJyxubezANBgkqhkiG9w0BAQsF
@@ -68,7 +68,7 @@ describe('Certd', function () {
const certd = new Certd(options)
const currentRootPath = certd.certStore.currentMarkPath
console.log('rootDir', currentRootPath)
expect(currentRootPath).match(/xiaojunnuo@qq.com\\certs\\_.docmirror.club\w*\\current.json/)
expect(currentRootPath).match(/xiaojunnuo@qq.com\\certs\\_.docmirror.club-\w*\\current.json/)
})
it('#writeAndReadCert', async function () {
const options = createOptions()
+7
View File
@@ -0,0 +1,7 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/
@@ -1,23 +1,24 @@
{
"name": "@certd/executor",
"version": "0.1.14",
"version": "0.2.2",
"description": "",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "echo \\\"Error: no test specified\\\" && exit 1",
"build": "webpack --config webpack.config.cjs ",
"rollup": "rollup --config rollup.config.js"
},
"type": "module",
"dependencies": {
"@certd/api": "^0.1.13",
"@certd/certd": "^0.1.13",
"@certd/dns-providers": "^0.1.13",
"@certd/plugins": "^0.1.13",
"@certd/api": "^0.2.1",
"@certd/certd": "^0.2.1",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20"
},
"devDependencies": {
"@certd/plugin-aliyun": "^0.2.2",
"@certd/plugin-host": "^0.2.1",
"@certd/plugin-tencent": "^0.2.2",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.0.1",
@@ -34,5 +35,5 @@
"author": "Greper",
"license": "MIT",
"sideEffects": false,
"gitHead": "4a421d5b142d453203c68ce6d1036e168ea2455b"
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
}
@@ -3,13 +3,8 @@ import { pluginRegistry, util } from '@certd/api'
import _ from 'lodash-es'
import dayjs from 'dayjs'
import { Trace } from './trace.js'
import DefaultPlugins from '@certd/plugins'
const logger = util.logger
// 安装默认插件和授权提供者
DefaultPlugins.install()
function createDefaultOptions () {
return {
args: {
@@ -28,14 +23,28 @@ export class Executor {
async run (options) {
logger.info('------------------- Cert-D ---------------------')
try {
this.transfer(options)
options = _.merge(createDefaultOptions(), options)
return await this.doRun(options)
} catch (e) {
logger.error('任务执行出错:', e)
logger.error('任务执行出错', e)
throw e
}
}
transfer (options) {
const providers = options.accessProviders
if (_.isArray(providers)) {
const map = {}
for (const provider of providers) {
if (provider.key) {
map[provider.key] = provider
}
}
options.accessProviders = map
}
}
async doRun (options) {
// 申请证书
logger.info('任务开始')
@@ -49,8 +58,11 @@ export class Executor {
logger.info('----------------------')
if (!cert.isNew) {
// 如果没有更新
if (!options.args.forceDeploy && !options.args.forceRedeploy) {
// 且不需要强制运行deploy
if (options.args.forceRedeploy) {
// 强制重新部署,清空保存的状态
await certd.certStore.setCurrentFile('context.json', '{}')
} else if (!options.args.forceDeploy) {
// 且不需要强制deploy
logger.info('证书无更新,无需重新部署')
logger.info('任务完成')
return { cert }
@@ -76,16 +88,16 @@ export class Executor {
logger.info('任务完成')
trace.print()
const result = resultTrace.get({ })
const returnData = {
if (result) {
if (result.status === 'error' && options.args.doNotThrowError === false) {
throw new Error(result.remark)
}
}
return {
cert,
context,
result
}
if (result.status === 'error' && options.args.doNotThrowError === false) {
process.exitCode = 1 // 设置错误码,以便执行者可以获取到异常退出
throw new Error(result.remark)
}
return returnData
}
async runCertd (certd) {
@@ -110,6 +122,7 @@ export class Executor {
logger.info('此流程已被禁用,跳过')
logger.info('')
deployTrace.set({ value: { current: 'skip', status: 'disabled', remark: '流程禁用' } })
deployTrace.set({ tasks: null })
continue
}
try {
@@ -123,6 +136,7 @@ export class Executor {
}
deployTrace.set({ value: { status: 'success', remark: '执行成功' } })
trace.set({ type: 'result', value: { status: 'success', remark: '执行成功' } })
} catch (e) {
deployTrace.set({ value: { status: 'error', remark: '执行失败:' + e.message } })
trace.set({ type: 'result', value: { status: 'error', remark: deployName + '执行失败:' + e.message } })
@@ -76,7 +76,9 @@ export class Trace {
}
}
const result = this.get({ type: 'result' })
this.printTraceLine(result, 'result', '')
if (result) {
this.printTraceLine(result, 'result', '')
}
const mainContext = {}
_.merge(mainContext, context)
delete mainContext.__trace__
@@ -1,8 +1,16 @@
import pkg from 'chai'
import { Executor } from '../src/index.js'
import { createOptions } from '../../../test/options.js'
import { createOptions } from '../../../../test/options.js'
import PluginAliyun from '@certd/plugin-aliyun'
import PluginTencent from '@certd/plugin-tencent'
import PluginHost from '@certd/plugin-host'
const { expect } = pkg
// 安装默认插件和授权提供者
PluginAliyun.install()
PluginTencent.install()
PluginHost.install()
describe('AutoDeploy', function () {
it('#run', async function () {
this.timeout(120000)
File diff suppressed because it is too large Load Diff
-29
View File
@@ -1,29 +0,0 @@
{
"name": "@certd/dns-providers",
"version": "0.1.13",
"description": "",
"main": "./src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"private": false,
"type": "module",
"author": "Greper",
"license": "MIT",
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/api": "^0.1.13",
"lodash-es": "^4.17.20",
"tencentcloud-sdk-nodejs": "^4.0.44"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
},
"gitHead": "4a421d5b142d453203c68ce6d1036e168ea2455b"
}
-16
View File
@@ -1,16 +0,0 @@
import _ from 'lodash-es'
import { AliyunDnsProvider } from './providers/aliyun.js'
import { DnspodDnsProvider } from './providers/dnspod.js'
import { dnsProviderRegistry } from '@certd/api'
export const DefaultDnsProviders = {
AliyunDnsProvider,
DnspodDnsProvider
}
export default {
install () {
_.forEach(DefaultDnsProviders, item => {
dnsProviderRegistry.install(item)
})
}
}
-2557
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-3571
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/
@@ -0,0 +1,29 @@
{
"name": "@certd/plugin-aliyun",
"version": "0.2.2",
"description": "",
"main": "src/index.js",
"type": "module",
"dependencies": {
"@alicloud/cs20151215": "^3.0.3",
"@alicloud/openapi-client": "^0.4.0",
"@alicloud/pop-core": "^1.7.10",
"@certd/api": "^0.2.1",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20"
},
"devDependencies": {
"@certd/certd": "^0.2.1",
"@certd/plugin-common": "^0.2.1",
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
},
"author": "Greper",
"license": "MIT",
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
}
@@ -1,5 +1,4 @@
import _ from 'lodash-es'
export class AliyunAccessProvider{
export class AliyunAccessProvider {
static define () {
return {
name: 'aliyun',
@@ -28,7 +27,4 @@ export class AliyunAccessProvider{
}
}
}
constructor () {
}
}
@@ -0,0 +1,25 @@
import _ from 'lodash-es'
import { AliyunDnsProvider } from './dns-providers/aliyun.js'
import { AliyunAccessProvider } from './access-providers/aliyun.js'
import { UploadCertToAliyun } from './plugins/upload-to-aliyun/index.js'
import { DeployCertToAliyunCDN } from './plugins/deploy-to-cdn/index.js'
import { DeployCertToAliyunAckIngress } from './plugins/deploy-to-ack-ingress/index.js'
import { pluginRegistry, accessProviderRegistry, dnsProviderRegistry } from '@certd/api'
export const Plugins = {
UploadCertToAliyun,
DeployCertToAliyunCDN,
DeployCertToAliyunAckIngress
}
export default {
install () {
_.forEach(Plugins, item => {
pluginRegistry.install(item)
})
accessProviderRegistry.install(AliyunAccessProvider)
dnsProviderRegistry.install(AliyunDnsProvider)
}
}
@@ -0,0 +1,200 @@
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
import Core from '@alicloud/pop-core'
import { K8sClient } from '@certd/plugin-common'
const ROAClient = Core.ROAClient
const define = {
name: 'deployCertToAliyunAckIngress',
label: '部署到阿里云AckIngress',
input: {
clusterId: {
label: '集群id',
component: {
placeholder: '集群id'
},
required: true
},
secretName: {
label: '保密字典Id',
component: {
placeholder: '保密字典Id'
},
required: true
},
regionId: {
label: '大区',
value: 'cn-shanghai',
component: {
placeholder: '集群所属大区'
},
required: true
},
namespace: {
label: '命名空间',
value: 'default',
component: {
placeholder: '命名空间'
},
required: true
},
ingressName: {
label: 'ingress名称',
value: '',
component: {
placeholder: 'ingress名称'
},
required: true,
helper: '可以传入一个数组'
},
ingressClass: {
label: 'ingress类型',
value: 'nginx',
component: {
placeholder: '暂时只支持nginx类型'
},
required: true
},
isPrivateIpAddress: {
label: '是否私网ip',
value: false,
component: {
placeholder: '集群连接端点是否是私网ip'
},
helper: '如果您当前certd运行在同一个私网下,可以选择是。',
required: true
},
accessProvider: {
label: 'Access提供者',
type: [String, Object],
desc: 'access授权',
component: {
name: 'access-provider-selector',
filter: 'aliyun'
},
required: true
}
},
output: {
}
}
export class DeployCertToAliyunAckIngress extends AbstractAliyunPlugin {
static define () {
return define
}
async execute ({ cert, props, context }) {
const accessProvider = this.getAccessProvider(props.accessProvider)
const client = this.getClient(accessProvider, props.regionId)
const kubeConfigStr = await this.getKubeConfig(client, props.clusterId, props.isPrivateIpAddress)
this.logger.info('kubeconfig已成功获取')
const k8sClient = new K8sClient(kubeConfigStr)
const ingressType = props.ingressClass || 'qcloud'
if (ingressType === 'qcloud') {
throw new Error('暂未实现')
// await this.patchQcloudCertSecret({ k8sClient, props, context })
} else {
await this.patchNginxCertSecret({ cert, k8sClient, props, context })
}
await this.sleep(3000) // 停留2秒,等待secret部署完成
// await this.restartIngress({ k8sClient, props })
return true
}
async restartIngress ({ k8sClient, props }) {
const { namespace } = props
const body = {
metadata: {
labels: {
certd: this.appendTimeSuffix('certd')
}
}
}
const ingressList = await k8sClient.getIngressList({ namespace })
console.log('ingressList:', ingressList)
if (!ingressList || !ingressList.body || !ingressList.body.items) {
return
}
const ingressNames = ingressList.body.items.filter(item => {
if (!item.spec.tls) {
return false
}
for (const tls of item.spec.tls) {
if (tls.secretName === props.secretName) {
return true
}
}
return false
}).map(item => {
return item.metadata.name
})
for (const ingress of ingressNames) {
await k8sClient.patchIngress({ namespace, ingressName: ingress, body })
this.logger.info(`ingress已重启:${ingress}`)
}
}
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}`)
}
}
getClient (aliyunProvider, regionId) {
return new ROAClient({
accessKeyId: aliyunProvider.accessKeyId,
accessKeySecret: aliyunProvider.accessKeySecret,
endpoint: `https://cs.${regionId}.aliyuncs.com`,
apiVersion: '2015-12-15'
})
}
async getKubeConfig (client, clusterId, isPrivateIpAddress = false) {
const httpMethod = 'GET'
const uriPath = `/k8s/${clusterId}/user_config`
const queries = {
PrivateIpAddress: isPrivateIpAddress
}
const body = '{}'
const headers = {
'Content-Type': 'application/json'
}
const requestOption = {}
try {
const res = await client.request(httpMethod, uriPath, queries, body, headers, requestOption)
return res.config
} catch (e) {
console.error('请求出错:', e)
throw e
}
}
}
@@ -1,4 +1,4 @@
import { AbstractAliyunPlugin } from '../../aliyun/abstract-aliyun.js'
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
import Core from '@alicloud/pop-core'
import dayjs from 'dayjs'
@@ -1,6 +1,10 @@
import pkg from 'chai'
import { createOptions } from '../../../../test/options.js'
import { Certd } from '../../src/index.js'
import { createOptions } from '../../../../../test/options.js'
import { Certd } from '@certd/certd'
import PluginAliyun from '../../src/index.js'
// 安装默认插件和授权提供者
PluginAliyun.install()
const { expect } = pkg
describe('AliyunDnsProvider', function () {
it('#申请证书-aliyun', async function () {
@@ -1,27 +1,33 @@
import pkg from 'chai'
import AliyunDnsProvider from '../../src/providers/aliyun.js'
import { createOptions } from '../../../../test/options.js'
import { AliyunDnsProvider } from '../../src/dns-providers/aliyun.js'
import { createOptions } from '../../../../../test/options.js'
const { expect } = pkg
export function getPluginOptions () {
const options = createOptions()
return { accessProviders: options.accessProviders, props: options.cert.dnsProvider }
}
describe('AliyunDnsProvider', function () {
it('#getDomainList', async function () {
const options = createOptions()
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
const options = getPluginOptions()
const aliyunDnsProvider = new AliyunDnsProvider(options)
const domainList = await aliyunDnsProvider.getDomainList()
console.log('domainList', domainList)
expect(domainList.length).gt(0)
})
it('#getRecords', async function () {
const options = createOptions()
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
const options = getPluginOptions()
const aliyunDnsProvider = new AliyunDnsProvider(options)
const recordList = await aliyunDnsProvider.getRecords('docmirror.cn', '*')
console.log('recordList', recordList)
expect(recordList.length).gt(0)
})
it('#createAndRemoveRecord', async function () {
const options = createOptions()
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
const options = getPluginOptions()
const aliyunDnsProvider = new AliyunDnsProvider(options)
const record = await aliyunDnsProvider.createRecord({ fullRecord: '___certd___.__test__.docmirror.cn', type: 'TXT', value: 'aaaa' })
console.log('recordId', record)
expect(record != null).ok
@@ -0,0 +1,42 @@
import _ from 'lodash-es'
import optionsPrivate from '../../../test/options.private.mjs'
const defaultOptions = {
version: '1.0.0',
args: {
directory: 'test',
dry: false
},
accessProviders: {
aliyun: {
providerType: 'aliyun',
accessKeyId: '',
accessKeySecret: ''
},
myLinux: {
providerType: 'SSH',
username: 'xxx',
password: 'xxx',
host: '1111.com',
port: 22,
publicKey: ''
}
},
cert: {
domains: ['*.docmirror.club', 'docmirror.club'],
email: 'xiaojunnuo@qq.com',
dnsProvider: { type: 'aliyun', accessProvider: 'aliyun' },
certProvider: 'letsencrypt',
csrInfo: {
country: 'CN',
state: 'GuangDong',
locality: 'ShengZhen',
organization: 'CertD Org.',
organizationUnit: 'IT Department',
emailAddress: 'xiaojunnuo@qq.com'
}
}
}
_.merge(defaultOptions, optionsPrivate)
export default defaultOptions
@@ -0,0 +1,67 @@
import pkg from 'chai'
import { DeployCertToAliyunAckIngress } from '../../src/plugins/deploy-to-ack-ingress/index.js'
import { Certd } from '@certd/certd'
import { createOptions } from '../../../../../test/options.js'
import { K8sClient } from '@certd/plugin-common'
const { expect } = pkg
async function getOptions () {
const options = createOptions()
options.args.test = false
options.cert.email = 'xiaojunnuo@qq.com'
options.cert.domains = ['*.docmirror.cn']
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const context = {}
const deployOpts = {
accessProviders: options.accessProviders,
cert,
props: {
accessProvider: 'aliyun-yonsz-prod',
regionId: 'cn-shanghai',
clusterId: 'c9e107ca518314f70973636965037fc00',
secretName: 'default-ingress-secret1638601684896',
namespace: 'default',
ingressClass: 'nginx'
},
context
}
return { options, deployOpts }
}
describe('DeployCertToAliyunAckIngressNginx', function () {
it('#getAliyunSecrets', async function () {
this.timeout(50000)
const { options, deployOpts } = await getOptions()
const plugin = new DeployCertToAliyunAckIngress(options)
const ackClient = plugin.getClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.regionId)
const kubeConfig = await plugin.getKubeConfig(ackClient, deployOpts.props.clusterId, false)
const k8sClient = new K8sClient(kubeConfig)
const secrets = await k8sClient.getSecret({ namespace: 'default' })
console.log('secrets:', secrets)
})
it('#getAliyunIngreses', async function () {
this.timeout(50000)
const { options, deployOpts } = await getOptions()
const plugin = new DeployCertToAliyunAckIngress(options)
const ackClient = plugin.getClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.regionId)
const kubeConfig = await plugin.getKubeConfig(ackClient, deployOpts.props.clusterId, false)
const k8sClient = new K8sClient(kubeConfig)
const list = await k8sClient.getIngressList({ namespace: 'default' })
console.log('list:', list)
})
it('#execute', async function () {
this.timeout(5000)
const { options, deployOpts } = await getOptions()
const plugin = new DeployCertToAliyunAckIngress(options)
const ret = await plugin.doExecute(deployOpts)
console.log('success', ret)
})
})
@@ -1,20 +1,20 @@
import pkg from 'chai'
import { DeployCertToAliyunCDN } from '../../src/aliyun/deploy-to-cdn/index.js'
import { DeployCertToAliyunCDN } from '../../src/plugins/deploy-to-cdn/index.js'
import { Certd } from '@certd/certd'
import createOptions from '../../../../test/options.js'
import { createOptions } from '../../../../../test/options.js'
const { expect } = pkg
describe('DeployToAliyunCDN', function () {
it('#execute', async function () {
this.timeout(5000)
const options = createOptions()
const plugin = new DeployCertToAliyunCDN()
const plugin = new DeployCertToAliyunCDN(options)
options.cert.domains = ['*.docmirror.cn', 'docmirror.cn']
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const ret = await plugin.doExecute({
accessProviders: options.accessProviders,
cert,
props: { domainName: 'certd-cdn-upload.docmirror.cn', certName: 'certd部署测试', certType: 'cas', accessProvider: 'aliyun' }
props: { domainName: 'certd-cdn-upload.docmirror.cn', certName: 'certd部署测试', from: 'cas', accessProvider: 'aliyun' }
})
console.log('context:', context, ret)
})
@@ -1,7 +1,7 @@
import pkg from 'chai'
import { UploadCertToAliyun } from '../../src/aliyun/upload-to-aliyun/index.js'
import { UploadCertToAliyun } from '../../src/plugins/upload-to-aliyun/index.js'
import { Certd } from '@certd/certd'
import { createOptions } from '../../../../test/options.js'
import { createOptions } from '../../../../../test/options.js'
const { expect } = pkg
describe('PluginUploadToAliyun', function () {
it('#execute', async function () {
@@ -9,12 +9,11 @@ describe('PluginUploadToAliyun', function () {
const options = createOptions()
options.cert.email = 'xiaojunnuo@qq.com'
options.cert.domains = ['_.docmirror.cn']
const plugin = new UploadCertToAliyun()
const plugin = new UploadCertToAliyun(options)
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const context = {}
const deployOpts = {
accessProviders: options.accessProviders,
cert,
props: { accessProvider: 'aliyun' },
context
@@ -22,7 +21,7 @@ describe('PluginUploadToAliyun', function () {
await plugin.doExecute(deployOpts)
console.log('context:', context)
// await plugin.sleep(1000)
// await plugin.sleep(1000)
// await plugin.rollback(deployOpts)
})
})
@@ -3,10 +3,6 @@
"env": {
"mocha": true
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2020
},
"overrides": [
{
"files": ["*.test.js", "*.spec.js"],
@@ -0,0 +1,7 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/
@@ -0,0 +1,23 @@
{
"name": "@certd/plugin-common",
"version": "0.2.1",
"description": "",
"main": "src/index.js",
"type": "module",
"dependencies": {
"kubernetes-client": "^9.0.0"
},
"devDependencies": {
"@certd/certd": "^0.2.1",
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
},
"author": "Greper",
"license": "MIT",
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
}
@@ -0,0 +1 @@
export { K8sClient } from './lib/k8s.client.js'
@@ -45,7 +45,7 @@ export class K8sClient {
* @param opts = {namespace:default}
* @returns secretsList
*/
async getSecret (opts) {
async getSecret (opts = {}) {
const namespace = opts.namespace || 'default'
const secrets = await this.client.api.v1.namespaces(namespace).secrets.get()
return secrets
@@ -87,6 +87,11 @@ export class K8sClient {
})
}
async getIngressList (opts) {
const namespace = opts.namespace || 'default'
return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses.get()
}
async getIngress (opts) {
const namespace = opts.namespace || 'default'
const ingressName = opts.ingressName
@@ -3,10 +3,6 @@
"env": {
"mocha": true
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2020
},
"overrides": [
{
"files": ["*.test.js", "*.spec.js"],
+7
View File
@@ -0,0 +1,7 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/
+26
View File
@@ -0,0 +1,26 @@
{
"name": "@certd/plugin-host",
"version": "0.2.1",
"description": "",
"main": "src/index.js",
"type": "module",
"dependencies": {
"@certd/api": "^0.2.1",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20",
"ssh2": "^0.8.9"
},
"devDependencies": {
"@certd/certd": "^0.2.1",
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"mocha": "^8.2.1"
},
"author": "Greper",
"license": "MIT",
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
}
@@ -0,0 +1,26 @@
export class SSHAccessProvider {
static define () {
return {
name: 'ssh',
label: '主机',
desc: '',
input: {
host: { required: true },
port: {
label: '端口',
type: Number,
default: '22',
required: true
},
username: {
default: 'root',
required: true
},
password: { desc: '登录密码' },
privateKey: {
desc: '密钥,密码或此项必填一项'
}
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
import _ from 'lodash-es'
import { SSHAccessProvider } from './access-providers/ssh.js'
import { UploadCertToHost } from './plugins/upload-to-host/index.js'
import { HostShellExecute } from './plugins/host-shell-execute/index.js'
import { pluginRegistry, accessProviderRegistry } from '@certd/api'
export const DefaultPlugins = {
UploadCertToHost,
HostShellExecute
}
export default {
install () {
_.forEach(DefaultPlugins, item => {
pluginRegistry.install(item)
})
accessProviderRegistry.install(SSHAccessProvider)
}
}
@@ -12,18 +12,21 @@ export class HostShellExecute extends AbstractHostPlugin {
name: 'hostShellExecute',
label: '执行远程主机脚本命令',
input: {
script: {
label: 'shell脚本命令'
},
accessProvider: {
label: '主机登录配置',
type: [String, Object],
desc: '登录',
component: {
name: 'access-provider-selector',
filter: 'host'
filter: 'ssh'
},
required: true
},
script: {
label: 'shell脚本命令',
component: {
name: 'a-textarea'
}
}
},
output: {
@@ -36,7 +39,7 @@ export class HostShellExecute extends AbstractHostPlugin {
const { script, accessProvider } = props
const connectConf = this.getAccessProvider(accessProvider)
const sshClient = new SshClient()
const ret = await sshClient.shell({
const ret = await sshClient.exec({
connectConf,
script
})
@@ -1,6 +1,8 @@
import ssh2 from 'ssh2'
import logger from '../utils/util.log.js'
import path from 'path'
import { util } from '@certd/api'
import _ from 'lodash-es'
const logger = util.logger
export class SshClient {
/**
*
@@ -13,7 +15,7 @@ export class SshClient {
}
* @param transports
*/
uploadFiles ({ connectConf, transports }) {
uploadFiles ({ connectConf, transports, sudo = false }) {
const conn = new ssh2.Client()
return new Promise((resolve, reject) => {
@@ -27,7 +29,8 @@ export class SshClient {
try {
for (const transport of transports) {
logger.info('上传文件:', JSON.stringify(transport))
await this.exec({ conn, cmd: 'mkdir ' + path.dirname(transport.remotePath) })
sudo = sudo ? 'sudo' : ''
await this.exec({ connectConf, script: `${sudo} mkdir -p ${path.dirname(transport.remotePath)} ` })
await this.fastPut({ sftp, ...transport })
}
resolve()
@@ -41,6 +44,43 @@ export class SshClient {
})
}
exec ({ connectConf, script }) {
if (_.isArray(script)) {
script = script.join('\n')
}
console.log('执行命令:', script)
return new Promise((resolve, reject) => {
this.connect({
connectConf,
onReady: (conn) => {
conn.exec(script, (err, stream) => {
if (err) {
reject(err)
return
}
let data = null
stream.on('close', (code, signal) => {
console.log(`[${connectConf.host}][close]:code:${code}`)
data = data ? data.toString() : null
if (code === 0) {
resolve(data)
} else {
reject(new Error(data))
}
conn.end()
}).on('data', (ret) => {
console.log(`[${connectConf.host}][info]: ` + ret)
data = ret
}).stderr.on('data', (err) => {
console.log(`[${connectConf.host}][error]: ` + err)
data = err
})
})
}
})
})
}
shell ({ connectConf, script }) {
return new Promise((resolve, reject) => {
this.connect({
@@ -87,24 +127,4 @@ export class SshClient {
})
})
}
exec ({ conn, cmd }) {
return new Promise((resolve, reject) => {
conn.exec(cmd, (err, stream) => {
if (err) {
logger.error('执行命令出错', err)
reject(err)
// return conn.end()
}
stream.on('close', (code, signal) => {
// logger.info('Stream :: close :: code: ' + code + ', signal: ' + signal)
// conn.end()
resolve()
}).on('data', (data) => {
logger.info('data', data.toString())
})
})
})
}
}
@@ -13,10 +13,10 @@ export class UploadCertToHost extends AbstractHostPlugin {
label: '上传证书到主机',
input: {
crtPath: {
label: '证书路径'
label: '证书保存路径'
},
keyPath: {
label: '私钥路径'
label: '私钥保存路径'
},
accessProvider: {
label: '主机登录配置',
@@ -24,9 +24,12 @@ export class UploadCertToHost extends AbstractHostPlugin {
desc: 'access授权',
component: {
name: 'access-provider-selector',
filter: 'host'
filter: 'ssh'
},
required: true
},
sudo: {
label: '是否sudo'
}
},
output: {
@@ -0,0 +1,52 @@
import pkg from 'chai'
import { HostShellExecute } from '../../src/plugins/host-shell-execute/index.js'
import { Certd } from '@certd/certd'
import { createOptions } from '../../../../../test/options.js'
const { expect } = pkg
describe('HostShellExecute', function () {
it('#execute', async function () {
this.timeout(10000)
const options = createOptions()
options.args = { test: false }
options.cert.email = 'xiaojunnuo@qq.com'
options.cert.domains = ['*.docmirror.cn']
const plugin = new HostShellExecute(options)
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const context = {}
const uploadOpts = {
cert,
props: { script: ['ls ', 'ls '], accessProvider: 'aliyun-ssh' },
context
}
const ret = await plugin.doExecute(uploadOpts)
expect(ret).ok
console.log('-----' + JSON.stringify(ret))
})
it('#execute-hk-restart-docker', async function () {
this.timeout(10000)
const options = createOptions()
const plugin = new HostShellExecute(options)
const uploadOpts = {
props: { script: ['cd /home/ubuntu/deloy/nginx-proxy\nsudo docker-compose build\nsudo docker-compose up -d\n'], accessProvider: 'aliyun-ssh-hk' },
context: {}
}
const ret = await plugin.doExecute(uploadOpts)
expect(ret).ok
console.log('-----' + JSON.stringify(ret))
})
it('#execute-publicKey-login', async function () {
this.timeout(10000)
const options = createOptions()
const plugin = new HostShellExecute(options)
const shellOpts = {
props: { script: ['ls'], accessProvider: 'tencent-ssh-base01' },
context: {}
}
const ret = await plugin.doExecute(shellOpts)
expect(ret).ok
console.log('-----' + JSON.stringify(ret))
})
})
@@ -0,0 +1,48 @@
import pkg from 'chai'
import { UploadCertToHost } from '../../src/plugins/upload-to-host/index.js'
import { Certd } from '@certd/certd'
import { createOptions } from '../../../../../test/options.js'
const { expect } = pkg
describe('PluginUploadToHost', function () {
it('#execute', async function () {
this.timeout(10000)
const options = createOptions()
options.args = { test: false }
options.cert.email = 'xiaojunnuo@qq.com'
options.cert.domains = ['*.docmirror.cn']
const plugin = new UploadCertToHost(options)
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const context = {}
const uploadOpts = {
cert,
props: { crtPath: '/root/certd/test/test.crt', keyPath: '/root/certd/test/test.key', accessProvider: 'aliyun-ssh' },
context
}
await plugin.doExecute(uploadOpts)
console.log('context:', context)
await plugin.doRollback(uploadOpts)
})
it('#execute-to-ubantu', async function () {
this.timeout(10000)
const options = createOptions()
options.args = { test: false }
options.cert.email = 'xiaojunnuo@qq.com'
options.cert.domains = ['*.docmirror.cn']
const plugin = new UploadCertToHost(options)
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const context = {}
const uploadOpts = {
cert,
props: { crtPath: '/home/ubuntu/deloy/nginx-proxy/ssl/test.crt', keyPath: '/home/ubuntu/deloy/nginx-proxy/ssl/test.key', accessProvider: 'aliyun-ssh-hk' },
context
}
await plugin.doExecute(uploadOpts)
console.log('context:', context)
await plugin.doRollback(uploadOpts)
})
})
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "standard",
"env": {
"mocha": true
},
"overrides": [
{
"files": ["*.test.js", "*.spec.js"],
"rules": {
"no-unused-expressions": "off"
}
}
]
}
@@ -0,0 +1,7 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/
@@ -1,19 +1,19 @@
{
"name": "@certd/plugins",
"version": "0.1.13",
"name": "@certd/plugin-tencent",
"version": "0.2.2",
"description": "",
"main": "./src/index.js",
"main": "src/index.js",
"type": "module",
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/api": "^0.1.13",
"@certd/api": "^0.2.1",
"@certd/plugin-common": "^0.2.1",
"dayjs": "^1.9.7",
"kubernetes-client": "^9.0.0",
"lodash-es": "^4.17.20",
"ssh2": "^0.8.9",
"tencentcloud-sdk-nodejs": "^4.0.44"
},
"devDependencies": {
"@certd/certd": "^0.2.1",
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",
@@ -24,5 +24,5 @@
},
"author": "Greper",
"license": "MIT",
"gitHead": "4a421d5b142d453203c68ce6d1036e168ea2455b"
"gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71"
}
@@ -1,4 +1,4 @@
export class DnspodAccessProvider {
export class DnspodAccessProvider {
static define () {
return {
name: 'dnspod',
@@ -23,8 +23,4 @@ export class DnspodAccessProvider {
}
}
}
constructor () {
}
}
@@ -1,4 +1,4 @@
export class TencentAccessProvider {
export class TencentAccessProvider {
static define () {
return {
name: 'tencent',
@@ -6,7 +6,7 @@ export class TencentAccessProvider {
input: {
secretId: {
type: String,
label:'secretId',
label: 'secretId',
component: {
placeholder: 'secretId',
rules: [{ required: true, message: '该项必填' }]
@@ -23,8 +23,4 @@ export class TencentAccessProvider {
}
}
}
constructor () {
}
}
@@ -29,7 +29,7 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
this.loginToken = accessProvider.id + ',' + accessProvider.token
}
async doRequest (options) {
async doRequest (options, successCodes = []) {
const config = {
method: 'post',
formData: {
@@ -43,8 +43,11 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
_.merge(config, options)
const ret = await request(config)
if (!ret || !ret.status || ret.status.code !== '1') {
throw new Error('请求失败:' + ret.status.message + ',api=' + config.url)
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
}
@@ -73,7 +76,7 @@ export class DnspodDnsProvider extends AbstractDnsProvider {
value: value,
mx: 1
}
})
}, ['104'])// 104错误码为记录已存在,无需再次添加
this.logger.info('添加域名解析成功:', fullRecord, value, JSON.stringify(ret.record))
return ret.record
}
@@ -0,0 +1,34 @@
import _ from 'lodash-es'
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)
}
}
@@ -1,4 +1,4 @@
import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js'
import { AbstractTencentPlugin } from '../abstract-tencent.js'
import dayjs from 'dayjs'
import tencentcloud from 'tencentcloud-sdk-nodejs'
@@ -44,7 +44,10 @@ export class DeployCertToTencentCDN extends AbstractTencentPlugin {
}
},
output: {
tencentCertId: {
type: String,
desc: '证书来源选择上传时,将返回此id'
}
}
}
}
@@ -1,4 +1,4 @@
import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js'
import { AbstractTencentPlugin } from '../abstract-tencent.js'
import tencentcloud from 'tencentcloud-sdk-nodejs'
export class DeployCertToTencentCLB extends AbstractTencentPlugin {
/**
@@ -1,6 +1,6 @@
import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js'
import { AbstractTencentPlugin } from '../abstract-tencent.js'
import tencentcloud from 'tencentcloud-sdk-nodejs'
import { K8sClient } from '../../utils/util.k8s.client.js'
import { K8sClient } from '@certd/plugin-common'
export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
/**
* 插件定义
@@ -41,6 +41,11 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
label: 'ingress名称',
desc: '支持多个(传入数组)'
},
ingressClass: {
type: String,
label: 'ingress类型',
desc: '可选 qcloud / nginx'
},
clusterIp: {
type: String,
label: '集群内网ip',
@@ -86,7 +91,13 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
// 修改内网解析ip地址
k8sClient.setLookup({ [clusterDomain]: { ip: props.clusterIp } })
}
await this.patchCertSecret({ k8sClient, props, context })
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
@@ -121,7 +132,7 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
return ret.Kubeconfig
}
async patchCertSecret ({ k8sClient, props, context }) {
async patchQcloudCertSecret ({ k8sClient, props, context }) {
const { tencentCertId } = context
if (tencentCertId == null) {
throw new Error('请先将【上传证书到腾讯云】作为前置任务')
@@ -151,6 +162,35 @@ export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
}
}
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
@@ -1,4 +1,3 @@
import dayjs from 'dayjs'
import tencentcloud from 'tencentcloud-sdk-nodejs'
import { AbstractTencentPlugin } from '../abstract-tencent.js'
@@ -1,13 +1,20 @@
import pkg from 'chai'
import { Certd } from '../../src/index.js'
import { createOptions } from '../../../../test/options.js'
import PluginTencent from '../../src/index.js'
import { createOptions } from '../../../../../test/options.js'
import { Certd } from '@certd/certd'
const { expect } = pkg
// 安装默认插件和授权提供者
PluginTencent.install()
describe('DnspodDnsProvider', function () {
it('#申请证书', async function () {
this.timeout(300000)
const options = createOptions()
options.cert.domains = ['*.certd.xyz', '*.test.certd.xyz', '*.base.certd.xyz', 'certd.xyz']
options.cert.dnsProvider = 'dnspod'
options.cert.dnsProvider = {
type: 'dnspod',
accessProvider: 'dnspod'
}
options.args = { forceCert: true }
const certd = new Certd(options)
const cert = await certd.certApply()
@@ -1,20 +1,31 @@
import pkg from 'chai'
import DnspodDnsProvider from '../../src/providers/dnspod.js'
import { Certd } from '../../src/index.js'
import { createOptions } from '../../../../test/options.js'
import { DnspodDnsProvider } from '../../src/dns-providers/dnspod.js'
import { createOptions, getDnsProviderOptions } from '../../../../../test/options.js'
const { expect } = pkg
describe('DnspodDnsProvider', function () {
it('#getDomainList', async function () {
const options = createOptions()
const dnsProvider = new DnspodDnsProvider(options.accessProviders.dnspod)
let options = createOptions()
options.cert.dnsProvider = {
type: 'dnspod',
accessProvider: 'dnspod'
}
options = getDnsProviderOptions(options)
const dnsProvider = new DnspodDnsProvider(options)
const domainList = await dnsProvider.getDomainList()
console.log('domainList', domainList)
expect(domainList.length).gt(0)
})
it('#createRecord&removeRecord', async function () {
const options = createOptions()
const dnsProvider = new DnspodDnsProvider(options.accessProviders.dnspod)
let options = createOptions()
options.cert.dnsProvider = {
type: 'dnspod',
accessProvider: 'dnspod'
}
options = getDnsProviderOptions(options)
const dnsProvider = new DnspodDnsProvider(options)
const record = await dnsProvider.createRecord({ fullRecord: '___certd___.__test__.certd.xyz', type: 'TXT', value: 'aaaa' })
console.log('recordId', record.id)
expect(record.id != null).ok

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