Skip to content

Commit 3495007

Browse files
authored
Merge pull request #7772 from dannyzaken/danny-5.14
[Backport to 5.14] ssl cert reload - run in endpoint bg (bz 2237903)
2 parents e893e9a + 7f68df0 commit 3495007

File tree

5 files changed

+82
-66
lines changed

5 files changed

+82
-66
lines changed

src/endpoint/endpoint.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ dbg.log0('endpoint: replacing old umask: ', old_umask.toString(8), 'with new uma
8080
/**
8181
* @param {EndpointOptions} options
8282
*/
83+
/* eslint-disable max-statements */
8384
async function main(options = {}) {
8485
try {
8586
// the primary just forks and returns, workers will continue to serve
@@ -144,11 +145,17 @@ async function main(options = {}) {
144145
const endpoint_request_handler = create_endpoint_handler(init_request_sdk, virtual_hosts);
145146
const endpoint_request_handler_sts = create_endpoint_handler(init_request_sdk, virtual_hosts, true);
146147

147-
const ssl_cert = await ssl_utils.get_ssl_certificate('S3');
148-
const ssl_options = { ...ssl_cert, honorCipherOrder: true };
148+
const ssl_cert_info = await ssl_utils.get_ssl_cert_info('S3');
149+
const ssl_options = { ...ssl_cert_info.cert, honorCipherOrder: true };
149150
const http_server = http.createServer(endpoint_request_handler);
150151
const https_server = https.createServer(ssl_options, endpoint_request_handler);
151152
const https_server_sts = https.createServer(ssl_options, endpoint_request_handler_sts);
153+
ssl_cert_info.on('update', updated_ssl_cert_info => {
154+
dbg.log0("Setting updated S3 ssl certs for endpoint.");
155+
const updated_ssl_options = { ...updated_ssl_cert_info.cert, honorCipherOrder: true };
156+
https_server.setSecureContext(updated_ssl_options);
157+
https_server_sts.setSecureContext(updated_ssl_options);
158+
});
152159

153160
if (http_port > 0) {
154161
dbg.log0('Starting S3 HTTP', http_port);

src/rpc/rpc_http_server.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,17 @@ class RpcHttpServer extends events.EventEmitter {
4848
const logging = options.logging;
4949
dbg.log0('HTTP SERVER:', 'port', port, 'secure', secure, 'logging', logging);
5050

51-
const ssl_cert = await ssl_utils.get_ssl_certificate('MGMT');
52-
const server = secure ?
53-
https.createServer({ ...ssl_cert, honorCipherOrder: true }) :
54-
http.createServer();
51+
let server;
52+
if (secure) {
53+
const ssl_cert_info = await ssl_utils.get_ssl_cert_info('MGMT');
54+
server = https.createServer({ ...ssl_cert_info.cert, honorCipherOrder: true });
55+
ssl_cert_info.on('update', updated_cert_info => {
56+
dbg.log0("Setting updated MGMT ssl certs for rpc server.");
57+
server.setSecureContext({ ...updated_cert_info.cert, honorCipherOrder: true });
58+
});
59+
} else {
60+
server = http.createServer();
61+
}
5562
this.install_on_server(server, options.default_handler);
5663
return P.fromCallback(callback => server.listen(port, callback))
5764
.then(() => server);

src/server/bg_services/server_monitor.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const os_utils = require('../../util/os_utils');
99
const Dispatcher = require('../notifications/dispatcher');
1010
const server_rpc = require('../server_rpc');
1111
const system_store = require('../system_services/system_store').get_instance();
12-
const ssl_utils = require('../../util/ssl_utils');
1312
const db_client = require('../../util/db_client');
1413

1514

@@ -53,7 +52,6 @@ async function run_monitors() {
5352

5453
_check_dns_and_phonehome();
5554
await _check_internal_ips();
56-
await _verify_ssl_certs();
5755
await _check_db_disk_usage();
5856
await _check_address_changes(CONTAINER_PLATFORM);
5957
}
@@ -80,17 +78,6 @@ function _check_internal_ips() {
8078
});
8179
}
8280

83-
async function _verify_ssl_certs() {
84-
dbg.log2('_verify_ssl_certs');
85-
const updated = await ssl_utils.update_certs_from_disk();
86-
if (updated) {
87-
dbg.log0('_verify_ssl_certs: SSL certificates changed, restarting relevant services');
88-
await os_utils.restart_services([
89-
'webserver'
90-
]);
91-
}
92-
}
93-
9481
async function _check_db_disk_usage() {
9582
dbg.log2('_check_db_disk_usage');
9683
const { fsUsedSize, fsTotalSize } = await db_client.instance().get_db_stats();

src/server/web_server.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,12 @@ async function main() {
9090
server_rpc.rpc.register_ws_transport(http_server);
9191
await P.ninvoke(http_server, 'listen', http_port);
9292

93-
const ssl_cert = await ssl_utils.get_ssl_certificate('MGMT');
94-
const https_server = https.createServer({ ...ssl_cert, honorCipherOrder: true }, app);
93+
const ssl_cert_info = await ssl_utils.get_ssl_cert_info('MGMT');
94+
const https_server = https.createServer({ ...ssl_cert_info.cert, honorCipherOrder: true }, app);
95+
ssl_cert_info.on('update', updated_cert_info => {
96+
dbg.log0("Setting updated MGMT ssl certs for web server.");
97+
https_server.setSecureContext({...updated_cert_info.cert, honorCipherOrder: true });
98+
});
9599
server_rpc.rpc.register_ws_transport(https_server);
96100
await P.ninvoke(https_server, 'listen', https_port);
97101

src/util/ssl_utils.js

Lines changed: 56 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,41 @@ const https = require('https');
88
const Semaphore = require('../util/semaphore');
99
const dbg = require('./debug_module')(__filename);
1010
const nb_native = require('./nb_native');
11+
const { EventEmitter } = require('events');
1112

12-
const init_cert_info = dir => ({
13-
dir,
14-
cert: null,
15-
is_loaded: false,
16-
is_generated: false,
17-
sem: new Semaphore(1)
18-
});
13+
class CertInfo extends EventEmitter {
14+
15+
constructor(dir) {
16+
super();
17+
this.dir = dir;
18+
this.cert = null;
19+
this.is_loaded = false;
20+
this.is_generated = false;
21+
this.sem = new Semaphore(1);
22+
}
23+
24+
async file_notification(event, filename) {
25+
try {
26+
const cert_on_disk = await _read_ssl_certificate(this.dir);
27+
if (this.cert.key === cert_on_disk.key) {
28+
return;
29+
}
30+
31+
this.cert = cert_on_disk;
32+
this.is_generated = false;
33+
34+
this.emit('update', this);
35+
} catch (err) {
36+
if (err.code !== 'ENOENT') {
37+
dbg.warn(`SSL certificate failed to update from dir ${this.dir}:`, err.message);
38+
}
39+
}
40+
}
41+
}
1942

2043
const certs = {
21-
MGMT: init_cert_info('/etc/mgmt-secret'),
22-
S3: init_cert_info('/etc/s3-secret'),
44+
MGMT: new CertInfo('/etc/mgmt-secret'),
45+
S3: new CertInfo('/etc/s3-secret'),
2346
};
2447

2548
function generate_ssl_certificate() {
@@ -36,19 +59,19 @@ function verify_ssl_certificate(certificate) {
3659
}
3760

3861
// Get SSL certificate (load once then serve from cache)
39-
function get_ssl_certificate(service) {
62+
function get_ssl_cert_info(service) {
4063
const cert_info = certs[service];
4164
if (!cert_info) {
4265
throw new Error(`Invalid service name, got: ${service}`);
4366
}
4467

4568
if (cert_info.is_loaded) {
46-
return cert_info.cert;
69+
return cert_info;
4770
}
4871

4972
return cert_info.sem.surround(async () => {
5073
if (cert_info.is_loaded) {
51-
return cert_info.cert;
74+
return cert_info;
5275
}
5376

5477
try {
@@ -68,40 +91,19 @@ function get_ssl_certificate(service) {
6891
}
6992

7093
cert_info.is_loaded = true;
71-
return cert_info.cert;
72-
});
73-
}
74-
75-
// For each cert that was loaded into memory we check if the cert was changed on disk.
76-
// If so we update it. If any of the certs was updated we return true else we return false.
77-
async function update_certs_from_disk() {
78-
const promiseList = Object.values(certs).map(cert_info =>
79-
cert_info.sem.surround(async () => {
80-
if (!cert_info.is_loaded) {
81-
return false;
82-
}
8394

84-
try {
85-
const cert_on_disk = await _read_ssl_certificate(cert_info.dir);
86-
if (cert_info.cert.key === cert_on_disk.key) {
87-
return false;
88-
}
89-
90-
cert_info.cert = cert_on_disk;
91-
cert_info.is_generated = false;
92-
return true;
93-
94-
} catch (err) {
95-
if (err.code !== 'ENOENT') {
96-
dbg.warn(`SSL certificate failed to update from dir ${cert_info.dir}:`, err.message);
97-
}
98-
return false;
95+
try {
96+
fs.watch(cert_info.dir, {}, cert_info.file_notification.bind(cert_info));
97+
} catch (err) {
98+
if (err.code === 'ENOENT') {
99+
dbg.warn("Certificate folder ", cert_info.dir, " does not exist. New certificate won't be loaded.");
100+
} else {
101+
dbg.error("Failed to watch certificate dir ", cert_info.dir, ". err = ", err);
99102
}
100-
})
101-
);
103+
}
102104

103-
const updatedList = await Promise.all(promiseList);
104-
return updatedList.some(Boolean);
105+
return cert_info;
106+
});
105107
}
106108

107109
// Read SSL certificate form disk
@@ -125,6 +127,15 @@ function is_using_generated_certs() {
125127
);
126128
}
127129

130+
function get_cert_dir(service) {
131+
const cert_info = certs[service];
132+
if (!cert_info) {
133+
throw new Error(`Invalid service name, got: ${service}`);
134+
}
135+
136+
return cert_info.dir;
137+
}
138+
128139
// create a default certificate and start an https server to test it in the browser
129140
function run_https_test_server() {
130141
const server = https.createServer(generate_ssl_certificate());
@@ -144,8 +155,8 @@ function run_https_test_server() {
144155

145156
exports.generate_ssl_certificate = generate_ssl_certificate;
146157
exports.verify_ssl_certificate = verify_ssl_certificate;
147-
exports.get_ssl_certificate = get_ssl_certificate;
158+
exports.get_ssl_cert_info = get_ssl_cert_info;
148159
exports.is_using_generated_certs = is_using_generated_certs;
149-
exports.update_certs_from_disk = update_certs_from_disk;
160+
exports.get_cert_dir = get_cert_dir;
150161

151162
if (require.main === module) run_https_test_server();

0 commit comments

Comments
 (0)