Compare commits

...

25 Commits

Author SHA1 Message Date
xiaojunnuo
0ff700849f v1.36.4 2025-07-10 23:41:05 +08:00
xiaojunnuo
5c695dea20 build: prepare to build 2025-07-10 23:36:24 +08:00
xiaojunnuo
c7ee4ca4db build: prepare to build 2025-07-10 23:32:50 +08:00
xiaojunnuo
c3da026b33 perf: 支持部署证书到网宿CDN 2025-07-10 23:30:33 +08:00
xiaojunnuo
98da4e1791 perf: 支持部署到阿里云vod 2025-07-10 21:40:35 +08:00
xiaojunnuo
8626b6d9f2 fix: 修复查看证书对话框翻译错误的bug 2025-07-10 18:27:56 +08:00
xiaojunnuo
80c5331a5d perf: 站点证书即将过期通知标题颜色优化为红色 2025-07-10 17:02:48 +08:00
xiaojunnuo
b37cffd704 fix: 执行windows nginx命令时,改为return code判断是否执行成功 2025-07-09 15:43:25 +08:00
xiaojunnuo
2af91dbf2a perf: 优化证书进度条颜色 2025-07-09 15:24:23 +08:00
xiaojunnuo
f2551318fc chore: 增加多dns提供商选项支持 2025-07-09 15:10:21 +08:00
xiaojunnuo
22eb84f944 chore: 2025-07-09 15:03:04 +08:00
xiaojunnuo
1ece0915f1 perf: 重置管理员密码同时可以清除管理员的2FA设置 2025-07-09 14:49:11 +08:00
xiaojunnuo
87853a2015 perf: output-selector from参数支持更丰富的过滤规则
规则:  pluginName:valueType:keyName
2025-07-09 14:34:24 +08:00
xiaojunnuo
46a1b74799 fix: 修复translation后分组编辑打不开的bug 2025-07-09 11:14:09 +08:00
xiaojunnuo
0f6e7e5eab build: publish 2025-07-07 22:22:09 +08:00
xiaojunnuo
5dfa9615d2 build: trigger build image 2025-07-07 22:21:52 +08:00
xiaojunnuo
1bde777bee v1.36.3 2025-07-07 22:20:26 +08:00
xiaojunnuo
fa4f5df3e7 build: prepare to build 2025-07-07 22:18:24 +08:00
xiaojunnuo
8a3c3810e0 build: prepare to build 2025-07-07 22:14:35 +08:00
xiaojunnuo
144532530a perf: 优化部署到腾讯TKE插件,支持Opaque类型选择,优化填写说明 2025-07-07 21:30:45 +08:00
xiaojunnuo
0f1129e19b chore: 2025-07-07 18:20:25 +08:00
xiaojunnuo
1f74580f15 chore: cname的key转为小写 2025-07-07 16:27:25 +08:00
xiaojunnuo
f93ba9970c fix: 修复开放接口添加按钮文本显示问题 2025-07-07 14:21:00 +08:00
xiaojunnuo
f87a3d0892 build: publish 2025-07-07 00:47:44 +08:00
xiaojunnuo
c661ad67d0 build: trigger build image 2025-07-07 00:47:26 +08:00
77 changed files with 1362 additions and 272 deletions

View File

@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
### Bug Fixes
* 修复查看证书对话框翻译错误的bug ([8626b6d](https://github.com/certd/certd/commit/8626b6d9f235c511766f2ae98e0a37f6cebb621c))
* 修复translation后分组编辑打不开的bug ([46a1b74](https://github.com/certd/certd/commit/46a1b7479923d2feb2dece202a5932b99981b2cd))
* 执行windows nginx命令时改为return code判断是否执行成功 ([b37cffd](https://github.com/certd/certd/commit/b37cffd704cd08b8bdd68a6e284706eabe59e78d))
### Performance Improvements
* 优化证书进度条颜色 ([2af91db](https://github.com/certd/certd/commit/2af91dbf2ae36a4ed17c6788bc2a2a79a3bb29f8))
* 站点证书即将过期通知标题颜色优化为红色 ([80c5331](https://github.com/certd/certd/commit/80c5331a5d4c320323d9b9b800e4ea3b72577b33))
* 支持部署到阿里云vod ([98da4e1](https://github.com/certd/certd/commit/98da4e1791ed8bb21daf2a9914fda875d14633c9))
* 支持部署证书到网宿CDN ([c3da026](https://github.com/certd/certd/commit/c3da026b33106f5195959825a68cadbe49efef00))
* 重置管理员密码同时可以清除管理员的2FA设置 ([1ece091](https://github.com/certd/certd/commit/1ece0915f172d5f8b8adb866434e7efcc5c8c46d))
* output-selector from参数支持更丰富的过滤规则 ([87853a2](https://github.com/certd/certd/commit/87853a201535f3bfe8505c40f8f5229d82ffcc73))
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
### Bug Fixes
* 修复开放接口添加按钮文本显示问题 ([f93ba99](https://github.com/certd/certd/commit/f93ba9970c12680f38eba2a7abd4b72cf3f5b6a6))
### Performance Improvements
* 优化部署到腾讯TKE插件支持Opaque类型选择优化填写说明 ([1445325](https://github.com/certd/certd/commit/144532530a865b634e68539e4888e26f52f73492))
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
### Bug Fixes

View File

@@ -1 +1 @@
23:01
22:21

View File

@@ -3,6 +3,27 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
### Bug Fixes
* 修复开放接口添加按钮文本显示问题 ([f93ba99](https://github.com/certd/certd/commit/f93ba9970c12680f38eba2a7abd4b72cf3f5b6a6))
### Performance Improvements
* 优化部署到腾讯TKE插件支持Opaque类型选择优化填写说明 ([1445325](https://github.com/certd/certd/commit/144532530a865b634e68539e4888e26f52f73492))
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
### Bug Fixes
* 修复notification编辑按钮无法打开对话框的bug ([0cea26c](https://github.com/certd/certd/commit/0cea26c6287f52adf273b4a525c37bea8555c68c))
* 优化更新飞牛os证书有效期修复某些情况下部署证书后飞牛无法访问https的bug ([610c919](https://github.com/certd/certd/commit/610c919c72037becc0ed326f5d5b18c963dfcb3a))
### Performance Improvements
* 证书检查支持自定义dns服务器 ([c53bb7c](https://github.com/certd/certd/commit/c53bb7cf677faa32729709ae0c10359db5194d7a))
## [1.36.1](https://github.com/certd/certd/compare/v1.36.0...v1.36.1) (2025-07-02)
### Bug Fixes

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.36.2"
"version": "1.36.4"
}

View File

@@ -28,6 +28,7 @@
"deploy1": "node --experimental-json-modules deploy.js ",
"check": "node --experimental-json-modules publish-check.js",
"init": "lerna run build",
"init:dev": "lerna run build",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs",

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/publishlab/node-acme-client/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/acme-client
## [1.36.3](https://github.com/publishlab/node-acme-client/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/acme-client
## [1.36.2](https://github.com/publishlab/node-acme-client/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.36.2",
"version": "1.36.4",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.36.2",
"@certd/basic": "^1.36.4",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
@@ -69,5 +69,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/basic
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/basic
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
### Performance Improvements

View File

@@ -1 +1 @@
00:43
23:36

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.36.2",
"version": "1.36.4",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/pipeline
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/pipeline
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.36.2",
"version": "1.36.4",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,8 +17,8 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.36.2",
"@certd/plus-core": "^1.36.1",
"@certd/basic": "^1.36.4",
"@certd/plus-core": "^1.36.4",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -44,5 +44,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/lib-huawei
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/lib-huawei
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.36.2",
"version": "1.36.4",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -24,5 +24,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/lib-iframe
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/lib-iframe
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.36.2",
"version": "1.36.4",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/jdcloud
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/jdcloud
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/jdcloud

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.36.2",
"version": "1.36.4",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -61,5 +61,5 @@
"fetch"
]
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/lib-k8s
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/lib-k8s
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.36.2",
"version": "1.36.4",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,7 +17,7 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.36.2",
"@certd/basic": "^1.36.4",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -32,5 +32,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/lib-server
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/lib-server
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/lib-server

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.36.2",
"version": "1.36.4",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -27,10 +27,10 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.36.2",
"@certd/basic": "^1.36.2",
"@certd/pipeline": "^1.36.2",
"@certd/plus-core": "^1.36.1",
"@certd/acme-client": "^1.36.4",
"@certd/basic": "^1.36.4",
"@certd/pipeline": "^1.36.4",
"@certd/plus-core": "^1.36.4",
"@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.20.3",
"@midwayjs/i18n": "~3.20.3",
@@ -61,5 +61,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.36.2",
"version": "1.36.4",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
### Performance Improvements
* 优化证书进度条颜色 ([2af91db](https://github.com/certd/certd/commit/2af91dbf2ae36a4ed17c6788bc2a2a79a3bb29f8))
* 支持部署到阿里云vod ([98da4e1](https://github.com/certd/certd/commit/98da4e1791ed8bb21daf2a9914fda875d14633c9))
* output-selector from参数支持更丰富的过滤规则 ([87853a2](https://github.com/certd/certd/commit/87853a201535f3bfe8505c40f8f5229d82ffcc73))
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/plugin-cert
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/plugin-cert

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.36.2",
"version": "1.36.4",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -16,10 +16,10 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/acme-client": "^1.36.2",
"@certd/basic": "^1.36.2",
"@certd/pipeline": "^1.36.2",
"@certd/plugin-lib": "^1.36.2",
"@certd/acme-client": "^1.36.4",
"@certd/basic": "^1.36.4",
"@certd/pipeline": "^1.36.4",
"@certd/plugin-lib": "^1.36.4",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",
@@ -43,5 +43,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -67,6 +67,7 @@ export abstract class CertApplyBaseConvertPlugin extends AbstractTaskPlugin {
@TaskOutput({
title: "域名证书",
type: "cert",
})
cert?: CertInfo;

View File

@@ -204,4 +204,11 @@ export class CertReader {
domain = domain.replaceAll(".", "_").replaceAll("*", "_");
return `${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`;
}
static appendTimeSuffix(name?: string) {
if (name == null) {
name = "certd";
}
return name + "_" + dayjs().format("YYYYMMDDHHmmssSSS");
}
}

View File

@@ -87,6 +87,7 @@ export class CertApplyUploadPlugin extends CertApplyBaseConvertPlugin {
@TaskOutput({
title: "证书MD5",
type: "certMd5",
})
certMd5?: string;

View File

@@ -65,12 +65,15 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
{ value: "dns", label: "DNS直接验证" },
{ value: "cname", label: "CNAME代理验证" },
{ value: "http", label: "HTTP文件验证" },
{ value: "dnses", label: "多DNS提供商" },
],
},
required: true,
helper: `1. <b>DNS直接验证</b>域名dns解析是在阿里云/腾讯云/华为云/CF/NameSilo/西数/火山/dns.la/京东云/51dns的选它
2. <b>CNAME代理验证</b>支持任何注册商的域名第一次需要手动添加CNAME记录建议将DNS服务器修改为阿里云/腾讯云的然后使用DNS直接验证
3. <b>HTTP文件验证</b>:不支持泛域名,需要配置网站文件上传`,
3. <b>HTTP文件验证</b>:不支持泛域名,需要配置网站文件上传
4. <b>多DNS提供商</b>每个域名可以选择独立的DNS提供商
`,
})
challengeType!: string;
@@ -159,13 +162,15 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
})
},
show: ctx.compute(({form})=>{
return form.challengeType === 'cname' || form.challengeType === 'http'
return form.challengeType === 'cname' || form.challengeType === 'http' || form.challengeType === 'dnses'
}),
helper: ctx.compute(({form})=>{
if(form.challengeType === 'cname' ){
return '请按照上面的提示给要申请证书的域名添加CNAME记录添加后点击验证验证成功后不要删除记录申请和续期证书会一直用它'
}else if (form.challengeType === 'http'){
return '请按照上面的提示,给每个域名设置文件上传配置,证书申请过程中会上传校验文件到网站根目录下'
}else if (form.challengeType === 'http'){
return '给每个域名单独配置dns提供商'
}
})
}
@@ -321,7 +326,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "a-input-number",
vModel: "value",
},
helper: "等待解析生效时长(秒)",
helper: "等待解析生效时长(秒)如果使用CNAME方式校验本地验证失败可以尝试延长此时间比如5-10分钟",
})
waitDnsDiffuseTime = 30;
@@ -402,7 +407,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
let dnsProvider: IDnsProvider = null;
let domainsVerifyPlan: DomainsVerifyPlan = null;
if (this.challengeType === "cname" || this.challengeType === "http") {
if (this.challengeType === "cname" || this.challengeType === "http" || this.challengeType === "dnses") {
domainsVerifyPlan = await this.createDomainsVerifyPlan();
} else {
const dnsProviderType = this.dnsProviderType;

View File

@@ -3,4 +3,4 @@ export { EVENT_CERT_APPLY_SUCCESS } from "./cert-plugin/base-convert.js";
export * from "./cert-plugin/index.js";
export * from "./cert-plugin/lego/index.js";
export * from "./cert-plugin/custom/index.js";
export const CertApplyPluginNames = ["CertApply", "CertApplyLego", "CertApplyUpload"];
export const CertApplyPluginNames = [":cert:"];

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
### Bug Fixes
* 执行windows nginx命令时改为return code判断是否执行成功 ([b37cffd](https://github.com/certd/certd/commit/b37cffd704cd08b8bdd68a6e284706eabe59e78d))
### Performance Improvements
* 支持部署到阿里云vod ([98da4e1](https://github.com/certd/certd/commit/98da4e1791ed8bb21daf2a9914fda875d14633c9))
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
**Note:** Version bump only for package @certd/plugin-lib
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
**Note:** Version bump only for package @certd/plugin-lib

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.36.2",
"version": "1.36.4",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -21,8 +21,8 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-s3": "^3.787.0",
"@certd/basic": "^1.36.2",
"@certd/pipeline": "^1.36.2",
"@certd/basic": "^1.36.4",
"@certd/pipeline": "^1.36.4",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -53,5 +53,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "8671887abcdfb93a7ecb709f223bd6add6f03c67"
"gitHead": "1bde777beead9dd23b8d3d98479d616170b8bb69"
}

View File

@@ -1,6 +1,5 @@
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
import { ILogger } from "@certd/basic";
export type AliyunClientV2Req = {
action: string;
version: string;

View File

@@ -33,8 +33,10 @@ export type CasCertInfo = { certId: number; certName: string; certIdentifier: st
export class AliyunSslClient {
opts: AliyunSslClientOpts;
logger: ILogger;
constructor(opts: AliyunSslClientOpts) {
this.opts = opts;
this.logger = opts.logger;
}
checkRet(ret: any) {

View File

@@ -204,9 +204,18 @@ export class AsyncSsh2Client {
stream
.on("close", (code: any, signal: any) => {
this.logger.info(`[${this.connConf.host}][close]:code:${code}`);
if (opts.throwOnStdErr == null && this.windows) {
opts.throwOnStdErr = true;
}
/**
* ]pipeline 执行命令:[10.123.0.2][exec]:cd /d D:\nginx-1.27.5 && D:\nginx-1.27.5\nginx.exe -t && D:\nginx-1.27.5\nginx.exe -s reload
* [2025-07-09T10:24:11.219] [ERROR]pipeline - [10. 123.0. 2][error]: nginx: the configuration file D: \nginx-1.27. 5/conf/nginx. conf syntax is ok
* [2025-07-09T10:24:11.231] [ERROR][10. 123. 0. 2] [error]: nginx: configuration file D: \nginx-1.27.5/conf/nginx.conf test is successful
* pipeline-
* [2025-07-09T10:24:11.473] [INFO]pipeline -[10.123.0.2][close]:code:0
* [2025-07-09T10:24:11.473][ERRoR] pipeline- [step][主机一执行远程主机脚本命令]<id:53hyarN3yvmbijNuMiNAt>: [Eror: nginx: the configuration fileD:\nginx-1.27.5/conf/nginx.conf syntax is ok
//需要忽略windows的错误
*/
// if (opts.throwOnStdErr == null && this.windows) {
// opts.throwOnStdErr = true;
// }
if (opts.throwOnStdErr && hasErrorLog) {
reject(new Error(data));
}
@@ -482,9 +491,9 @@ export class SshClient {
* Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\cmd.exe"
* @param options
*/
async exec(options: { connectConf: SshAccess; script: string | Array<string>; env?: any }): Promise<string> {
async exec(options: { connectConf: SshAccess; script: string | Array<string>; env?: any; throwOnStdErr?: boolean }): Promise<string> {
let { script } = options;
const { connectConf } = options;
const { connectConf, throwOnStdErr } = options;
// this.logger.info('命令:', script);
return await this._call({
@@ -529,7 +538,7 @@ export class SshClient {
script = envScripts.join(newLine) + newLine + script;
}
}
return await conn.exec(script as string, {});
return await conn.exec(script as string, { throwOnStdErr });
},
});
}

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
### Bug Fixes
* 修复查看证书对话框翻译错误的bug ([8626b6d](https://github.com/certd/certd/commit/8626b6d9f235c511766f2ae98e0a37f6cebb621c))
* 修复translation后分组编辑打不开的bug ([46a1b74](https://github.com/certd/certd/commit/46a1b7479923d2feb2dece202a5932b99981b2cd))
### Performance Improvements
* 优化证书进度条颜色 ([2af91db](https://github.com/certd/certd/commit/2af91dbf2ae36a4ed17c6788bc2a2a79a3bb29f8))
* output-selector from参数支持更丰富的过滤规则 ([87853a2](https://github.com/certd/certd/commit/87853a201535f3bfe8505c40f8f5229d82ffcc73))
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
### Bug Fixes
* 修复开放接口添加按钮文本显示问题 ([f93ba99](https://github.com/certd/certd/commit/f93ba9970c12680f38eba2a7abd4b72cf3f5b6a6))
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.36.2",
"version": "1.36.4",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -103,8 +103,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.36.2",
"@certd/pipeline": "^1.36.2",
"@certd/lib-iframe": "^1.36.4",
"@certd/pipeline": "^1.36.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -167,10 +167,14 @@ async function onDomainsChanged(domains: string[]) {
let planItem = planRef.value[domain];
const domainGroupItem = domainGroups[domain];
if (!planItem) {
let type = props.defaultType || "cname";
if (type === "dnses") {
type = "dns";
}
planItem = {
domain,
//@ts-ignore
type: props.defaultType || "cname",
type: type,
//@ts-ignore
cnameVerifyPlan: {},
//@ts-ignore

View File

@@ -9,7 +9,6 @@
<script setup lang="ts">
import { inject, ref, watch } from "vue";
import { CertApplyPluginNames } from "/@/constants";
defineOptions({
name: "CertDomainsGetter",
@@ -54,8 +53,9 @@ function getDomainFromPipeline(inputKey: string) {
return;
}
if (!CertApplyPluginNames.includes(certStep.type)) {
targetStepId = getStepIdFromInputKey(certStep.input?.cert);
const firstLevelValue = certStep.input.cert;
if (firstLevelValue && typeof firstLevelValue === "string" && firstLevelValue.indexOf(".") > 0) {
targetStepId = getStepIdFromInputKey(firstLevelValue);
certStep = findStepFromPipeline(targetStepId);
if (!certStep) {
errorRef.value = "找不到目标步骤,请先选择域名证书";

View File

@@ -4,6 +4,7 @@
<script lang="ts">
import { inject, onMounted, Ref, ref, watch } from "vue";
import { usePluginStore } from "/@/store/plugin";
export default {
name: "OutputSelector",
@@ -27,10 +28,11 @@ export default {
const currentStepIndex = inject("currentStepIndex") as Ref<number>;
const currentTask = inject("currentTask") as Ref<any>;
const getPluginGroups = inject("getPluginGroups") as any;
const pluginGroups = getPluginGroups();
function onCreate() {
options.value = pluginGroups.getPreStepOutputOptions({
const pluginStore = usePluginStore();
async function onCreate() {
await pluginStore.init();
options.value = pluginStore.group.getPreStepOutputOptions({
pipeline: pipeline.value,
currentStageIndex: currentStageIndex.value,
currentTaskIndex: currentTaskIndex.value,
@@ -38,11 +40,38 @@ export default {
currentTask: currentTask.value,
});
if (props.from) {
let froms = [];
if (typeof props.from === "string") {
options.value = options.value.filter((item: any) => item.type === props.from);
froms = [props.from];
} else {
options.value = options.value.filter((item: any) => props.from.includes(item.type));
froms = props.from;
}
function match(from: string, item: any) {
// pluginType:valueType:keyName
if (from.includes(":")) {
const [pluginType, valueType, keyName] = from.split(":");
if (pluginType && item.type !== pluginType) {
return false;
}
if (valueType && item.valueType !== valueType) {
return false;
}
if (keyName && item.key !== keyName) {
return false;
}
return true;
} else {
return item.type === from;
}
}
options.value = options.value.filter((item: any) => {
for (const from of froms) {
if (match(from, item)) {
return true;
}
}
return false;
});
}
if (props.modelValue != null) {
@@ -55,19 +84,10 @@ export default {
ctx.emit("update:modelValue", value);
}
}
onMounted(() => {
onCreate();
onMounted(async () => {
await onCreate();
});
watch(
() => {
return pluginGroups.value?.map;
},
() => {
onCreate();
}
);
function onChanged(value: any) {
ctx.emit("update:modelValue", value);
}

View File

@@ -1 +1 @@
export const CertApplyPluginNames = ["CertApply", "CertApplyLego", "CertApplyUpload"];
export const CertApplyPluginNames = [":cert:"];

View File

@@ -273,8 +273,7 @@ export default {
monitorCronSetting: "Monitoring Schedule",
cronTrigger: "Scheduled trigger for monitoring",
dnsServer: "DNS Server",
// dnsServerHelper: "使用自定义的域名解析服务器1.1.1.1,8.8.8.8",
dnsServerHelper: "Use a custom domain name resolution server, such as: 1.1.1.1,8.8.8.8",
dnsServerHelper: "Use a custom domain name resolution server, such as: 1.1.1.1 , support multiple",
},
},
checkStatus: {
@@ -472,7 +471,7 @@ export default {
statusError: "Error",
actionImportBatch: "Batch Import",
actionSyncIp: "Sync IP",
modalTitleSyncIp: "Sync IP",
TitleSyncIp: "Sync IP",
modalContentSyncIp: "Are you sure to sync IP?",
notificationSyncComplete: "Sync Complete",
actionCheckAll: "Check All",
@@ -709,4 +708,8 @@ export default {
showRunStrategyHelper: "Allow modify the run strategy of the task",
},
},
modal: {
close: "Close",
viewCertificateTitle: "View Certificate",
},
};

View File

@@ -278,7 +278,7 @@ export default {
monitorCronSetting: "监控定时设置",
cronTrigger: "定时触发监控",
dnsServer: "DNS服务器",
dnsServerHelper: "使用自定义的域名解析服务器1.1.1.1,8.8.8.8",
dnsServerHelper: "使用自定义的域名解析服务器1.1.1.1 , 支持多个",
},
},
checkStatus: {
@@ -711,4 +711,8 @@ export default {
showRunStrategyHelper: "任务设置中是否允许选择运行策略",
},
},
modal: {
close: "关闭",
viewCertificateTitle: "查看证书",
},
};

View File

@@ -87,10 +87,13 @@ export class PluginGroups {
for (const step of steps) {
const stepDefine = this.get(step.type);
for (const key in stepDefine?.output) {
const inputDefine = stepDefine.output[key];
options.push({
value: `step.${step.id}.${key}`,
label: `${stepDefine.output[key].title}【from${step.title}`,
label: `${inputDefine.title}【from${step.title}`,
type: step.type,
valueType: inputDefine.type,
key: key,
});
}
}

View File

@@ -228,7 +228,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const leftDays = dayjs(value).diff(dayjs(), "day");
const color = leftDays < 20 ? "red" : "#389e0d";
const percent = (leftDays / 90) * 100;
return <a-progress title={expireDate + t("certd.expires")} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}${t("certd.days")}`} />;
const textColor = leftDays < 20 ? "red" : leftDays > 60 ? "#389e0d" : "";
const format = () => {
return <span style={{ color: textColor }}>{`${leftDays}${t("certd.days")}`}</span>;
};
return <a-progress title={expireDate + t("certd.expires")} percent={percent} strokeColor={color} format={format} />;
},
},
},

View File

@@ -53,13 +53,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 600,
},
},
actionbar: {
buttons: {
add: {
text: t("certd.actionbar.add"),
},
},
},
actionbar: {},
rowHandle: {
width: 300,
fixed: "right",

View File

@@ -363,13 +363,19 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
},
column: {
cellRender({ row }) {
if (!row.lastVars?.certExpiresTime) {
const value = row.lastVars?.certExpiresTime;
if (!value) {
return "-";
}
const leftDays = dayjs(row.lastVars.certExpiresTime).diff(dayjs(), "day");
const expireDate = dayjs(value).format("YYYY-MM-DD");
const leftDays = dayjs(value).diff(dayjs(), "day");
const color = leftDays < 20 ? "red" : "#389e0d";
const percent = (leftDays / 90) * 100;
return <a-progress percent={percent} strokeColor={color} format={percent => `${leftDays}`} />;
const textColor = leftDays < 20 ? "red" : leftDays > 60 ? "#389e0d" : "";
const format = () => {
return <span style={{ color: textColor }}>{`${leftDays}${t("certd.days")}`}</span>;
};
return <a-progress title={expireDate + t("certd.expires")} percent={percent} strokeColor={color} format={format} />;
},
width: 150,
},

View File

@@ -1,6 +1,4 @@
// @ts-ignore
import { useI18n } from "vue-i18n";
import { ref } from "vue";
import { useI18n } from "/src/locales";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { pipelineGroupApi } from "./api";

View File

@@ -299,7 +299,7 @@ import { useSettingStore } from "/@/store/settings";
import { useUserStore } from "/@/store/user";
import TaskShortcuts from "./component/shortcut/task-shortcuts.vue";
import { eachSteps, findStep } from "../utils";
import { PluginGroups } from "/@/store/plugin";
import { PluginGroups, usePluginStore } from "/@/store/plugin";
import { getCronNextTimes } from "/@/components/cron-editor/utils";
import { useCertViewer } from "/@/views/certd/pipeline/use";
@@ -494,16 +494,15 @@ export default defineComponent({
}
);
const pluginGroupsRef: Ref<PluginGroups> = ref();
const pluginStore = usePluginStore();
const fetchPlugins = async () => {
pluginGroupsRef.value = await props.options.getPluginGroups();
await pluginStore.init();
};
fetchPlugins();
provide("pipeline", pipeline);
provide("getPluginGroups", () => {
return pluginGroupsRef.value;
return pluginStore.group;
});
provide("currentHistory", currentHistory);

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.4](https://github.com/certd/certd/compare/v1.36.3...v1.36.4) (2025-07-10)
### Performance Improvements
* 站点证书即将过期通知标题颜色优化为红色 ([80c5331](https://github.com/certd/certd/commit/80c5331a5d4c320323d9b9b800e4ea3b72577b33))
* 支持部署到阿里云vod ([98da4e1](https://github.com/certd/certd/commit/98da4e1791ed8bb21daf2a9914fda875d14633c9))
* 支持部署证书到网宿CDN ([c3da026](https://github.com/certd/certd/commit/c3da026b33106f5195959825a68cadbe49efef00))
* 重置管理员密码同时可以清除管理员的2FA设置 ([1ece091](https://github.com/certd/certd/commit/1ece0915f172d5f8b8adb866434e7efcc5c8c46d))
* output-selector from参数支持更丰富的过滤规则 ([87853a2](https://github.com/certd/certd/commit/87853a201535f3bfe8505c40f8f5229d82ffcc73))
## [1.36.3](https://github.com/certd/certd/compare/v1.36.2...v1.36.3) (2025-07-07)
### Performance Improvements
* 优化部署到腾讯TKE插件支持Opaque类型选择优化填写说明 ([1445325](https://github.com/certd/certd/commit/144532530a865b634e68539e4888e26f52f73492))
## [1.36.2](https://github.com/certd/certd/compare/v1.36.1...v1.36.2) (2025-07-06)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.36.2",
"version": "1.36.4",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -42,20 +42,20 @@
"@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-iam": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0",
"@certd/acme-client": "^1.36.2",
"@certd/basic": "^1.36.2",
"@certd/commercial-core": "^1.36.1",
"@certd/acme-client": "^1.36.4",
"@certd/basic": "^1.36.4",
"@certd/commercial-core": "^1.36.4",
"@certd/cv4pve-api-javascript": "^8.4.1",
"@certd/jdcloud": "^1.36.2",
"@certd/lib-huawei": "^1.36.2",
"@certd/lib-k8s": "^1.36.2",
"@certd/lib-server": "^1.36.2",
"@certd/midway-flyway-js": "^1.36.2",
"@certd/pipeline": "^1.36.2",
"@certd/plugin-cert": "^1.36.2",
"@certd/plugin-lib": "^1.36.2",
"@certd/plugin-plus": "^1.36.1",
"@certd/plus-core": "^1.36.1",
"@certd/jdcloud": "^1.36.4",
"@certd/lib-huawei": "^1.36.4",
"@certd/lib-k8s": "^1.36.4",
"@certd/lib-server": "^1.36.4",
"@certd/midway-flyway-js": "^1.36.4",
"@certd/pipeline": "^1.36.4",
"@certd/plugin-cert": "^1.36.4",
"@certd/plugin-lib": "^1.36.4",
"@certd/plugin-plus": "^1.36.4",
"@certd/plus-core": "^1.36.4",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",

View File

@@ -26,11 +26,11 @@ process.on('uncaughtException', error => {
});
@Configuration({
// detectorOptions: {
// ignore: [
// '**/plugins/**'
// ]
// },
detectorOptions: {
ignore: [
'**/plugins/**'
]
},
imports: [
koa,
orm,

View File

@@ -3,6 +3,7 @@ import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
import { CommonException } from '@certd/lib-server';
import { UserService } from '../../modules/sys/authority/service/user-service.js';
import { logger } from '@certd/basic';
import {UserSettingsService} from "../../modules/mine/service/user-settings-service.js";
/**
* 重置密码模式
@@ -13,6 +14,10 @@ import { logger } from '@certd/basic';
export class ResetPasswdMiddleware implements IWebMiddleware {
@Inject()
userService: UserService;
@Inject()
userSettingsService: UserSettingsService;
@Config('system.resetAdminPasswd')
private resetAdminPasswd: boolean;
resolve() {
@@ -31,8 +36,12 @@ export class ResetPasswdMiddleware implements IWebMiddleware {
const newPasswd = '123456';
await this.userService.resetPassword(1, newPasswd);
await this.userService.updateStatus(1, 1);
await this.userSettingsService.deleteWhere({
userId: 1,
key:"user.two.factor"
})
const user = await this.userService.info(1);
logger.info(`重置1号管理员用户的密码完成用户名${user.username},新密码:${newPasswd}`);
logger.info(`重置1号管理员用户的密码完成2FA设置已删除用户名:${user.username},新密码:${newPasswd},请在登录进去之后尽快修改密码`);
}
}
}

View File

@@ -100,7 +100,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
}
param.hostRecord = hostRecord;
const cnameKey = utils.id.simpleNanoId();
const cnameKey = utils.id.simpleNanoId().toLowerCase();
const safeDomain = param.domain.replaceAll('.', '-');
param.recordValue = `${safeDomain}.${cnameKey}.${cnameProvider.domain}`;
}

View File

@@ -1,10 +1,10 @@
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, CodeException, Constants, PageReq } from "@certd/lib-server";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { CertInfoEntity } from "../entity/cert-info.js";
import { utils } from "@certd/basic";
import { CertInfo, CertReader } from "@certd/plugin-cert";
import {Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, CodeException, Constants, PageReq} from "@certd/lib-server";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {MoreThan, Repository} from "typeorm";
import {CertInfoEntity} from "../entity/cert-info.js";
import {utils} from "@certd/basic";
import {CertInfo, CertReader} from "@certd/plugin-cert";
export type UploadCertReq = {
id?: number;
@@ -107,6 +107,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
},
where: {
userId,
expiresTime: MoreThan(new Date().getTime())
},
});
//遍历查找

View File

@@ -291,7 +291,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
body: {
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
content,
url
url,
errorMessage: "站点证书即将过期"
}
},
site.userId

View File

@@ -41,7 +41,7 @@ export function getDefaultDeployPlugin() {
let certApplyNames = ''
for (const name of CertApplyPluginNames) {
certApplyNames += `
- ${name}`
- "${name}"`
}
const metadata =`
input: # 插件的输入参数

View File

@@ -25,3 +25,4 @@ export * from './plugin-flex/index.js'
export * from './plugin-farcdn/index.js'
export * from './plugin-fnos/index.js'
export * from './plugin-rainyun/index.js'
export * from './plugin-wangsu/index.js'

View File

@@ -0,0 +1,185 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { AliyunAccess, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
@IsTaskPlugin({
name: "AliyunDeployCertToVod",
title: "阿里云-部署至VOD",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: "部署证书到阿里云视频点播vod",
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
export class AliyunDeployCertToVod extends AbstractTaskPlugin {
@TaskInput({
title: "域名证书",
helper: "请选择证书申请任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
},
required: true
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
// @TaskInput({
// title: "大区",
// value: "cn-hangzhou",
// component: {
// name: "a-auto-complete",
// vModel: "value",
// options: [
// { value: "cn-hangzhou", label: "华东1杭州" },
// { value: "ap-southeast-1", label: "新加坡" }
// ]
// },
// required: true
// })
// regionId!: string;
@TaskInput({
title: "证书接入点",
helper: "不会选就保持默认即可",
value: "cas.aliyuncs.com",
component: {
name: "a-select",
options: [
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" },
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" }
]
},
required: true
})
casEndpoint!: string;
@TaskInput({
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "access-selector",
type: "aliyun"
},
required: true
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: "加速域名",
helper: "请选择要部署证书的域名",
action: AliyunDeployCertToVod.prototype.onGetDomainList.name,
watches: ["accessId"],
pager: true,
search: true
})
)
domainList!: string[];
async onInstance() {
}
async execute(): Promise<void> {
this.logger.info("开始部署证书到阿里云VOD");
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
for (const siteId of this.domainList) {
/**
* let queries : {[key: string ]: any} = { };
* queries["DomainName"] = "324234234";
* queries["CertName"] = "ccccccc";
* queries["SSLProtocol"] = "on";
* queries["SSLPub"] = "cert";
* queries["SSLPri"] = "key";
* // runtime options
* let runtime = new $Util.RuntimeOptions({ });
* let request = new $OpenApi.OpenApiRequest({
* query: OpenApiUtil.query(queries),
* });
*/
const res = await client.doRequest({
action: "SetVodDomainCertificate",
version: "2017-03-21",
protocol: "HTTPS",
data: {
query: {
DomainName: siteId,
CertName: this.appendTimeSuffix("certd"),
SSLProtocol: "on",
SSLPub: this.cert.crt,
SSLPri: this.cert.key
}
}
});
this.logger.info(`部署站点[${siteId}]证书成功:${JSON.stringify(res)}`);
}
}
async getClient(access: AliyunAccess) {
const endpoint = `vod.cn-shanghai.aliyuncs.com`;
return access.getClient(endpoint);
}
async onGetDomainList(data: PageSearch) {
if (!this.accessId) {
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
/**
* let queries : {[key: string ]: any} = { };
* queries["PageSize"] = 50;
* queries["PageNumber"] = 1;
* queries["DomainName"] = "5555";
* // runtime options
* let runtime = new $Util.RuntimeOptions({ });
* let request = new $OpenApi.OpenApiRequest({
* query: OpenApiUtil.query(queries),
* });
*/
const client = await this.getClient(access);
const res = await client.doRequest({
action: "DescribeVodUserDomains",
version: "2017-03-21",
protocol: "HTTPS",
data: {
query: {
DomainName: data.searchKey,
PageNumber: data.pageNo,
PageSize: data.pageSize
}
}
});
const list = res?.Domains.PageData;
if (!list || list.length === 0) {
throw new Error("没有找到加速域名,请先在阿里云添加点播加速域名");
}
const options = list.map((item: any) => {
return {
label: item.DomainName,
value: item.DomainName,
domain: item.DomainName
};
});
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
}
}
new AliyunDeployCertToVod();

View File

@@ -7,3 +7,4 @@ export * from './deploy-to-alb/index.js';
export * from './deploy-to-nlb/index.js';
export * from './deploy-to-slb/index.js';
export * from './deploy-to-fc/index.js';
export * from './deploy-to-vod/index.js';

View File

@@ -26,7 +26,7 @@ const regionDict = [
title: '阿里云-上传证书到阿里云',
icon: 'svg:icon-aliyun',
group: pluginGroups.aliyun.key,
desc: '如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出',
desc: '上传证书到阿里云数字证书管理服务CAS注意不会部署到任何应用上如果不想在阿里云上同一份证书上传多次,可以把此任务作为前置任务,其他阿里云任务证书那一项选择此任务的输出',
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,

View File

@@ -1,4 +1,6 @@
import dayjs from 'dayjs';
import dayjs from "dayjs";
import { AliyunSslClient } from "@certd/plugin-lib";
import { CertInfo, CertReader } from "@certd/plugin-cert";
export const ZoneOptions = [{ value: 'cn-hangzhou' }];
export function appendTimeSuffix(name: string) {
@@ -13,3 +15,27 @@ export function checkRet(ret: any) {
throw new Error('执行失败:' + ret.Message);
}
}
export async function getAliyunCertId(opts:{
cert: string | CertInfo,
sslClient: AliyunSslClient,
}) {
const { cert, sslClient } = opts;
let certId: any = cert;
let certName: any = CertReader.appendTimeSuffix("certd");
if (typeof cert === "object") {
const certReader = new CertReader(cert)
certName = certReader.buildCertName()
certId = await sslClient.uploadCert({
name: certName,
cert: cert,
});
sslClient.logger.info("上传证书成功", certId, certName);
}
return {
certId,
certName,
};
}

View File

@@ -1,101 +1,77 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { utils } from "@certd/basic";
import dayjs from "dayjs";
import { CertApplyPluginNames } from "@certd/plugin-cert";
import yaml from "js-yaml";
@IsTaskPlugin({
name: 'DeployCertToTencentTKEIngress',
title: '腾讯云-部署到TKE-ingress',
name: "DeployCertToTencentTKEIngress",
title: "腾讯云-部署到TKE",
needPlus: false,
icon: 'svg:icon-tencentcloud',
icon: "svg:icon-tencentcloud",
group: pluginGroups.tencent.key,
desc: 'serverless集群请使用K8S部署插件Qcloud类型需要【上传到腾讯云】作为前置任务ApiServer未开启外网访问则需要做域名的内网IP映射',
desc: "修改TKE集群密钥配置支持Opaque和TLS证书类型。注意\n1. serverless集群请使用K8S部署插件\n2. Opaque类型需要【上传到腾讯云】作为前置任务;\n3. ApiServer需要开通公网访问或者certd可访问实际上底层仍然是通过KubeClient进行部署",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
@TaskInput({ title: '大区', value: 'ap-guangzhou', required: true })
region!: string;
@TaskInput({
title: '集群ID',
required: true,
desc: '例如cls-6lbj1vee',
request: true,
})
clusterId!: string;
@TaskInput({ title: '集群namespace', value: 'default', required: true })
namespace!: string;
@TaskInput({ title: '证书的secret名称', required: true })
secretName!: string | string[];
@TaskInput({ title: 'ingress名称', required: true })
ingressName!: string | string[];
@TaskInput({
title: 'ingress类型',
title: "ingress证书类型",
component: {
name: 'a-auto-complete',
vModel: 'value',
options: [{ value: 'qcloud' }, { value: 'nginx' }],
name: "a-select",
vModel: "value",
options: [{ value: "nginx", label: "TLS证书格式Nginx可用" }, {
value: "qcloud",
label: "Opaque格式CLB可用原qcloud"
}]
},
helper: '可选 qcloud / nginx',
helper: "clb将部署Opaque类型的证书nginx类型将部署TLS证书格式",
required: true
})
ingressClass!: string;
// @TaskInput({ title: "集群内网ip", helper: "如果开启了外网的话,无需设置" })
// clusterIp!: string;
@TaskInput({
title: '集群域名',
helper: '可不填,默认为:[clusterId].ccs.tencent-cloud.com',
})
clusterDomain!: string;
/**
* AccessProvider的key,或者一个包含access的具体的对象
*/
@TaskInput({
title: 'Access授权',
helper: 'access授权',
title: "Access授权",
helper: "access授权",
component: {
name: 'access-selector',
type: 'tencent',
name: "access-selector",
type: "tencent"
},
required: true,
required: true
})
accessId!: string;
@TaskInput({
title: '腾讯云证书id',
helper: '请选择“上传证书到腾讯云”前置任务的输出',
title: "腾讯云证书id",
helper: "请选择“上传证书到腾讯云”前置任务的输出",
component: {
name: 'output-selector',
from: 'UploadCertToTencent',
name: "output-selector",
from: "UploadCertToTencent"
},
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.ingressClass === "qcloud"
return form.ingressClass === "qcloud"|| form.ingressClass === "clb"
})
}
`,
required: true,
required: true
})
tencentCertId!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames],
name: "output-selector",
from: [...CertApplyPluginNames]
},
mergeScript: `
return {
@@ -104,26 +80,90 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
})
}
`,
required: true,
required: true
})
cert!: any;
@TaskInput({ title: "大区", value: "ap-guangzhou", required: true })
region!: string;
@TaskInput({
title: "集群ID",
required: true,
desc: "例如cls-6lbj1vee",
request: true
})
clusterId!: string;
@TaskInput({ title: "集群namespace", value: "default", required: true })
namespace!: string;
@TaskInput({
title: "证书的secret名称",
helper: "集群->配置管理->Secret,复制名称",
required: true,
component: {
name: "a-select",
vModel: "value",
mode: "tags",
open: false
}
})
secretName!: string | string[];
@TaskInput({
title: "集群域名",
helper: "ApiServer需要开通公网访问填写`ApiServer公网IP:443`\n默认为:[clusterId].ccs.tencent-cloud.com可能访问不通",
component: {
placeholder: "xx.xxx.xx.xx:443"
}
})
clusterDomain!: string;
@TaskInput({
title: "ingress名称",
required: false,
helper: "填写之后会自动重启ingress",
component: {
name: "a-select",
vModel: "value",
mode: "tags",
open: false
}
})
ingressName!: string | string[];
// @TaskInput({ title: "集群内网ip", helper: "如果开启了外网的话,无需设置" })
// clusterIp!: string;
K8sClient: any;
async onInstance() {
// const TkeClient = this.tencentcloud.tke.v20180525.Client;
const k8sSdk = await import('@certd/lib-k8s');
const k8sSdk = await import("@certd/lib-k8s");
this.K8sClient = k8sSdk.K8sClient;
}
async execute(): Promise<void> {
const accessProvider = await this.getAccess(this.accessId);
const tkeClient = await this.getTkeClient(accessProvider, this.region);
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, this.clusterId);
let kubeConfigStr = await this.getTkeKubeConfig(tkeClient, this.clusterId);
this.logger.info('kubeconfig已成功获取');
if (this.clusterDomain) {
const kubeConfig = yaml.load(kubeConfigStr);
kubeConfig.clusters[0].cluster.server = `https://${this.clusterDomain}`;
kubeConfigStr = yaml.dump(kubeConfig);
}
this.logger.info("kubeconfig已成功获取");
const k8sClient = new this.K8sClient({
kubeConfigStr,
logger: this.logger,
logger: this.logger
});
// if (this.clusterIp != null) {
// if (!this.clusterDomain) {
@@ -132,31 +172,34 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
// // 修改内网解析ip地址
// k8sClient.setLookup({ [this.clusterDomain]: { ip: this.clusterIp } });
// }
const ingressType = this.ingressClass || 'qcloud';
if (ingressType === 'qcloud') {
const ingressType = this.ingressClass || "qcloud";
if (ingressType === "qcloud" || ingressType === "clb") {
await this.patchQcloudCertSecret({ k8sClient });
} else {
await this.patchNginxCertSecret({ k8sClient });
}
await utils.sleep(5000); // 停留2秒等待secret部署完成
await this.restartIngress({ k8sClient });
if (this.ingressName) {
this.logger.info("正在重启ingress:", this.ingressName);
await this.restartIngress({ k8sClient });
}
}
async getTkeClient(accessProvider: any, region = 'ap-guangzhou') {
const sdk = await import('tencentcloud-sdk-nodejs/tencentcloud/services/tke/v20180525/index.js');
async getTkeClient(accessProvider: any, region = "ap-guangzhou") {
const sdk = await import("tencentcloud-sdk-nodejs/tencentcloud/services/tke/v20180525/index.js");
const TkeClient = sdk.v20180525.Client;
const clientConfig = {
credential: {
secretId: accessProvider.secretId,
secretKey: accessProvider.secretKey,
secretKey: accessProvider.secretKey
},
region,
profile: {
httpProfile: {
endpoint: 'tke.tencentcloudapi.com',
},
},
endpoint: "tke.tencentcloudapi.com"
}
}
};
return new TkeClient(clientConfig);
@@ -165,49 +208,42 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
async getTkeKubeConfig(client: any, clusterId: string) {
// Depends on tencentcloud-sdk-nodejs version 4.0.3 or higher
const params = {
ClusterId: clusterId,
ClusterId: clusterId
};
const ret = await client.DescribeClusterKubeconfig(params);
this.checkRet(ret);
this.logger.info('注意:后续操作需要在【集群->基本信息】中开启外网或内网访问,https://console.cloud.tencent.com/tke2/cluster');
this.logger.info("注意:后续操作需要在【集群->基本信息】中开启外网或内网访问,https://console.cloud.tencent.com/tke2/cluster");
return ret.Kubeconfig;
}
appendTimeSuffix(name: string) {
if (name == null) {
name = 'certd';
}
return name + '-' + dayjs().format('YYYYMMDD-HHmmss');
}
async patchQcloudCertSecret(options: { k8sClient: any }) {
if (this.tencentCertId == null) {
throw new Error('请先将【上传证书到腾讯云】作为前置任务');
throw new Error("请先将【上传证书到腾讯云】作为前置任务");
}
this.logger.info('腾讯云证书ID:', this.tencentCertId);
const certIdBase64 = Buffer.from(this.tencentCertId).toString('base64');
this.logger.info("腾讯云证书ID:", this.tencentCertId);
const certIdBase64 = Buffer.from(this.tencentCertId).toString("base64");
const { namespace, secretName } = this;
const body = {
data: {
qcloud_cert_id: certIdBase64,
qcloud_cert_id: certIdBase64
},
metadata: {
labels: {
certd: this.appendTimeSuffix('certd'),
},
},
certd: this.appendTimeSuffix("certd")
}
}
};
let secretNames: any = secretName;
if (typeof secretName === 'string') {
if (typeof secretName === "string") {
secretNames = [secretName];
}
for (const secret of secretNames) {
await options.k8sClient.patchSecret({
namespace,
secretName: secret,
body,
body
});
this.logger.info(`CertSecret已更新:${secret}`);
}
@@ -218,24 +254,24 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
const { cert } = this;
const crt = cert.crt;
const key = cert.key;
const crtBase64 = Buffer.from(crt).toString('base64');
const keyBase64 = Buffer.from(key).toString('base64');
const crtBase64 = Buffer.from(crt).toString("base64");
const keyBase64 = Buffer.from(key).toString("base64");
const { namespace, secretName } = this;
const body = {
data: {
'tls.crt': crtBase64,
'tls.key': keyBase64,
"tls.crt": crtBase64,
"tls.key": keyBase64
},
metadata: {
labels: {
certd: this.appendTimeSuffix('certd'),
},
},
certd: this.appendTimeSuffix("certd")
}
}
};
let secretNames = secretName;
if (typeof secretName === 'string') {
if (typeof secretName === "string") {
secretNames = [secretName];
}
for (const secret of secretNames) {
@@ -251,12 +287,12 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
const body = {
metadata: {
labels: {
certd: this.appendTimeSuffix('certd'),
},
},
certd: this.appendTimeSuffix("certd")
}
}
};
let ingressNames = this.ingressName;
if (typeof ingressName === 'string') {
let ingressNames = this.ingressName || [];
if (typeof ingressName === "string") {
ingressNames = [ingressName];
}
for (const ingress of ingressNames) {
@@ -264,9 +300,11 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
this.logger.info(`ingress已重启:${ingress}`);
}
}
checkRet(ret: any) {
if (!ret || ret.Error) {
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message);
throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message);
}
}
}

View File

@@ -0,0 +1,161 @@
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
import { HttpRequestConfig } from "@certd/basic";
import { CertInfo } from "@certd/plugin-cert";
/**
*/
@IsAccess({
name: "wangsu",
title: "网宿授权",
desc: "",
icon: "svg:icon-lucky"
})
export class WangsuAccess extends BaseAccess {
@AccessInput({
title: "accessKeyId",
component: {
placeholder: "accessKeyId",
component: {
name: "a-input",
vModel: "value"
}
},
helper: "[点击前往获取AccessKey](https://console.wangsu.com/account/accessKey?rsr=ws)",
encrypt: false,
required: true
})
accessKeyId!: string;
@AccessInput({
title: "accessKeySecret",
component: {
placeholder: "accessKeySecret",
component: {
name: "a-input",
vModel: "value"
}
},
encrypt: true,
required: true
})
accessKeySecret!: string;
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
async onTestRequest() {
await this.getCertList({ });
return "ok";
}
async getCertList(req: { }) {
/**
* certificate-id
* name
* dns-names
*/
const res = await this.doRequest({
url: "/api/ssl/certificate",
method: "GET",
});
return res["ssl-certificate"]
}
async getCertInfo(req:{certId:string}){
return await this.doRequest({
url: `/api/certificate/${req.certId}`,
method:"GET",
});
}
async updateCert(req: {
certId: string,
cert: CertInfo,
}) {
const certInfo= await this.getCertInfo({certId:req.certId});
const name = certInfo.name;
const {cert,certId} = req;
return await this.doRequest({
url: `/api/certificate/${certId}`,
method:"PUT",
data: {
/**
* name: string;
* certificate?: string;
* privateKey?: string;
* autoRenew?: string;
* isNeedAlarm?: string;
* csrId?: number;
* comment?: string;
*/
name:name,
certificate: cert.crt,
privateKey: cert.key,
autoRenew:"false",
isNeedAlarm:"false",
comment: "certd"
}
});
}
async doRequest(req: HttpRequestConfig) {
const data: any = req.data;
const {AkSkConfig,AkSkAuth} = await import("./lib/index.js");
const akskConfig = new AkSkConfig();
akskConfig.accessKey = this.accessKeyId;
akskConfig.secretKey = this.accessKeySecret;
akskConfig.endPoint = "open.chinanetcenter.com";
akskConfig.uri = req.url;
akskConfig.method = req.method;
const requestMsg = AkSkAuth.transferHttpRequestMsg(akskConfig,data?JSON.stringify(data):"");
AkSkAuth.getAuthAndSetHeaders(requestMsg, akskConfig.accessKey, akskConfig.secretKey);
let response = undefined
try{
response = await this.ctx.http.request({
method: requestMsg.method,
url: requestMsg.url,
headers: requestMsg.headers,
data: requestMsg.body
});
}catch (e) {
if (e.response?.data?.result) {
throw new Error(e.response?.data?.result);
}
throw e;
}
if (response.code != null && response.code != 0){
throw new Error(response.message);
}
if (response.data != null && response.code!==null){
return response.data;
}
return response;
}
}
new WangsuAccess();

View File

@@ -0,0 +1,2 @@
export * from "./plugins/index.js";
export * from "./access.js";

View File

@@ -0,0 +1,87 @@
import { HttpRequestMsg } from "../model/HttpRequestMsg.js";
import { AkSkConfig } from "../model/AkSkConfig.js";
import { CryptoUtils } from "../util/CryptoUtils.js";
import { HttpUtils } from "../util/HttpUtils.js";
import { Constant } from "../common/Constant.js";
export class AkSkAuth {
public static invoke(akSkConfig: AkSkConfig, jsonBody: string): Promise<string | null> {
const requestMsg = AkSkAuth.transferHttpRequestMsg(akSkConfig, jsonBody);
AkSkAuth.getAuthAndSetHeaders(requestMsg, akSkConfig.accessKey, akSkConfig.secretKey);
return HttpUtils.call(requestMsg);
}
static transferHttpRequestMsg(akSkConfig: AkSkConfig, jsonBody: string): HttpRequestMsg {
const requestMsg = new HttpRequestMsg();
requestMsg.uri = akSkConfig.uri;
if (akSkConfig.endPoint && akSkConfig.endPoint !== Constant.END_POINT) {
requestMsg.host = akSkConfig.endPoint;
requestMsg.url = `${Constant.HTTPS_REQUEST_PREFIX}${akSkConfig.endPoint}${requestMsg.uri}`;
} else {
requestMsg.host = Constant.HTTP_DOMAIN;
requestMsg.url = `${Constant.HTTP_REQUEST_PREFIX}${requestMsg.uri}`;
}
requestMsg.method = akSkConfig.method;
requestMsg.signedHeaders = AkSkAuth.getSignedHeaders(akSkConfig.signedHeaders);
if (['POST', 'PUT', 'PATCH', 'DELETE'].indexOf(akSkConfig.method) !== -1) {
requestMsg.body = jsonBody;
}
return requestMsg;
}
static getAuthAndSetHeaders(requestMsg: HttpRequestMsg, accessKey: string, secretKey: string): void {
const timeStamp = (Date.now() / 1000 | 0).toString();
requestMsg.headers['Host'] = requestMsg.host;
requestMsg.headers[Constant.HEAD_SIGN_ACCESS_KEY] = accessKey;
requestMsg.headers[Constant.HEAD_SIGN_TIMESTAMP] = timeStamp;
requestMsg.headers["Accept"] = Constant.APPLICATION_JSON;
const signature = AkSkAuth.getSignature(requestMsg, secretKey, timeStamp);
requestMsg.headers['Authorization'] = AkSkAuth.genAuthorization(accessKey, AkSkAuth.getSignedHeaders(requestMsg.signedHeaders), signature);
}
private static genAuthorization(accessKey: string, signedHeaders: string, signature: string): string {
return `${Constant.HEAD_SIGN_ALGORITHM} Credential=${accessKey}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
}
private static getSignature(requestMsg: HttpRequestMsg, secretKey: string, timestamp: string): string {
let bodyStr = requestMsg.body || "";
const hashedRequestPayload = CryptoUtils.sha256Hex(bodyStr);
const canonicalRequest = `${requestMsg.method}\n${requestMsg.uri.split("?")[0]}\n${decodeURIComponent(requestMsg.getQueryString())}\n${AkSkAuth.getCanonicalHeaders(requestMsg.headers, AkSkAuth.getSignedHeaders(requestMsg.signedHeaders))}\n${AkSkAuth.getSignedHeaders(requestMsg.signedHeaders)}\n${hashedRequestPayload}`;
const stringToSign = `${Constant.HEAD_SIGN_ALGORITHM}\n${timestamp}\n${CryptoUtils.sha256Hex(canonicalRequest)}`;
return CryptoUtils.hmac256(secretKey, stringToSign).toLowerCase();
}
private static getCanonicalHeaders(headers: Record<string, string>, signedHeaders: string): string {
const headerNames = signedHeaders.split(";");
let canonicalHeaders = "";
for (const headerName of headerNames) {
const headerValue = AkSkAuth.getValueByHeader(headerName, headers);
if (headerValue !== null) {
canonicalHeaders += `${headerName}:${headerValue.toLowerCase()}\n`;
} else {
// Handle missing headers if necessary, e.g., log a warning or skip
console.warn(`Header ${headerName} not found in provided headers.`);
}
}
return canonicalHeaders;
}
private static getSignedHeaders(signedHeaders: string): string {
if (!signedHeaders) {
return "content-type;host";
}
const headers = signedHeaders.split(";");
return headers.map(header => header.toLowerCase()).sort().join(";");
}
private static getValueByHeader(name: string, customHeaderMap: { [key: string]: string }): string | null {
for (const key in customHeaderMap) {
if (key.toLowerCase() === name.toLowerCase()) {
return customHeaderMap[key];
}
}
return null;
}
}

View File

@@ -0,0 +1,19 @@
export class Constant {
private constructor() {}
public static readonly HTTP_REQUEST_PREFIX: string = "https://open.chinanetcenter.com";
public static readonly HTTPS_REQUEST_PREFIX: string = "https://";
public static readonly HTTP_DOMAIN: string = "open.chinanetcenter.com";
public static readonly APPLICATION_JSON: string = "application/json";
public static readonly HEAD_SIGN_ACCESS_KEY: string = "x-cnc-accessKey";
public static readonly HEAD_SIGN_TIMESTAMP: string = "x-cnc-timestamp";
public static readonly HEAD_SIGN_ALGORITHM: string = "CNC-HMAC-SHA256";
public static readonly X_CNC_AUTH_METHOD: string = "x-cnc-auth-method";
public static readonly AUTH_METHOD: string = "AKSK";
public static readonly END_POINT: string = "{endPoint}";
}

View File

@@ -0,0 +1,9 @@
export class ApiAuthException extends Error {
public cause?: any;
constructor(message: string, cause?: any) {
super(message);
this.cause = cause;
this.name = 'ApiAuthException';
}
}

View File

@@ -0,0 +1,4 @@
import { AkSkConfig } from "./model/AkSkConfig.js";
import { AkSkAuth } from "./auth/AkSkAuth.js";
export { AkSkAuth, AkSkConfig}

View File

@@ -0,0 +1,56 @@
export class AkSkConfig {
private _accessKey: string | undefined;
private _secretKey: string | undefined;
private _uri: string | undefined;
private _endPoint: string | undefined;
private _method: string | undefined;
private _signedHeaders: string | undefined;
public get accessKey(): string {
return this._accessKey;
}
public set accessKey(value: string) {
this._accessKey = value;
}
public get secretKey(): string {
return this._secretKey;
}
public set secretKey(value: string) {
this._secretKey = value;
}
public get uri(): string {
return this._uri;
}
public set uri(value: string) {
this._uri = value;
}
public get endPoint(): string {
return this._endPoint;
}
public set endPoint(value: string) {
this._endPoint = value;
}
public get method(): string {
return this._method;
}
public set method(value: string) {
this._method = value;
}
public get signedHeaders(): string {
return this._signedHeaders;
}
public set signedHeaders(value: string) {
this._signedHeaders = value;
}
}

View File

@@ -0,0 +1,75 @@
import { Constant } from '../common/Constant.js'; // Assuming you have a TypeScript version of this
export class HttpRequestMsg {
uri: string ;
url: string;
host: string;
method: string;
protocol: string;
params: Record<string, string>;
headers: Record<string, string>;
body: string;
signedHeaders: string;
msg: any;
constructor() {
this.params = {};
this.headers = {};
this.putHeader('Content-Type', Constant.APPLICATION_JSON);
this.putHeader(Constant.X_CNC_AUTH_METHOD, Constant.AUTH_METHOD);
}
putParam(name: string, value: string): void {
this.params[name] = value;
}
getParam(name: string): string | null {
const value = this.params[name];
return value && value.trim() !== '' ? value : null;
}
getQueryString(): string {
if(this.uri == undefined)
return "";
const index = this.uri.indexOf("?");
if (this.method === 'POST' || index === -1) {
return "";
}
return this.uri.substring(index + 1);
}
putHeader(name: string, value: string): void {
this.headers[name] = value;
}
getHeader(name: string): string | null {
for (const key in this.headers) {
if (key.toLowerCase() === name.toLowerCase()) {
return this.headers[key];
}
}
return null;
}
getHeaderByNames(...names: string[]): string | null {
for (const name of names) {
const value = this.getHeader(name);
if (value) {
return value;
}
}
return null;
}
removeHeader(name: string): void {
for (const key in this.headers) {
if (key.toLowerCase() === name.toLowerCase()) {
delete this.headers[key];
}
}
}
setJsonBody(object: any): void {
this.body = JSON.stringify(object);
}
}

View File

@@ -0,0 +1,23 @@
import CryptoJS from 'crypto-js';
export class CryptoUtils {
private constructor() {}
/**
* hmac+sha256+hex
*/
public static sha256Hex(s: string): string {
const hash = CryptoJS.SHA256(s);
return hash.toString(CryptoJS.enc.Hex).toLowerCase();
}
/**
* hmac+sha256
*/
public static hmac256(secretKey: string, message: string): string {
const keyWordArray = CryptoJS.enc.Utf8.parse(secretKey);
const messageWordArray = CryptoJS.enc.Utf8.parse(message);
const hash = CryptoJS.HmacSHA256(messageWordArray, keyWordArray);
return hash.toString(CryptoJS.enc.Hex).toLowerCase();
}
}

View File

@@ -0,0 +1,30 @@
import { HttpRequestMsg } from '../model/HttpRequestMsg.js'; // Assuming you have a TypeScript version of this
import { ApiAuthException } from '../exception/ApiAuthException.js'; // Assuming you have a TypeScript version of this
import axios, { AxiosError } from 'axios';
export class HttpUtils {
private constructor() { }
public static async call(requestMsg: HttpRequestMsg): Promise<string | null> {
var response;
try {
response = await axios({
method: requestMsg.method,
url: requestMsg.url,
headers: requestMsg.headers,
data: requestMsg.body
});
console.info("API invoke success. Response:", response.data);
return response.data;
} catch (error) {
if (error instanceof AxiosError) {
// Handle AxiosError specifically
console.error('API invoke failed. Response:', error.response.data);
return error.response.data;
} else {
// Handle other types of errors
console.error('API invoke failed.', error);
}
throw new ApiAuthException('API invoke failed.');
}
}
}

View File

@@ -0,0 +1 @@
export * from "./plugin-refresh-cert.js";

View File

@@ -0,0 +1,121 @@
import {
AbstractTaskPlugin,
IsTaskPlugin,
PageSearch,
pluginGroups,
RunStrategy,
TaskInput
} from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { WangsuAccess } from "../access.js";
@IsTaskPlugin({
//命名规范,插件类型+功能就是目录plugin-demo中的demo大写字母开头驼峰命名
name: "WangsuRefreshCert",
title: "网宿-更新证书",
desc: "网宿证书自动更新",
icon: "svg:icon-lucky",
//插件分组
group: pluginGroups.cdn.key,
needPlus: false,
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
//类名规范跟上面插件名称name一致
export class WangsuRefreshCert extends AbstractTaskPlugin {
//证书选择,此项必须要有
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
// required: true, // 必填
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
//授权选择框
@TaskInput({
title: "网宿授权",
component: {
name: "access-selector",
type: "wangsu" //固定授权类型
},
required: true //必填
})
accessId!: string;
//
@TaskInput(
createRemoteSelectInputDefine({
title: "证书Id",
helper: "要更新的网宿证书id",
action: WangsuRefreshCert.prototype.onGetCertList.name,
pager: false,
search: false
})
)
certList!: string[];
//插件实例化时执行的方法
async onInstance() {
}
//插件执行方法
async execute(): Promise<void> {
const access = await this.getAccess<WangsuAccess>(this.accessId);
for (const item of this.certList) {
this.logger.info(`----------- 开始更新证书:${item}`);
await access.updateCert({
certId: item,
cert: this.cert
});
this.logger.info(`----------- 更新证书${item}成功`);
}
this.logger.info("部署完成");
}
async onGetCertList(data: PageSearch = {}) {
const access = await this.getAccess<WangsuAccess>(this.accessId);
const list = await access.getCertList({});
if (!list || list.length === 0) {
throw new Error("没有找到证书,请先在控制台上传一次证书且关联域名");
}
/**
* certificate-id
* name
* dns-names
*/
const options = list.map((item: any) => {
const domains = item["dns-names"]
const certId = item["certificate-id"];
return {
label: `${item.name}<${certId}-${domains[0]}>`,
value: certId,
domain: item["dns-names"]
};
});
return {
list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains),
total: list.length,
pageNo: 1,
pageSize: list.length
};
}
}
//实例化一下,注册插件
new WangsuRefreshCert();

72
pnpm-lock.yaml generated
View File

@@ -46,7 +46,7 @@ importers:
packages/core/acme-client:
dependencies:
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../basic
'@peculiar/x509':
specifier: ^1.11.0
@@ -207,7 +207,7 @@ importers:
packages/core/pipeline:
dependencies:
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../basic
'@certd/plus-core':
specifier: ^1.36.1
@@ -415,7 +415,7 @@ importers:
packages/libs/lib-k8s:
dependencies:
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/basic
'@kubernetes/client-node':
specifier: 0.21.0
@@ -455,13 +455,13 @@ importers:
packages/libs/lib-server:
dependencies:
'@certd/acme-client':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/pipeline
'@certd/plus-core':
specifier: ^1.36.1
@@ -607,16 +607,16 @@ importers:
packages/plugins/plugin-cert:
dependencies:
'@certd/acme-client':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/pipeline
'@certd/plugin-lib':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../plugin-lib
'@google-cloud/publicca':
specifier: ^1.3.0
@@ -698,10 +698,10 @@ importers:
specifier: ^3.787.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/pipeline
'@kubernetes/client-node':
specifier: 0.21.0
@@ -789,19 +789,19 @@ importers:
packages/pro/commercial-core:
dependencies:
'@certd/basic':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../core/basic
'@certd/lib-server':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../libs/lib-server
'@certd/pipeline':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../core/pipeline
'@certd/plugin-plus':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../plugin-plus
'@certd/plus-core':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../plus-core
'@midwayjs/core':
specifier: ~3.20.3
@@ -886,22 +886,22 @@ importers:
specifier: ^1.0.2
version: 1.0.3
'@certd/basic':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../core/basic
'@certd/lib-k8s':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../libs/lib-k8s
'@certd/pipeline':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../plugins/plugin-lib
'@certd/plus-core':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../plus-core
ali-oss:
specifier: ^6.21.0
@@ -1004,7 +1004,7 @@ importers:
packages/pro/plus-core:
dependencies:
'@certd/basic':
specifier: ^1.36.0
specifier: ^1.36.1
version: link:../../core/basic
dayjs:
specifier: ^1.11.7
@@ -1294,10 +1294,10 @@ importers:
version: 0.1.3(zod@3.24.4)
devDependencies:
'@certd/lib-iframe':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../libs/lib-iframe
'@certd/pipeline':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/pipeline
'@rollup/plugin-commonjs':
specifier: ^25.0.7
@@ -1480,10 +1480,10 @@ importers:
specifier: ^3.705.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/acme-client':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/basic
'@certd/commercial-core':
specifier: ^1.36.1
@@ -1492,28 +1492,28 @@ importers:
specifier: ^8.4.1
version: 8.4.1
'@certd/jdcloud':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../libs/lib-jdcloud
'@certd/lib-huawei':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../libs/lib-huawei
'@certd/lib-k8s':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../libs/lib-k8s
'@certd/lib-server':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../libs/lib-server
'@certd/midway-flyway-js':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../libs/midway-flyway-js
'@certd/pipeline':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.36.1
specifier: ^1.36.2
version: link:../../plugins/plugin-lib
'@certd/plugin-plus':
specifier: ^1.36.1