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
net/http: something about ACME letsencrypt #11651
Comments
I'm a big fan of LetsEncrypt, but please describe what you need and not what your proposed fix is. The proposed fix is not acceptable (we won't be exposing internals), but I don't even know what the problem is. |
Thanks Brad, Here's a user story:
Instead of knowing what all hosts and certificates are beforehand, and without having to restart the server, the service can host secure websites dynamically. That's what I'm doing. It's as easy as pie in node.js because they expose an SNICallback before certificates are handled. Since go currently requires so much statically defined up-front, the easiest way to accomplish this is to manually create a tlsConn from a net.Conn and then pass that to http.newConn. Also, it's not exposing "internals" any more than it used to or any more than the TLS and net modules currently do (see my example). It's simply exposing a capability. I suppose you could also add an SNICallback like node.js does, but from the code I looked at, it seems that that would be far more changes rather than taking the approach that http.ListenAndServe and http.ListenAndServeTLS approach, which would simply encapsulate capability in a composite style. Another option perhaps would be to move a more complete version of what I've created as https.ListenAndServeSNI, which still needs an SNICallback. Is that explanation clear? |
I think you just need to implement your own |
So you need to present a TLS server certificate as a function of the client's presented SNI hostname? /cc @agl |
Yes, that's what I've done. But then I also need to pass it to http.NewConn. And yes, I need to see the servername in order to know which certificate to pass, I won't know it on startup. That's all being done in the demo I've been referencing here: (the repo is obviously not intended for the story I described, but it may have some similar aspects - and one thing leads to another...) This is actually my first go project (coming from node.js). |
Does crypto/tls.Config.GetCertificate not work for you? (Note: the behaviour of that changed a little since Go 1.4.) |
I don't see what that has to do with the server sending the client a certificate dynamically, but I'm brand new to go. Here's the rough code without error checking, channels, goroutines, etc: // I start a raw TCP server
ln, err := net.Listen("tcp", ":" + config.Port)
// I begin accepting connections
conn, err := ln.Accept()
// the vhost pkg peeks at the incoming buffer and let's me see the servername
tlsConn, err := vhost.TLS(conn)
// I grab the SNI
servername := tlsConn.Host()
// Now I want to determine which certificate to issue
// 1. check some cache I create (or maybe GetCertificate?)
// 2. if the expiration is bad, jump ahead to step 5
// 3. if it doesn't exist check on the file system
// 3. if it doesn't exist try SNICallback (from user code)
// 4. if it doesn't exist, try retrieving a brand new certificate from letsencrypt.org
// 5. if that fails, return a dummy certificate
// Now make the encrypted TCP plainly readable
plainConn := tls.Server(tlsConn, tlsConfig)
// Now make it an http server request
srv := &http.Server{Handler: myHandler}
c, err := srv.NewConn(plainConn)
// Now handle that http request
go c.Serve() When the server starts I may not have the certificate. What I'm trying to build is basically type SNICallback func(domainname string) (t *tls.Config)
http.ListenAndServeSNI(addr string, sniCallback SNICallback, handler Handler) error Except I want a build a higher-level function that would use such a function with letsencrypt. For the time being I'd just shell out to the letsencrypt python client and there's someone already working on a go client for use with caddyserver (and hopefully the code I'm working on will also find its way there). |
Sorry, I didn't understand what
Okay, yes, that's the piece that I was missing and didn't discover as I was |
Updated: I had I got my demo implemented and then I realized that there's not a way to specify the I could keep I could solve this problem manually, but again, there's no way for me to cast Why would I want to manage Let's say that I'm loading hundreds or thousands of certificates over a long period of time - many of which are transient, occasionally I may want to flush the pool and start over (kilobytes turn to megabytes and so on). Also, I assume that the |
On second thought, maybe that's just me exhibiting typical control-freakishness programmer paranoia. My app probably won't become that popular and even if I were BlueHost and I loaded every intermediate certificate in the whole world over the next 3 years without restarting the service, there are only maybe a few hundred intermediates in the world and since most of the certificates would be provisioned from the top 10%, it would only be a few dozen that actually get loaded. What do you think? Is there value in being able to replace CertPool? Is there value in being able to create http.Server.Conn from net.Conn? |
I'm afraid the above doesn't make any sense to me and I suspect that I'm lacking the required context.
|
Again, thanks for taking the time to help. No, I'm not validating client certificates. Apparently I meant My demo worked, so I thought I was using the right one... but since in the case of my demo there is no intermediate ca in the chain, only my fabricated root ca, it would have worked no matter what because I had the client already accept my fabricated root ca. I realize now that I probably should have been using Concern 1: Can't Create http.Server.Conn from net.ConnIn order to do things like count bytes of network traffic to a particular site, or examine raw headers (it seems http.Server.Conn.Request may strip certain headers - for example 'Transfer-Encoding' is stripped from client requests - which is odd, but not against the spec) or anything that relies on having access to the raw underlying socket, I need to be able to create http.Server.Conn from net.Conn so that I can spy on net.Conn or tls.Conn directly. Concern 2: Can't replace or delete from RootCAsRealistically, this probably isn't worth addressing if my current understanding is correct. In short: Also I'm always adding to Back the the earlier example as context:
And what happens when a browser connects
And what happens in the code
|
Brad is the expert here; I really know nothing. However, there is
There's an error in your understanding here: |
As for the Listener interface, I think I understand what you're saying - I need to create my own type with an Accept and Close method, etc, and do my magic at that layer which I then pass along. As for the x509 stuff, does that mean that LoadX509KeyPair is not expecting just a key and server cert pair, but rather the private key and the complete bundle of all intermediate certificates as well as the server certificate? My experience with TLS has all been in node.js, which is extremely explicit about how to load certificates - private, cert, and chain are always explicitly declared as such, not bundled - and haproxy, which is extremely lax - bundle the files any which way as long as no true root (self-signed) cas are included. Go's conventions are different from either of theirs so I'm being humbled quite a bit to realize that I don't know these things as well as I thought. I'll have to play around with this on Monday, but I think you've helped me understand both some of the idiomaticies of go that were eluding me and some implementation details that I had the wrong understanding of. Thank you very much. |
And it turns out that I've been doing it wrong in node.js for the past year too. It just always happened to work because I always happened to be using well-known intermediate certificate authorities. Alas I have discovered the error of my ways with a little help from openssl. openssl s_client -showcerts -connect example.com:443 -servername example.com -CAfile /path/to/root.pem How embarrassing. My entire life is a lie... I'm gonna go cry in the bathroom now. ... Thanks so much for all the help. My understanding of Go's tls handing and how interfaces work is up by about a billion and I finally got a demo that works without crazy changes. https://github.com/coolaj86/golang-https-example |
Solution (Note to Les Googlers)
My question was, as I misinterpreted the documentation.
GetCertificate == SNICallback
net.Conn
tohttp.Conn
, you can only castnet.Listener
tohttp.Listener
server := &http.Serve{}; server.Server(tlsListener)
See examples at
The crux of the issue In 68 lines:
Original Question
https://LetsEncrypt.org's release is just around the corner, but it's currently not possible to dynamically retrieve and renew certificates in go with an active http server.
To do so requires fixing a tiny regression introduced sometime between the move from httputils and now. See https://golang.org/pkg/net/http/httputil/#ServerConn.
We need to re-expose http.NewConn and http.conn.Serve.
See the diff: coolaj86@1a30898
I've got a working demo of dynamically loading certificates with this change here:
https://gist.github.com/coolaj86/16ed8fd810e19dec71be
I've already signed the CLA and with a little coaching I'm sure I could turn my example into an appropriate test case, but I'm way out of my league with the 37-page explanation of how to make a pull request...
See also #11649
The text was updated successfully, but these errors were encountered: