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 }
// If cert/key files are specified, and found, use them. // If cert/key files are specified, not found, and the default values, generate // them (self-signed CA used as a cert), and use them. // If cert/key files are not specified, use Let's Encrypt. func setupTLS(ws *webserver.Server, config *serverinit.Config, hostname string) { cert, key := config.OptionalString("httpsCert", ""), config.OptionalString("httpsKey", "") if !config.OptionalBool("https", true) { return } if (cert != "") != (key != "") { exitf("httpsCert and httpsKey must both be either present or absent") } defCert := osutil.DefaultTLSCert() defKey := osutil.DefaultTLSKey() const hint = "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n\"trustedCerts\": [\"%s\"]," if cert == defCert && key == defKey { _, err1 := wkfs.Stat(cert) _, err2 := wkfs.Stat(key) if err1 != nil || err2 != nil { if os.IsNotExist(err1) || os.IsNotExist(err2) { sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self-signed TLS cert: %q", err) } log.Printf(hint, sig) } else { exitf("Could not stat cert or key: %q, %q", err1, err2) } } } if cert == "" && key == "" { // Use Let's Encrypt if no files are specified, and we have a usable hostname. if netutil.IsFQDN(hostname) { m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(hostname), Cache: autocert.DirCache(osutil.DefaultLetsEncryptCache()), } log.Print("TLS enabled, with Let's Encrypt") ws.SetTLS(webserver.TLSSetup{ CertManager: m.GetCertificate, }) return } // Otherwise generate new certificates sig, err := httputil.GenSelfTLSFiles(hostname, defCert, defKey) if err != nil { exitf("Could not generate self signed creds: %q", err) } log.Printf(hint, sig) cert = defCert key = defKey } data, err := wkfs.ReadFile(cert) if err != nil { exitf("Failed to read pem certificate: %s", err) } sig, err := httputil.CertFingerprint(data) if err != nil { exitf("certificate error: %v", err) } log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig) ws.SetTLS(webserver.TLSSetup{ CertFile: cert, KeyFile: key, }) }