func setupTLS(ws *webserver.Server, conf *config) error { if conf.HTTPSCert != "" && conf.HTTPSKey != "" { ws.SetTLS(webserver.TLSSetup{ CertFile: conf.HTTPSCert, KeyFile: conf.HTTPSKey, }) return nil } if !conf.CertManager { return nil } // As all requests to the publisher are proxied through Camlistore's app // handler, it makes sense to assume that both Camlistore and the publisher // are behind the same domain name. Therefore, it follows that // camlistored's autocert is the one actually getting a cert (and answering // the challenge) for the both of them. Plus, if they run on the same host // (default setup), they can't both listen on 443 to answer the TLS-SNI // challenge. // TODO(mpl): however, camlistored and publisher could be running on // different hosts, in which case we need to find a way for camlistored to // share its autocert cache with publisher. But I think that can wait a // later CL. hostname := os.Getenv("CAMLI_API_HOST") hostname = strings.TrimPrefix(hostname, "https://") hostname = strings.SplitN(hostname, "/", 2)[0] hostname = strings.SplitN(hostname, ":", 2)[0] if !netutil.IsFQDN(hostname) { return fmt.Errorf("cannot ask Let's Encrypt for a cert because %v is not a fully qualified domain name", hostname) } logger.Print("TLS enabled, with Let's Encrypt") // TODO(mpl): we only want publisher to use the same cache as // camlistored, and we don't actually need an autocert.Manager. // So we could just instantiate an autocert.DirCache, and generate // from there a *tls.Certificate, but it looks like it would mean // extracting quite a bit of code from the autocert pkg to do it properly. // Instead, I've opted for using an autocert.Manager (even though it's // never going to reply to any challenge), but with NOOP for Put and // Delete, just to be on the safe side. It's simple enough, but there's // still a catch: there's no ServerName in the clientHello we get, so we // reinject one so we can simply use the autocert.Manager.GetCertificate // method as the way to get the certificate from the cache. Is that OK? m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(hostname), Cache: roCertCacheDir(osutil.DefaultLetsEncryptCache()), } ws.SetTLS(webserver.TLSSetup{ CertManager: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { if clientHello.ServerName == "" { clientHello.ServerName = hostname } return m.GetCertificate(clientHello) }, }) return nil }
func (srv *Server) newTLSConfig(cfg ServerConfig) *tls.Config { tlsConfig := utils.SecureTLSConfig() if cfg.AutocertDNSName == "" { // No official DNS name, no certificate. tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { cert, _ := srv.localCertificate(clientHello.ServerName) return cert, nil } return tlsConfig } m := autocert.Manager{ Prompt: autocert.AcceptTOS, Cache: srv.state.AutocertCache(), HostPolicy: autocert.HostWhitelist(cfg.AutocertDNSName), } if cfg.AutocertURL != "" { m.Client = &acme.Client{ DirectoryURL: cfg.AutocertURL, } } tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { logger.Infof("getting certificate for server name %q", clientHello.ServerName) // Get the locally created certificate and whether it's appropriate // for the SNI name. If not, we'll try to get an acme cert and // fall back to the local certificate if that fails. cert, shouldUse := srv.localCertificate(clientHello.ServerName) if shouldUse { return cert, nil } acmeCert, err := m.GetCertificate(clientHello) if err == nil { return acmeCert, nil } logger.Errorf("cannot get autocert certificate for %q: %v", clientHello.ServerName, err) return cert, nil } return tlsConfig }