// LETSENCRYPTPROD returns a new Automatic TLS Listener using letsencrypt.org service // receives two parameters, the first is the domain of the server // and the second is optionally, the cache directory, if you skip it then the cache directory is "./certcache" // if you want to disable cache directory then simple give it a value of empty string "" // // does NOT supports localhost domains for testing, use LETSENCRYPT instead. // // this is the recommended function to use when you're ready for production state func LETSENCRYPTPROD(addr string, cacheDirOptional ...string) (net.Listener, error) { if portIdx := strings.IndexByte(addr, ':'); portIdx == -1 { addr += ":443" } ln, err := TCP4(addr) if err != nil { return nil, err } cacheDir := "./certcache" if len(cacheDirOptional) > 0 { cacheDir = cacheDirOptional[0] } m := autocert.Manager{ Prompt: autocert.AcceptTOS, } // HostPolicy is missing, if user wants it, then she/he should manually // configure the autocertmanager and use the `iris.Serve` to pass that listener if cacheDir == "" { // then the user passed empty by own will, then I guess she/he doesnt' want any cache directory } else { m.Cache = autocert.DirCache(cacheDir) } tlsConfig := &tls.Config{GetCertificate: m.GetCertificate} tlsLn := tls.NewListener(ln, tlsConfig) return tlsLn, nil }
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 serveHTTPS(httpServer *http.Server) error { log.Printf("Starting TLS server on %s", *httpsAddr) httpsServer := new(http.Server) *httpsServer = *httpServer httpsServer.Addr = *httpsAddr cacheDir := autocert.DirCache("letsencrypt.cache") var domain string if !inProd { if *tlsCertFile != "" && *tlsKeyFile != "" { return httpsServer.ListenAndServeTLS(*tlsCertFile, *tlsKeyFile) } // Otherwise use Let's Encrypt, i.e. same use case as in prod if strings.HasPrefix(*httpsAddr, ":") { return errors.New("for Let's Encrypt, -https needs to start with a host name") } host, _, err := net.SplitHostPort(*httpsAddr) if err != nil { return err } domain = host } else { domain = "camlistore.org" cacheDir = autocert.DirCache(prodLECacheDir) } m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(domain), Cache: cacheDir, } if *adminEmail != "" { m.Email = *adminEmail } httpsServer.TLSConfig = &tls.Config{ GetCertificate: m.GetCertificate, } log.Printf("Listening for HTTPS on %v", *httpsAddr) ln, err := net.Listen("tcp", *httpsAddr) if err != nil { return err } return httpsServer.Serve(tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, httpsServer.TLSConfig)) }
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 }