Compare commits

...

38 Commits

Author SHA1 Message Date
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
66 changed files with 8507 additions and 8273 deletions

4
.gitignore vendored
View File

@@ -14,3 +14,7 @@ node_modules/
/packages/*/node_modules
/packages/ui/certd-server/tmp/
/packages/ui/certd-ui/dist/
/other
/dev-sidecar-test
/packages/core/certd/yarn.lock

101
README.md
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
doc/step1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
doc/step2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
doc/step3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
doc/tasks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -2,5 +2,5 @@
"packages": [
"packages/*/*"
],
"version": "0.1.16"
"version": "0.2.0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/api",
"version": "0.1.16",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/api",
"version": "0.1.16",
"version": "0.2.0",
"description": "",
"main": "src/index.js",
"type": "module",

View File

@@ -35,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
}
}

7
packages/core/certd/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.vscode/
node_modules/
npm-debug.log
yarn-error.log
yarn.lock
package-lock.json
/.idea/

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/certd",
"version": "0.1.16",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/certd",
"version": "0.1.16",
"version": "0.2.0",
"description": "a ssl cert keeper",
"main": "src/index.js",
"scripts": {
@@ -10,8 +10,8 @@
"author": "Greper",
"license": "MIT",
"dependencies": {
"@certd/acme-client": "^0.1.6",
"@certd/api": "^0.1.16",
"@certd/acme-client": "^0.2.0",
"@certd/api": "^0.2.0",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20",
"node-forge": "^0.10.0"

View File

@@ -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')

View File

@@ -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()

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/executor",
"version": "0.1.16",
"version": "0.2.0",
"description": "",
"main": "src/index.js",
"scripts": {
@@ -10,15 +10,15 @@
},
"type": "module",
"dependencies": {
"@certd/api": "^0.1.16",
"@certd/certd": "^0.1.16",
"@certd/api": "^0.2.0",
"@certd/certd": "^0.2.0",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20"
},
"devDependencies": {
"@certd/plugin-aliyun": "^0.1.16",
"@certd/plugin-host": "^0.1.16",
"@certd/plugin-tencent": "^0.1.16",
"@certd/plugin-aliyun": "^0.2.0",
"@certd/plugin-host": "^0.2.0",
"@certd/plugin-tencent": "^0.2.0",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.0.1",

View File

@@ -27,7 +27,7 @@ export class Executor {
options = _.merge(createDefaultOptions(), options)
return await this.doRun(options)
} catch (e) {
logger.error('任务执行出错' + e.message, e)
logger.error('任务执行出错', e)
throw e
}
}
@@ -58,14 +58,14 @@ 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 }
} else {
// 强制重新运行,清空保存的状态
await certd.certStore.setCurrentFile('context.json', '{}')
}
}
// 读取上次执行进度
@@ -88,15 +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) {
throw new Error(result.remark)
}
return returnData
}
async runCertd (certd) {

View File

@@ -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__

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/plugin-aliyun",
"version": "0.1.16",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,17 +1,17 @@
{
"name": "@certd/plugin-aliyun",
"version": "0.1.16",
"version": "0.2.0",
"description": "",
"main": "src/index.js",
"type": "module",
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/api": "^0.1.16",
"@certd/api": "^0.2.0",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20"
},
"devDependencies": {
"@certd/certd": "^0.1.16",
"@certd/certd": "^0.2.0",
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/plugin-host",
"version": "0.1.16",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,17 +1,17 @@
{
"name": "@certd/plugin-host",
"version": "0.1.16",
"version": "0.2.0",
"description": "",
"main": "src/index.js",
"type": "module",
"dependencies": {
"@certd/api": "^0.1.16",
"@certd/api": "^0.2.0",
"dayjs": "^1.9.7",
"lodash-es": "^4.17.20",
"ssh2": "^0.8.9"
},
"devDependencies": {
"@certd/certd": "^0.1.16",
"@certd/certd": "^0.2.0",
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",

View File

@@ -17,7 +17,7 @@ export class SSHAccessProvider {
required: true
},
password: { desc: '登录密码' },
publicKey: {
privateKey: {
desc: '密钥,密码或此项必填一项'
}
}

View File

@@ -15,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) => {
@@ -29,7 +29,8 @@ export class SshClient {
try {
for (const transport of transports) {
logger.info('上传文件:', JSON.stringify(transport))
await this.exec({ connectConf, script: 'mkdir -p ' + 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()
@@ -47,6 +48,7 @@ export class SshClient {
if (_.isArray(script)) {
script = script.join('\n')
}
console.log('执行命令:', script)
return new Promise((resolve, reject) => {
this.connect({
connectConf,
@@ -58,11 +60,12 @@ export class SshClient {
}
let data = null
stream.on('close', (code, signal) => {
console.log(`[${connectConf.host}][close]:code:${code}, signal:${signal} `)
console.log(`[${connectConf.host}][close]:code:${code}`)
data = data ? data.toString() : null
if (code === 0) {
data = data ? data.toString() : null
resolve(data)
} else {
reject(new Error(data))
}
conn.end()
}).on('data', (ret) => {
@@ -70,8 +73,7 @@ export class SshClient {
data = ret
}).stderr.on('data', (err) => {
console.log(`[${connectConf.host}][error]: ` + err)
reject(new Error(err.toString()))
stream.close()
data = err
})
})
}

View File

@@ -27,6 +27,9 @@ export class UploadCertToHost extends AbstractHostPlugin {
filter: 'ssh'
},
required: true
},
sudo: {
label: '是否sudo'
}
},
output: {
@@ -45,7 +48,6 @@ export class UploadCertToHost extends AbstractHostPlugin {
async execute ({ cert, props, context }) {
const { crtPath, keyPath, accessProvider } = props
const connectConf = this.getAccessProvider(accessProvider)
console.log('connectConf', connectConf)
const sshClient = new SshClient()
await sshClient.uploadFiles({
connectConf,

View File

@@ -22,7 +22,31 @@ describe('HostShellExecute', function () {
const ret = await plugin.doExecute(uploadOpts)
expect(ret).ok
console.log('-----' + JSON.stringify(ret))
})
await plugin.doRollback(uploadOpts)
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))
})
})

View File

@@ -24,4 +24,25 @@ describe('PluginUploadToHost', function () {
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)
})
})

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/plugin-tencent",
"version": "0.1.16",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,18 +1,18 @@
{
"name": "@certd/plugin-tencent",
"version": "0.1.16",
"version": "0.2.0",
"description": "",
"main": "src/index.js",
"type": "module",
"dependencies": {
"@certd/api": "^0.1.16",
"@certd/api": "^0.2.0",
"dayjs": "^1.9.7",
"kubernetes-client": "^9.0.0",
"lodash-es": "^4.17.20",
"tencentcloud-sdk-nodejs": "^4.0.44"
},
"devDependencies": {
"@certd/certd": "^0.1.16",
"@certd/certd": "^0.2.0",
"chai": "^4.2.0",
"eslint": "^7.15.0",
"eslint-config-standard": "^16.0.2",

View File

@@ -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
}

View File

@@ -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

View File

@@ -0,0 +1,59 @@
import pkg from 'chai'
import { DeployCertToTencentTKEIngress } from '../../src/plugins/deploy-to-tke-ingress/index.js'
import { Certd } from '@certd/certd'
import { createOptions } from '../../../../../test/options.js'
import { K8sClient } from '../../src/utils/util.k8s.client.js'
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: 'tencent-yonsz',
region: 'ap-guangzhou',
clusterId: 'cls-6lbj1vee'
},
context
}
return { options, deployOpts }
}
describe('DeployCertToTencentTKEIngressNginx', function () {
it('#getTKESecrets', async function () {
this.timeout(50000)
const { options, deployOpts } = await getOptions()
const plugin = new DeployCertToTencentTKEIngress(options)
const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region)
const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props.clusterId)
const k8sClient = new K8sClient(kubeConfig)
k8sClient.setLookup({
'cls-6lbj1vee.ccs.tencent-cloud.com': { ip: '13.123.123.123' }
})
const secrets = await k8sClient.getSecret({ namespace: 'stress' })
console.log('secrets:', secrets)
})
it('#execute', async function () {
this.timeout(5000)
const { options, deployOpts } = await getOptions()
deployOpts.props.ingressName = 'stress-ingress-nginx'
deployOpts.props.ingressClass = 'nginx'
deployOpts.props.secretName = 'stress-all'
deployOpts.props.namespace = 'stress'
const plugin = new DeployCertToTencentTKEIngress(options)
const ret = await plugin.doExecute(deployOpts)
console.log('sucess', ret)
})
})

View File

@@ -0,0 +1,6 @@
FROM registry.cn-shenzhen.aliyuncs.com/greper/node:15.8.0-alpine
ENV TZ=Asia/Shanghai
EXPOSE 3000
ADD ./ /app/
RUN cd /app/ && ls
ENTRYPOINT node /app/bin/www.js

View File

@@ -7,6 +7,8 @@ import Static from 'koa-static'
import fs from 'fs'
import _ from 'lodash-es'
import './install.js'
import pathUtil from './utils/util.path.js'
import compress from 'koa-compress'
const app = new Koa()
// error handler
@@ -18,8 +20,14 @@ app.use(bodyparser({
}))
app.use(json())
app.use(logger())
// gzip
// app.use(compress({ threshold: 5120 }))
app.use(Static(new URL('public', import.meta.url).pathname))
const staticPlugin = Static(pathUtil.join('public'), {
maxage: 30 * 24 * 60 * 3600,
gzip: true
})
app.use(staticPlugin)
// logger
app.use(async (ctx, next) => {
@@ -29,8 +37,6 @@ app.use(async (ctx, next) => {
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
console.log('url', import.meta.url)
// routes
const files = fs.readdirSync(new URL('controllers/', import.meta.url))
// 过滤出.js文件:

View File

@@ -3,7 +3,7 @@ import { accessProviderRegistry } from '@certd/api'
import _ from 'lodash-es'
import { Ret } from '../models/Ret.js'
const router = Router()
router.prefix('/access-providers')
router.prefix('/api/access-providers')
router.get('/list', function (ctx, next) {
const list = []

View File

@@ -3,7 +3,7 @@ import { dnsProviderRegistry } from '@certd/api'
import _ from 'lodash-es'
import { Ret } from '../models/Ret.js'
const router = Router()
router.prefix('/dns-providers')
router.prefix('/api/dns-providers')
router.get('/list', function (ctx, next) {
const list = []

View File

@@ -3,7 +3,7 @@ import fs from 'fs'
import exportsService from '../service/exports-service.js'
const router = Router()
router.prefix('/exports')
router.prefix('/api/exports')
router.post('/toZip', async function (ctx, next) {
// const request = ctx.request

View File

@@ -1,7 +1,7 @@
import Router from 'koa-router'
const router = Router()
router.get('/', async (ctx, next) => {
router.get('/api/', async (ctx, next) => {
await ctx.render('index', {
title: 'Hello CertD!'
})

View File

@@ -3,7 +3,7 @@ import { pluginRegistry } from '@certd/api'
import _ from 'lodash-es'
import { Ret } from '../models/Ret.js'
const router = Router()
router.prefix('/plugins')
router.prefix('/api/plugins')
router.get('/list', function (ctx, next) {
const list = []

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/server",
"version": "0.1.16",
"version": "0.2.0",
"private": false,
"type": "module",
"scripts": {
@@ -10,16 +10,17 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@certd/api": "^0.1.16",
"@certd/executor": "^0.1.16",
"@certd/plugin-aliyun": "^0.1.16",
"@certd/plugin-host": "^0.1.16",
"@certd/plugin-tencent": "^0.1.16",
"@certd/api": "^0.2.0",
"@certd/executor": "^0.2.0",
"@certd/plugin-aliyun": "^0.2.0",
"@certd/plugin-host": "^0.2.0",
"@certd/plugin-tencent": "^0.2.0",
"compressing": "^1.5.1",
"debug": "^4.1.1",
"fs-extra": "^9.1.0",
"koa": "^2.7.0",
"koa-bodyparser": "^4.2.1",
"koa-compress": "^5.0.1",
"koa-convert": "^1.2.0",
"koa-json": "^2.0.2",
"koa-logger": "^3.2.0",
@@ -31,7 +32,7 @@
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.18.0",
"eslint": "^7.19.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,17 @@
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="500" height="500" viewBox="0 0 500.000000 500.000000"
>
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
fill="#2c3e50"
transform="translate(124, 60) scale(4,4)"
></path>
<path d="M13.00-21.91L21.24-21.91L21.24-17.23L14.04-17.23Q10.39-17.23 9-15.62L9-15.62Q7.65-14.08 7.65-10.93L7.65-10.93Q7.65-7.42 9.86-5.80L9.86-5.80Q10.75-5.17 11.81-4.93Q12.87-4.68 14.76-4.68L14.76-4.68L21.24-4.68L21.24 0L13.00 0Q9.67 0 7.74-0.67Q5.80-1.35 4.32-3.01L4.32-3.01Q1.48-6.17 1.48-11.03L1.48-11.03Q1.48-16.88 4.86-19.75L4.86-19.75Q6.21-20.93 8.10-21.42Q9.99-21.91 13.00-21.91L13.00-21.91ZM31.05-13.32L43.74-13.32L43.74-8.64L31.05-8.64Q31.23-6.48 32.44-5.58Q33.66-4.68 36.41-4.68L36.41-4.68L43.74-4.68L43.74 0L35.73 0Q33.12 0 31.48-0.47Q29.83-0.94 28.39-2.02L28.39-2.02Q24.39-5.13 24.39-11.25L24.39-11.25Q24.39-15.21 26.50-18.18L26.50-18.18Q27.94-20.20 29.97-21.06Q31.99-21.91 35.28-21.91L35.28-21.91L43.74-21.91L43.74-17.23L35.73-17.23Q33.25-17.23 32.27-16.40Q31.27-15.57 31.05-13.32L31.05-13.32ZM48.64 0L48.64-21.91L57.55-21.91Q60.30-21.91 61.81-21.53Q63.31-21.15 64.31-20.25L64.31-20.25Q65.30-19.35 65.70-18Q66.10-16.65 66.10-14.13L66.10-14.13L66.10-12.01L60.30-12.01L60.30-13.18Q60.30-15.52 59.49-16.38Q58.68-17.23 56.38-17.23L56.38-17.23L54.67-17.23L54.67 0L48.64 0ZM67.50-21.91L71.33-21.91L71.33-30.02L77.36-30.02L77.36-21.91L82.94-21.91L82.94-17.23L77.36-17.23L77.36-9.27Q77.36-6.48 77.85-5.76L77.85-5.76Q78.61-4.68 80.55-4.68L80.55-4.68L82.94-4.68L82.94 0L78.57 0Q74.66 0 72.99-1.89Q71.33-3.78 71.33-8.23L71.33-8.23L71.33-17.23L67.50-17.23L67.50-21.91ZM96.08-21.91L101.75-21.91L101.75-30.02L107.73-30.02L107.73 0L97.38 0Q94.23 0 92.61-0.49L92.61-0.49Q88.78-1.71 86.90-5.26L86.90-5.26Q85.59-7.65 85.59-11.12L85.59-11.12Q85.59-16.74 89.37-19.84L89.37-19.84Q91.84-21.91 96.08-21.91L96.08-21.91ZM97.38-4.68L101.75-4.68L101.75-17.23L97.38-17.23Q94.50-17.23 93.02-15.30L93.02-15.30Q91.71-13.68 91.71-11.12L91.71-11.12Q91.71-7.38 93.87-5.76L93.87-5.76Q95.36-4.68 97.38-4.68L97.38-4.68Z"
fill="#2c3e50"
transform="translate(28, 430) scale(4,4)"
></path>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,8 +0,0 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}

View File

@@ -27,12 +27,16 @@ export default {
fs.writeJsonSync(optionsFilePath, options)
// 依赖版本
const exePkgJson = fs.readFileSync('node_modules/@certd/executor/package.json')
const exePkgJson = fs.readFileSync(pathUtil.join('node_modules/@certd/executor/package.json'))
const executorPkg = JSON.parse(exePkgJson)
const currentVersion = executorPkg.version
const templatePkg = require('../templates/certd-run/package.json')
const templatePkgJson = fs.readFileSync(pathUtil.join('templates/certd-run/package.json'))
const templatePkg = JSON.parse(templatePkgJson)
templatePkg.dependencies['@certd/executor'] = '^' + currentVersion
templatePkg.dependencies['@certd/plugin-aliyun'] = '^' + currentVersion
templatePkg.dependencies['@certd/plugin-host'] = '^' + currentVersion
templatePkg.dependencies['@certd/plugin-tencent'] = '^' + currentVersion
const pkgFilePath = path.join(targetProjectDir, 'package.json')
fs.writeJsonSync(pkgFilePath, templatePkg)

View File

@@ -2,11 +2,17 @@ import { Executor } from '@certd/executor'
import PluginAliyun from '@certd/plugin-aliyun'
import PluginTencent from '@certd/plugin-tencent'
import PluginHost from '@certd/plugin-host'
import options from './options.json'
// 安装默认插件和授权提供者
PluginAliyun.install()
PluginTencent.install()
PluginHost.install()
// import options
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const options =require('./options.json')
//开始执行
const executor = new Executor()
executor.run(options)
await executor.run(options)

View File

@@ -2,7 +2,6 @@ import os from 'os'
export default {
join (...dirs) {
const url = new URL('../' + dirs.join('/'), import.meta.url)
console.log('url', url)
let path = url.pathname
if (os.type() === 'Windows_NT') {
path = path.substring(1)

View File

@@ -0,0 +1 @@
VUE_APP_API=/api

View File

@@ -2,4 +2,8 @@ module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
// plugins: [['import', {
// libraryName: 'ant-design-vue',
// style: true // or 'css'
// }]]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/certd-ui",
"version": "0.1.16",
"version": "0.2.0",
"private": false,
"scripts": {
"dev": "vue-cli-service serve",
@@ -9,9 +9,9 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@certd/dns-providers": "^0.1.13",
"@certd/plugins": "^0.1.13",
"@ant-design/icons-vue": "^6.0.1",
"ant-design-vue": "^2.0.0",
"axios": "^0.21.1",
"core-js": "^3.8.1",
"lodash-es": "^4.17.20",
"vue": "^3.0.4",
@@ -31,7 +31,9 @@
"@vue/eslint-config-standard": "^6.0.0",
"@vue/test-utils": "^2.0.0-0",
"babel-eslint": "^10.1.0",
"babel-plugin-import": "^1.13.3",
"chai": "^4.2.0",
"compression-webpack-plugin": "^5.0.1",
"eslint": "^7.15.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,86 @@
import _ from 'lodash'
import {
AutoComplete,
Alert,
Avatar,
Badge,
Button,
Calendar,
Card,
Cascader,
Checkbox,
Col,
DatePicker,
Divider,
Dropdown,
Form,
Input,
InputNumber,
Layout,
List,
LocaleProvider,
Modal,
Radio,
Row,
Select,
Switch,
Tabs,
Tag,
TimePicker,
Tooltip,
Drawer,
// ColorPicker,
ConfigProvider,
Descriptions,
Space
} from 'ant-design-vue'
const list = {
AutoComplete,
Alert,
Avatar,
Badge,
Button,
Calendar,
Card,
Cascader,
Checkbox,
Col,
DatePicker,
Divider,
Dropdown,
Form,
Input,
InputNumber,
Layout,
List,
LocaleProvider,
TimePicker,
Modal,
Radio,
Row,
Select,
Switch,
Tabs,
Tag,
Tooltip,
Drawer,
// ColorPicker,
ConfigProvider,
Descriptions,
Space
}
export default function (app) {
_.forEach(list, item => {
app.use(item)
//
// app.config.globalProperties.$message = message
// app.config.globalProperties.$notification = notification
app.config.globalProperties.$info = Modal.info
app.config.globalProperties.$success = Modal.success
app.config.globalProperties.$error = Modal.error
app.config.globalProperties.$warning = Modal.warning
app.config.globalProperties.$confirm = Modal.confirm
app.config.globalProperties.$destroyAll = Modal.destroyAll
})
}

View File

@@ -55,10 +55,8 @@ export function errorLog (error) {
// 打印到控制台
console.log(error)
// 显示提示
notification({
message: error.message,
type: 'error',
duration: 5 * 1000
notification.error({
message: error.message
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,17 @@
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="500" height="500" viewBox="0 0 500.000000 500.000000"
>
<path d="M28.34 56.68h28.34V36.12H28.34a7.79 7.79 0 1 1 0-15.58h19.84v9.05h8.5V12H28.34a16.29 16.29 0 0 0 0 32.58h19.84v3.56H28.34a19.84 19.84 0 0 1 0-39.68h28.34V0H28.34a28.34 28.34 0 0 0 0 56.68z"
fill="#2c3e50"
transform="translate(124, 60) scale(4,4)"
></path>
<path d="M13.00-21.91L21.24-21.91L21.24-17.23L14.04-17.23Q10.39-17.23 9-15.62L9-15.62Q7.65-14.08 7.65-10.93L7.65-10.93Q7.65-7.42 9.86-5.80L9.86-5.80Q10.75-5.17 11.81-4.93Q12.87-4.68 14.76-4.68L14.76-4.68L21.24-4.68L21.24 0L13.00 0Q9.67 0 7.74-0.67Q5.80-1.35 4.32-3.01L4.32-3.01Q1.48-6.17 1.48-11.03L1.48-11.03Q1.48-16.88 4.86-19.75L4.86-19.75Q6.21-20.93 8.10-21.42Q9.99-21.91 13.00-21.91L13.00-21.91ZM31.05-13.32L43.74-13.32L43.74-8.64L31.05-8.64Q31.23-6.48 32.44-5.58Q33.66-4.68 36.41-4.68L36.41-4.68L43.74-4.68L43.74 0L35.73 0Q33.12 0 31.48-0.47Q29.83-0.94 28.39-2.02L28.39-2.02Q24.39-5.13 24.39-11.25L24.39-11.25Q24.39-15.21 26.50-18.18L26.50-18.18Q27.94-20.20 29.97-21.06Q31.99-21.91 35.28-21.91L35.28-21.91L43.74-21.91L43.74-17.23L35.73-17.23Q33.25-17.23 32.27-16.40Q31.27-15.57 31.05-13.32L31.05-13.32ZM48.64 0L48.64-21.91L57.55-21.91Q60.30-21.91 61.81-21.53Q63.31-21.15 64.31-20.25L64.31-20.25Q65.30-19.35 65.70-18Q66.10-16.65 66.10-14.13L66.10-14.13L66.10-12.01L60.30-12.01L60.30-13.18Q60.30-15.52 59.49-16.38Q58.68-17.23 56.38-17.23L56.38-17.23L54.67-17.23L54.67 0L48.64 0ZM67.50-21.91L71.33-21.91L71.33-30.02L77.36-30.02L77.36-21.91L82.94-21.91L82.94-17.23L77.36-17.23L77.36-9.27Q77.36-6.48 77.85-5.76L77.85-5.76Q78.61-4.68 80.55-4.68L80.55-4.68L82.94-4.68L82.94 0L78.57 0Q74.66 0 72.99-1.89Q71.33-3.78 71.33-8.23L71.33-8.23L71.33-17.23L67.50-17.23L67.50-21.91ZM96.08-21.91L101.75-21.91L101.75-30.02L107.73-30.02L107.73 0L97.38 0Q94.23 0 92.61-0.49L92.61-0.49Q88.78-1.71 86.90-5.26L86.90-5.26Q85.59-7.65 85.59-11.12L85.59-11.12Q85.59-16.74 89.37-19.84L89.37-19.84Q91.84-21.91 96.08-21.91L96.08-21.91ZM97.38-4.68L101.75-4.68L101.75-17.23L97.38-17.23Q94.50-17.23 93.02-15.30L93.02-15.30Q91.71-13.68 91.71-11.12L91.71-11.12Q91.71-7.38 93.87-5.76L93.87-5.76Q95.36-4.68 97.38-4.68L97.38-4.68Z"
fill="#2c3e50"
transform="translate(28, 430) scale(4,4)"
></path>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -20,7 +20,7 @@ const icons = {
DeleteOutlined
}
export default function (app) {
_.forEach(icons, item => {
app.component(item.name, item)
_.forEach(icons, (item, key) => {
app.component(key, item)
})
}

View File

@@ -10,7 +10,7 @@ import components from './components'
const app = createApp(App)
app.config.productionTip = false
app.use(i18n)
app.use(Antd)
icons(app)
app.use(Antd)
app.use(components)
app.use(router).mount('#app')

View File

@@ -160,10 +160,10 @@
<d-container>
<template #header>
<div><a-button @click="exportsToZip">导出可执行项目</a-button></div>
<br/>
<div> <a-button @click="exportsToJson">复制options.json</a-button></div>
<div><a-button @click="exportsToZip">导出可执行项目</a-button></div>
<br/>
<!-- <div> <a-button @click="exportsToJson">复制options.json</a-button></div>-->
<!-- <br/>-->
</template>
<pre class="json">{{options}}</pre>

View File

@@ -1,9 +1,47 @@
const CompressionWebpackPlugin = require('compression-webpack-plugin')
// 设置不参与构建的库
const externals = {}
// cdnDependencies.forEach(pkg => { externals[pkg.name] = pkg.library })
module.exports = {
lintOnSave: true,
pages: {
index: {
entry: 'src/main.js',
// template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
title: 'Cert-D'
}
},
devServer: {
proxy: {
'/': {
target: 'http://localhost:3000/'
}
}
},
css: {
loaderOptions: {
less: {
javascriptEnabled: true
}
}
},
configureWebpack: config => {
const configNew = {}
if (process.env.NODE_ENV === 'production') {
configNew.externals = externals
configNew.plugins = [
// gzip
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
test: new RegExp('\\.(' + ['js', 'css'].join('|') + ')$'),
threshold: 5120,
minRatio: 0.8,
deleteOriginalAssets: false
})
]
}
return configNew
}
}