Compare commits

...
13 Commits
56 changed files with 804 additions and 323 deletions
+15
View File
@@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
### Bug Fixes
* 某些证书提供商的证书确实commonName导致无法转换证书的问题 ([ac87bc5](https://github.com/certd/certd/commit/ac87bc57e957ea4679707bfd38d6840e26319bed))
* 修复站点监控通知渠道设置无效的bug ([a00453c](https://github.com/certd/certd/commit/a00453c83a58114ce2873dd6e6aaf313f1ce0f87))
### Performance Improvements
* 修改 HTTPS 服务器监听地址 ([e1cf64a](https://github.com/certd/certd/commit/e1cf64ae16d4abfe4299ff16d5088c30cf3c6365))
* 优化流水线页面,增加下次执行时间、查看证书显示 ([c820315](https://github.com/certd/certd/commit/c8203154094fae3d17198747f49f5f41ddf29a4e))
* 站点证书监控支持定时设置,重试次数设置 ([d3c2f8e](https://github.com/certd/certd/commit/d3c2f8eb436e670772d14a54acd6b541c5aa3978))
* 证书申请支持letencrypt profile选项 ([2eb0e54](https://github.com/certd/certd/commit/2eb0e54909d8ad36708e07c12fd598998159bc43))
* aliyun alb支持部署扩展证书 ([2a19b61](https://github.com/certd/certd/commit/2a19b61b7a78620c06396c2cc37cc77d738b6d12))
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
### Features
+1 -1
View File
@@ -1 +1 @@
23:51
00:12
+8
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.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
### Features
* 完善注释 ([6702ca1](https://github.com/certd/certd/commit/6702ca10a17f5d7dbff789b039f7269496f66b97))
* AWS 中国区 CloudFront 证书部署(IAM 证书) ([8a55bed](https://github.com/certd/certd/commit/8a55beda924b3be2a53b9ba80d9487cefa8bf887))
* **lego:** support for command options ([b84159f](https://github.com/certd/certd/commit/b84159f2f11531f058837c2e82d66499f3740f20))
## [1.34.11](https://github.com/certd/certd/compare/v1.34.10...v1.34.11) (2025-06-05)
### Bug Fixes
+1 -1
View File
@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.35.0"
"version": "1.35.1"
}
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/publishlab/node-acme-client/compare/v1.35.0...v1.35.1) (2025-06-07)
### Performance Improvements
* 证书申请支持letencrypt profile选项 ([2eb0e54](https://github.com/publishlab/node-acme-client/commit/2eb0e54909d8ad36708e07c12fd598998159bc43))
# [1.35.0](https://github.com/publishlab/node-acme-client/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/acme-client
+3 -3
View File
@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.35.0",
"version": "1.35.1",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.35.0",
"@certd/basic": "^1.35.1",
"@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": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
+3
View File
@@ -75,6 +75,9 @@ export default async (client, userOpts) => {
log("[auto] Placing new certificate order with ACME provider");
const orderPayload = { identifiers: uniqueDomains.map((d) => ({ type: "dns", value: d })) };
if (opts.profile && client.sslProvider === 'letsencrypt' ){
orderPayload.profile = opts.profile;
}
const order = await client.createOrder(orderPayload);
const authorizations = await client.getAuthorizations(order);
+2
View File
@@ -90,10 +90,12 @@ const defaultOpts = {
*/
class AcmeClient {
sslProvider
constructor(opts) {
if (!Buffer.isBuffer(opts.accountKey)) {
opts.accountKey = Buffer.from(opts.accountKey);
}
this.sslProvider = opts.sslProvider;
this.opts = { ...defaultOpts, ...opts };
this.backoffOpts = {
+1
View File
@@ -66,6 +66,7 @@ export interface ClientAutoOptions {
challengePriority?: string[];
preferredChain?: string;
signal?: AbortSignal;
profile?:string;
}
export class Client {
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
**Note:** Version bump only for package @certd/basic
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/basic
+1 -1
View File
@@ -1 +1 @@
00:09
09:08
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.35.0",
"version": "1.35.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
**Note:** Version bump only for package @certd/pipeline
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/pipeline
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.35.0",
"version": "1.35.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,8 +17,8 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.35.0",
"@certd/plus-core": "^1.35.0",
"@certd/basic": "^1.35.1",
"@certd/plus-core": "^1.35.1",
"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": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
**Note:** Version bump only for package @certd/lib-huawei
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/lib-huawei
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.35.0",
"version": "1.35.1",
"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": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
**Note:** Version bump only for package @certd/lib-iframe
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/lib-iframe
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.35.0",
"version": "1.35.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
**Note:** Version bump only for package @certd/jdcloud
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/jdcloud
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.35.0",
"version": "1.35.1",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -61,5 +61,5 @@
"fetch"
]
},
"gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
**Note:** Version bump only for package @certd/lib-k8s
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/lib-k8s
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.35.0",
"version": "1.35.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,7 +17,7 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.35.0",
"@certd/basic": "^1.35.1",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -32,5 +32,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
**Note:** Version bump only for package @certd/lib-server
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/lib-server
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.35.0",
"version": "1.35.1",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -27,10 +27,10 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.35.0",
"@certd/basic": "^1.35.0",
"@certd/pipeline": "^1.35.0",
"@certd/plus-core": "^1.35.0",
"@certd/acme-client": "^1.35.1",
"@certd/basic": "^1.35.1",
"@certd/pipeline": "^1.35.1",
"@certd/plus-core": "^1.35.1",
"@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": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
**Note:** Version bump only for package @certd/midway-flyway-js
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/midway-flyway-js
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.35.0",
"version": "1.35.1",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
+10
View File
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
### Bug Fixes
* 某些证书提供商的证书确实commonName导致无法转换证书的问题 ([ac87bc5](https://github.com/certd/certd/commit/ac87bc57e957ea4679707bfd38d6840e26319bed))
### Performance Improvements
* 证书申请支持letencrypt profile选项 ([2eb0e54](https://github.com/certd/certd/commit/2eb0e54909d8ad36708e07c12fd598998159bc43))
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
### Features
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.35.0",
"version": "1.35.1",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -16,10 +16,10 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/acme-client": "^1.35.0",
"@certd/basic": "^1.35.0",
"@certd/pipeline": "^1.35.0",
"@certd/plugin-lib": "^1.35.0",
"@certd/acme-client": "^1.35.1",
"@certd/basic": "^1.35.1",
"@certd/pipeline": "^1.35.1",
"@certd/plugin-lib": "^1.35.1",
"@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": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
@@ -327,8 +327,9 @@ export class AcmeService {
csrInfo: any;
isTest?: boolean;
privateKeyType?: string;
profile?: string;
}): Promise<CertInfo> {
const { email, isTest, csrInfo, dnsProvider, domainsVerifyPlan } = options;
const { email, isTest, csrInfo, dnsProvider, domainsVerifyPlan, profile } = options;
const client: acme.Client = await this.getAcmeClient(email, isTest);
let domains = options.domains;
@@ -400,6 +401,7 @@ export class AcmeService {
return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordReq, recordRes, dnsProvider, httpUploader);
},
signal: this.options.signal,
profile,
});
const crtString = crt.toString();
@@ -89,7 +89,10 @@ export class CertReader {
getAllDomains() {
const { detail } = this.getCrtDetail();
const domains = [detail.domains.commonName];
const domains = [];
if (detail.domains?.commonName) {
domains.push(detail.domains.commonName);
}
domains.push(...detail.domains.altNames);
//去重
return uniq(domains);
@@ -102,12 +105,23 @@ export class CertReader {
static getMainDomain(crt: string) {
const { detail } = CertReader.readCertDetail(crt);
return detail.domains.commonName;
return CertReader.getMainDomainFromDetail(detail);
}
getMainDomain() {
const { detail } = this.getCrtDetail();
return detail.domains.commonName;
return CertReader.getMainDomainFromDetail(detail);
}
static getMainDomainFromDetail(detail: CertificateInfo) {
let domain = detail?.domains?.commonName;
if (domain == null) {
domain = detail?.domains?.altNames?.[0];
}
if (domain == null) {
domain = "unknown";
}
return domain;
}
saveToFile(type: "crt" | "key" | "pfx" | "der" | "oc" | "one" | "ic" | "jks", filepath?: string) {
@@ -179,8 +193,7 @@ export class CertReader {
}
buildCertFileName(suffix: string, applyTime: any, prefix = "cert") {
const detail = this.getCrtDetail();
let domain = detail.detail.domains.commonName;
let domain = this.getMainDomain();
domain = domain.replaceAll(".", "_").replaceAll("*", "_");
const timeStr = dayjs(applyTime).format("YYYYMMDDHHmmss");
return `${prefix}_${domain}_${timeStr}.${suffix}`;
@@ -188,7 +201,7 @@ export class CertReader {
buildCertName() {
let domain = this.getMainDomain();
domain = domain.replaceAll("*", "_").replaceAll("*", "_");
domain = domain.replaceAll(".", "_").replaceAll("*", "_");
return `${domain}_${dayjs().format("YYYYMMDDHHmmssSSS")}`;
}
}
@@ -248,6 +248,30 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
})
privateKeyType!: PrivateKeyType;
@TaskInput({
title: "证书配置",
value: "classic",
component: {
name: "a-select",
vModel: "value",
options: [
{ value: "classic", label: "经典(classic)" },
{ value: "tlsserver", label: "TLS服务器(tlsserver)" },
{ value: "shortlived", label: "短暂的(shortlived)" },
],
},
helper: "如无特殊需求,默认即可",
required: false,
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.sslProvider === 'letsencrypt'
})
}
`,
})
certProfile!: string;
@TaskInput({
title: "使用代理",
value: false,
@@ -395,6 +419,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
csrInfo,
isTest: false,
privateKeyType: this.privateKeyType,
profile: this.certProfile,
});
const certInfo = this.formatCerts(cert);
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
### Performance Improvements
* aliyun alb支持部署扩展证书 ([2a19b61](https://github.com/certd/certd/commit/2a19b61b7a78620c06396c2cc37cc77d738b6d12))
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/plugin-lib
+5 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.35.0",
"version": "1.35.1",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,11 +17,12 @@
},
"dependencies": {
"@alicloud/openapi-client": "^0.4.14",
"@alicloud/openapi-util": "^0.3.2",
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-s3": "^3.787.0",
"@certd/basic": "^1.35.0",
"@certd/pipeline": "^1.35.0",
"@certd/basic": "^1.35.1",
"@certd/pipeline": "^1.35.1",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -52,5 +53,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "ab3a3156f24d7fc70f8a907c5f6fc754413a89d6"
"gitHead": "f55f9b4dd338bb03e5ba2e1d81915c3f17b00502"
}
@@ -54,7 +54,7 @@ export class AliyunClientV2 {
const $OpenApi = await import("@alicloud/openapi-client");
const $Util = await import("@alicloud/tea-util");
const OpenApiUtil = await import("@alicloud/openapi-util");
const params = new $OpenApi.Params({
// 接口名称
action: req.action,
@@ -74,6 +74,10 @@ export class AliyunClientV2 {
bodyType: "json",
});
if (req.data?.query) {
//@ts-ignore
req.data.query = OpenApiUtil.default.default.query(req.data.query);
}
const runtime = new $Util.RuntimeOptions({});
const request = new $OpenApi.OpenApiRequest(req.data);
// 复制代码运行请自行打印 API 的返回值
@@ -29,7 +29,7 @@ export type AliyunSslUploadCertReq = {
cert: AliyunCertInfo;
};
export type CasCertInfo = { certId: number; certName: string; certIdentifier: string };
export type CasCertInfo = { certId: number; certName: string; certIdentifier: string; notAfter: number };
export class AliyunSslClient {
opts: AliyunSslClientOpts;
@@ -68,6 +68,7 @@ export class AliyunSslClient {
certId: certId,
certName: res.Name,
certIdentifier: res.CertIdentifier,
notAfter: res.NotAfter,
};
}
@@ -148,4 +149,8 @@ export class AliyunSslClient {
this.checkRet(res);
return res;
}
async deleteCert(certId: any) {
await this.doRequest("DeleteUserCertificate", { CertId: certId }, { method: "POST" });
}
}
+7
View File
@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
### Performance Improvements
* 优化流水线页面,增加下次执行时间、查看证书显示 ([c820315](https://github.com/certd/certd/commit/c8203154094fae3d17198747f49f5f41ddf29a4e))
* 站点证书监控支持定时设置,重试次数设置 ([d3c2f8e](https://github.com/certd/certd/commit/d3c2f8eb436e670772d14a54acd6b541c5aa3978))
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
**Note:** Version bump only for package @certd/ui-client
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.35.0",
"version": "1.35.1",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -102,8 +102,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.35.0",
"@certd/pipeline": "^1.35.0",
"@certd/lib-iframe": "^1.35.1",
"@certd/pipeline": "^1.35.1",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
@@ -1,17 +1,7 @@
<template>
<div class="cron-editor">
<div class="flex-o">
<cron-light
:disabled="disabled"
:readonly="readonly"
:period="period"
class="flex-o cron-ant"
locale="zh-CN"
format="quartz"
:model-value="modelValue"
@update:model-value="onUpdate"
@error="onError"
/>
<cron-light :disabled="disabled" :readonly="readonly" :period="period" class="flex-o cron-ant" locale="zh-CN" format="quartz" :model-value="modelValue" @update:model-value="onUpdate" @error="onError" />
</div>
<div class="mt-5 flex">
<a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input>
@@ -26,13 +16,15 @@
import parser from "cron-parser";
import { computed, ref } from "vue";
import dayjs from "dayjs";
import { getCronNextTimes } from "/@/components/cron-editor/utils";
defineOptions({
name: "CronEditor"
name: "CronEditor",
});
const props = defineProps<{
modelValue?: string;
disabled?: boolean;
readonly?: boolean;
allowEveryMin?: boolean;
}>();
const period = ref<string>("");
@@ -58,9 +50,12 @@ const onUpdate = (value: string) => {
if (arr[0] === "*") {
arr[0] = "0";
}
if (arr[1] === "*") {
arr[1] = "0";
if (!props.allowEveryMin) {
if (arr[1] === "*") {
arr[1] = "0";
}
}
value = arr.join(" ");
emit("update:modelValue", value);
@@ -90,10 +85,10 @@ const nextTime = computed(() => {
if (props.modelValue == null) {
return "请先设置正确的cron表达式";
}
try {
const interval = parser.parseExpression(props.modelValue);
const next = interval.next().getTime();
return dayjs(next).format("YYYY-MM-DD HH:mm:ss");
const nextTimes = getCronNextTimes(props.modelValue, 2);
return nextTimes.join(",");
} catch (e) {
console.log(e);
return "请先设置正确的cron表达式";
@@ -0,0 +1,15 @@
import parser from "cron-parser";
import dayjs from "dayjs";
export function getCronNextTimes(cron: string, count: number = 1) {
if (cron == null) {
return [];
}
const nextTimes = [];
const interval = parser.parseExpression(cron);
for (let i = 0; i < count; i++) {
const next = interval.next().getTime();
nextTimes.push(dayjs(next).format("YYYY-MM-DD HH:mm:ss"));
}
return nextTimes;
}
@@ -76,7 +76,7 @@ export default {
.text-editable {
flex: 1;
line-height: 34px;
overflow: hidden;
span.fs-iconify {
display: inline-flex;
justify-content: center;
@@ -3,6 +3,8 @@ import { request } from "/src/api/service";
const apiPrefix = "/monitor/site/setting";
export type UserSiteMonitorSetting = {
notificationId?: number;
retryTimes?: number;
cron?: string;
};
export async function SiteMonitorSettingsGet() {
@@ -11,6 +11,19 @@
</div>
<div class="helper">设置通知渠道</div>
</a-form-item>
<a-form-item label="重试次数" :name="['retryTimes']">
<div class="flex">
<a-input-number v-model:value="formState.retryTimes" />
</div>
<div class="helper">监控请求重试次数</div>
</a-form-item>
<a-form-item label="监控定时设置" :name="['cron']">
<div class="flex">
<cron-editor v-model="formState.cron" :disabled="!settingsStore.isPlus" :allow-every-min="userStore.isAdmin" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">定时触发监控</div>
</a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
<loading-button type="primary" html-type="button" :click="doSave">保存</loading-button>
</a-form-item>
@@ -27,8 +40,10 @@ import { notification } from "ant-design-vue";
import { merge } from "lodash-es";
import { useSettingStore } from "/src/store/settings";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { useUserStore } from "/@/store/user";
const settingsStore = useSettingStore();
const userStore = useUserStore();
defineOptions({
name: "UserSecurity",
});
@@ -5,17 +5,15 @@ import { useRouter } from "vue-router";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, useUi } from "@fast-crud/fast-crud";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { Modal, notification } from "ant-design-vue";
import { env } from "/@/utils/util.env";
import { useUserStore } from "/@/store/user";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/settings";
import { cloneDeep } from "lodash-es";
import { useModal } from "/@/use/use-modal";
import CertView from "./cert-view.vue";
import { eachStages } from "./utils";
import { setRunnableIds, useCertPipelineCreator } from "/@/views/certd/pipeline/certd-form/use";
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
import GroupSelector from "/@/views/certd/pipeline/group/group-selector.vue";
import { useCertViewer } from "/@/views/certd/pipeline/use";
export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
@@ -61,59 +59,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
return res;
};
const model = useModal();
const viewCert = async (row: any) => {
const cert = await api.GetCert(row.id);
if (!cert) {
notification.error({ message: "请先运行一次流水线" });
return;
}
model.success({
title: "查看证书",
maskClosable: true,
okText: "关闭",
width: 800,
content: () => {
return <CertView cert={cert}></CertView>;
},
});
};
const downloadCert = async (row: any) => {
const files = await api.GetFiles(row.id);
model.success({
title: "点击链接下载",
maskClosable: true,
okText: "关闭",
content: () => {
const children = [];
for (const file of files) {
const downloadUrl = `${env.API}/pi/history/download?pipelineId=${row.id}&fileId=${file.id}`;
children.push(
<div>
<div class={"flex-o m-5"}>
<fs-icon icon={"ant-design:cloud-download-outlined"} class={"mr-5 fs-16"}></fs-icon>
<a href={downloadUrl} target={"_blank"}>
{file.filename}
</a>
</div>
</div>
);
}
if (children.length === 0) {
return <div>暂无文件下载</div>;
}
return (
<div class={"mt-3"}>
<div> {children}</div>
</div>
);
},
});
};
const { viewCert, downloadCert } = useCertViewer();
const userStore = useUserStore();
const settingStore = useSettingStore();
@@ -208,6 +154,10 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
},
table: {
scroll: { x: 1500 },
remove: {
confirmTitle: "确定要删除吗?",
confirmMessage: "将删除该流水线相关的所有数据,包括执行历史、证书文件、证书仓库记录等",
},
},
tabs: {
name: "groupId",
@@ -281,7 +231,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
type: "link",
icon: "ph:certificate",
async click({ row }) {
await viewCert(row);
await viewCert(row.id);
},
},
download: {
@@ -291,7 +241,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
tooltip: { title: "下载证书" },
icon: "ant-design:download-outlined",
async click({ row }) {
await downloadCert(row);
await downloadCert(row.id);
},
},
remove: {
@@ -34,6 +34,8 @@ const pipelineOptions: PipelineOptions = {
stages: [],
triggers: [],
...JSON.parse(detail.pipeline.content || "{}"),
type: detail.pipeline.type,
from: detail.pipeline.from,
},
} as PipelineDetail;
},
@@ -1,18 +1,28 @@
<template>
<fs-page v-if="pipeline" class="page-pipeline-edit">
<template #header>
<div class="title">
<div class="title flex-1">
<fs-button class="back" icon="ion:chevron-back-outline" @click="goBack"></fs-button>
<text-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></text-editable>
</div>
<div class="more">
<template v-if="editMode">
<a-button type="primary" :loading="saveLoading" @click="save">保存</a-button>
<a-button class="ml-5" @click="cancel">取消</a-button>
</template>
<template v-else>
<a-button type="primary" @click="edit">编辑</a-button>
</template>
<div class="more flex items-center flex-1 justify-end">
<div class="flex items-center hidden md:block">
<a-tag v-if="nextTriggerTimes" color="blue">下次执行时间:{{ nextTriggerTimes }}</a-tag>
</div>
<div class="basis-40 flex justify-end mr-10">
<template v-if="editMode">
<fs-button type="primary" :loading="saveLoading" @click="save">保存</fs-button>
<fs-button class="ml-5" @click="cancel">取消</fs-button>
</template>
<template v-else>
<fs-button icon="ant-design:edit-outlined" type="primary" @click="edit">编辑</fs-button>
</template>
</div>
<div v-if="isCert" class="flex items-center hidden md:block">
<fs-button icon="ant-design:eye-outlined" class="mr-5" type="primary" text="查看证书" @click="viewCert(pipeline.id)"> </fs-button>
<fs-button icon="ant-design:download-outlined" class="mr-5" type="primary" text="下载证书" @click="downloadCert(pipeline.id)"> </fs-button>
</div>
</div>
</template>
@@ -255,7 +265,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, provide, Ref, ref, watch } from "vue";
import { defineComponent, onMounted, onUnmounted, provide, Ref, ref, watch, computed } from "vue";
import { useRouter } from "vue-router";
import PiTaskForm from "./component/task-form/index.vue";
import PiTriggerForm from "./component/trigger-form/index.vue";
@@ -275,6 +285,8 @@ import { useUserStore } from "/@/store/user";
import TaskShortcuts from "./component/shortcut/task-shortcuts.vue";
import { eachSteps, findStep } from "../utils";
import { PluginGroups } from "/@/store/plugin";
import { getCronNextTimes } from "/@/components/cron-editor/utils";
import { useCertViewer } from "/@/views/certd/pipeline/use";
export default defineComponent({
name: "PipelineEdit",
@@ -309,6 +321,22 @@ export default defineComponent({
const currentHistory: Ref<any> = ref({});
const nextTriggerTimes = computed(() => {
const triggers = pipeline.value.triggers;
if (!triggers || triggers.length === 0) {
return null;
}
let nextTimes: any = [];
for (const item of triggers) {
if (!item.props?.cron) {
continue;
}
const ret = getCronNextTimes(item.props?.cron, 1);
nextTimes.push(...ret);
}
return nextTimes.join(",");
});
const router = useRouter();
function goBack() {
router.back();
@@ -838,7 +866,12 @@ export default defineComponent({
};
});
const { viewCert, downloadCert } = useCertViewer();
const isCert = computed(() => {
return currentPipeline.value?.type?.startsWith("cert");
});
return {
isCert,
pipeline,
currentHistory,
histories,
@@ -852,6 +885,9 @@ export default defineComponent({
...useHistory(),
...useNotification(),
...useScroll(),
nextTriggerTimes,
viewCert,
downloadCert,
};
},
});
@@ -0,0 +1,65 @@
import * as api from "/@/views/certd/pipeline/api";
import { notification } from "ant-design-vue";
import CertView from "/@/views/certd/pipeline/cert-view.vue";
import { env } from "/@/utils/util.env";
import { useModal } from "/@/use/use-modal";
export function useCertViewer() {
const model = useModal();
const viewCert = async (id: number) => {
const cert = await api.GetCert(id);
if (!cert) {
notification.error({ message: "请先运行一次流水线" });
return;
}
model.success({
title: "查看证书",
maskClosable: true,
okText: "关闭",
width: 800,
content: () => {
return <CertView cert={cert}></CertView>;
},
});
};
const downloadCert = async (id: any) => {
const files = await api.GetFiles(id);
model.success({
title: "点击链接下载",
maskClosable: true,
okText: "关闭",
content: () => {
const children = [];
for (const file of files) {
const downloadUrl = `${env.API}/pi/history/download?pipelineId=${id}&fileId=${file.id}`;
children.push(
<div>
<div class={"flex-o m-5"}>
<fs-icon icon={"ant-design:cloud-download-outlined"} class={"mr-5 fs-16"}></fs-icon>
<a href={downloadUrl} target={"_blank"}>
{file.filename}
</a>
</div>
</div>
);
}
if (children.length === 0) {
return <div>暂无文件下载</div>;
}
return (
<div class={"mt-3"}>
<div> {children}</div>
</div>
);
},
});
};
return {
viewCert,
downloadCert,
};
}
+13
View File
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.1](https://github.com/certd/certd/compare/v1.35.0...v1.35.1) (2025-06-07)
### Bug Fixes
* 修复站点监控通知渠道设置无效的bug ([a00453c](https://github.com/certd/certd/commit/a00453c83a58114ce2873dd6e6aaf313f1ce0f87))
### Performance Improvements
* 修改 HTTPS 服务器监听地址 ([e1cf64a](https://github.com/certd/certd/commit/e1cf64ae16d4abfe4299ff16d5088c30cf3c6365))
* 优化流水线页面,增加下次执行时间、查看证书显示 ([c820315](https://github.com/certd/certd/commit/c8203154094fae3d17198747f49f5f41ddf29a4e))
* 站点证书监控支持定时设置,重试次数设置 ([d3c2f8e](https://github.com/certd/certd/commit/d3c2f8eb436e670772d14a54acd6b541c5aa3978))
* aliyun alb支持部署扩展证书 ([2a19b61](https://github.com/certd/certd/commit/2a19b61b7a78620c06396c2cc37cc77d738b6d12))
# [1.35.0](https://github.com/certd/certd/compare/v1.34.11...v1.35.0) (2025-06-05)
### Features
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.35.0",
"version": "1.35.1",
"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.35.0",
"@certd/basic": "^1.35.0",
"@certd/commercial-core": "^1.35.0",
"@certd/acme-client": "^1.35.1",
"@certd/basic": "^1.35.1",
"@certd/commercial-core": "^1.35.1",
"@certd/cv4pve-api-javascript": "^8.4.1",
"@certd/jdcloud": "^1.35.0",
"@certd/lib-huawei": "^1.35.0",
"@certd/lib-k8s": "^1.35.0",
"@certd/lib-server": "^1.35.0",
"@certd/midway-flyway-js": "^1.35.0",
"@certd/pipeline": "^1.35.0",
"@certd/plugin-cert": "^1.35.0",
"@certd/plugin-lib": "^1.35.0",
"@certd/plugin-plus": "^1.35.0",
"@certd/plus-core": "^1.35.0",
"@certd/jdcloud": "^1.35.1",
"@certd/lib-huawei": "^1.35.1",
"@certd/lib-k8s": "^1.35.1",
"@certd/lib-server": "^1.35.1",
"@certd/midway-flyway-js": "^1.35.1",
"@certd/pipeline": "^1.35.1",
"@certd/plugin-cert": "^1.35.1",
"@certd/plugin-lib": "^1.35.1",
"@certd/plugin-plus": "^1.35.1",
"@certd/plus-core": "^1.35.1",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",
@@ -154,4 +154,5 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
await this.service.saveSetting(userId, setting);
return this.ok({});
}
}
@@ -4,6 +4,8 @@ import { logger } from '@certd/basic';
import { SysSettingsService } from '@certd/lib-server';
import { SiteInfoService } from '../monitor/index.js';
import { Cron } from '../cron/cron.js';
import {UserSettingsService} from "../mine/service/user-settings-service.js";
import {UserSiteMonitorSetting} from "../mine/service/models.js";
@Autoload()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
@@ -22,6 +24,8 @@ export class AutoCRegisterCron {
@Inject()
sysSettingsService: SysSettingsService;
@Inject()
userSettingsService: UserSettingsService;
@Inject()
siteInfoService: SiteInfoService;
@@ -39,39 +43,30 @@ export class AutoCRegisterCron {
// console.log('meta', meta);
// const metas = listPropertyDataFromClass(CLASS_KEY, this.echoPlugin);
// console.log('metas', metas);
this.registerSiteMonitorCron();
await this.registerSiteMonitorCron();
}
registerSiteMonitorCron() {
const job = async () => {
logger.info('站点证书检查开始执行');
async registerSiteMonitorCron() {
//先注册公共job
await this.siteInfoService.registerSiteMonitorJob()
let offset = 0;
const limit = 50;
while (true) {
const res = await this.siteInfoService.page({
query: { disabled: false },
page: { offset, limit },
});
const { records } = res;
if (records.length === 0) {
break;
}
offset += records.length;
await this.siteInfoService.checkList(records);
//注册用户独立的检查时间
const monitorSettingList = await this.userSettingsService.list({
query:{
key: UserSiteMonitorSetting.__key__,
}
})
for (const item of monitorSettingList) {
const setting = item.setting ?? JSON.parse(item.setting)
if(!setting?.cron){
continue
}
await this.siteInfoService.registerSiteMonitorJob(item.userId)
}
logger.info('站点证书检查完成');
};
this.cron.register({
name: 'siteMonitor',
cron: '0 0 0 * * *',
job,
});
if (this.immediateTriggerSiteMonitor) {
job();
logger.info(`立即触发一次站点证书检查任务`)
await this.siteInfoService.triggerJobOnce()
}
}
}
@@ -58,7 +58,7 @@ export class HttpsServer {
opts.app.callback()
);
this.server = httpServer;
const hostname = '0.0.0.0';
const hostname = '::';
// A function that runs in the context of the http server
// and reports what type of server listens on which port
function listeningReporter() {
@@ -25,6 +25,8 @@ export class UserSiteMonitorSetting extends BaseSettings {
static __key__ = "user.site.monitor";
notificationId?:number= 0;
cron?:string = undefined;
retryTimes?:number = 3;
}
export class UserEmailSetting extends BaseSettings {
@@ -1,22 +1,23 @@
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from "@certd/lib-server";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { SiteInfoEntity } from "../entity/site-info.js";
import { siteTester } from "./site-tester.js";
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, NeedSuiteException, NeedVIPException, SysSettingsService} from "@certd/lib-server";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {Repository} from "typeorm";
import {SiteInfoEntity} from "../entity/site-info.js";
import {siteTester} from "./site-tester.js";
import dayjs from "dayjs";
import { logger, utils } from "@certd/basic";
import { PeerCertificate } from "tls";
import { NotificationService } from "../../pipeline/service/notification-service.js";
import { isComm, isPlus } from "@certd/plus-core";
import { UserSuiteService } from "@certd/commercial-core";
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
import { UserSiteMonitorSetting } from "../../mine/service/models.js";
import { SiteIpService } from "./site-ip-service.js";
import { SiteIpEntity } from "../entity/site-ip.js";
import {logger, utils} from "@certd/basic";
import {PeerCertificate} from "tls";
import {NotificationService} from "../../pipeline/service/notification-service.js";
import {isComm, isPlus} from "@certd/plus-core";
import {UserSuiteService} from "@certd/commercial-core";
import {UserSettingsService} from "../../mine/service/user-settings-service.js";
import {UserSiteMonitorSetting} from "../../mine/service/models.js";
import {SiteIpService} from "./site-ip-service.js";
import {SiteIpEntity} from "../entity/site-ip.js";
import {Cron} from "../../cron/cron.js";
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
@Scope(ScopeEnum.Request, {allowDowngrade: true})
export class SiteInfoService extends BaseService<SiteInfoEntity> {
@InjectEntityModel(SiteInfoEntity)
repository: Repository<SiteInfoEntity>;
@@ -36,6 +37,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
@Inject()
siteIpService: SiteIpService;
@Inject()
cron: Cron;
//@ts-ignore
getRepository() {
return this.repository;
@@ -70,7 +75,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
});
if (found) {
return { id: found.id };
return {id: found.id};
}
return await super.add(data);
@@ -89,7 +94,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
throw new Error("userId is required");
}
return await this.repository.count({
where: { userId }
where: {userId}
});
}
@@ -141,7 +146,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
};
if (site.ipCheck) {
delete updateData.checkStatus
delete updateData.checkStatus
}
await this.update(updateData);
@@ -239,9 +244,11 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async sendCheckErrorNotify(site: SiteInfoEntity, fromIpCheck = false) {
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
// 发邮件
await this.notificationService.send(
{
id: setting?.notificationId,
useDefault: true,
logger: logger,
body: {
@@ -262,11 +269,13 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
const expires = site.certExpiresTime;
const validDays = dayjs(expires).diff(dayjs(), "day");
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
const content = `站点名称: ${site.name} \n站点域名: ${site.domain} \n证书域名: ${site.certDomains} \n颁发机构: ${site.certProvider} \n过期时间: ${dayjs(site.certExpiresTime).format("YYYY-MM-DD")} \n`;
if (validDays >= 0 && validDays < tipDays) {
// 发通知
await this.notificationService.send(
{
id: setting?.notificationId,
useDefault: true,
logger: logger,
body: {
@@ -281,6 +290,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
//发过期通知
await this.notificationService.send(
{
id: setting?.notificationId,
useDefault: true,
logger: logger,
body: {
@@ -300,17 +310,36 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
throw new Error("userId is required");
}
const sites = await this.repository.find({
where: { userId }
where: {userId}
});
this.checkList(sites);
this.checkList(sites,false);
}
async checkList(sites: SiteInfoEntity[]) {
async checkList(sites: SiteInfoEntity[],isCommon: boolean) {
const cache = {}
const getFromCache = async (userId: number) =>{
if (cache[userId]) {
return cache[userId];
}
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting)
cache[userId] = setting
return setting;
}
for (const site of sites) {
this.doCheck(site).catch(e => {
let retryTimes = 3;
const setting = await getFromCache(site.userId)
if (isCommon) {
//公共的检查,排除有设置cron的用户
if (setting?.cron) {
//设置了cron,跳过公共检查
continue;
}
retryTimes = setting.retryTimes??retryTimes
}
this.doCheck(site,true,retryTimes).catch(e => {
logger.error(`检查站点证书失败,${site.domain}`, e.message);
});
await utils.sleep(200);
await utils.sleep(100);
}
}
@@ -320,6 +349,12 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async saveSetting(userId: number, bean: UserSiteMonitorSetting) {
await this.userSettingsService.saveSetting(userId, bean);
if(bean.cron){
//注册job
await this.registerSiteMonitorJob(userId);
}else{
this.clearSiteMonitorJob(userId);
}
}
async ipCheckChange(req: { id: any; ipCheck: any }) {
@@ -396,4 +431,67 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
};
await batchAdd(list);
}
clearSiteMonitorJob(userId: number) {
this.cron.remove(`siteMonitor-${userId}`);
}
async registerSiteMonitorJob(userId?: number) {
if(!userId){
//注册公共job
logger.info(`注册站点证书检查定时任务`)
this.cron.register({
name: 'siteMonitor',
cron: '0 0 0 * * *',
job:async ()=>{
await this.triggerJobOnce()
},
});
logger.info(`注册站点证书检查定时任务完成`)
}else{
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting);
if (!setting.cron) {
return;
}
//注册个人的
this.cron.register({
name: `siteMonitor-${userId}`,
cron: setting.cron,
job: () => this.triggerJobOnce(userId),
});
}
}
async triggerJobOnce(userId?:number) {
logger.info(`站点证书检查开始执行[${userId??'所有用户'}]`);
const query:any = { disabled: false };
if(userId){
query.userId = userId;
//判断是否已关闭
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting);
if (!setting.cron) {
return;
}
}
let offset = 0;
const limit = 50;
while (true) {
const res = await this.page({
query: query,
page: { offset, limit },
});
const { records } = res;
if (records.length === 0) {
break;
}
offset += records.length;
const isCommon = !userId;
await this.checkList(records,isCommon);
}
logger.info(`站点证书检查完成[${userId??'所有用户'}]`);
}
}
@@ -110,6 +110,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
async add(bean: PipelineEntity) {
bean.status = ResultType.none
await this.save(bean);
return bean;
}
@@ -191,6 +192,9 @@ export class PipelineService extends BaseService<PipelineEntity> {
await this.checkMaxPipelineCount(bean, pipeline, domains);
}
if (!bean.status ){
bean.status = ResultType.none;
}
if (!isUpdate) {
//如果是添加,先保存一下,获取到id,更新pipeline.id
await this.addOrUpdate(bean);
@@ -1,29 +1,36 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo ,CertApplyPluginNames, CertReader} from '@certd/plugin-cert';
import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
import {
AliyunAccess,
AliyunClient,
AliyunClientV2,
AliyunSslClient,
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from "@certd/plugin-lib";
@IsTaskPlugin({
name: 'AliyunDeployCertToALB',
title: '阿里云-部署至ALB(应用负载均衡)',
icon: 'svg:icon-aliyun',
name: "AliyunDeployCertToALB",
title: "阿里云-部署至ALB(应用负载均衡)",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: 'ALB,更新监听器的默认证书',
desc: "ALB,更新监听器的默认证书",
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
export class AliyunDeployCertToALB extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量',
title: "域名证书",
helper: "请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量",
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'uploadCertToAliyun'],
name: "output-selector",
from: [...CertApplyPluginNames, "uploadCertToAliyun"]
},
required: true,
required: true
})
cert!: CertInfo | number;
@@ -31,122 +38,257 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
title: "证书接入点",
helper: "不会选就保持默认即可",
value: "cas.aliyuncs.com",
component: {
name: 'access-selector',
type: 'aliyun',
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,
required: true
})
casEndpoint!: string;
@TaskInput({
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "access-selector",
type: "aliyun"
},
required: true
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: 'ALB所在地区',
typeName: 'AliyunDeployCertToALB',
title: "ALB所在地区",
typeName: "AliyunDeployCertToALB",
multi: false,
action: AliyunDeployCertToALB.prototype.onGetRegionList.name,
watches: ['accessId'],
watches: ["accessId"]
})
)
regionId: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '负载均衡列表',
helper: '要部署证书的负载均衡ID',
typeName: 'AliyunDeployCertToALB',
title: "负载均衡列表",
helper: "要部署证书的负载均衡ID",
typeName: "AliyunDeployCertToALB",
action: AliyunDeployCertToALB.prototype.onGetLoadBalanceList.name,
watches: ['regionId'],
watches: ["regionId"]
})
)
loadBalancers!: string[];
@TaskInput(
createRemoteSelectInputDefine({
title: '监听器列表',
helper: '要部署证书的监听器列表',
typeName: 'AliyunDeployCertToALB',
title: "监听器列表",
helper: "要部署证书的监听器列表",
typeName: "AliyunDeployCertToALB",
action: AliyunDeployCertToALB.prototype.onGetListenerList.name,
watches: ['loadBalancers'],
watches: ["loadBalancers"]
})
)
listeners!: 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;
async onInstance() {}
@TaskInput({
title: "部署证书类型",
value: "default",
component: {
name: "a-select",
vModel: "value",
options: [
{
label: "默认证书",
value: "default"
},
{
label: "扩展证书",
value: "extension"
}
]
}
}
)
deployType!: string;
async onInstance() {
}
async getLBClient(access: AliyunAccess, region: string) {
const client = new AliyunClient({ logger: this.logger });
const version = '2020-06-16';
const version = "2020-06-16";
await client.init({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
//https://wafopenapi.cn-hangzhou.aliyuncs.com
endpoint: `https://alb.${region}.aliyuncs.com`,
apiVersion: version,
apiVersion: version
});
return client;
}
getALBClientV2(access: AliyunAccess) {
return access.getClient(`alb.${this.regionId}.aliyuncs.com`);
}
async execute(): Promise<void> {
this.logger.info(`开始部署证书到阿里云(alb)`);
const access = await this.getAccess<AliyunAccess>(this.accessId);
const certId = await this.getAliyunCertId(access);
const client = await this.getLBClient(access, this.regionId);
if (this.deployType === "extension") {
//部署扩展证书
const client = this.getALBClientV2(access);
await this.deployExtensionCert(client, certId);
} else {
const client = await this.getLBClient(access, this.regionId);
await this.deployDefaultCert(certId, client);
}
this.logger.info("执行完成");
}
private async deployDefaultCert(certId: any, client: AliyunClient) {
for (const listener of this.listeners) {
//查询原来的证书
let params: any = {};
params = {
ListenerId: listener,
Certificates: [
{
CertificateId: certId,
},
],
CertificateId: certId
}
]
};
const res = await client.request('UpdateListenerAttribute', params);
const res = await client.request("UpdateListenerAttribute", params);
this.checkRet(res);
this.logger.info(`部署${listener}监听器证书成功`, JSON.stringify(res));
//删除旧证书关联
}
this.logger.info('执行完成');
}
async deployExtensionCert(client: AliyunClientV2, certId: any) {
for (const listenerId of this.listeners) {
this.logger.info(`开始部署监听器${listenerId}的扩展证书`);
await client.doRequest({
// 接口名称
action: "AssociateAdditionalCertificatesWithListener",
// 接口版本
version: "2020-06-16",
data: {
query: {
ListenerId: listenerId,
Certificates: [
{
CertificateId: certId
}
]
}
}
});
this.logger.info(`部署监听器${listenerId}的扩展证书成功`);
}
await this.ctx.utils.sleep(10000)
for (const listener of this.listeners) {
await this.clearInvalidCert(client, listener);
}
}
async clearInvalidCert(client: AliyunClientV2, listener: string) {
const req = {
// 接口名称
action: "ListListenerCertificates",
// 接口版本
version: "2020-06-16",
data: {
query: {
ListenerId: listener
}
}
};
const res = await client.doRequest(req);
const list = res.Certificates;
if (list.length === 0) {
this.logger.info(`监听器${listener}没有绑定证书`);
return
}
const sslClient = new AliyunSslClient({
access: client.access,
logger: this.logger,
endpoint: this.casEndpoint
});
const certIds = [];
for (const item of list) {
if (item.Status !== "Associated") {
continue;
}
if (item.IsDefault) {
continue;
}
certIds.push( parseInt(item.CertificateId));
}
//检查是否过期,过期则删除
const invalidCertIds = [];
for (const certId of certIds) {
const res = await sslClient.getCertInfo(certId);
if (res.notAfter < new Date().getTime()) {
invalidCertIds.push(certId);
}
}
if (invalidCertIds.length === 0) {
this.logger.info(`监听器${listener}没有过期的证书`);
return
}
this.logger.info(`开始解绑过期的证书:${invalidCertIds}`);
await client.doRequest({
// 接口名称
action: "DissociateAdditionalCertificatesFromListener",
// 接口版本
version: "2020-06-16",
data: {
query: {
ListenerId: listener,
Certificates: invalidCertIds.map((item) => {
return {
CertificateId: item
}
})
}
}
});
this.logger.info(`解绑过期证书成功`);
}
async getAliyunCertId(access: AliyunAccess) {
let certId: any = this.cert;
if (typeof this.cert === 'object') {
if (typeof this.cert === "object") {
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: this.casEndpoint,
endpoint: this.casEndpoint
});
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt));
certId = await sslClient.uploadCert({
name: certName,
cert: this.cert,
cert: this.cert
});
}
return certId;
@@ -154,74 +296,74 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
async onGetRegionList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
throw new Error("请选择Access授权");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getLBClient(access, 'cn-shanghai');
const client = await this.getLBClient(access, "cn-shanghai");
const res = await client.request('DescribeRegions', {});
const res = await client.request("DescribeRegions", {});
this.checkRet(res);
if (!res?.Regions || res?.Regions.length === 0) {
throw new Error('没有找到Regions列表');
throw new Error("没有找到Regions列表");
}
return res.Regions.map((item: any) => {
return {
label: item.LocalName,
value: item.RegionId,
endpoint: item.RegionEndpoint,
endpoint: item.RegionEndpoint
};
});
}
async onGetLoadBalanceList(data: any) {
if (!this.accessId) {
throw new Error('请先选择Access授权');
throw new Error("请先选择Access授权");
}
if (!this.regionId) {
throw new Error('请先选择地区');
throw new Error("请先选择地区");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getLBClient(access, this.regionId);
const params = {
MaxResults: 100,
MaxResults: 100
};
const res = await client.request('ListLoadBalancers', params);
const res = await client.request("ListLoadBalancers", params);
this.checkRet(res);
if (!res?.LoadBalancers || res?.LoadBalancers.length === 0) {
throw new Error('没有找到LoadBalancers');
throw new Error("没有找到LoadBalancers");
}
return res.LoadBalancers.map((item: any) => {
const label = `${item.LoadBalancerId}<${item.LoadBalancerName}}>`;
return {
label: label,
value: item.LoadBalancerId,
value: item.LoadBalancerId
};
});
}
async onGetListenerList(data: any) {
if (!this.accessId) {
throw new Error('请先选择Access授权');
throw new Error("请先选择Access授权");
}
if (!this.regionId) {
throw new Error('请先选择地区');
throw new Error("请先选择地区");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getLBClient(access, this.regionId);
const params: any = {
MaxResults: 100,
MaxResults: 100
};
if (this.loadBalancers && this.loadBalancers.length > 0) {
params.LoadBalancerIds = this.loadBalancers;
}
const res = await client.request('ListListeners', params);
const res = await client.request("ListListeners", params);
this.checkRet(res);
if (!res?.Listeners || res?.Listeners.length === 0) {
throw new Error('没有找到HTTPS监听器');
throw new Error("没有找到HTTPS监听器");
}
return res.Listeners.map((item: any) => {
@@ -229,16 +371,19 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
return {
label: label,
value: item.ListenerId,
lbid: item.LoadBalancerId,
lbid: item.LoadBalancerId
};
});
}
checkRet(ret: any) {
if (ret.Code != null) {
throw new Error(ret.Message);
}
}
}
new AliyunDeployCertToALB();
+44 -41
View File
@@ -46,7 +46,7 @@ importers:
packages/core/acme-client:
dependencies:
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../basic
'@peculiar/x509':
specifier: ^1.11.0
@@ -207,10 +207,10 @@ importers:
packages/core/pipeline:
dependencies:
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../basic
'@certd/plus-core':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../pro/plus-core
dayjs:
specifier: ^1.11.7
@@ -415,7 +415,7 @@ importers:
packages/libs/lib-k8s:
dependencies:
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/basic
'@kubernetes/client-node':
specifier: 0.21.0
@@ -455,16 +455,16 @@ importers:
packages/libs/lib-server:
dependencies:
'@certd/acme-client':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/pipeline
'@certd/plus-core':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../pro/plus-core
'@midwayjs/cache':
specifier: ~3.14.0
@@ -607,16 +607,16 @@ importers:
packages/plugins/plugin-cert:
dependencies:
'@certd/acme-client':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/pipeline
'@certd/plugin-lib':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../plugin-lib
'@google-cloud/publicca':
specifier: ^1.3.0
@@ -685,6 +685,9 @@ importers:
'@alicloud/openapi-client':
specifier: ^0.4.14
version: 0.4.14
'@alicloud/openapi-util':
specifier: ^0.3.2
version: 0.3.2
'@alicloud/pop-core':
specifier: ^1.7.10
version: 1.8.0
@@ -695,10 +698,10 @@ importers:
specifier: ^3.787.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/basic
'@certd/pipeline':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/pipeline
'@kubernetes/client-node':
specifier: 0.21.0
@@ -786,19 +789,19 @@ importers:
packages/pro/commercial-core:
dependencies:
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/basic
'@certd/lib-server':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../libs/lib-server
'@certd/pipeline':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/pipeline
'@certd/plugin-plus':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../plugin-plus
'@certd/plus-core':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../plus-core
'@midwayjs/core':
specifier: ~3.20.3
@@ -883,22 +886,22 @@ importers:
specifier: ^1.0.2
version: 1.0.3
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/basic
'@certd/lib-k8s':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../libs/lib-k8s
'@certd/pipeline':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../plugins/plugin-lib
'@certd/plus-core':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../plus-core
ali-oss:
specifier: ^6.21.0
@@ -1001,7 +1004,7 @@ importers:
packages/pro/plus-core:
dependencies:
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/basic
dayjs:
specifier: ^1.11.7
@@ -1291,10 +1294,10 @@ importers:
version: 0.1.3(zod@3.24.4)
devDependencies:
'@certd/lib-iframe':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../libs/lib-iframe
'@certd/pipeline':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/pipeline
'@rollup/plugin-commonjs':
specifier: ^25.0.7
@@ -1477,46 +1480,46 @@ importers:
specifier: ^3.705.0
version: 3.810.0(aws-crt@1.26.2)
'@certd/acme-client':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/acme-client
'@certd/basic':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/basic
'@certd/commercial-core':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../pro/commercial-core
'@certd/cv4pve-api-javascript':
specifier: ^8.4.1
version: 8.4.1
'@certd/jdcloud':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../libs/lib-jdcloud
'@certd/lib-huawei':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../libs/lib-huawei
'@certd/lib-k8s':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../libs/lib-k8s
'@certd/lib-server':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../libs/lib-server
'@certd/midway-flyway-js':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../libs/midway-flyway-js
'@certd/pipeline':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../core/pipeline
'@certd/plugin-cert':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../plugins/plugin-cert
'@certd/plugin-lib':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../plugins/plugin-lib
'@certd/plugin-plus':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../pro/plugin-plus
'@certd/plus-core':
specifier: ^1.34.11
specifier: ^1.35.0
version: link:../../pro/plus-core
'@huaweicloud/huaweicloud-sdk-cdn':
specifier: ^3.1.120