@abtnode/core 1.6.4 → 1.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/blocklet/manager/disk.js +125 -130
- package/lib/cert.js +116 -0
- package/lib/index.js +18 -8
- package/lib/migrations/1.6.4-security.js +18 -2
- package/lib/migrations/1.6.5-security.js +60 -0
- package/lib/migrations/1.6.7-certificate.js +30 -0
- package/lib/router/helper.js +99 -107
- package/lib/router/manager.js +10 -6
- package/lib/states/base.js +3 -212
- package/lib/states/blocklet.js +58 -17
- package/lib/states/index.js +4 -21
- package/lib/states/node.js +2 -1
- package/lib/util/blocklet.js +1 -1
- package/lib/util/index.js +2 -0
- package/package.json +22 -20
package/lib/cert.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const { EventEmitter } = require('events');
|
|
2
|
+
const CertificateManager = require('@abtnode/certificate-manager/sdk/manager');
|
|
3
|
+
const logger = require('@abtnode/logger')('@abtnode/core:cert');
|
|
4
|
+
const states = require('./states');
|
|
5
|
+
|
|
6
|
+
const onCertExpired = async (cert) => {
|
|
7
|
+
logger.info('send certificate expire notification', { domain: cert.domain });
|
|
8
|
+
states.notification.create({
|
|
9
|
+
title: 'SSL Certificate Expired',
|
|
10
|
+
description: `Your SSL certificate for domain ${cert.domain} has expired, please update it in Blocklet Server`,
|
|
11
|
+
severity: 'error',
|
|
12
|
+
entityType: 'certificate',
|
|
13
|
+
entityId: cert.id,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const onCertAboutExpire = (cert) => {
|
|
18
|
+
logger.info('send certificate about-expire notification', { domain: cert.domain });
|
|
19
|
+
states.notification.create({
|
|
20
|
+
title: 'SSL Certificate Expire Warning',
|
|
21
|
+
description: `Your SSL certificate for domain ${cert.domain} will expire in ${
|
|
22
|
+
cert.expireInDays
|
|
23
|
+
} days (on ${new Date(cert.validTo).toLocaleString()}), please remember to update it in Blocklet Server`,
|
|
24
|
+
severity: 'warning',
|
|
25
|
+
entityType: 'certificate',
|
|
26
|
+
entityId: cert.id, // eslint-disable-line no-underscore-dangle
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const onCertIssued = (cert) => {
|
|
31
|
+
states.notification.create({
|
|
32
|
+
title: 'Certificate Issued',
|
|
33
|
+
description: `The ${cert.domain} certificate is issued successfully`,
|
|
34
|
+
severity: 'success',
|
|
35
|
+
entityType: 'certificate',
|
|
36
|
+
entityId: cert.id,
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
class Cert extends EventEmitter {
|
|
41
|
+
constructor({ maintainerEmail, dataDir }) {
|
|
42
|
+
super();
|
|
43
|
+
|
|
44
|
+
this.manager = new CertificateManager({ maintainerEmail, dataDir });
|
|
45
|
+
|
|
46
|
+
this.manager.on('cert.issued', (cert) => {
|
|
47
|
+
this.emit('cert.issued', cert);
|
|
48
|
+
onCertIssued(cert);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.manager.on('cert.expired', onCertExpired);
|
|
52
|
+
this.manager.on('cert.about_to_expire', onCertAboutExpire);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
start() {
|
|
56
|
+
return this.manager.start();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static fixCertificate(entity) {
|
|
60
|
+
// Hack: this logic exists because gql does not allow line breaks in arg values
|
|
61
|
+
entity.privateKey = entity.privateKey.split('|').join('\n');
|
|
62
|
+
entity.certificate = entity.certificate.split('|').join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getAll() {
|
|
66
|
+
return this.manager.getAll();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getAllNormal() {
|
|
70
|
+
return this.manager.getAllNormal();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getByDomain(domain) {
|
|
74
|
+
return this.manager.getByDomain(domain);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async add(data) {
|
|
78
|
+
if (!data.certificate || !data.privateKey) {
|
|
79
|
+
throw new Error('certificate and privateKey is required');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Cert.fixCertificate(data);
|
|
83
|
+
|
|
84
|
+
await this.manager.add(data);
|
|
85
|
+
logger.info('add certificate result', { name: data.name });
|
|
86
|
+
this.emit('cert.added', data);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async issue({ domain }) {
|
|
90
|
+
logger.info(`generate certificate for ${domain}`);
|
|
91
|
+
|
|
92
|
+
return this.manager.issue(domain);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async upsertByDomain(data) {
|
|
96
|
+
return this.manager.upsertByDomain(data);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async update(data) {
|
|
100
|
+
return this.manager.update(data.id, { name: data.name, public: data.public });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async remove({ id }) {
|
|
104
|
+
await this.manager.remove(id);
|
|
105
|
+
logger.info('delete certificate', { id });
|
|
106
|
+
this.emit('cert.removed');
|
|
107
|
+
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async addWithoutValidations(data) {
|
|
112
|
+
return this.manager.addWithoutValidations(data);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = Cert;
|
package/lib/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
toBlockletSource,
|
|
12
12
|
} = require('@blocklet/meta/lib/constants');
|
|
13
13
|
const { listProviders } = require('@abtnode/router-provider');
|
|
14
|
+
const { DEFAULT_CERTIFICATE_EMAIL } = require('@abtnode/constant');
|
|
14
15
|
|
|
15
16
|
const RoutingSnapshot = require('./states/routing-snapshot');
|
|
16
17
|
const BlockletRegistry = require('./blocklet/registry');
|
|
@@ -22,6 +23,7 @@ const NodeAPI = require('./api/node');
|
|
|
22
23
|
const TeamAPI = require('./api/team');
|
|
23
24
|
const WebHook = require('./webhook');
|
|
24
25
|
const states = require('./states');
|
|
26
|
+
const Cert = require('./cert');
|
|
25
27
|
|
|
26
28
|
const IP = require('./util/ip');
|
|
27
29
|
const DomainStatus = require('./util/domain-status');
|
|
@@ -108,7 +110,11 @@ function ABTNode(options) {
|
|
|
108
110
|
// Initialize storage
|
|
109
111
|
states.init(dataDirs, options);
|
|
110
112
|
|
|
111
|
-
const
|
|
113
|
+
const certManager = new Cert({
|
|
114
|
+
maintainerEmail: DEFAULT_CERTIFICATE_EMAIL,
|
|
115
|
+
dataDir: dataDirs.certManagerModule,
|
|
116
|
+
});
|
|
117
|
+
const routerManager = new RouterManager({ certManager });
|
|
112
118
|
const routingSnapshot = new RoutingSnapshot({
|
|
113
119
|
baseDir: dataDirs.core,
|
|
114
120
|
getRoutingData: async () => {
|
|
@@ -117,7 +123,6 @@ function ABTNode(options) {
|
|
|
117
123
|
return { sites };
|
|
118
124
|
},
|
|
119
125
|
});
|
|
120
|
-
|
|
121
126
|
const blockletRegistry = new BlockletRegistry();
|
|
122
127
|
const blockletManager = new BlockletManager({
|
|
123
128
|
dataDirs,
|
|
@@ -127,6 +132,8 @@ function ABTNode(options) {
|
|
|
127
132
|
daemon: options.daemon,
|
|
128
133
|
});
|
|
129
134
|
|
|
135
|
+
certManager.start(); // 启动证书服务
|
|
136
|
+
|
|
130
137
|
const {
|
|
131
138
|
handleRouting,
|
|
132
139
|
getRoutingRulesByDid,
|
|
@@ -141,10 +148,10 @@ function ABTNode(options) {
|
|
|
141
148
|
getCertificates,
|
|
142
149
|
getSitesFromSnapshot,
|
|
143
150
|
getRoutingCrons,
|
|
144
|
-
|
|
151
|
+
ensureDashboardCertificate,
|
|
152
|
+
} = getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager });
|
|
145
153
|
|
|
146
154
|
const teamManager = new TeamManager({ nodeDid: options.nodeDid, dataDirs, states });
|
|
147
|
-
|
|
148
155
|
const nodeAPI = new NodeAPI(states.node, blockletRegistry);
|
|
149
156
|
const teamAPI = new TeamAPI({ states, teamManager });
|
|
150
157
|
|
|
@@ -311,6 +318,7 @@ function ABTNode(options) {
|
|
|
311
318
|
takeRoutingSnapshot,
|
|
312
319
|
getSitesFromSnapshot,
|
|
313
320
|
ensureDashboardRouting,
|
|
321
|
+
ensureDashboardCertificate,
|
|
314
322
|
|
|
315
323
|
addDomainAlias: routerManager.addDomainAlias.bind(routerManager),
|
|
316
324
|
deleteDomainAlias: routerManager.deleteDomainAlias.bind(routerManager),
|
|
@@ -319,11 +327,13 @@ function ABTNode(options) {
|
|
|
319
327
|
checkDomains: domainStatus.checkDomainsStatus.bind(domainStatus),
|
|
320
328
|
handleRouting,
|
|
321
329
|
|
|
322
|
-
|
|
330
|
+
certManager,
|
|
331
|
+
updateNginxHttpsCert: certManager.update.bind(certManager), // TODO: 重命名:updateCert
|
|
323
332
|
getCertificates,
|
|
324
|
-
addCertificate:
|
|
325
|
-
deleteCertificate:
|
|
326
|
-
findCertificateByDomain:
|
|
333
|
+
addCertificate: certManager.add.bind(certManager),
|
|
334
|
+
deleteCertificate: certManager.remove.bind(certManager),
|
|
335
|
+
findCertificateByDomain: certManager.getByDomain.bind(certManager),
|
|
336
|
+
issueLetsEncryptCert: certManager.issue.bind(certManager),
|
|
327
337
|
|
|
328
338
|
// Access Key
|
|
329
339
|
getAccessKeys: states.accessKey.list.bind(states.accessKey),
|
|
@@ -7,7 +7,7 @@ const cloneDeep = require('lodash/cloneDeep');
|
|
|
7
7
|
const security = require('@abtnode/util/lib/security');
|
|
8
8
|
|
|
9
9
|
module.exports = async ({ states, configFile, dataDir }) => {
|
|
10
|
-
if (process.env.CI) {
|
|
10
|
+
if (process.env.CI || process.env.NODE_ENV === 'test') {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -34,8 +34,24 @@ module.exports = async ({ states, configFile, dataDir }) => {
|
|
|
34
34
|
return c;
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
const newChildren = cloneDeep(item.children || []).map((x) => {
|
|
38
|
+
return {
|
|
39
|
+
...x,
|
|
40
|
+
configs: cloneDeep(x.configs || []).map((c) => {
|
|
41
|
+
if (c.secure) {
|
|
42
|
+
c.value = security.encrypt(c.value, item.did, fs.readFileSync(file));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return c;
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await states.blockletExtras.update({ did: item.did }, { $set: { configs: newConfigs, children: newChildren } });
|
|
38
51
|
}
|
|
52
|
+
|
|
53
|
+
states.node.compactDatafile();
|
|
54
|
+
states.blockletExtras.compactDatafile();
|
|
39
55
|
} catch (err) {
|
|
40
56
|
console.error(err);
|
|
41
57
|
throw err;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const uniq = require('lodash/uniq');
|
|
5
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
6
|
+
const security = require('@abtnode/util/lib/security');
|
|
7
|
+
const { forEachBlocklet } = require('@blocklet/meta/lib/util');
|
|
8
|
+
|
|
9
|
+
module.exports = async ({ states, dataDir }) => {
|
|
10
|
+
if (process.env.CI || process.env.NODE_ENV === 'test') {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const file = path.join(dataDir, '.sock');
|
|
15
|
+
if (fs.existsSync(file) === false) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const extras = await states.blockletExtras.find();
|
|
21
|
+
const blocklets = await states.blocklet.find();
|
|
22
|
+
for (const blocklet of blocklets) {
|
|
23
|
+
forEachBlocklet(
|
|
24
|
+
blocklet,
|
|
25
|
+
(b) => {
|
|
26
|
+
let configKeys;
|
|
27
|
+
const extra = extras.find((x) => x.did === blocklet.meta.did);
|
|
28
|
+
const rootKeys = (extra.configs || []).map((x) => x.key);
|
|
29
|
+
if (b.meta.did === blocklet.meta.did) {
|
|
30
|
+
configKeys = rootKeys;
|
|
31
|
+
} else {
|
|
32
|
+
const childExtra = (extra.children || []).find((x) => x.did === b.meta.did);
|
|
33
|
+
const childKeys = childExtra ? childExtra.configs.map((x) => x.key) : [];
|
|
34
|
+
configKeys = uniq([...rootKeys, ...childKeys]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
b.environments = cloneDeep(b.environments || [])
|
|
38
|
+
.filter((x) => configKeys.includes(x.key) === false)
|
|
39
|
+
.map((c) => {
|
|
40
|
+
if (c.key === 'BLOCKLET_APP_SK') {
|
|
41
|
+
c.value = security.encrypt(c.value, b.meta.did, fs.readFileSync(file));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return c;
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
{ sync: true }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
await states.blocklet.updateById(blocklet._id, {
|
|
51
|
+
$set: { environments: blocklet.environments, children: blocklet.children, migratedFrom: '1.6.5' },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
states.blocklet.compactDatafile();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(err);
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module.exports = async ({ states, node, printInfo }) => {
|
|
2
|
+
printInfo('Migrate certificates...');
|
|
3
|
+
const certs = await states.certificate.find(
|
|
4
|
+
{},
|
|
5
|
+
{
|
|
6
|
+
domain: 1,
|
|
7
|
+
privateKey: 1,
|
|
8
|
+
certificate: 1,
|
|
9
|
+
createdAt: 1,
|
|
10
|
+
updatedAt: 1,
|
|
11
|
+
}
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const tasks = certs.map((cert) => {
|
|
15
|
+
const data = {
|
|
16
|
+
_id: cert.id,
|
|
17
|
+
domain: cert.domain,
|
|
18
|
+
privateKey: cert.privateKey,
|
|
19
|
+
certificate: cert.certificate,
|
|
20
|
+
createdAt: cert.createdAt,
|
|
21
|
+
updatedAt: cert.updatedAt,
|
|
22
|
+
source: 'lets_encrypt',
|
|
23
|
+
status: 'generated',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return node.certManager.addWithoutValidations(data);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await Promise.all(tasks);
|
|
30
|
+
};
|
package/lib/router/helper.js
CHANGED
|
@@ -21,15 +21,13 @@ const {
|
|
|
21
21
|
NAME_FOR_WELLKNOWN_SITE,
|
|
22
22
|
DEFAULT_HTTP_PORT,
|
|
23
23
|
DEFAULT_HTTPS_PORT,
|
|
24
|
-
DAY_IN_MS,
|
|
25
24
|
NODE_MODES,
|
|
26
25
|
ROUTING_RULE_TYPES,
|
|
27
26
|
CERTIFICATE_EXPIRES_OFFSET,
|
|
28
|
-
CERTIFICATE_EXPIRES_WARNING_OFFSET,
|
|
29
|
-
DEFAULT_DAEMON_PORT,
|
|
30
27
|
DEFAULT_SERVICE_PATH,
|
|
31
28
|
SLOT_FOR_IP_DNS_SITE,
|
|
32
29
|
BLOCKLET_SITE_GROUP_SUFFIX,
|
|
30
|
+
WELLKNOWN_ACME_CHALLENGE_PREFIX,
|
|
33
31
|
} = require('@abtnode/constant');
|
|
34
32
|
const {
|
|
35
33
|
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
@@ -403,11 +401,10 @@ const decompressCertificates = async (source, dest) => {
|
|
|
403
401
|
return dest;
|
|
404
402
|
};
|
|
405
403
|
|
|
406
|
-
module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager }) {
|
|
404
|
+
module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager }) {
|
|
407
405
|
const nodeState = states.node;
|
|
408
406
|
const blockletState = states.blocklet;
|
|
409
407
|
const siteState = states.site;
|
|
410
|
-
const httpsCertState = states.certificate;
|
|
411
408
|
const notification = states.notification;
|
|
412
409
|
|
|
413
410
|
// site level duplication detection
|
|
@@ -468,58 +465,105 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
468
465
|
const certificate = fs.readFileSync(certificateFilePath).toString();
|
|
469
466
|
const privateKey = fs.readFileSync(privateKeyFilePath).toString();
|
|
470
467
|
|
|
471
|
-
await
|
|
468
|
+
await certManager.upsertByDomain({
|
|
469
|
+
domain: dashboardDomain,
|
|
470
|
+
privateKey,
|
|
471
|
+
certificate,
|
|
472
|
+
isProtected: true,
|
|
473
|
+
});
|
|
472
474
|
logger.info('dashboard certificate updated');
|
|
473
475
|
} catch (error) {
|
|
474
|
-
logger.error('
|
|
476
|
+
logger.error('update dashboard certificate failed', { error });
|
|
477
|
+
throw error;
|
|
475
478
|
} finally {
|
|
476
479
|
fs.removeSync(destFolder);
|
|
477
480
|
}
|
|
478
481
|
};
|
|
479
482
|
|
|
483
|
+
const ensureDashboardCertificate = async () => {
|
|
484
|
+
const info = await nodeState.read();
|
|
485
|
+
const downloadUrl = get(info, 'routing.dashboardCertDownloadAddress', '');
|
|
486
|
+
const dashboardDomain = get(info, 'routing.dashboardDomain', '');
|
|
487
|
+
if (!dashboardDomain || !downloadUrl) {
|
|
488
|
+
throw new Error('dashboardCertDownloadAddress and dashboardDomain are not found in the routing configs');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const cert = await certManager.getByDomain(dashboardDomain);
|
|
492
|
+
if (cert) {
|
|
493
|
+
return { status: 'existed' };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
logger.debug('downloading certificate', { url: downloadUrl, dashboardDomain });
|
|
497
|
+
await updateDashboardCertificate({ checkExpire: false });
|
|
498
|
+
logger.debug('downloading certificate', { url: downloadUrl, dashboardDomain });
|
|
499
|
+
return { status: 'downloaded' };
|
|
500
|
+
};
|
|
501
|
+
|
|
480
502
|
const addWellknownSite = async (sites, context) => {
|
|
481
503
|
const site = (sites || []).find((x) => x.name === NAME_FOR_WELLKNOWN_SITE);
|
|
482
504
|
|
|
483
505
|
try {
|
|
484
|
-
const
|
|
506
|
+
const info = await nodeState.read();
|
|
507
|
+
const proxyTarget = {
|
|
508
|
+
port: info.port,
|
|
509
|
+
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
510
|
+
interfaceName: BLOCKLET_INTERFACE_WELLKNOWN,
|
|
511
|
+
};
|
|
512
|
+
|
|
485
513
|
const didResolverWellknownRule = {
|
|
486
|
-
from: { pathPrefix },
|
|
487
|
-
to:
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
},
|
|
514
|
+
from: { pathPrefix: joinUrl(WELLKNOWN_PATH_PREFIX, '/did.json') },
|
|
515
|
+
to: proxyTarget,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const acmeChallengeWellknownRule = {
|
|
519
|
+
from: { pathPrefix: WELLKNOWN_ACME_CHALLENGE_PREFIX },
|
|
520
|
+
to: proxyTarget,
|
|
492
521
|
};
|
|
493
522
|
|
|
494
523
|
if (site) {
|
|
495
|
-
|
|
496
|
-
|
|
524
|
+
let changed = false;
|
|
525
|
+
const exists = (prefix) => !!site.rules.find((r) => r.from.pathPrefix === normalizePathPrefix(prefix));
|
|
526
|
+
|
|
527
|
+
if (!exists(didResolverWellknownRule.from.pathPrefix)) {
|
|
528
|
+
await routerManager.addRoutingRule(
|
|
529
|
+
{
|
|
530
|
+
id: site.id,
|
|
531
|
+
rule: didResolverWellknownRule,
|
|
532
|
+
},
|
|
533
|
+
context
|
|
534
|
+
);
|
|
535
|
+
changed = true;
|
|
497
536
|
}
|
|
498
537
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
context
|
|
505
|
-
);
|
|
506
|
-
} else {
|
|
507
|
-
await routerManager.addRoutingSite(
|
|
508
|
-
{
|
|
509
|
-
site: {
|
|
510
|
-
domain: DOMAIN_FOR_INTERNAL_SITE,
|
|
511
|
-
port: await getWellknownSitePort(),
|
|
512
|
-
name: NAME_FOR_WELLKNOWN_SITE,
|
|
513
|
-
rules: [didResolverWellknownRule],
|
|
514
|
-
isProtected: true,
|
|
538
|
+
if (!exists(acmeChallengeWellknownRule.from.pathPrefix)) {
|
|
539
|
+
await routerManager.addRoutingRule(
|
|
540
|
+
{
|
|
541
|
+
id: site.id,
|
|
542
|
+
rule: acmeChallengeWellknownRule,
|
|
515
543
|
},
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
544
|
+
context
|
|
545
|
+
);
|
|
546
|
+
changed = true;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return changed;
|
|
521
550
|
}
|
|
522
551
|
|
|
552
|
+
await routerManager.addRoutingSite(
|
|
553
|
+
{
|
|
554
|
+
site: {
|
|
555
|
+
domain: DOMAIN_FOR_INTERNAL_SITE,
|
|
556
|
+
port: await getWellknownSitePort(),
|
|
557
|
+
name: NAME_FOR_WELLKNOWN_SITE,
|
|
558
|
+
rules: [didResolverWellknownRule, acmeChallengeWellknownRule],
|
|
559
|
+
isProtected: true,
|
|
560
|
+
},
|
|
561
|
+
skipCheckDynamicBlacklist: true,
|
|
562
|
+
skipValidation: true,
|
|
563
|
+
},
|
|
564
|
+
context
|
|
565
|
+
);
|
|
566
|
+
|
|
523
567
|
return true;
|
|
524
568
|
} catch (err) {
|
|
525
569
|
console.error('add well-known site failed:', err);
|
|
@@ -533,7 +577,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
533
577
|
*
|
|
534
578
|
* @returns {boolean} if routing changed
|
|
535
579
|
*/
|
|
536
|
-
const ensureDashboardRouting = async (context = {}
|
|
580
|
+
const ensureDashboardRouting = async (context = {}) => {
|
|
537
581
|
const info = await nodeState.read();
|
|
538
582
|
|
|
539
583
|
const provider = getProviderFromNodeInfo(info);
|
|
@@ -618,18 +662,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
618
662
|
updatedResult.push(wellknownRes);
|
|
619
663
|
}
|
|
620
664
|
|
|
621
|
-
// Download dashboard certificates if not exists
|
|
622
|
-
const certDownloadAddress = get(info, 'routing.dashboardCertDownloadAddress', '');
|
|
623
|
-
if (dashboardDomain && certDownloadAddress) {
|
|
624
|
-
const cert = await routerManager.findCertificateByDomain(dashboardDomain);
|
|
625
|
-
if (!cert) {
|
|
626
|
-
await updateDashboardCertificate({ checkExpire: false });
|
|
627
|
-
if (typeof output === 'function') {
|
|
628
|
-
output('Dashboard HTTPS certificate was downloaded successfully!');
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
665
|
if (updatedResult.length) {
|
|
634
666
|
const hash = await takeRoutingSnapshot({ message: 'ensure dashboard routing rules', dryRun: false }, context);
|
|
635
667
|
logger.info('take routing snapshot on ensure dashboard routing rules', { updatedResult, hash });
|
|
@@ -1068,9 +1100,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1068
1100
|
sites = await ensureAuthService(sites, blockletManager);
|
|
1069
1101
|
sites = await ensureServiceRule(sites);
|
|
1070
1102
|
|
|
1071
|
-
const certificates = httpsEnabled
|
|
1072
|
-
? await httpsCertState.find({ type: providerName }, { domain: 1, certificate: 1, privateKey: 1 })
|
|
1073
|
-
: [];
|
|
1103
|
+
const certificates = httpsEnabled ? await certManager.getAllNormal() : [];
|
|
1074
1104
|
|
|
1075
1105
|
return {
|
|
1076
1106
|
sites,
|
|
@@ -1093,9 +1123,9 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1093
1123
|
},
|
|
1094
1124
|
});
|
|
1095
1125
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1126
|
+
certManager.on('cert.added', () => routers[providerName].restart());
|
|
1127
|
+
certManager.on('cert.removed', () => routers[providerName].restart());
|
|
1128
|
+
certManager.on('cert.issued', () => routers[providerName].restart());
|
|
1099
1129
|
|
|
1100
1130
|
await routers[providerName].start();
|
|
1101
1131
|
}
|
|
@@ -1257,18 +1287,21 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1257
1287
|
};
|
|
1258
1288
|
|
|
1259
1289
|
const getCertificates = async () => {
|
|
1260
|
-
const certificates = await
|
|
1290
|
+
const certificates = await certManager.getAll();
|
|
1261
1291
|
const sites = await getSitesFromSnapshot();
|
|
1292
|
+
|
|
1293
|
+
const isMatch = (cert, domain) =>
|
|
1294
|
+
domain !== DOMAIN_FOR_DEFAULT_SITE && domain && routerManager.isCertMatchedDomain(cert, domain);
|
|
1295
|
+
|
|
1262
1296
|
return certificates.map((cert) => {
|
|
1263
1297
|
cert.matchedSites = [];
|
|
1264
1298
|
sites.forEach((site) => {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1299
|
+
const domains = [site.domain, ...(site.domainAliases || []).map((x) => x.value)];
|
|
1300
|
+
domains.forEach((domain) => {
|
|
1301
|
+
if (isMatch(cert, domain)) {
|
|
1302
|
+
cert.matchedSites.push({ id: site.id, domain });
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1272
1305
|
});
|
|
1273
1306
|
|
|
1274
1307
|
return cert;
|
|
@@ -1281,42 +1314,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1281
1314
|
return { domain, isHttps: !!matchedCert, matchedCert };
|
|
1282
1315
|
};
|
|
1283
1316
|
|
|
1284
|
-
const checkCertificatesExpiration = async () => {
|
|
1285
|
-
const now = Date.now();
|
|
1286
|
-
|
|
1287
|
-
const certificates = await getCertificates();
|
|
1288
|
-
for (let i = 0; i < certificates.length; i++) {
|
|
1289
|
-
const cert = certificates[i];
|
|
1290
|
-
const alreadyExpired = now >= cert.validTo;
|
|
1291
|
-
const aboutToExpire = cert.validTo - now > 0 && cert.validTo - now < CERTIFICATE_EXPIRES_WARNING_OFFSET;
|
|
1292
|
-
|
|
1293
|
-
if (alreadyExpired) {
|
|
1294
|
-
logger.info('send certificate expire notification', { domain: cert.domain });
|
|
1295
|
-
notification.create({
|
|
1296
|
-
title: 'SSL Certificate Expired',
|
|
1297
|
-
description: `Your SSL certificate for domain ${cert.domain} has expired, please update it in Blocklet Server`,
|
|
1298
|
-
severity: 'error',
|
|
1299
|
-
entityType: 'certificate',
|
|
1300
|
-
entityId: cert._id, // eslint-disable-line no-underscore-dangle
|
|
1301
|
-
});
|
|
1302
|
-
} else if (aboutToExpire) {
|
|
1303
|
-
logger.info('send certificate about-expire notification', { domain: cert.domain });
|
|
1304
|
-
const expireInDays = Math.ceil((cert.validTo - now) / DAY_IN_MS);
|
|
1305
|
-
notification.create({
|
|
1306
|
-
title: 'SSL Certificate Expire Warning',
|
|
1307
|
-
description: `Your SSL certificate for domain ${
|
|
1308
|
-
cert.domain
|
|
1309
|
-
} will expire in ${expireInDays} days (on ${new Date(
|
|
1310
|
-
cert.validTo
|
|
1311
|
-
).toLocaleString()}), please remember to update it in Blocklet Server`,
|
|
1312
|
-
severity: 'warning',
|
|
1313
|
-
entityType: 'certificate',
|
|
1314
|
-
entityId: cert._id, // eslint-disable-line no-underscore-dangle
|
|
1315
|
-
});
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
};
|
|
1319
|
-
|
|
1320
1317
|
return {
|
|
1321
1318
|
ensureDashboardRouting,
|
|
1322
1319
|
ensureBlockletRouting,
|
|
@@ -1328,9 +1325,10 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1328
1325
|
takeRoutingSnapshot,
|
|
1329
1326
|
getRoutingSites,
|
|
1330
1327
|
getSnapshotSites,
|
|
1331
|
-
getCertificates,
|
|
1332
1328
|
getSitesFromSnapshot,
|
|
1329
|
+
getCertificates,
|
|
1333
1330
|
checkDomain,
|
|
1331
|
+
ensureDashboardCertificate,
|
|
1334
1332
|
|
|
1335
1333
|
getRoutingCrons: () => [
|
|
1336
1334
|
{
|
|
@@ -1339,12 +1337,6 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1339
1337
|
fn: () => updateDashboardCertificate({ checkExpire: true }),
|
|
1340
1338
|
options: { runOnInit: true },
|
|
1341
1339
|
},
|
|
1342
|
-
{
|
|
1343
|
-
name: 'check-certificate-expiration',
|
|
1344
|
-
time: '0 0 9 * * *', // check on 09:00 every day
|
|
1345
|
-
fn: checkCertificatesExpiration,
|
|
1346
|
-
options: { runOnInit: false },
|
|
1347
|
-
},
|
|
1348
1340
|
{
|
|
1349
1341
|
name: 'rotate-log-files',
|
|
1350
1342
|
time: '5 0 0 * * *', // rotate at 05:00 every day
|