-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
https server should allow changing credentials dynamically #4464
Comments
An API like
Doesn't seem so reasonable if the implication is that the server needs to track active sockets. If the logic is something like /cc @nodejs/crypto |
Perhaps it should be called |
I don't really like disconnecting sockets either, IMO this is not reasonable and has way too much implicit behavior for suggested method name. |
@petkaantonov could you explain some use-cases for this? I can't imagine one so perhaps it'd be helpful for others as well to hear why this might be needed. |
The use case is an embedded system where https server is handling mission critical information. If administrator wants/needs to change the certificates, it will happen on the fly. Without this modification https server needs to be restarted leading to possible loss of data. |
Iirc this check was in the |
@petkaantonov ... still interested in this? I share some of the same concerns voiced already but would definitely welcome a PR to review. |
I could work on a PR this weekend |
This it's really necessary |
By terminating existing connections when they try to write something, aren't you losing data? |
Why would you have to terminate existing connections? Why not just let existing connections stay open as long as they want, and only give the new cert to new connections? If you want to, you can always kill connections yourself. |
For a Let's Encrypt certificate, the cert needs to be cycled every 90 days or so. Long-term, they are planning on pushing this number as low as 2 days. In P2P protocols that have "trust", up-time and reliability are important in maintaining high trust on the network. Severing existing connections to cycle a server can hurt your reputation, but gracefully shutting down by rejecting new connections and finishing current connections increases the time your service is offline, especially when serving long-term connections (i.e. streaming a GB file to a client). While you can get around this in most enterprise settings by doing a rolling update of your clients, most end users aren't setup with load balancing, let alone rolling updates. They simply SNI is a solution, but not all clients support SNI and it is really just a hack for the job, we aren't trying to serve multiple hostnames from a single ip. Also, SNI bleeds information outside of SSL (the hostname) in order for the server to determine which cert needs to be served. It looks like the If so, we can simply
|
I know it is unofficial API, but has anyone tried: |
@indutny, I can give it a shot! I'm out of my depth here though, I know just enough about SSL right now to be dangerous, which isn't enough to put together a convincing test case :-\ |
@indutny I'm not sure if this is convincing, but it seems to work! Script: var https = require('https')
var fs = require('fs')
var opts = {
key: fs.readFileSync('./good_cert/privkey.pem', 'utf8'),
cert: fs.readFileSync('./good_cert/fullchain.pem', 'utf8'),
ca: fs.readFileSync('./good_cert/chain.pem', 'utf8')
}
var bad_opts = {
key: fs.readFileSync('./bad_cert/privkey.pem', 'utf8'),
cert: fs.readFileSync('./bad_cert/fullchain.pem', 'utf8'),
ca: fs.readFileSync('./bad_cert/chain.pem', 'utf8')
}
var s = https.createServer(opts, (req, res) => {
console.log('connect')
res.writeHead(200)
res.write('Hello World!\n')
s._sharedCreds.context.setCert(bad_opts.cert)
s._sharedCreds.context.setKey(bad_opts.key)
res.end('Goodbye World!\n')
})
s.listen(9999) Result: It looks like the current connection continues using the old cert, but the next connection uses the new cert.
|
Cool, thank you for testing! Each connection maintains the reference to the cert. @bnoordhuis what do you think about making an API on top of this? |
@indutny No objections. |
Another, perhaps more composable and flexible and even secure approach, would be to allow the certificate data to be provided through a callback instead of as static properties - This would allow for packages dedicated to ACMEv1 or ACMEv2 (or any other custom CA protocol) to fully take over responsibility of providing the certificates, including things like storing (disk or ram - potentially unswappable memory), caching, invalidation, lifecycle management etc. Leaving this to (forcing it upon) each application developer to handle such critical flow is surely possible but perhaps not ideal. |
@indutny, not available to the application I hope? The cert should be irrelevant once the handshake is established, and especially the private key shouldn't be thrown around, imo. I don't want to have to trust all npm packages that get direct or indirect access to the socket. |
@grantila npm packages that you use have full access to the file system and networking. Although, getting cert is complicated, I don't see how one can trust more to them than we already do. |
Dear @retrohacker your code seems to works fine for me in websocket server. Thank you! I think we do not need global.gc() or close existing connections because valid certificate is only needed during handshake. When connection is established expired certificate is not problem for that connection. Once certificate file is changed new connections will use new certificate for handshake. Question is why _sharedCreds.context.setCert and _sharedCreds.context.setKey are not part of public API? We can use it but without warranty because it is not documented. Code below is minimalistic example for WSS websocket server with hot reloading certificate after certificate file is changed (by certbot or acme.sh)... var ws = require('ws');
var fs = require('fs');
var https = require('https');
var options = {
server : https.createServer({
cert : fs.readFileSync('/path/to/crt'),
key : fs.readFileSync('/path/to/key'),
}).listen(2000)
};
var timeout;
fs.watch('/path/to/crt', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
options.server._sharedCreds.context.setCert(fs.readFileSync('/path/to/crt'));
options.server._sharedCreds.context.setKey(fs.readFileSync('/path/to/key'));
}, 1000);
});
new ws.Server(options); It is enough to watch just certificate file (because when private key is changed also certificate must be changed). Timeout is used because fs.watch fires callback several times depending on OS/platform. |
@nolimitdev , I tried concatenating the end domain cert of my server along with the ca certs that are required for authenticating the client certs for incoming requests, my code looks like this now:
However I am not able to make any requests as the server isn't able to verify the client cert that I am providing for the request. (since I've removed the ca field in options and concatenated ca certs in cert field itself.) |
Ca certs usually have expiration for several years. Maybe you do not need hot reloading for it at all. Just use hot reloading for domain certificate and key using setCert() and setKey(). |
@nolimitdev , context.addCACert seems to work for adding additional CA certs, still can't find a way to remove an already added CA cert from the array though. |
@nolimitdev , I need to add a new CA cert, not a renewed CA cert that already existed. It is a custom CA cert, which means I have to add it in my trusted ca fields option. |
This commit adds a setSecureContext() method to TLS servers. In order to maintain backwards compatibility, the method takes the options needed to create a new SecureContext, rather than an instance of SecureContext. Fixes: nodejs#4464 Refs: nodejs#10349 Refs: nodejs/help#603 Refs: nodejs#15115 PR-URL: nodejs#23644 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit adds a setSecureContext() method to TLS servers. In order to maintain backwards compatibility, the method takes the options needed to create a new SecureContext, rather than an instance of SecureContext. Fixes: #4464 Refs: #10349 Refs: nodejs/help#603 Refs: #15115 PR-URL: #23644 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Hi. Have you found a method to remove a single CA cert ? Thanks |
As I'm using pfx, I modified @nolimitdev 's solution to:
|
Hello, I also try with use case : automatic rotation of the certificates on random interval on the servers. Thanks by advance. |
This commit adds a setSecureContext() method to TLS servers. In order to maintain backwards compatibility, the method takes the options needed to create a new SecureContext, rather than an instance of SecureContext. Fixes: nodejs#4464 Refs: nodejs#10349 Refs: nodejs/help#603 Refs: nodejs#15115 PR-URL: nodejs#23644 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This needs to be added, https should support hot-reloading of SSL certificates/context |
Hello @DominusVilicus , Do you know when it could be possible to get this change ? Thanks |
We need to change the certificate info (credentials/secureContext) dynamically while the server is online without having to shut down and start a new server. We have patched node with code that enables this, but it would be nice to have it on upstream as well.
The API we have is:
server.setSharedCredentials(secureContext)
Used to change the server's TLS options such as the server certificate on the fly.
secureContext
must be an instance ofSecureContext
as is created withtls.createSecureContext()
.After the call, if there were active sockets using old context, those sockets will be disconnected next time data is sent over them.
The text was updated successfully, but these errors were encountered: