Kestrelhttpserver: Allow to replace the server cert at runtime

Created on 8 Oct 2017  路  34Comments  路  Source: aspnet/KestrelHttpServer

Right now, the SSL certificate that Kestrel will use is here:

https://github.com/aspnet/KestrelHttpServer/blob/dev/src/Kestrel.Https/Internal/HttpsConnectionAdapter.cs#L25-L26

There are certain scenarios that we want to support replacing the certificate while the server is still running. This can be done quite easily if we could set the _serverCertificate so all new connections will be able to use that.

Scenarios for that include using Let's Encrypt certificates and not having to restart every 3 months.

While making it public is one option, I think it would be better to _not_ hold a reference to the _serverCertificate from the options but use the value in the options itself. That will mean that the caller could hold on to the options and change the certificate value used without messing with any internal state.

enhancement

Most helpful comment

As an alternative, we hope to get SNI support at some point and one of the API proposals is for a callback where you can select your own cert for each connection. This would allow you to change the cert dynamically.

All 34 comments

As an alternative, we hope to get SNI support at some point and one of the API proposals is for a callback where you can select your own cert for each connection. This would allow you to change the cert dynamically.

I would say at worst, this should wait until the "option bag" for SslStream is completed, then you could expose that or pass it into Kestrel. At best you will get the callback as mentioned above...

I doubt the new options bag will be exposed by kestrel APIs. For now it's Core only.

The middleware could expose a callback today but before we do anything, somebody should hack together a lets encrypt connection adapter for kestrel so that we can see all of the touch points.

I implemented a very rough draft here:
https://github.com/ayende/Kestrel.LetsEncrypt

No error handling and this is mostly to see that I _could_ do it.
Pretty much the only thing that I actually needed to do was to change the way Kestrel is getting the certificate. But in order to do that I had to pretty much bring all of Kestrel.Https

@ayende Sweet!

But in order to do that I had to pretty much bring all of Kestrel.Https

Yea, but that's not a big deal it's only a few files. This should also let us prove out if a callback is enough.

A callback for the certificate would be enough, yes.

The major issue here is that we need some way to prove to Let's Encrypt that we are the owners of the domain. We can do that in various ways, but the easiest from our perspective at this point is to use the http-01 challenge.

This basically means that we ask Let's Encrypt for a certificate, and we need to prove we own this domain. We do that by getting a token from Let's Encrypt and asking it to call back to us (over HTTP, using port 80) and thus proving how this can happen.

I thought about having a kestrel instance that is always on using port 80, but it seems easier to just open it to the few seconds we need to do the verification.

It would probably be easier to do this using DNS, but that require you to use some API and bind you to a specific DNS provider.

I couldn't find a way to make Kestrel route based on the listen source anyway, and I don't think it matters too much.

https://github.com/dotnet/corefx/issues/22510

Can't use protected data. Like secure string it either throws or just isn't (secure)

Let me qualify that. On anything other than windows

The actual problem here is that we need to persist the cert to disk. We can do that with OS permissions so only the current user can touch it, or provide callbacks to the user to handle caching the certificate if they want to customize that (such as storing in a vault).

Why does it need to go to disk? Can't you just use the bytes [] ?

Let's Encrypt encourages you to keep the same certificate around, rather than renew it on every start.
Otherwise, you might hit rate limits.

I guess that's a problem for people with crappy code that restart often of which I am sure yours is not ;). Maybe make an option to "persist certificates to disk"

@Drawaes Consider the case of a user running in development mode.
They still want to use HTTPS, and while they can run with a self signed, it is much easier all around to be able to use a real one from Let's Encrypt. In that case, it is easy to have a debug / run cycle that will hit the limits.

For that matter, even CI that continuously deploy will have to deal with this, in production.
I think that a better option would indeed be to update the code there to use a callback, instead of making this decision.

I updated the code, so this should be clearer now.

Yeah dev it's fine...

@ayende for development mode user could use staging let's encrypt environment.

Actually the more I think about it, when does a Dev have a publicly facing DNS to get a cert in the first place?

@Drawaes In my case when Dev is developing let's encrypt's automated certificate renewal :)

Let's Encrypt encourages you to keep the same certificate around, rather than renew it on every start.
Otherwise, you might hit rate limits.

They only issue domain validated certs and only for 90 days at a time. Rate is 5 times a week for same cert; 20 times for domain and all its sub-domains; which is quite low for a test server - so you'd want to be caching it.

@ayende's use ASP.NET's IDataProtectionProvider and IDataProtector?

@Soarc Except that you want to hit your website in the browser (or from you manager's browser) and have it Just Work.

@Drawaes dynamic DNS would do quite well for these cases. Or CI deployments, etc.

@benaadams Yes, you have to cache that.
I couldn't find how IDataProtecter would work on Linux, and anyway, I just externalize all of that so user can handle it on their own.

It works via DI so is its externalized to the users choice of mechanism; though you'd probably need to change the LetsEncryptCertificateFetcher to a service to grab it?

The problem is that at this point you'll need to provide Not just how to protect it but also _where_ to protect it.
The fetcher is also going to start a whole new Kestrel instance to do the http-01 validation, so confusing with regards to whose service provider to use.

@joshfree To implement this, we'll need SNI on Core to support something like this in a future release. Heads-up.

cc @shirhatti

@saurabh500 @corivera

SNI uses the SSLStream from .Net Core to transmit data. I am not sure how SNI is relevant here.

@saurabh500 Proposal at this issue would possibly enable this. @Tratcher can share some more details.

@shirhatti @DamianEdwards Let's decide if we want this feature in 2.2 which would drive feature requirements from the CoreFX side.

We know this feature is not the intended purpose of SNI, however the the proposed SNI API with the delegate callback would also enable this scenario.

Ah OK. The SNI here is Server Name Indication and not the SNI that SQL server uses. SNI in Sql server is an acronym for SQL Network Interface. @joshfree We are not the right team here :)

Closing this one. We'll revisit the bigger scenario in the future.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

felschr picture felschr  路  6Comments

ayende picture ayende  路  6Comments

chtbof picture chtbof  路  3Comments

txdv picture txdv  路  7Comments

Tazer picture Tazer  路  4Comments