From e5edfbfa6d8285faa4b930df16327faace7de1e7 Mon Sep 17 00:00:00 2001
From: GitHub Actions Bot
Date: Tue, 16 Jul 2024 19:24:08 +0000
Subject: [PATCH 01/29] =?UTF-8?q?=F0=9F=94=B1:=20[acme]=20sync=20upgrade?=
=?UTF-8?q?=20with=206=20commits=20[trident-sync]?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bump v5.4.0
Bump dependencies
Retry HTTP requests on server errors or when rate limited
Forgot to refresh directory timestamp after successful get
Add utility method tests
---
packages/core/acme-client/CHANGELOG.md | 7 +-
packages/core/acme-client/package.json | 12 +-
packages/core/acme-client/src/axios.js | 88 ++++++++++-
packages/core/acme-client/src/http.js | 7 +-
packages/core/acme-client/src/util.js | 53 ++++++-
.../core/acme-client/test/10-http.spec.js | 92 +++++++----
.../core/acme-client/test/10-util.spec.js | 145 ++++++++++++++++++
.../acme-client/test/fixtures/letsencrypt.crt | 23 +++
packages/core/acme-client/test/setup.js | 7 +
9 files changed, 382 insertions(+), 52 deletions(-)
create mode 100644 packages/core/acme-client/test/10-util.spec.js
create mode 100644 packages/core/acme-client/test/fixtures/letsencrypt.crt
diff --git a/packages/core/acme-client/CHANGELOG.md b/packages/core/acme-client/CHANGELOG.md
index 6f08cb2f..57d0328c 100644
--- a/packages/core/acme-client/CHANGELOG.md
+++ b/packages/core/acme-client/CHANGELOG.md
@@ -1,9 +1,10 @@
# Changelog
-## v5.4.0
+## v5.4.0 (2024-07-16)
* `added` Directory URLs for [Google](https://cloud.google.com/certificate-manager/docs/overview) ACME provider
-* `fixed` Invalidate ACME directory cache after 24 hours
+* `fixed` Invalidate ACME provider directory cache after 24 hours
+* `fixed` Retry HTTP requests on server errors or when rate limited - [#89](https://github.com/publishlab/node-acme-client/issues/89)
## v5.3.1 (2024-05-22)
@@ -13,7 +14,7 @@
## v5.3.0 (2024-02-05)
* `added` Support and tests for satisfying `tls-alpn-01` challenges
-* `changed` Replace `jsrsasign` with `@peculiar/x509` for certificate and CSR generation and parsing
+* `changed` Replace `jsrsasign` with `@peculiar/x509` for certificate and CSR handling
* `changed` Method `getChallengeKeyAuthorization()` now returns `$token.$thumbprint` when called with a `tls-alpn-01` challenge
* Previously returned base64url encoded SHA256 digest of `$token.$thumbprint` erroneously
* This change is not considered breaking since the previous behavior was incorrect
diff --git a/packages/core/acme-client/package.json b/packages/core/acme-client/package.json
index 1ca90e1b..07414ce5 100644
--- a/packages/core/acme-client/package.json
+++ b/packages/core/acme-client/package.json
@@ -2,7 +2,7 @@
"name": "acme-client",
"description": "Simple and unopinionated ACME client",
"author": "nmorsman",
- "version": "5.3.1",
+ "version": "5.4.0",
"main": "src/index.js",
"types": "types/index.d.ts",
"license": "MIT",
@@ -15,23 +15,23 @@
"types"
],
"dependencies": {
- "@peculiar/x509": "^1.10.0",
+ "@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",
- "debug": "^4.1.1",
+ "debug": "^4.3.5",
"node-forge": "^1.3.1"
},
"devDependencies": {
- "@types/node": "^20.12.12",
+ "@types/node": "^20.14.10",
"chai": "^4.4.1",
"chai-as-promised": "^7.1.2",
"eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.1",
"jsdoc-to-markdown": "^8.0.1",
- "mocha": "^10.4.0",
+ "mocha": "^10.6.0",
"nock": "^13.5.4",
- "tsd": "^0.31.0"
+ "tsd": "^0.31.1"
},
"scripts": {
"build-docs": "jsdoc2md src/client.js > docs/client.md && jsdoc2md src/crypto/index.js > docs/crypto.md && jsdoc2md src/crypto/forge.js > docs/forge.md",
diff --git a/packages/core/acme-client/src/axios.js b/packages/core/acme-client/src/axios.js
index e5ea3c66..b62c92b1 100644
--- a/packages/core/acme-client/src/axios.js
+++ b/packages/core/acme-client/src/axios.js
@@ -3,10 +3,14 @@
*/
const axios = require('axios');
+const { parseRetryAfterHeader } = require('./util');
+const { log } = require('./logger');
const pkg = require('./../package.json');
+const { AxiosError } = axios;
+
/**
- * Instance
+ * Defaults
*/
const instance = axios.create();
@@ -19,6 +23,9 @@ instance.defaults.acmeSettings = {
httpChallengePort: 80,
httpsChallengePort: 443,
tlsAlpnChallengePort: 443,
+
+ retryMaxAttempts: 5,
+ retryDefaultDelay: 5,
};
/**
@@ -30,6 +37,85 @@ instance.defaults.acmeSettings = {
instance.defaults.adapter = 'http';
+/**
+ * Retry requests on server errors or when rate limited
+ *
+ * https://datatracker.ietf.org/doc/html/rfc8555#section-6.6
+ */
+
+function isRetryableError(error) {
+ return (error.code !== 'ECONNABORTED')
+ && (error.code !== 'ERR_NOCK_NO_MATCH')
+ && (!error.response
+ || (error.response.status === 429)
+ || ((error.response.status >= 500) && (error.response.status <= 599)));
+}
+
+/* https://github.com/axios/axios/blob/main/lib/core/settle.js */
+function validateStatus(response) {
+ const validator = response.config.retryValidateStatus;
+
+ if (!response.status || !validator || validator(response.status)) {
+ return response;
+ }
+
+ throw new AxiosError(
+ `Request failed with status code ${response.status}`,
+ (Math.floor(response.status / 100) === 4) ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE,
+ response.config,
+ response.request,
+ response,
+ );
+}
+
+/* Pass all responses through the error interceptor */
+instance.interceptors.request.use((config) => {
+ if (!('retryValidateStatus' in config)) {
+ config.retryValidateStatus = config.validateStatus;
+ }
+
+ config.validateStatus = () => false;
+ return config;
+});
+
+/* Handle request retries if applicable */
+instance.interceptors.response.use(null, async (error) => {
+ const { config, response } = error;
+
+ if (!config) {
+ return Promise.reject(error);
+ }
+
+ /* Pick up errors we want to retry */
+ if (isRetryableError(error)) {
+ const { retryMaxAttempts, retryDefaultDelay } = instance.defaults.acmeSettings;
+ config.retryAttempt = ('retryAttempt' in config) ? (config.retryAttempt + 1) : 1;
+
+ if (config.retryAttempt <= retryMaxAttempts) {
+ const code = response ? `HTTP ${response.status}` : error.code;
+ log(`Caught ${code}, retry attempt ${config.retryAttempt}/${retryMaxAttempts} to URL ${config.url}`);
+
+ /* Attempt to parse Retry-After header, fallback to default delay */
+ let retryAfter = response ? parseRetryAfterHeader(response.headers['retry-after']) : 0;
+
+ if (retryAfter > 0) {
+ log(`Found retry-after response header with value: ${response.headers['retry-after']}, waiting ${retryAfter} seconds`);
+ }
+ else {
+ retryAfter = (retryDefaultDelay * config.retryAttempt);
+ log(`Unable to locate or parse retry-after response header, waiting ${retryAfter} seconds`);
+ }
+
+ /* Wait and retry the request */
+ await new Promise((resolve) => { setTimeout(resolve, (retryAfter * 1000)); });
+ return instance(config);
+ }
+ }
+
+ /* Validate and return response */
+ return validateStatus(response);
+});
+
/**
* Export instance
*/
diff --git a/packages/core/acme-client/src/http.js b/packages/core/acme-client/src/http.js
index cf56d940..0e1b4dcd 100644
--- a/packages/core/acme-client/src/http.js
+++ b/packages/core/acme-client/src/http.js
@@ -70,9 +70,11 @@ class HttpClient {
*/
async getDirectory() {
- const age = (Math.floor(Date.now() / 1000) - this.directoryTimestamp);
+ const now = Math.floor(Date.now() / 1000);
+ const age = (now - this.directoryTimestamp);
if (!this.directoryCache || (age > this.directoryMaxAge)) {
+ log(`Refreshing ACME directory, age: ${age}`);
const resp = await this.request(this.directoryUrl, 'get');
if (resp.status >= 400) {
@@ -84,6 +86,7 @@ class HttpClient {
}
this.directoryCache = resp.data;
+ this.directoryTimestamp = now;
}
return this.directoryCache;
@@ -108,7 +111,7 @@ class HttpClient {
*
* https://datatracker.ietf.org/doc/html/rfc8555#section-7.2
*
- * @returns {Promise} nonce
+ * @returns {Promise} Nonce
*/
async getNonce() {
diff --git a/packages/core/acme-client/src/util.js b/packages/core/acme-client/src/util.js
index be2cdd49..242b47eb 100644
--- a/packages/core/acme-client/src/util.js
+++ b/packages/core/acme-client/src/util.js
@@ -84,9 +84,12 @@ function retry(fn, { attempts = 5, min = 5000, max = 30000 } = {}) {
}
/**
- * Parse URLs from link header
+ * Parse URLs from Link header
*
- * @param {string} header Link header contents
+ * https://datatracker.ietf.org/doc/html/rfc8555#section-7.4.2
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
+ *
+ * @param {string} header Header contents
* @param {string} rel Link relation, default: `alternate`
* @returns {string[]} Array of URLs
*/
@@ -102,6 +105,37 @@ function parseLinkHeader(header, rel = 'alternate') {
return results.filter((r) => r);
}
+/**
+ * Parse date or duration from Retry-After header
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
+ *
+ * @param {string} header Header contents
+ * @returns {number} Retry duration in seconds
+ */
+
+function parseRetryAfterHeader(header) {
+ const sec = parseInt(header, 10);
+ const date = new Date(header);
+
+ /* Seconds into the future */
+ if (Number.isSafeInteger(sec) && (sec > 0)) {
+ return sec;
+ }
+
+ /* Future date string */
+ if (date instanceof Date && !Number.isNaN(date)) {
+ const now = new Date();
+ const diff = Math.ceil((date.getTime() - now.getTime()) / 1000);
+
+ if (diff > 0) {
+ return diff;
+ }
+ }
+
+ return 0;
+}
+
/**
* Find certificate chain with preferred issuer common name
* - If issuer is found in multiple chains, the closest to root wins
@@ -161,14 +195,16 @@ function findCertificateChainForIssuer(chains, issuer) {
function formatResponseError(resp) {
let result;
- if (resp.data.error) {
- result = resp.data.error.detail || resp.data.error;
- }
- else {
- result = resp.data.detail || JSON.stringify(resp.data);
+ if (resp.data) {
+ if (resp.data.error) {
+ result = resp.data.error.detail || resp.data.error;
+ }
+ else {
+ result = resp.data.detail || JSON.stringify(resp.data);
+ }
}
- return result.replace(/\n/g, '');
+ return (result || '').replace(/\n/g, '');
}
/**
@@ -296,6 +332,7 @@ async function retrieveTlsAlpnCertificate(host, port, timeout = 30000) {
module.exports = {
retry,
parseLinkHeader,
+ parseRetryAfterHeader,
findCertificateChainForIssuer,
formatResponseError,
getAuthoritativeDnsResolver,
diff --git a/packages/core/acme-client/test/10-http.spec.js b/packages/core/acme-client/test/10-http.spec.js
index 6967d9ec..743b7b6e 100644
--- a/packages/core/acme-client/test/10-http.spec.js
+++ b/packages/core/acme-client/test/10-http.spec.js
@@ -12,33 +12,12 @@ const pkg = require('./../package.json');
describe('http', () => {
let testClient;
+ const endpoint = `http://${uuid()}.example.com`;
const defaultUserAgent = `node-${pkg.name}/${pkg.version}`;
const customUserAgent = 'custom-ua-123';
- const primaryEndpoint = `http://${uuid()}.example.com`;
- const defaultUaEndpoint = `http://${uuid()}.example.com`;
- const customUaEndpoint = `http://${uuid()}.example.com`;
-
- /**
- * HTTP mocking
- */
-
- before(() => {
- const defaultUaOpts = { reqheaders: { 'User-Agent': defaultUserAgent } };
- const customUaOpts = { reqheaders: { 'User-Agent': customUserAgent } };
-
- nock(primaryEndpoint)
- .persist().get('/').reply(200, 'ok');
-
- nock(defaultUaEndpoint, defaultUaOpts)
- .persist().get('/').reply(200, 'ok');
-
- nock(customUaEndpoint, customUaOpts)
- .persist().get('/').reply(200, 'ok');
- });
-
- after(() => {
- axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
+ afterEach(() => {
+ nock.cleanAll();
});
/**
@@ -54,7 +33,8 @@ describe('http', () => {
*/
it('should http get', async () => {
- const resp = await testClient.request(primaryEndpoint, 'get');
+ nock(endpoint).get('/').reply(200, 'ok');
+ const resp = await testClient.request(endpoint, 'get');
assert.isObject(resp);
assert.strictEqual(resp.status, 200);
@@ -66,28 +46,76 @@ describe('http', () => {
*/
it('should request using default user-agent', async () => {
- const resp = await testClient.request(defaultUaEndpoint, 'get');
+ nock(endpoint).matchHeader('user-agent', defaultUserAgent).get('/').reply(200, 'ok');
+ axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
+ const resp = await testClient.request(endpoint, 'get');
assert.isObject(resp);
assert.strictEqual(resp.status, 200);
assert.strictEqual(resp.data, 'ok');
});
- it('should not request using custom user-agent', async () => {
- await assert.isRejected(testClient.request(customUaEndpoint, 'get'));
+ it('should reject using custom user-agent', async () => {
+ nock(endpoint).matchHeader('user-agent', defaultUserAgent).get('/').reply(200, 'ok');
+ axios.defaults.headers.common['User-Agent'] = customUserAgent;
+ await assert.isRejected(testClient.request(endpoint, 'get'));
});
it('should request using custom user-agent', async () => {
+ nock(endpoint).matchHeader('user-agent', customUserAgent).get('/').reply(200, 'ok');
axios.defaults.headers.common['User-Agent'] = customUserAgent;
- const resp = await testClient.request(customUaEndpoint, 'get');
+ const resp = await testClient.request(endpoint, 'get');
assert.isObject(resp);
assert.strictEqual(resp.status, 200);
assert.strictEqual(resp.data, 'ok');
});
- it('should not request using default user-agent', async () => {
- axios.defaults.headers.common['User-Agent'] = customUserAgent;
- await assert.isRejected(testClient.request(defaultUaEndpoint, 'get'));
+ it('should reject using default user-agent', async () => {
+ nock(endpoint).matchHeader('user-agent', customUserAgent).get('/').reply(200, 'ok');
+ axios.defaults.headers.common['User-Agent'] = defaultUserAgent;
+ await assert.isRejected(testClient.request(endpoint, 'get'));
+ });
+
+ /**
+ * Retry on HTTP errors
+ */
+
+ it('should retry on 429 rate limit', async () => {
+ let rateLimitCount = 0;
+
+ nock(endpoint).persist().get('/').reply(() => {
+ rateLimitCount += 1;
+
+ if (rateLimitCount < 3) {
+ return [429, 'Rate Limit Exceeded', { 'Retry-After': 1 }];
+ }
+
+ return [200, 'ok'];
+ });
+
+ assert.strictEqual(rateLimitCount, 0);
+ const resp = await testClient.request(endpoint, 'get');
+
+ assert.isObject(resp);
+ assert.strictEqual(resp.status, 200);
+ assert.strictEqual(resp.data, 'ok');
+ assert.strictEqual(rateLimitCount, 3);
+ });
+
+ it('should retry on 5xx server error', async () => {
+ let serverErrorCount = 0;
+
+ nock(endpoint).persist().get('/').reply(() => {
+ serverErrorCount += 1;
+ return [500, 'Internal Server Error', { 'Retry-After': 1 }];
+ });
+
+ assert.strictEqual(serverErrorCount, 0);
+ const resp = await testClient.request(endpoint, 'get');
+
+ assert.isObject(resp);
+ assert.strictEqual(resp.status, 500);
+ assert.strictEqual(serverErrorCount, 4);
});
});
diff --git a/packages/core/acme-client/test/10-util.spec.js b/packages/core/acme-client/test/10-util.spec.js
new file mode 100644
index 00000000..c9768be8
--- /dev/null
+++ b/packages/core/acme-client/test/10-util.spec.js
@@ -0,0 +1,145 @@
+/**
+ * Utility method tests
+ */
+
+const dns = require('dns').promises;
+const fs = require('fs').promises;
+const path = require('path');
+const { assert } = require('chai');
+const util = require('./../src/util');
+const { readCertificateInfo } = require('./../src/crypto');
+
+describe('util', () => {
+ const testCertPath1 = path.join(__dirname, 'fixtures', 'certificate.crt');
+ const testCertPath2 = path.join(__dirname, 'fixtures', 'letsencrypt.crt');
+
+ it('retry()', async () => {
+ let attempts = 0;
+ const backoffOpts = {
+ min: 100,
+ max: 500,
+ };
+
+ await assert.isRejected(util.retry(() => {
+ throw new Error('oops');
+ }, backoffOpts));
+
+ const r = await util.retry(() => {
+ attempts += 1;
+
+ if (attempts < 3) {
+ throw new Error('oops');
+ }
+
+ return 'abc';
+ }, backoffOpts);
+
+ assert.strictEqual(r, 'abc');
+ assert.strictEqual(attempts, 3);
+ });
+
+ it('parseLinkHeader()', () => {
+ const r1 = util.parseLinkHeader(';rel="alternate"');
+ assert.isArray(r1);
+ assert.strictEqual(r1.length, 1);
+ assert.strictEqual(r1[0], 'https://example.com/a');
+
+ const r2 = util.parseLinkHeader(';rel="test"');
+ assert.isArray(r2);
+ assert.strictEqual(r2.length, 0);
+
+ const r3 = util.parseLinkHeader('; rel="test"', 'test');
+ assert.isArray(r3);
+ assert.strictEqual(r3.length, 1);
+ assert.strictEqual(r3[0], 'http://example.com/c');
+
+ const r4 = util.parseLinkHeader(`; rel="alternate",
+ ; rel="nope",
+ ;rel="alternate",
+ ; rel="alternate"`);
+ assert.isArray(r4);
+ assert.strictEqual(r4.length, 3);
+ assert.strictEqual(r4[0], 'https://example.com/a');
+ assert.strictEqual(r4[1], 'https://example.com/b');
+ assert.strictEqual(r4[2], 'https://example.com/c');
+ });
+
+ it('parseRetryAfterHeader()', () => {
+ const r1 = util.parseRetryAfterHeader('');
+ assert.strictEqual(r1, 0);
+
+ const r2 = util.parseRetryAfterHeader('abcdef');
+ assert.strictEqual(r2, 0);
+
+ const r3 = util.parseRetryAfterHeader('123');
+ assert.strictEqual(r3, 123);
+
+ const r4 = util.parseRetryAfterHeader('123.456');
+ assert.strictEqual(r4, 123);
+
+ const r5 = util.parseRetryAfterHeader('-555');
+ assert.strictEqual(r5, 0);
+
+ const r6 = util.parseRetryAfterHeader('Wed, 21 Oct 2015 07:28:00 GMT');
+ assert.strictEqual(r6, 0);
+
+ const now = new Date();
+ const future = new Date(now.getTime() + 123000);
+ const r7 = util.parseRetryAfterHeader(future.toUTCString());
+ assert.isTrue(r7 > 100);
+ });
+
+ it('findCertificateChainForIssuer()', async () => {
+ const certs = [
+ (await fs.readFile(testCertPath1)).toString(),
+ (await fs.readFile(testCertPath2)).toString(),
+ ];
+
+ const r1 = util.findCertificateChainForIssuer(certs, 'abc123');
+ const r2 = util.findCertificateChainForIssuer(certs, 'example.com');
+ const r3 = util.findCertificateChainForIssuer(certs, 'E6');
+
+ [r1, r2, r3].forEach((r) => {
+ assert.isString(r);
+ assert.isNotEmpty(r);
+ });
+
+ assert.strictEqual(readCertificateInfo(r1).issuer.commonName, 'example.com');
+ assert.strictEqual(readCertificateInfo(r2).issuer.commonName, 'example.com');
+ assert.strictEqual(readCertificateInfo(r3).issuer.commonName, 'E6');
+ });
+
+ it('formatResponseError()', () => {
+ const e1 = util.formatResponseError({ data: { error: 'aaa' } });
+ assert.strictEqual(e1, 'aaa');
+
+ const e2 = util.formatResponseError({ data: { error: { detail: 'bbb' } } });
+ assert.strictEqual(e2, 'bbb');
+
+ const e3 = util.formatResponseError({ data: { detail: 'ccc' } });
+ assert.strictEqual(e3, 'ccc');
+
+ const e4 = util.formatResponseError({ data: { a: 123 } });
+ assert.strictEqual(e4, '{"a":123}');
+
+ const e5 = util.formatResponseError({});
+ assert.isString(e5);
+ assert.isEmpty(e5);
+ });
+
+ it('getAuthoritativeDnsResolver()', async () => {
+ /* valid domain - should not use global default */
+ const r1 = await util.getAuthoritativeDnsResolver('example.com');
+ assert.instanceOf(r1, dns.Resolver);
+ assert.isNotEmpty(r1.getServers());
+ assert.notDeepEqual(r1.getServers(), dns.getServers());
+
+ /* invalid domain - fallback to global default */
+ const r2 = await util.getAuthoritativeDnsResolver('invalid.xtldx');
+ assert.instanceOf(r2, dns.Resolver);
+ assert.deepStrictEqual(r2.getServers(), dns.getServers());
+ });
+
+ /* TODO: Figure out how to test this */
+ it('retrieveTlsAlpnCertificate()');
+});
diff --git a/packages/core/acme-client/test/fixtures/letsencrypt.crt b/packages/core/acme-client/test/fixtures/letsencrypt.crt
new file mode 100644
index 00000000..7efd44ca
--- /dev/null
+++ b/packages/core/acme-client/test/fixtures/letsencrypt.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDzzCCA1WgAwIBAgISA0ghDoSv5DpT3Pd3lqwjbVDDMAoGCCqGSM49BAMDMDIx
+CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
+NjAeFw0yNDA2MTAxNzEyMjZaFw0yNDA5MDgxNzEyMjVaMBQxEjAQBgNVBAMTCWxl
+bmNyLm9yZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEHJ3DjN7pYV3mftHzaP
+V/WI0RhOJnSI5AIFEPFHDi8UowOINRGIfm9FHGIDqrb4Rmyvr9JrrqBdFGDen8BW
+6OGjggJnMIICYzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
+CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFIdCTnxqmpOELDyzPaEM
+seB36lUOMB8GA1UdIwQYMBaAFJMnRpgDqVFojpjWxEJI2yO/WJTSMFUGCCsGAQUF
+BwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2U2Lm8ubGVuY3Iub3JnMCIGCCsG
+AQUFBzAChhZodHRwOi8vZTYuaS5sZW5jci5vcmcvMG8GA1UdEQRoMGaCCWxlbmNy
+Lm9yZ4IPbGV0c2VuY3J5cHQuY29tgg9sZXRzZW5jcnlwdC5vcmeCDXd3dy5sZW5j
+ci5vcmeCE3d3dy5sZXRzZW5jcnlwdC5jb22CE3d3dy5sZXRzZW5jcnlwdC5vcmcw
+EwYDVR0gBAwwCjAIBgZngQwBAgEwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEAdgA/
+F0tP1yJHWJQdZRyEvg0S7ZA3fx+FauvBvyiF7PhkbgAAAZADWfneAAAEAwBHMEUC
+IGlp+dPU2hLT2suTMYkYMlt/xbzSnKLZDA/wYSsPACP7AiEAxbAzx6mkzn0cs0hh
+ti6sLf0pcbmDhxHdlJRjuo6SQZEAdwDf4VbrqgWvtZwPhnGNqMAyTq5W2W6n9aVq
+AdHBO75SXAAAAZADWfqrAAAEAwBIMEYCIQCrAmDUrlX3oGhri1qCIb65Cuf8h2GR
+LC1VfXBenX7dCAIhALXwbhCQ1vO1WLv4CqyihMHOwFaICYqN/N6ylaBlVAM4MAoG
+CCqGSM49BAMDA2gAMGUCMFdgjOXGl+hE2ABDsAeuNq8wi34yTMUHk0KMTOjRAfy9
+rOCGQqvP0myoYlyzXOH9uQIxAMdkG1ZWBZS1dHavbPf1I/MjYpzX6gy0jVHIXXu5
+aYWylBi/Uf2RPj0LWFZh8tNa1Q==
+-----END CERTIFICATE-----
diff --git a/packages/core/acme-client/test/setup.js b/packages/core/acme-client/test/setup.js
index 9be01fc0..749a7ec7 100644
--- a/packages/core/acme-client/test/setup.js
+++ b/packages/core/acme-client/test/setup.js
@@ -29,6 +29,13 @@ if (process.env.ACME_TLSALPN_PORT) {
axios.defaults.acmeSettings.tlsAlpnChallengePort = process.env.ACME_TLSALPN_PORT;
}
+/**
+ * Greatly reduce retry duration while testing
+ */
+
+axios.defaults.acmeSettings.retryMaxAttempts = 3;
+axios.defaults.acmeSettings.retryDefaultDelay = 1;
+
/**
* External account binding
*/
From 7b451bbf6e6337507f4627b5a845f5bd96ab4f7b Mon Sep 17 00:00:00 2001
From: xiaojunnuo
Date: Tue, 13 Aug 2024 20:30:42 +0800
Subject: [PATCH 02/29] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=88=90?=
=?UTF-8?q?=E5=8A=9F=E5=90=8E=E8=B7=B3=E8=BF=87=E7=9A=84=E6=8F=90=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/core/pipeline/src/plugin/api.ts | 7 ++-
.../src/plugin/cert-plugin/base.ts | 7 +--
.../component/output-selector/index.vue | 4 +-
.../pipeline/component/step-form/index.vue | 47 +++++++++++--------
packages/ui/certd-server/.env.pgdev.yaml | 3 --
packages/ui/certd-server/.env.preview.yaml | 4 +-
packages/ui/certd-server/.env.production.yaml | 10 +++-
.../certd-server/src/config/config.default.ts | 3 ++
.../certd-server/src/config/config.preview.ts | 21 ---------
.../src/config/config.production.ts | 21 ---------
.../certd-server/src/config/config.syncdb.ts | 11 -----
.../src/config/config.unittest.ts | 7 ---
packages/ui/certd-server/src/configuration.ts | 10 +---
.../authority/controller/user-controller.ts | 10 +---
.../modules/authority/service/user-service.ts | 13 +++++
.../src/modules/auto/auto-register-cron.ts | 6 +--
.../pipeline/service/history-service.ts | 9 ++--
.../pipeline/service/pipeline-service.ts | 4 +-
.../plugin-aliyun/access/aliyun-access.ts | 3 +-
.../plugin/deploy-to-ack-ingress/index.ts | 12 ++---
.../plugin/deploy-to-cdn/index.ts | 30 ++----------
.../plugin/upload-to-aliyun/index.ts | 14 ++----
.../plugins/plugin-deploy-to-cdn.ts | 14 ++----
.../plugin-demo/plugins/plugin-test.ts | 41 ++++++++--------
.../plugin/host-shell-execute/index.ts | 9 +---
.../plugin/upload-to-host/index.ts | 10 +---
.../src/plugins/plugin-other/access/index.ts | 1 +
.../plugins/plugin-other/access/k8s-access.ts | 19 ++++++++
.../plugin-tencent/access/dnspod-access.ts | 3 +-
.../plugin/deploy-to-cdn/index.ts | 11 +----
.../plugin/deploy-to-clb/index.ts | 15 ++----
.../plugin/deploy-to-tke-ingress/index.ts | 14 ++----
.../plugin/upload-to-tencent/index.ts | 10 +---
33 files changed, 151 insertions(+), 252 deletions(-)
delete mode 100644 packages/ui/certd-server/src/config/config.preview.ts
delete mode 100644 packages/ui/certd-server/src/config/config.production.ts
delete mode 100644 packages/ui/certd-server/src/config/config.syncdb.ts
delete mode 100644 packages/ui/certd-server/src/config/config.unittest.ts
create mode 100644 packages/ui/certd-server/src/plugins/plugin-other/access/index.ts
create mode 100644 packages/ui/certd-server/src/plugins/plugin-other/access/k8s-access.ts
diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts
index 4f18a968..4da1c0d4 100644
--- a/packages/core/pipeline/src/plugin/api.ts
+++ b/packages/core/pipeline/src/plugin/api.ts
@@ -6,7 +6,7 @@ import { IAccessService } from "../access/index.js";
import { IEmailService } from "../service/index.js";
import { IContext } from "../core/index.js";
import { AxiosInstance } from "axios";
-import { logger } from "../utils/index.js";
+import { ILogger, logger } from "../utils/index.js";
export enum ContextScope {
global,
@@ -69,6 +69,9 @@ export type TaskInstanceContext = {
export abstract class AbstractTaskPlugin implements ITaskPlugin {
_result: TaskResult = { clearLastStatus: false, files: [], pipelineVars: {} };
ctx!: TaskInstanceContext;
+ logger!: ILogger;
+ accessService!: IAccessService;
+
clearLastStatus() {
this._result.clearLastStatus = true;
}
@@ -79,6 +82,8 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
setCtx(ctx: TaskInstanceContext) {
this.ctx = ctx;
+ this.logger = ctx.logger;
+ this.accessService = ctx.accessService;
}
randomFileId() {
diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts
index 527b0188..984cf437 100644
--- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts
+++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts
@@ -1,7 +1,6 @@
-import { AbstractTaskPlugin, HttpClient, IAccessService, IContext, Step, TaskInput, TaskOutput } from "@certd/pipeline";
+import { AbstractTaskPlugin, HttpClient, IContext, Step, TaskInput, TaskOutput } from "@certd/pipeline";
import dayjs from "dayjs";
import type { CertInfo } from "./acme.js";
-import { Logger } from "log4js";
import { CertReader } from "./cert-reader.js";
import JSZip from "jszip";
@@ -91,9 +90,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
// })
csrInfo!: string;
- logger!: Logger;
userContext!: IContext;
- accessService!: IAccessService;
http!: HttpClient;
lastStatus!: Step;
@@ -103,8 +100,6 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
cert?: CertInfo;
async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
this.userContext = this.ctx.userContext;
this.http = this.ctx.http;
this.lastStatus = this.ctx.lastStatus as Step;
diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/output-selector/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/output-selector/index.vue
index 0e21fa1c..d4230fdf 100644
--- a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/output-selector/index.vue
+++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/output-selector/index.vue
@@ -23,7 +23,7 @@ export default {
const currentStepIndex = inject("currentStepIndex") as Ref;
const currentTask = inject("currentTask") as Ref;
- const getPluginGroups = inject("getPluginGroups") as Ref;
+ const getPluginGroups = inject("getPluginGroups") as any;
const pluginGroups = getPluginGroups();
function onCreate() {
options.value = pluginGroups.getPreStepOutputOptions({
@@ -42,7 +42,7 @@ export default {
watch(
() => {
- return pluginGroups.value.map;
+ return pluginGroups.value?.map;
},
() => {
onCreate();
diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/step-form/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/step-form/index.vue
index b4c82a9b..8d0b45e7 100644
--- a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/step-form/index.vue
+++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/step-form/index.vue
@@ -61,24 +61,7 @@
-
+
@@ -289,10 +272,36 @@ export default {
};
}
+ const runStrategyProps = ref({
+ title: "运行策略",
+ key: "strategy.runStrategy",
+ component: {
+ name: "a-select",
+ vModel: "value",
+ options: [
+ { value: 0, label: "正常运行(证书申请任务请选择它)" },
+ { value: 1, label: "成功后跳过(非证书任务请选择它)" }
+ ]
+ },
+ helper: {
+ render: () => {
+ return (
+
+
正常运行:每次都运行,证书任务需要每次都运行
+
成功后跳过:在证书没变化时,该任务成功一次之后跳过,不重复部署
+
保持默认即可,如果你想要再次测试部署,可以临时设置为正常运行
+
+ );
+ }
+ },
+ rules: [{ required: true, message: "此项必填" }]
+ });
+
return {
...useStepForm(),
labelCol: { span: 6 },
- wrapperCol: { span: 16 }
+ wrapperCol: { span: 16 },
+ runStrategyProps
};
}
};
diff --git a/packages/ui/certd-server/.env.pgdev.yaml b/packages/ui/certd-server/.env.pgdev.yaml
index 220ac326..ba30ee9f 100644
--- a/packages/ui/certd-server/.env.pgdev.yaml
+++ b/packages/ui/certd-server/.env.pgdev.yaml
@@ -1,6 +1,3 @@
-koa:
- port: 7001
-
flyway:
scriptDir: './db/migration-pg'
diff --git a/packages/ui/certd-server/.env.preview.yaml b/packages/ui/certd-server/.env.preview.yaml
index 10b4b96d..a03a3e16 100644
--- a/packages/ui/certd-server/.env.preview.yaml
+++ b/packages/ui/certd-server/.env.preview.yaml
@@ -1,2 +1,2 @@
-koa:
- port: 7001
+preview:
+ enabled: true
diff --git a/packages/ui/certd-server/.env.production.yaml b/packages/ui/certd-server/.env.production.yaml
index 10b4b96d..b6c1623c 100644
--- a/packages/ui/certd-server/.env.production.yaml
+++ b/packages/ui/certd-server/.env.production.yaml
@@ -1,2 +1,8 @@
-koa:
- port: 7001
+preview:
+ enabled: false
+
+typeorm:
+ dataSource:
+ default:
+ logging: false
+
diff --git a/packages/ui/certd-server/src/config/config.default.ts b/packages/ui/certd-server/src/config/config.default.ts
index 1a843c67..7929535d 100644
--- a/packages/ui/certd-server/src/config/config.default.ts
+++ b/packages/ui/certd-server/src/config/config.default.ts
@@ -38,7 +38,10 @@ const development = {
},
},
cron: {
+ //启动时立即触发一次
immediateTriggerOnce: false,
+ //启动时仅注册admin(id=1)用户的
+ onlyAdminUser: false,
},
/**
* 演示环境
diff --git a/packages/ui/certd-server/src/config/config.preview.ts b/packages/ui/certd-server/src/config/config.preview.ts
deleted file mode 100644
index ebe30965..00000000
--- a/packages/ui/certd-server/src/config/config.preview.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { MidwayConfig } from '@midwayjs/core';
-import { mergeConfig } from './loader.js';
-
-const preview = {
- /**
- * 演示环境
- */
- preview: {
- enabled: true,
- },
- typeorm: {
- dataSource: {
- default: {
- logging: false,
- },
- },
- },
-} as MidwayConfig;
-
-mergeConfig(preview, 'preview');
-export default preview;
diff --git a/packages/ui/certd-server/src/config/config.production.ts b/packages/ui/certd-server/src/config/config.production.ts
deleted file mode 100644
index 41a3442a..00000000
--- a/packages/ui/certd-server/src/config/config.production.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { MidwayConfig } from '@midwayjs/core';
-import { mergeConfig } from './loader.js';
-
-const production = {
- /**
- * 演示环境
- */
- preview: {
- enabled: false,
- },
- typeorm: {
- dataSource: {
- default: {
- logging: false,
- },
- },
- },
-} as MidwayConfig;
-
-mergeConfig(production, 'production');
-export default production;
diff --git a/packages/ui/certd-server/src/config/config.syncdb.ts b/packages/ui/certd-server/src/config/config.syncdb.ts
deleted file mode 100644
index 478af50b..00000000
--- a/packages/ui/certd-server/src/config/config.syncdb.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { MidwayConfig } from '@midwayjs/core';
-
-export default {
- typeorm: {
- dataSource: {
- default: {
- synchronize: true, // 如果第一次使用,不存在表,有同步的需求可以写 true
- },
- },
- },
-} as MidwayConfig;
diff --git a/packages/ui/certd-server/src/config/config.unittest.ts b/packages/ui/certd-server/src/config/config.unittest.ts
deleted file mode 100644
index 423dfcd3..00000000
--- a/packages/ui/certd-server/src/config/config.unittest.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { MidwayConfig } from '@midwayjs/core';
-
-export default {
- koa: {
- port: null,
- },
-} as MidwayConfig;
diff --git a/packages/ui/certd-server/src/configuration.ts b/packages/ui/certd-server/src/configuration.ts
index 2e85a1d1..f46d5163 100644
--- a/packages/ui/certd-server/src/configuration.ts
+++ b/packages/ui/certd-server/src/configuration.ts
@@ -1,4 +1,4 @@
-import { Configuration, App } from '@midwayjs/core';
+import { App, Configuration } from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
import * as orm from '@midwayjs/typeorm';
import * as cache from '@midwayjs/cache';
@@ -14,12 +14,7 @@ import { PreviewMiddleware } from './middleware/preview.js';
import { AuthorityMiddleware } from './middleware/authority.js';
import { logger } from './utils/logger.js';
import { ResetPasswdMiddleware } from './middleware/reset-passwd/middleware.js';
-// import { DefaultErrorFilter } from './filter/default.filter.js';
-// import { NotFoundFilter } from './filter/notfound.filter.js';
import DefaultConfig from './config/config.default.js';
-import ProductionConfig from './config/config.production.js';
-import PreviewConfig from './config/config.preview.js';
-import UnittestConfig from './config/config.unittest.js';
process.on('uncaughtException', error => {
console.error('未捕获的异常:', error);
@@ -43,9 +38,6 @@ process.on('uncaughtException', error => {
importConfigs: [
{
default: DefaultConfig,
- preview: PreviewConfig,
- production: ProductionConfig,
- unittest: UnittestConfig,
},
],
})
diff --git a/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts b/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts
index 4503ce9c..8e535d54 100644
--- a/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts
+++ b/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts
@@ -1,12 +1,4 @@
-import {
- Provide,
- Controller,
- Post,
- Inject,
- Body,
- Query,
- ALL,
-} from '@midwayjs/core';
+import { Provide, Controller, Post, Inject, Body, Query, ALL } from '@midwayjs/core';
import { UserService } from '../service/user-service.js';
import { CrudController } from '../../../basic/crud-controller.js';
import { RoleService } from '../service/role-service.js';
diff --git a/packages/ui/certd-server/src/modules/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/authority/service/user-service.ts
index c124800a..5e883c7a 100644
--- a/packages/ui/certd-server/src/modules/authority/service/user-service.ts
+++ b/packages/ui/certd-server/src/modules/authority/service/user-service.ts
@@ -191,4 +191,17 @@ export class UserService extends BaseService {
};
await this.update(param);
}
+
+ async delete(ids: any) {
+ if (typeof ids === 'string') {
+ ids = ids.split(',');
+ ids = ids.map(id => parseInt(id));
+ }
+ if (ids instanceof Array) {
+ if (ids.includes(1)) {
+ throw new CommonException('不能删除管理员');
+ }
+ }
+ await super.delete(ids);
+ }
}
diff --git a/packages/ui/certd-server/src/modules/auto/auto-register-cron.ts b/packages/ui/certd-server/src/modules/auto/auto-register-cron.ts
index 8cfbacba..da50e3a6 100644
--- a/packages/ui/certd-server/src/modules/auto/auto-register-cron.ts
+++ b/packages/ui/certd-server/src/modules/auto/auto-register-cron.ts
@@ -8,8 +8,8 @@ export class AutoRegisterCron {
@Inject()
pipelineService: PipelineService;
- @Config('preview.enabled')
- private preview: boolean;
+ @Config('cron.onlyAdminUser')
+ private onlyAdminUser: boolean;
// @Inject()
// echoPlugin: EchoPlugin;
@@ -19,7 +19,7 @@ export class AutoRegisterCron {
@Init()
async init() {
logger.info('加载定时trigger开始');
- await this.pipelineService.onStartup(this.immediateTriggerOnce, this.preview);
+ await this.pipelineService.onStartup(this.immediateTriggerOnce, this.onlyAdminUser);
// logger.info(this.echoPlugin, this.echoPlugin.test);
// logger.info('加载定时trigger完成');
//
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts
index af34254e..cb8d97a3 100644
--- a/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts
+++ b/packages/ui/certd-server/src/modules/pipeline/service/history-service.ts
@@ -136,10 +136,13 @@ export class HistoryService extends BaseService {
}
async deleteByIds(ids: number[], userId: number) {
- await this.repository.delete({
+ const condition: any = {
id: In(ids),
- userId,
- });
+ };
+ if (userId != null) {
+ condition.userId = userId;
+ }
+ await this.repository.delete(condition);
await this.logService.deleteByHistoryIds(ids);
}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
index 568436c0..1940e7d0 100644
--- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
+++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
@@ -149,10 +149,10 @@ export class PipelineService extends BaseService {
/**
* 应用启动后初始加载记录
*/
- async onStartup(immediateTriggerOnce: boolean, preview: boolean) {
+ async onStartup(immediateTriggerOnce: boolean, onlyAdminUser: boolean) {
logger.info('加载定时trigger开始');
await this.foreachPipeline(async entity => {
- if (preview && entity.userId !== 1) {
+ if (onlyAdminUser && entity.userId !== 1) {
return;
}
const pipeline = JSON.parse(entity.content ?? '{}');
diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/access/aliyun-access.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/access/aliyun-access.ts
index dba037ff..d7ec28ce 100644
--- a/packages/ui/certd-server/src/plugins/plugin-aliyun/access/aliyun-access.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/access/aliyun-access.ts
@@ -11,8 +11,7 @@ export class AliyunAccess {
component: {
placeholder: 'accessKeyId',
},
- helper:
- '注意:证书申请,需要dns解析权限;其他阿里云插件,也需要对应的权限,比如证书上传需要证书管理权限',
+ helper: '注意:证书申请,需要dns解析权限;其他阿里云插件,也需要对应的权限,比如证书上传需要证书管理权限',
required: true,
})
accessKeyId = '';
diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts
index bfcbf2bf..87fbd61e 100644
--- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts
@@ -1,4 +1,4 @@
-import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
// @ts-ignore
import { ROAClient } from '@alicloud/pop-core';
import { AliyunAccess } from '../../access/index.js';
@@ -106,13 +106,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
})
accessId!: string;
- accessService!: IAccessService;
- logger!: ILogger;
-
- async onInstance(): Promise {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance(): Promise {}
async execute(): Promise {
console.log('开始部署证书到阿里云cdn');
const { regionId, ingressClass, clusterId, isPrivateIpAddress, cert } = this;
@@ -121,7 +115,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
const kubeConfigStr = await this.getKubeConfig(client, clusterId, isPrivateIpAddress);
this.logger.info('kubeconfig已成功获取');
- const k8sClient = new K8sClient(kubeConfigStr,this.logger);
+ const k8sClient = new K8sClient(kubeConfigStr, this.logger);
const ingressType = ingressClass || 'qcloud';
if (ingressType === 'qcloud') {
throw new Error('暂未实现');
diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts
index 05f57a3c..1ecf4d62 100644
--- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-cdn/index.ts
@@ -1,11 +1,4 @@
-import {
- AbstractTaskPlugin,
- IAccessService,
- ILogger,
- IsTaskPlugin, pluginGroups,
- RunStrategy,
- TaskInput,
-} from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import dayjs from 'dayjs';
import Core from '@alicloud/pop-core';
import RPCClient from '@alicloud/pop-core';
@@ -57,18 +50,10 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
})
accessId!: string;
- accessService!: IAccessService;
- logger!: ILogger;
-
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
console.log('开始部署证书到阿里云cdn');
- const access = (await this.accessService.getById(
- this.accessId
- )) as AliyunAccess;
+ const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
const client = this.getClient(access);
const params = await this.buildParams();
await this.doRequest(client, params);
@@ -85,8 +70,7 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
}
async buildParams() {
- const CertName =
- (this.certName ?? 'certd') + '-' + dayjs().format('YYYYMMDDHHmmss');
+ const CertName = (this.certName ?? 'certd') + '-' + dayjs().format('YYYYMMDDHHmmss');
const cert: any = this.cert;
return {
RegionId: 'cn-hangzhou',
@@ -103,11 +87,7 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
const requestOption = {
method: 'POST',
};
- const ret: any = await client.request(
- 'SetDomainServerCertificate',
- params,
- requestOption
- );
+ const ret: any = await client.request('SetDomainServerCertificate', params, requestOption);
this.checkRet(ret);
this.logger.info('设置cdn证书成功:', ret.RequestId);
}
diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts
index c8940eaa..0430aab9 100644
--- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/upload-to-aliyun/index.ts
@@ -1,8 +1,7 @@
-import { AbstractTaskPlugin, IAccessService, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import Core from '@alicloud/pop-core';
import { AliyunAccess } from '../../access/index.js';
import { appendTimeSuffix, checkRet, ZoneOptions } from '../../utils/index.js';
-import { Logger } from 'log4js';
@IsTaskPlugin({
name: 'uploadCertToAliyun',
@@ -26,8 +25,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
title: '大区',
value: 'cn-hangzhou',
component: {
- name: 'a-select',
- mode: 'tags',
+ name: 'a-auto-complete',
vModel: 'value',
options: ZoneOptions,
},
@@ -61,13 +59,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
})
aliyunCertId!: string;
- accessService!: IAccessService;
- logger!: Logger;
-
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
console.log('开始部署证书到阿里云cdn');
diff --git a/packages/ui/certd-server/src/plugins/plugin-cloudflare/plugins/plugin-deploy-to-cdn.ts b/packages/ui/certd-server/src/plugins/plugin-cloudflare/plugins/plugin-deploy-to-cdn.ts
index cd1dd610..9b82e665 100644
--- a/packages/ui/certd-server/src/plugins/plugin-cloudflare/plugins/plugin-deploy-to-cdn.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-cloudflare/plugins/plugin-deploy-to-cdn.ts
@@ -1,4 +1,4 @@
-import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
@IsTaskPlugin({
@@ -28,8 +28,8 @@ export class CloudflareDeployToCDNPlugin extends AbstractTaskPlugin {
title: '选择框',
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
- name: 'a-select',
- mode: 'tags',
+ name: 'a-auto-complete',
+ vModel: 'value',
options: [
{ value: '1', label: '选项1' },
{ value: '2', label: '选项2' },
@@ -71,13 +71,7 @@ export class CloudflareDeployToCDNPlugin extends AbstractTaskPlugin {
})
accessId!: string;
- accessService!: IAccessService;
- logger!: ILogger;
-
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
const { select, text, cert, accessId } = this;
const certReader = new CertReader(cert);
diff --git a/packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.ts b/packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.ts
index 719daad8..48fffd54 100644
--- a/packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.ts
@@ -1,6 +1,5 @@
-import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
-import { K8sClient } from '@certd/lib-k8s';
@IsTaskPlugin({
name: 'demoTest',
@@ -28,8 +27,8 @@ export class DemoTestPlugin extends AbstractTaskPlugin {
title: '选择框',
component: {
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
- name: 'a-select',
- mode: 'tags',
+ name: 'a-auto-complete',
+ vModel: 'value',
options: [
{ value: '1', label: '选项1' },
{ value: '2', label: '选项2' },
@@ -55,7 +54,7 @@ export class DemoTestPlugin extends AbstractTaskPlugin {
component: {
name: 'pi-output-selector',
},
- required: true,
+ // required: true,
})
cert!: CertInfo;
@@ -67,31 +66,33 @@ export class DemoTestPlugin extends AbstractTaskPlugin {
name: 'pi-access-selector',
type: 'demo', //固定授权类型
},
- rules: [{ required: true, message: '此项必填' }],
+ // rules: [{ required: true, message: '此项必填' }],
})
accessId!: string;
- accessService!: IAccessService;
- logger!: ILogger;
-
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
const { select, text, cert, accessId } = this;
- const certReader = new CertReader(cert);
- const access = await this.accessService.getById(accessId);
- this.logger.debug('access', access);
- this.logger.debug('certReader', certReader);
+
+ try {
+ const access = await this.accessService.getById(accessId);
+ this.logger.debug('access', access);
+ } catch (e) {
+ this.logger.error('获取授权失败', e);
+ }
+
+ try {
+ const certReader = new CertReader(cert);
+ this.logger.debug('certReader', certReader);
+ } catch (e) {
+ this.logger.error('读取crt失败', e);
+ }
+
this.logger.info('DemoTestPlugin execute');
this.logger.info('text:', text);
this.logger.info('select:', select);
this.logger.info('switch:', this.switch);
this.logger.info('授权id:', accessId);
- //TODO 这里实现你要部署的执行方法
-
- new K8sClient('111', null);
}
}
//TODO 这里实例化插件,进行注册
diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts
index 1b9b7361..5194c1dd 100644
--- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/host-shell-execute/index.ts
@@ -1,4 +1,4 @@
-import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { SshClient } from '../../lib/ssh.js';
@IsTaskPlugin({
@@ -34,12 +34,7 @@ export class HostShellExecutePlugin extends AbstractTaskPlugin {
})
script!: string;
- accessService!: IAccessService;
- logger!: ILogger;
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
const { script, accessId } = this;
const connectConf = await this.accessService.getById(accessId);
diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts
index 4a9d1cdb..4397205a 100644
--- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts
@@ -1,4 +1,4 @@
-import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import { SshClient } from '../../lib/ssh.js';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import * as fs from 'fs';
@@ -86,13 +86,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin {
})
hostKeyPath!: string;
- accessService!: IAccessService;
- logger!: ILogger;
-
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
copyFile(srcFile: string, destFile: string) {
const dir = destFile.substring(0, destFile.lastIndexOf('/'));
diff --git a/packages/ui/certd-server/src/plugins/plugin-other/access/index.ts b/packages/ui/certd-server/src/plugins/plugin-other/access/index.ts
new file mode 100644
index 00000000..e4a8d690
--- /dev/null
+++ b/packages/ui/certd-server/src/plugins/plugin-other/access/index.ts
@@ -0,0 +1 @@
+export * from './k8s-access.js';
diff --git a/packages/ui/certd-server/src/plugins/plugin-other/access/k8s-access.ts b/packages/ui/certd-server/src/plugins/plugin-other/access/k8s-access.ts
new file mode 100644
index 00000000..355fc8a9
--- /dev/null
+++ b/packages/ui/certd-server/src/plugins/plugin-other/access/k8s-access.ts
@@ -0,0 +1,19 @@
+import { IsAccess, AccessInput } from '@certd/pipeline';
+
+@IsAccess({
+ name: 'k8s',
+ title: 'k8s授权',
+ desc: '',
+})
+export class K8sAccess {
+ @AccessInput({
+ title: 'kubeconfig',
+ component: {
+ placeholder: 'kubeconfig',
+ },
+ required: true,
+ })
+ kubeconfig = '';
+}
+
+new K8sAccess();
diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/access/dnspod-access.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/access/dnspod-access.ts
index 59eb81f4..19887128 100644
--- a/packages/ui/certd-server/src/plugins/plugin-tencent/access/dnspod-access.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-tencent/access/dnspod-access.ts
@@ -10,8 +10,7 @@ export class DnspodAccess {
title: '端点',
component: {
placeholder: 'endpoint',
- name: 'a-select',
- mode: 'tags',
+ name: 'a-auto-complete',
vModel: 'value',
options: [
{ value: 'https://dnsapi.cn', label: '中国站' },
diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts
index ad148ed0..851d493e 100644
--- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts
@@ -1,4 +1,4 @@
-import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs';
import { TencentAccess } from '../../access/index.js';
import { CertInfo } from '@certd/plugin-cert';
@@ -59,14 +59,7 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin {
// })
// endpoint!: string;
- accessService!: IAccessService;
-
- logger!: ILogger;
-
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
const accessProvider: TencentAccess = (await this.accessService.getById(this.accessId)) as TencentAccess;
diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-clb/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-clb/index.ts
index 6bbfcdea..0f9a09f6 100644
--- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-clb/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-clb/index.ts
@@ -1,4 +1,4 @@
-import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs';
import { TencentAccess } from '../../access/index.js';
import dayjs from 'dayjs';
@@ -17,10 +17,9 @@ import dayjs from 'dayjs';
export class DeployToClbPlugin extends AbstractTaskPlugin {
@TaskInput({
title: '大区',
- value: 'ap-guangzhou',
component: {
- name: 'a-select',
- mode: 'tags',
+ name: 'a-auto-complete',
+ vModel: 'value',
options: [
{ value: 'ap-guangzhou' },
{ value: 'ap-beijing' },
@@ -93,13 +92,7 @@ export class DeployToClbPlugin extends AbstractTaskPlugin {
})
accessId!: string;
- accessService!: IAccessService;
- logger!: ILogger;
-
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
const accessProvider = (await this.accessService.getById(this.accessId)) as TencentAccess;
const client = this.getClient(accessProvider, this.region);
diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-tke-ingress/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-tke-ingress/index.ts
index 34aa5b7c..5382a8c2 100644
--- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-tke-ingress/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-tke-ingress/index.ts
@@ -1,8 +1,7 @@
-import { AbstractTaskPlugin, IAccessService, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs';
import { K8sClient } from '@certd/lib-k8s';
import dayjs from 'dayjs';
-import { Logger } from 'log4js';
@IsTaskPlugin({
name: 'DeployCertToTencentTKEIngress',
@@ -38,8 +37,10 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
@TaskInput({
title: 'ingress类型',
+ value: 'qcloud',
component: {
- name: 'a-select',
+ name: 'a-auto-complete',
+ vModel: 'value',
options: [{ value: 'qcloud' }, { value: 'nginx' }],
},
helper: '可选 qcloud / nginx',
@@ -89,12 +90,7 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
})
cert!: any;
- logger!: Logger;
- accessService!: IAccessService;
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
const accessProvider = await this.accessService.getById(this.accessId);
const tkeClient = this.getTkeClient(accessProvider, this.region);
diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts
index ad9d90bb..f409074b 100644
--- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/upload-to-tencent/index.ts
@@ -1,4 +1,4 @@
-import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs';
import dayjs from 'dayjs';
@@ -43,13 +43,7 @@ export class UploadToTencentPlugin extends AbstractTaskPlugin {
})
tencentCertId?: string;
- accessService!: IAccessService;
- logger!: ILogger;
-
- async onInstance() {
- this.accessService = this.ctx.accessService;
- this.logger = this.ctx.logger;
- }
+ async onInstance() {}
async execute(): Promise {
const { accessId, name, cert } = this;
From 746bb9d385e2f397daef4976eca1d4782a2f5ebd Mon Sep 17 00:00:00 2001
From: xiaojunnuo
Date: Wed, 14 Aug 2024 15:10:55 +0800
Subject: [PATCH 03/29] =?UTF-8?q?perf:=20=E6=9B=B4=E6=96=B0k8s=E5=BA=95?=
=?UTF-8?q?=E5=B1=82api=E5=BA=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/libs/lib-k8s/package.json | 3 +-
packages/libs/lib-k8s/src/lib/k8s.client.ts | 122 ++++++++++--------
.../src/plugin/cert-plugin/lego/index.ts | 2 +-
.../db/migration-pg/v10007__access_text.sql | 1 +
packages/ui/certd-server/src/plugins/index.ts | 1 +
.../plugin/deploy-to-ack-ingress/index.ts | 13 +-
.../plugins/plugin-other/access/k8s-access.ts | 2 +
.../plugin/deploy-to-tke-ingress/index.ts | 5 +-
8 files changed, 85 insertions(+), 64 deletions(-)
create mode 100644 packages/ui/certd-server/db/migration-pg/v10007__access_text.sql
diff --git a/packages/libs/lib-k8s/package.json b/packages/libs/lib-k8s/package.json
index 57514d94..772b42f6 100644
--- a/packages/libs/lib-k8s/package.json
+++ b/packages/libs/lib-k8s/package.json
@@ -13,8 +13,7 @@
"preview": "vite preview"
},
"dependencies": {
- "kubernetes-client": "^9.0.0",
- "shelljs": "^0.8.5"
+ "@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
"@certd/pipeline": "^1.23.1",
diff --git a/packages/libs/lib-k8s/src/lib/k8s.client.ts b/packages/libs/lib-k8s/src/lib/k8s.client.ts
index cc909e9d..6af30be2 100644
--- a/packages/libs/lib-k8s/src/lib/k8s.client.ts
+++ b/packages/libs/lib-k8s/src/lib/k8s.client.ts
@@ -1,32 +1,40 @@
-import kubernetesClient from 'kubernetes-client';
-//@ts-ignore
+import { KubeConfig, CoreV1Api, V1Secret, NetworkingV1Api, V1Ingress } from '@kubernetes/client-node';
import dns from 'dns';
import { ILogger } from '@certd/pipeline';
-//@ts-ignore
-const { KubeConfig, Client, Request } = kubernetesClient;
-
-export class K8sClient {
+export type K8sClientOpts = {
kubeConfigStr: string;
- lookup!: any;
- client!: any;
logger: ILogger;
- constructor(kubeConfigStr: string, logger: ILogger) {
- this.kubeConfigStr = kubeConfigStr;
- this.logger = logger;
+ //{ [domain]:{ip:'xxx.xx.xxx'} }
+ //暂时没用
+ lookup?: any;
+};
+export class K8sClient {
+ kubeconfig!: KubeConfig;
+ kubeConfigStr: string;
+ lookup!: (hostnameReq: any, options: any, callback: any) => void;
+ client!: CoreV1Api;
+ logger: ILogger;
+ constructor(opts: K8sClientOpts) {
+ this.kubeConfigStr = opts.kubeConfigStr;
+ this.logger = opts.logger;
+ this.setLookup(opts.lookup);
this.init();
}
init() {
const kubeconfig = new KubeConfig();
kubeconfig.loadFromString(this.kubeConfigStr);
- const reqOpts = { kubeconfig, request: {} } as any;
- if (this.lookup) {
- reqOpts.request.lookup = this.lookup;
- }
+ this.kubeconfig = kubeconfig;
+ this.client = kubeconfig.makeApiClient(CoreV1Api);
- const backend = new Request(reqOpts);
- this.client = new Client({ backend, version: '1.13' });
+ // const reqOpts = { kubeconfig, request: {} } as any;
+ // if (this.lookup) {
+ // reqOpts.request.lookup = this.lookup;
+ // }
+ //
+ // const backend = new Request(reqOpts);
+ // this.client = new Client({ backend, version: '1.13' });
}
/**
@@ -34,6 +42,9 @@ export class K8sClient {
* @param localRecords { [domain]:{ip:'xxx.xx.xxx'} }
*/
setLookup(localRecords: { [key: string]: { ip: string } }) {
+ if (localRecords == null) {
+ return;
+ }
this.lookup = (hostnameReq: any, options: any, callback: any) => {
this.logger.info('custom lookup', hostnameReq, localRecords);
if (localRecords[hostnameReq]) {
@@ -43,7 +54,6 @@ export class K8sClient {
dns.lookup(hostnameReq, options, callback);
}
};
- this.init();
}
/**
@@ -51,9 +61,9 @@ export class K8sClient {
* @param opts = {namespace:default}
* @returns secretsList
*/
- async getSecret(opts: { namespace: string }) {
+ async getSecrets(opts: { namespace: string }) {
const namespace = opts.namespace || 'default';
- return await this.client.api.v1.namespaces(namespace).secrets.get();
+ return await this.client.listNamespacedSecret(namespace);
}
/**
@@ -61,59 +71,61 @@ export class K8sClient {
* @param opts {namespace:default, body:yamlStr}
* @returns {Promise<*>}
*/
- async createSecret(opts: any) {
+ async createSecret(opts: { namespace: string; body: V1Secret }) {
const namespace = opts.namespace || 'default';
- const created = await this.client.api.v1.namespaces(namespace).secrets.post({
- body: opts.body,
- });
- this.logger.info('new secrets:', created);
- return created;
+ const created = await this.client.createNamespacedSecret(namespace, opts.body);
+ this.logger.info('new secrets:', created.body);
+ return created.body;
}
- async updateSecret(opts: any) {
+ // async updateSecret(opts: any) {
+ // const namespace = opts.namespace || 'default';
+ // const secretName = opts.secretName;
+ // if (secretName == null) {
+ // throw new Error('secretName 不能为空');
+ // }
+ // return await this.client.replaceNamespacedSecret(secretName, namespace, opts.body);
+ // }
+
+ async patchSecret(opts: { namespace: string; secretName: string; body: V1Secret }) {
const namespace = opts.namespace || 'default';
const secretName = opts.secretName;
if (secretName == null) {
throw new Error('secretName 不能为空');
}
- return await this.client.api.v1.namespaces(namespace).secrets(secretName).put({
- body: opts.body,
- });
+ const res = await this.client.patchNamespacedSecret(secretName, namespace, opts.body);
+ this.logger.info('secret patched:', res.body);
+ return res.body;
}
- async patchSecret(opts: any) {
+ async getIngressList(opts: { namespace: string }) {
const namespace = opts.namespace || 'default';
- const secretName = opts.secretName;
- if (secretName == null) {
- throw new Error('secretName 不能为空');
- }
- return await this.client.api.v1.namespaces(namespace).secrets(secretName).patch({
- body: opts.body,
- });
+ const client = this.kubeconfig.makeApiClient(NetworkingV1Api);
+ const res = await client.listNamespacedIngress(namespace);
+ this.logger.info('ingress list get:', res.body);
+ return res.body;
}
- async getIngressList(opts: any) {
- const namespace = opts.namespace || 'default';
- return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses.get();
- }
+ // async getIngress(opts: { namespace: string; ingressName: string }) {
+ // const namespace = opts.namespace || 'default';
+ // const ingressName = opts.ingressName;
+ // if (!ingressName) {
+ // throw new Error('ingressName 不能为空');
+ // }
+ // const client = this.kubeconfig.makeApiClient(NetworkingV1Api);
+ // const res = await client.listNamespacedIngress();
+ // return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).get();
+ // }
- async getIngress(opts: any) {
+ async patchIngress(opts: { namespace: string; ingressName: string; body: V1Ingress }) {
const namespace = opts.namespace || 'default';
const ingressName = opts.ingressName;
if (!ingressName) {
throw new Error('ingressName 不能为空');
}
- return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).get();
- }
-
- async patchIngress(opts: any) {
- const namespace = opts.namespace || 'default';
- const ingressName = opts.ingressName;
- if (!ingressName) {
- throw new Error('ingressName 不能为空');
- }
- return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).patch({
- body: opts.body,
- });
+ const client = this.kubeconfig.makeApiClient(NetworkingV1Api);
+ const res = await client.patchNamespacedIngress(ingressName, namespace, opts.body);
+ this.logger.info('ingress patched:', res.body);
+ return res;
}
}
diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts
index ac709556..e1fb6df2 100644
--- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts
+++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/lego/index.ts
@@ -97,7 +97,7 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
this.http = this.ctx.http;
this.lastStatus = this.ctx.lastStatus as Step;
if (this.legoEabAccessId) {
- this.eab = await this.ctx.accessService.getById(this.legoEabAccessId);
+ this.eab = await this.accessService.getById(this.legoEabAccessId);
}
}
async onInit(): Promise {}
diff --git a/packages/ui/certd-server/db/migration-pg/v10007__access_text.sql b/packages/ui/certd-server/db/migration-pg/v10007__access_text.sql
new file mode 100644
index 00000000..2dbecaf7
--- /dev/null
+++ b/packages/ui/certd-server/db/migration-pg/v10007__access_text.sql
@@ -0,0 +1 @@
+alter table cd_access alter column setting type text using setting::text;
diff --git a/packages/ui/certd-server/src/plugins/index.ts b/packages/ui/certd-server/src/plugins/index.ts
index 875ae53d..8cc824c3 100644
--- a/packages/ui/certd-server/src/plugins/index.ts
+++ b/packages/ui/certd-server/src/plugins/index.ts
@@ -4,3 +4,4 @@ export * from './plugin-tencent/index.js';
export * from './plugin-host/index.js';
export * from './plugin-huawei/index.js';
export * from './plugin-demo/index.js';
+export * from './plugin-other/index.js';
diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts
index 87fbd61e..1290899a 100644
--- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-ack-ingress/index.ts
@@ -115,7 +115,10 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
const kubeConfigStr = await this.getKubeConfig(client, clusterId, isPrivateIpAddress);
this.logger.info('kubeconfig已成功获取');
- const k8sClient = new K8sClient(kubeConfigStr, this.logger);
+ const k8sClient = new K8sClient({
+ kubeConfigStr,
+ logger: this.logger,
+ });
const ingressType = ingressClass || 'qcloud';
if (ingressType === 'qcloud') {
throw new Error('暂未实现');
@@ -128,7 +131,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
// await this.restartIngress({ k8sClient, props })
}
- async restartIngress(options: { k8sClient: any }) {
+ async restartIngress(options: { k8sClient: K8sClient }) {
const { k8sClient } = options;
const { namespace } = this;
@@ -141,10 +144,10 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
};
const ingressList = await k8sClient.getIngressList({ namespace });
console.log('ingressList:', ingressList);
- if (!ingressList || !ingressList.body || !ingressList.body.items) {
+ if (!ingressList || !ingressList.items) {
return;
}
- const ingressNames = ingressList.body.items
+ const ingressNames = ingressList.items
.filter((item: any) => {
if (!item.spec.tls) {
return false;
@@ -165,7 +168,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractTaskPlugin {
}
}
- async patchNginxCertSecret(options: { cert: any; k8sClient: any }) {
+ async patchNginxCertSecret(options: { cert: CertInfo; k8sClient: K8sClient }) {
const { cert, k8sClient } = options;
const crt = cert.crt;
const key = cert.key;
diff --git a/packages/ui/certd-server/src/plugins/plugin-other/access/k8s-access.ts b/packages/ui/certd-server/src/plugins/plugin-other/access/k8s-access.ts
index 355fc8a9..70811b24 100644
--- a/packages/ui/certd-server/src/plugins/plugin-other/access/k8s-access.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-other/access/k8s-access.ts
@@ -9,6 +9,8 @@ export class K8sAccess {
@AccessInput({
title: 'kubeconfig',
component: {
+ name: 'a-textarea',
+ vModel: 'value',
placeholder: 'kubeconfig',
},
required: true,
diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-tke-ingress/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-tke-ingress/index.ts
index 5382a8c2..8b41c38d 100644
--- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-tke-ingress/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-tke-ingress/index.ts
@@ -97,7 +97,10 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, this.clusterId);
this.logger.info('kubeconfig已成功获取');
- const k8sClient = new K8sClient(kubeConfigStr, this.logger);
+ const k8sClient = new K8sClient({
+ kubeConfigStr,
+ logger: this.logger,
+ });
if (this.clusterIp != null) {
if (!this.clusterDomain) {
this.clusterDomain = `${this.clusterId}.ccs.tencent-cloud.com`;
From db9d27468e880ed4d6227b576081756d9bab087e Mon Sep 17 00:00:00 2001
From: xiaojunnuo
Date: Wed, 14 Aug 2024 21:24:12 +0800
Subject: [PATCH 04/29] =?UTF-8?q?chore:=20license=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
LICENSE.md | 30 +++++
README.md | 33 ++++--
.../core/pipeline/src/core/license.spec.ts | 1 -
packages/core/pipeline/src/core/license.ts | 42 +++++--
.../certd-client/src/api/modules/api.user.ts | 9 +-
.../src/layout/components/vip-info/api.ts | 9 ++
.../src/layout/components/vip-info/index.vue | 83 +++++++++++++
.../src/layout/layout-framework.vue | 61 +++++-----
.../src/layout/layout-outside.vue | 1 -
packages/ui/certd-client/src/router/index.ts | 1 +
.../ui/certd-client/src/store/modules/user.ts | 47 ++++++--
.../ui/certd-client/src/style/common.less | 8 +-
.../db/migration-pg/v10007__access_text.sql | 4 +
.../ui/certd-server/src/basic/constants.ts | 4 +
.../src/basic/exception/vip-exception.ts | 10 ++
packages/ui/certd-server/src/configuration.ts | 2 -
.../src/middleware/global-exception.ts | 2 +-
.../src/middleware/report.middleware.ts | 27 -----
.../ui/certd-server/src/middleware/report.ts | 24 ----
.../mine/controller/mine-controller.ts | 9 ++
.../pipeline/service/pipeline-service.ts | 22 +++-
.../system/controller/plus-controller.ts | 57 +++++++++
.../src/plugins/plugin-other/index.ts | 1 +
.../src/plugins/plugin-other/plugins/index.ts | 1 +
.../plugin-other/plugins/plugin-k8s.ts | 109 ++++++++++++++++++
25 files changed, 483 insertions(+), 114 deletions(-)
create mode 100644 LICENSE.md
create mode 100644 packages/ui/certd-client/src/layout/components/vip-info/api.ts
create mode 100644 packages/ui/certd-client/src/layout/components/vip-info/index.vue
create mode 100644 packages/ui/certd-server/src/basic/exception/vip-exception.ts
delete mode 100644 packages/ui/certd-server/src/middleware/report.middleware.ts
delete mode 100644 packages/ui/certd-server/src/middleware/report.ts
create mode 100644 packages/ui/certd-server/src/modules/system/controller/plus-controller.ts
create mode 100644 packages/ui/certd-server/src/plugins/plugin-other/index.ts
create mode 100644 packages/ui/certd-server/src/plugins/plugin-other/plugins/index.ts
create mode 100644 packages/ui/certd-server/src/plugins/plugin-other/plugins/plugin-k8s.ts
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 00000000..bfab44e5
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,30 @@
+# Certd Open Source License
+
+- This project is licensed under the **GNU Affero General Public License (AGPL)** with the following additional terms.
+- 本项目遵循 GNU Affero General Public License(AGPL),并附加以下条款。
+
+## 1. License Terms ( 许可证条款 )
+
+1. **Freedom to Use** (自由使用)
+ - You are free to use, copy, modify, and distribute the source code of this project for personal or organizational use, provided that you comply with the terms of this license.
+ - 您可以自由使用、复制、修改和分发本项目的源代码,前提是您遵循本许可证的条款。
+
+2. **Modification for Personal Use** (个人使用的修改)
+ - Individuals and companies are allowed to modify the project according to their needs for non-commercial purposes. However, modifications to the logo, copyright information, or any code related to licensing are strictly prohibited.
+ - 个人和公司允许根据自身需求对本项目进行修改以供非商业用途。但任何对logo、版权信息或与许可相关代码的修改都是严格禁止的。
+
+3. **Commercial Authorization** (商业授权)
+ - If you wish to make any form of monetary gain from this project, you must first obtain commercial authorization from the original author. Users should contact the author directly to negotiate the relevant licensing terms.
+ - 如果您希望从本项目获得任何形式的经济收益,您必须首先从原作者处获得商业授权,用户应直接与作者联系,以协商相关许可条款。
+
+4. **Retention of Rights** (保留权利)
+ - All rights, title, and interest in the project remain with the original author.
+ - 本项目的所有权利、标题和利益仍归原作者所有。
+
+## 2. As a contributor ( 作为贡献者 )
+ - you should agree that your contributed code:
+ - 您应同意您贡献的代码:
+ 1. - The original author can adjust the open-source agreement to be more strict or relaxed.
+ - 原作者可以调整开源协议以使其更严格或更宽松。
+ 2. - Can be used for commercial purposes.
+ - 可用于商业用途。
diff --git a/README.md b/README.md
index 694218ff..601cc8bc 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-# CertD
+# Certd
-CertD 是一个免费全自动申请和自动部署更新SSL证书的工具。
-后缀D取自linux守护进程的命名风格,意为证书守护进程。
+Certd 是一个免费全自动申请和自动部署更新SSL证书的工具。
+后缀d取自linux守护进程的命名风格,意为证书守护进程。
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签
@@ -180,26 +180,37 @@ docker compose up -d
## 十、捐赠
-媳妇儿说:“一天到晚搞开源,也不管管老婆孩子!😡😡😡”
-拜托各位捐赠支持一下,让媳妇儿开心开心,我也能有更多时间进行开源项目,感谢🙏🙏🙏
-
-
-
+支持开源,为爱发电,我已入驻爱发电
+https://afdian.com/a/greper
+
+发电权益:
+1. 可加入发电专属群(先加我好友,发送发电截图,我拉你进群)
+2. 你的需求优先实现
+3. 可以获得作者一对一技术支持
+4. 更多权益陆续增加中...
## 十一、贡献代码
-[贡献插件教程](./plugin.md)
+1. [贡献插件教程](./plugin.md)
+2. 作为贡献者,代表您同意您贡献的代码如下许可:
+ 1. 可以调整开源协议以使其更严格或更宽松。
+ 2. 可以用于商业用途。
+## 十二、 开源许可
+* 本项目遵循 GNU Affero General Public License(AGPL)开源协议。
+* 允许个人和公司使用、复制、修改和分发本项目,禁止任何形式的商业用途
+* 未获得商业授权情况下,禁止任何对logo、版权信息及授权许可相关代码的修改。
+* 如需商业授权,请联系作者。
-## 十二、我的其他项目(求Star)
+## 十三、我的其他项目(求Star)
* [袖手GPT](https://ai.handsfree.work/) ChatGPT,国内可用,无需FQ,每日免费额度
* [fast-crud](https://gitee.com/fast-crud/fast-crud/) 基于vue3的crud快速开发框架
* [dev-sidecar](https://github.com/docmirror/dev-sidecar/) 直连访问github工具,无需FQ,解决github无法访问的问题
-## 十三、更新日志
+## 十四、更新日志
更新日志:[CHANGELOG](./CHANGELOG.md)
diff --git a/packages/core/pipeline/src/core/license.spec.ts b/packages/core/pipeline/src/core/license.spec.ts
index 4f3c2543..772ad5cb 100644
--- a/packages/core/pipeline/src/core/license.spec.ts
+++ b/packages/core/pipeline/src/core/license.spec.ts
@@ -3,7 +3,6 @@ import { equal } from "assert";
describe("license", function () {
it("#license", async function () {
const req = {
- appKey: "z4nXOeTeSnnpUpnmsV",
subjectId: "999",
license: "",
};
diff --git a/packages/core/pipeline/src/core/license.ts b/packages/core/pipeline/src/core/license.ts
index a5cef7e4..0c145b31 100644
--- a/packages/core/pipeline/src/core/license.ts
+++ b/packages/core/pipeline/src/core/license.ts
@@ -2,8 +2,8 @@ import { createVerify } from "node:crypto";
import { logger } from "../utils/index.js";
const SecreteKey =
- "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQXY3TGtMaUp1dGM0NzhTU3RaTExjajVGZXh1YjJwR2NLMGxwa0hwVnlZWjhMY29rRFhuUlAKUGQ5UlJSTVRTaGJsbFl2Mzd4QUhOV1ZIQ0ZsWHkrQklVU001bUlBU1NDQTV0azlJNmpZZ2F4bEFDQm1BY0lGMwozKzBjeGZIYVkrVW9YdVluMkZ6YUt2Ym5GdFZIZ0lkMDg4a3d4clZTZzlCT3BDRVZIR1pxR2I5TWN5MXVHVXhUClFTVENCbmpoTWZlZ0p6cXVPYWVOY0ZPSE5tbmtWRWpLTythbTBPeEhNS1lyS3ZnQnVEbzdoVnFENlBFMUd6V3AKZHdwZUV4QXZDSVJxL2pWTkdRK3FtMkRWOVNJZ3U5bmF4MktmSUtFeU50dUFFS1VpekdqL0VmRFhDM1cxMExhegpKaGNYNGw1SUFZU1o3L3JWVmpGbExWSVl0WDU1T054L1Z3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K";
-const appKey = "z4nXOeTeSnnpUpnmsV";
+ "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQkNnS0NBUUVBMjdoZDM0NjRYbyt3TkpmTTNCWjE5MXlQK2NLaTd3ck9CbXdjTWJPZUdsNlJOMUVtTGhyMgplOFdvOGpmMW9IVXc5RFV6L2I2ZHU3Q3ZXMXZNUDA1Q3dSS3lNd2U3Q1BYRGQ2U01mSkwxRFZyUkw5Ylh0cEYzCjJkQVA5UENrakFJcFMvRE5jVkhLRXk1QW8yMnFFenpTKzlUT0JVY2srREdZcmo4KzI5U3h2aEZDRE5ZbEE2d1EKbEkyRWc5TWNBV2xDU3p1S1JWa2ZWUWdYVlU3SmE5OXp1Um1oWWtYZjFxQzBLcVAwQkpDakdDNEV6ZHorMmwyaAo2T3RxVHVVLzRkemlYYnRMUS8vU0JqNEgxdi9PZ3dUZjJkSVBjUnRHOXlWVTB2ZlQzVzdUTkdlMjU3em5ESDBYCkd6Wm4zdWJxTXJuL084b2ltMHRrS3ZHZXZ1V2ZraWNwVVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==";
+export const appKey = "GGtrKRWRknFdIID0rW";
export type LicenseVerifyReq = {
subjectId: string;
license: string;
@@ -18,11 +18,15 @@ type License = {
duration: number;
version: number;
secret: string;
+ level: number;
signature: string;
};
class LicenseHolder {
isPlus = false;
+ expireTime = 0;
+ level = 1;
+ message?: string = undefined;
}
const holder = new LicenseHolder();
holder.isPlus = false;
@@ -35,9 +39,20 @@ class LicenseVerifier {
return await this.verify(req);
}
- setPlus(value: boolean) {
- holder.isPlus = value;
- return value;
+ setPlus(value: boolean, info: any = {}) {
+ if (value && !info) {
+ holder.isPlus = true;
+ holder.expireTime = info.expireTime;
+ holder.level = info.level;
+ } else {
+ holder.isPlus = false;
+ holder.expireTime = 0;
+ holder.level = 1;
+ holder.message = info.message;
+ }
+ return {
+ ...holder,
+ };
}
async verify(req: LicenseVerifyReq) {
this.licenseReq = req;
@@ -54,7 +69,7 @@ class LicenseVerifier {
const json: License = JSON.parse(licenseJson);
if (json.expireTime < Date.now()) {
logger.warn("授权已过期");
- return this.setPlus(false);
+ return this.setPlus(false, { message: "授权已过期" });
}
const content = `${appKey},${this.licenseReq.subjectId},${json.code},${json.secret},${json.activeTime},${json.duration},${json.expireTime},${json.version}`;
const publicKey = Buffer.from(SecreteKey, "base64").toString();
@@ -62,9 +77,12 @@ class LicenseVerifier {
this.checked = true;
if (!res) {
logger.warn("授权校验失败");
- return this.setPlus(false);
+ return this.setPlus(false, { message: "授权校验失败" });
}
- return this.setPlus(true);
+ return this.setPlus(true, {
+ expireTime: json.expireTime,
+ level: json.level || 1,
+ });
}
verifySignature(content: string, signature: any, publicKey: string) {
@@ -80,6 +98,14 @@ export function isPlus() {
return holder.isPlus;
}
+export function getPlusInfo() {
+ return {
+ isPlus: holder.isPlus,
+ level: holder.level,
+ expireTime: holder.expireTime,
+ };
+}
+
export async function verify(req: LicenseVerifyReq) {
return await verifier.reVerify(req);
}
diff --git a/packages/ui/certd-client/src/api/modules/api.user.ts b/packages/ui/certd-client/src/api/modules/api.user.ts
index 6eccf47d..d51e38eb 100644
--- a/packages/ui/certd-client/src/api/modules/api.user.ts
+++ b/packages/ui/certd-client/src/api/modules/api.user.ts
@@ -60,7 +60,14 @@ export async function mine(): Promise {
});
}
return await request({
- url: "/sys/authority/user/mine",
+ url: "/mine/info",
+ method: "post"
+ });
+}
+
+export async function getPlusInfo() {
+ return await request({
+ url: "/mine/plusInfo",
method: "post"
});
}
diff --git a/packages/ui/certd-client/src/layout/components/vip-info/api.ts b/packages/ui/certd-client/src/layout/components/vip-info/api.ts
new file mode 100644
index 00000000..f90a058a
--- /dev/null
+++ b/packages/ui/certd-client/src/layout/components/vip-info/api.ts
@@ -0,0 +1,9 @@
+import { request } from "/@/api/service";
+
+export async function doActive(form: any) {
+ return await request({
+ url: "/sys/plus/active",
+ method: "post",
+ data: form
+ });
+}
diff --git a/packages/ui/certd-client/src/layout/components/vip-info/index.vue b/packages/ui/certd-client/src/layout/components/vip-info/index.vue
new file mode 100644
index 00000000..d45e49ac
--- /dev/null
+++ b/packages/ui/certd-client/src/layout/components/vip-info/index.vue
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+ 专业版
+ {{ expireTime }}
+
+ 当前免费版
+
+
+
+
+
+
diff --git a/packages/ui/certd-client/src/layout/layout-framework.vue b/packages/ui/certd-client/src/layout/layout-framework.vue
index c3d58f08..929de14b 100644
--- a/packages/ui/certd-client/src/layout/layout-framework.vue
+++ b/packages/ui/certd-client/src/layout/layout-framework.vue
@@ -12,14 +12,14 @@