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, }) }
func main() { launchConfig.MaybeDeploy() flag.Parse() var kv keyValue var httpsListenAddr string if metadata.OnGCE() { httpsListenAddr = ":443" dsClient, err := datastore.NewClient(context.Background(), GCEProjectID) if err != nil { log.Fatalf("Error creating datastore client for records: %v", err) } kv = cachedStore{ dsClient: dsClient, cache: lru.New(cacheSize), } } else { httpsListenAddr = ":4430" kv = memkv{skv: sorted.NewMemoryKeyValue()} } if err := kv.Set("6401800c.camlistore.net.", "159.203.246.79"); err != nil { log.Fatalf("Error adding %v:%v record: %v", "6401800c.camlistore.net.", "159.203.246.79", err) } if err := kv.Set(domain, *flagServerIP); err != nil { log.Fatalf("Error adding %v:%v record: %v", domain, *flagServerIP, err) } if err := kv.Set("www.camlistore.net.", *flagServerIP); err != nil { log.Fatalf("Error adding %v:%v record: %v", "www.camlistore.net.", *flagServerIP, err) } ds := newDNSServer(kv) cs := &gpgchallenge.Server{ OnSuccess: func(identity string, address string) error { log.Printf("Adding %v.camlistore.net. as %v", identity, address) return ds.dataSource.Set(strings.ToLower(identity+".camlistore.net."), address) }, } tcperr := make(chan error, 1) udperr := make(chan error, 1) httperr := make(chan error, 1) log.Printf("serving DNS on %s\n", *addr) go func() { tcperr <- dns.ListenAndServe(*addr, "tcp", ds) }() go func() { udperr <- dns.ListenAndServe(*addr, "udp", ds) }() if metadata.OnGCE() { // TODO(mpl): if we want to get a cert for anything // *.camlistore.net, it's a bit of a chicken and egg problem, since // we need camnetdns itself to be already running and answering DNS // queries. It's probably doable, but easier for now to just ask // one for camnetdns.camlistore.org, since that name is not // resolved by camnetdns. hostname := strings.TrimSuffix(authorityNS, ".") m := autocert.Manager{ Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist(hostname), Cache: autocert.DirCache(osutil.DefaultLetsEncryptCache()), } ln, err := tls.Listen("tcp", httpsListenAddr, &tls.Config{ Rand: rand.Reader, Time: time.Now, NextProtos: []string{http2.NextProtoTLS, "http/1.1"}, MinVersion: tls.VersionTLS12, GetCertificate: m.GetCertificate, }) if err != nil { log.Fatalf("Error listening on %v: %v", httpsListenAddr, err) } go func() { httperr <- http.Serve(ln, cs) }() } select { case err := <-tcperr: log.Fatalf("DNS over TCP error: %v", err) case err := <-udperr: log.Fatalf("DNS error: %v", err) case err := <-httperr: log.Fatalf("HTTP server error: %v", err) } }