Brad Fitzpatrick would like Alex Vaghin to review this change.
acme/autocert: add Listener
Now users can do 1-line LetsEncrypt HTTPS servers:
log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
Updates golang/go#17053
Change-Id: I13fcd3985ebf6bc97a7524cceeb7641cf1b66b22
---
A acme/autocert/listener.go
A acme/autocert/listener_test.go
2 files changed, 123 insertions(+), 0 deletions(-)
diff --git a/acme/autocert/listener.go b/acme/autocert/listener.go
new file mode 100644
index 0000000..3fd54fe
--- /dev/null
+++ b/acme/autocert/listener.go
@@ -0,0 +1,102 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package autocert
+
+import (
+ "crypto/tls"
+ "net"
+ "time"
+)
+
+// NewListener returns a net.Listener that listens on the standard TLS
+// port (443) on all interfaces and returns *tls.Conn connections with
+// LetsEncrypt certificates for the provided domain or domains.
+//
+// Use of this function implies acceptance of the LetsEncrypt Terms of
+// Service. If domains is not empty, the provided domains are passed
+// to HostWhitelist. If domains is empty, the listener will do
+// LetsEncrypt challenges for any requested domain, which is not
+// recommended.
+//
+// NewListener is a convenience function for a common configuration.
+// More complex configurations can use the autocert.Manager type or
+// even the golang.org/x/crypto/acme package directly.
+//
+// The returned Listener also enables TCP keep-alives on the accepted
+// connections. The returned *tls.Conn are returned before their TLS
+// handshake has completed.
+func NewListener(domains ...string) net.Listener {
+ m := &Manager{
+ Prompt: AcceptTOS,
+ }
+ if len(domains) > 0 {
+ m.HostPolicy = HostWhitelist(domains...)
+ }
+ return m.Listener()
+}
+
+// Listener listens on the standard TLS port (443) on all interfaces
+// and returns a net.Listener returning *tls.Conn connections.
+//
+// The returned Listener also enables TCP keep-alives on the accepted
+// connections. The returned *tls.Conn are returned before their TLS
+// handshake has completed.
+func (m *Manager) Listener() net.Listener {
+ ln := &listener{
+ m: m,
+ conf: &tls.Config{
+ GetCertificate: m.GetCertificate, // bonus: panic on nil m
+ },
+ }
+ ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
+ return ln
+}
+
+type listener struct {
+ m *Manager
+ conf *tls.Config
+
+ tcpListener net.Listener
+ tcpListenErr error
+}
+
+func (ln *listener) Accept() (net.Conn, error) {
+ if ln.tcpListenErr != nil {
+ return nil, ln.tcpListenErr
+ }
+ conn, err := ln.tcpListener.Accept()
+ if err != nil {
+ return nil, err
+ }
+ tcpConn := conn.(*net.TCPConn)
+
+ // Because Listener is a convenience function, help out with
+ // this too. This is not possible for the caller to set once
+ // we return a *tcp.Conn wrapping an inaccessible net.Conn.
+ // If callers don't want this, they can do things the manual
+ // way and tweak as neede. But this is what net/http does
+ // itself, so copy that. If net/http changes, we can change
+ // here too.
+ tcpConn.SetKeepAlive(true)
+ tcpConn.SetKeepAlivePeriod(3 * time.Minute)
+
+ return tls.Client(tcpConn, ln.conf), nil
+}
+
+func (ln *listener) Addr() net.Addr {
+ if ln.tcpListener != nil {
+ return ln.tcpListener.Addr()
+ }
+ // net.Listen failed. Return something non-nil in case callers
+ // call Addr before Accept:
+ return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
+}
+
+func (ln *listener) Close() error {
+ if ln.tcpListenErr != nil {
+ return ln.tcpListenErr
+ }
+ return ln.tcpListener.Close()
+}
diff --git a/acme/autocert/listener_test.go b/acme/autocert/listener_test.go
new file mode 100644
index 0000000..9272ebb
--- /dev/null
+++ b/acme/autocert/listener_test.go
@@ -0,0 +1,21 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package autocert_test
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ "golang.org/x/crypto/acme/autocert"
+)
+
+func ExampleNewListener() {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello, TLS user! Your config: %+v", r.TLS)
+ })
+ log.Fatal(http.Serve(autocert.NewListener("example.com"), mux))
+}
To view, visit change 39207. To unsubscribe, visit settings.
Brad Fitzpatrick posted comments on this change.
Patch set 1:Run-TryBot +1
Gobot Gobot posted comments on this change.
Patch set 1:
TryBots beginning. Status page: http://farmer.golang.org/try?commit=caeeaee3
Gobot Gobot posted comments on this change.
Patch set 1:TryBot-Result +1
TryBots are happy.
Ivan Babrou posted comments on this change.
Patch set 1:
(1 comment)
File acme/autocert/listener.go:
Patch Set #1, Line 79: // way and tweak as neede. But this is what net/http does
Typo: "neede".
To view, visit change 39207. To unsubscribe, visit settings.
Emmanuel Odeke posted comments on this change.
File acme/autocert/listener.go:
Patch Set #1, Line 79: neede
Minor typo:
s/neede/needed
To view, visit change 39207. To unsubscribe, visit settings.
Alex Vaghin posted comments on this change.
Patch set 1:Code-Review +2
Nice!
Victor Vrancean posted comments on this change.
Patch set 1:
(1 comment)
File acme/autocert/listener.go:
Patch Set #1, Line 32: Prompt: AcceptTOS,
Should the manager include a DirCache in /tmp? Without a cache, users are likely to trigger a LE rate-limit, which would lock them out for 7 days.
IMO the cache behavior should at least be mentioned in the docs.
To view, visit change 39207. To unsubscribe, visit settings.
Brad Fitzpatrick posted comments on this change.
Patch Set #1, Line 32: Prompt: AcceptTOS,
Should the manager include a DirCache in /tmp? Without a cache, users are l
Oh, right, I meant to add that. Thanks! Will do.
To view, visit change 39207. To unsubscribe, visit settings.
Brad Fitzpatrick uploaded patch set #2 to this change.
acme/autocert: add Listener
Now users can do 1-line LetsEncrypt HTTPS servers:
log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
Updates golang/go#17053
Change-Id: I13fcd3985ebf6bc97a7524cceeb7641cf1b66b22
---
A acme/autocert/listener.go
A acme/autocert/listener_test.go
2 files changed, 176 insertions(+), 0 deletions(-)
To view, visit change 39207. To unsubscribe, visit settings.
Brad Fitzpatrick posted comments on this change.
Patch set 2:Run-TryBot +1
Now with a stupid bug fixed so it actually works. (I wrote it on a plane without network access.)
Also now with cache dir support.
PTAL
Gobot Gobot posted comments on this change.
Patch set 2:
TryBots beginning. Status page: http://farmer.golang.org/try?commit=ef6ca2c6
Gobot Gobot posted comments on this change.
Patch set 2:TryBot-Result +1
TryBots are happy.
To view, visit change 39207. To unsubscribe, visit settings.
Alex Vaghin posted comments on this change.
Patch set 2:-Code-Review
(2 comments)
File acme/autocert/listener.go:
Patch Set #2, Line 51: log.Printf("warning: autocert.NewListener not using a cache: %v", err)
I think this is the only log usage within the acme and acme/autocert pkgs.
Maybe we could also use it for https://github.com/golang/go/issues/19800 then.
Patch Set #2, Line 149: panic("No Windows TEMP or TMP environment variables found; unexpected.")
But you also support windows in the homeDir func. Why not use that instead?
If you remove this panic, it'll go fall through 'till the last return where it's already using homeDir().
To view, visit change 39207. To unsubscribe, visit settings.
Nathan Youngman posted comments on this change.
Patch set 2:
(1 comment)
File acme/autocert/listener.go:
Patch Set #2, Line 100: // way and tweak as neede. But this is what net/http does
s/neede/needed/
To view, visit change 39207. To unsubscribe, visit settings.
Brad Fitzpatrick uploaded patch set #3 to this change.
acme/autocert: add Listener
Now users can do 1-line LetsEncrypt HTTPS servers:
log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
Updates golang/go#17053
Change-Id: I13fcd3985ebf6bc97a7524cceeb7641cf1b66b22
---
A acme/autocert/listener.go
A acme/autocert/listener_test.go
2 files changed, 174 insertions(+), 0 deletions(-)
To view, visit change 39207. To unsubscribe, visit settings.
Brad Fitzpatrick posted comments on this change.
Patch set 3:
(5 comments)
File acme/autocert/listener.go:
Minor typo:
Done
Patch Set #1, Line 79: m *Manager
Typo: "neede".
Done
File acme/autocert/listener.go:
Patch Set #2, Line 51: log.Printf("warning: autocert.NewListener not using a cache: %v", err)
I think this is the only log usage within the acme and acme/autocert pkgs.
Let's evaluate them on a case-by-case basis.
In this case I'm fine with it.
It's a warning. People can check their logs to see those.
If they don't like it, they can use Manager + Manager.Listener by hand.
Patch Set #2, Line 100: // way and tweak as needed. But this is what net/http does
s/neede/needed/
Done
Patch Set #2, Line 149: if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
But you also support windows in the homeDir func. Why not use that instead?
These funcs were copy/pasted from another one of my projects.
But fixed.
To view, visit change 39207. To unsubscribe, visit settings.
Brad Fitzpatrick posted comments on this change.
Patch set 3:Run-TryBot +1
PTAL
Gobot Gobot posted comments on this change.
Patch set 3:
TryBots beginning. Status page: http://farmer.golang.org/try?commit=bfe318ce
Gobot Gobot posted comments on this change.
Patch set 3:TryBot-Result +1
TryBots are happy.
To view, visit change 39207. To unsubscribe, visit settings.
Alex Vaghin posted comments on this change.
Patch set 3:Code-Review +2
(1 comment)
Patch Set #2, Line 51: log.Printf("warning: autocert.NewListener not using a cache: %v", err)
Let's evaluate them on a case-by-case basis.
Sounds good!
To view, visit change 39207. To unsubscribe, visit settings.
Brad Fitzpatrick merged this change.
acme/autocert: add Listener
Now users can do 1-line LetsEncrypt HTTPS servers:
log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
Updates golang/go#17053
Change-Id: I13fcd3985ebf6bc97a7524cceeb7641cf1b66b22
Reviewed-on: https://go-review.googlesource.com/39207
Run-TryBot: Brad Fitzpatrick <brad...@golang.org>
TryBot-Result: Gobot Gobot <go...@golang.org>
Reviewed-by: Alex Vaghin <dd...@google.com>
---
A acme/autocert/listener.go
A acme/autocert/listener_test.go
2 files changed, 174 insertions(+), 0 deletions(-)
diff --git a/acme/autocert/listener.go b/acme/autocert/listener.go
new file mode 100644
index 0000000..d4c93d2
--- /dev/null
+++ b/acme/autocert/listener.go
@@ -0,0 +1,153 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package autocert
+
+import (
+ "crypto/tls"
+ "log"
+ "net"
+ "os"
+ "path/filepath"
+ "runtime"
+ "time"
+)
+
+// NewListener returns a net.Listener that listens on the standard TLS
+// port (443) on all interfaces and returns *tls.Conn connections with
+// LetsEncrypt certificates for the provided domain or domains.
+//
+// It enables one-line HTTPS servers:
+//
+// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
+//
+// NewListener is a convenience function for a common configuration.
+// More complex or custom configurations can use the autocert.Manager
+// type instead.
+//
+// Use of this function implies acceptance of the LetsEncrypt Terms of
+// Service. If domains is not empty, the provided domains are passed
+// to HostWhitelist. If domains is empty, the listener will do
+// LetsEncrypt challenges for any requested domain, which is not
+// recommended.
+//
+// Certificates are cached in a "golang-autocert" directory under an
+// operating system-specific cache or temp directory. This may not
+// be suitable for servers spanning multiple machines.
+//
+// The returned Listener also enables TCP keep-alives on the accepted
+// connections. The returned *tls.Conn are returned before their TLS
+// handshake has completed.
+func NewListener(domains ...string) net.Listener {
+ m := &Manager{
+ Prompt: AcceptTOS,
+ }
+ if len(domains) > 0 {
+ m.HostPolicy = HostWhitelist(domains...)
+ }
+ dir := cacheDir()
+ if err := os.MkdirAll(dir, 0700); err != nil {
+ log.Printf("warning: autocert.NewListener not using a cache: %v", err)
+ } else {
+ m.Cache = DirCache(dir)
+ }
+ return m.Listener()
+}
+
+// Listener listens on the standard TLS port (443) on all interfaces
+// and returns a net.Listener returning *tls.Conn connections.
+//
+// The returned Listener also enables TCP keep-alives on the accepted
+// connections. The returned *tls.Conn are returned before their TLS
+// handshake has completed.
+//
+// Unlike NewListener, it is the caller's responsibility to initialize
+// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
+ // way and tweak as needed. But this is what net/http does
+ // itself, so copy that. If net/http changes, we can change
+ // here too.
+ tcpConn.SetKeepAlive(true)
+ tcpConn.SetKeepAlivePeriod(3 * time.Minute)
+
+ return tls.Server(tcpConn, ln.conf), nil
+}
+
+func (ln *listener) Addr() net.Addr {
+ if ln.tcpListener != nil {
+ return ln.tcpListener.Addr()
+ }
+ // net.Listen failed. Return something non-nil in case callers
+ // call Addr before Accept:
+ return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
+}
+
+func (ln *listener) Close() error {
+ if ln.tcpListenErr != nil {
+ return ln.tcpListenErr
+ }
+ return ln.tcpListener.Close()
+}
+
+func homeDir() string {
+ if runtime.GOOS == "windows" {
+ return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
+ }
+ if h := os.Getenv("HOME"); h != "" {
+ return h
+ }
+ return "/"
+}
+
+func cacheDir() string {
+ const base = "golang-autocert"
+ switch runtime.GOOS {
+ case "darwin":
+ return filepath.Join(homeDir(), "Library", "Caches", base)
+ case "windows":
+ for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} {
+ if v := os.Getenv(ev); v != "" {
+ return filepath.Join(v, base)
+ }
+ }
+ // Worst case:
+ return filepath.Join(homeDir(), base)
+ }
+ if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" {
+ return filepath.Join(xdg, base)
+ }
+ return filepath.Join(homeDir(), ".cache", base)