From 964b7dee3f095db6c1cf550c06cd041fe98111a4 Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Sat, 15 Feb 2020 15:10:26 +0000 Subject: [PATCH] add tls config to server (#1202) * add tls config * add TLSConfig to acme provider --- api/server/acme/acme.go | 6 +++- api/server/acme/autocert/autocert.go | 29 +++++++++++++--- api/server/acme/autocert/autocert_test.go | 4 +-- api/server/acme/autocert/cache.go | 37 +++++++++++++++++++++ api/server/acme/certmagic/certmagic.go | 16 +++++++-- api/server/acme/certmagic/certmagic_test.go | 12 +++---- api/server/http/http.go | 2 +- server/grpc/grpc.go | 9 ++++- server/options.go | 17 ++++++++++ 9 files changed, 114 insertions(+), 18 deletions(-) create mode 100644 api/server/acme/autocert/cache.go diff --git a/api/server/acme/acme.go b/api/server/acme/acme.go index f9df8962..c962d038 100644 --- a/api/server/acme/acme.go +++ b/api/server/acme/acme.go @@ -2,6 +2,7 @@ package acme import ( + "crypto/tls" "errors" "net" ) @@ -14,7 +15,10 @@ var ( // Provider is a ACME provider interface type Provider interface { - NewListener(...string) (net.Listener, error) + // Listen returns a new listener + Listen(...string) (net.Listener, error) + // TLSConfig returns a tls config + TLSConfig(...string) (*tls.Config, error) } // The Let's Encrypt ACME endpoints diff --git a/api/server/acme/autocert/autocert.go b/api/server/acme/autocert/autocert.go index 48152517..3b190f05 100644 --- a/api/server/acme/autocert/autocert.go +++ b/api/server/acme/autocert/autocert.go @@ -3,7 +3,10 @@ package autocert import ( + "crypto/tls" + "log" "net" + "os" "github.com/micro/go-micro/v2/api/server/acme" "golang.org/x/crypto/acme/autocert" @@ -12,12 +15,30 @@ import ( // autoCertACME is the ACME provider from golang.org/x/crypto/acme/autocert type autocertProvider struct{} -// NewListener implements acme.Provider -func (a *autocertProvider) NewListener(ACMEHosts ...string) (net.Listener, error) { - return autocert.NewListener(ACMEHosts...), nil +// Listen implements acme.Provider +func (a *autocertProvider) Listen(hosts ...string) (net.Listener, error) { + return autocert.NewListener(hosts...), nil +} + +// TLSConfig returns a new tls config +func (a *autocertProvider) TLSConfig(hosts ...string) (*tls.Config, error) { + // create a new manager + m := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + } + if len(hosts) > 0 { + m.HostPolicy = autocert.HostWhitelist(hosts...) + } + dir := cacheDir() + if err := os.MkdirAll(dir, 0700); err != nil { + log.Printf("warning: autocert not using a cache: %v", err) + } else { + m.Cache = autocert.DirCache(dir) + } + return m.TLSConfig(), nil } // New returns an autocert acme.Provider -func New() acme.Provider { +func NewProvider() acme.Provider { return &autocertProvider{} } diff --git a/api/server/acme/autocert/autocert_test.go b/api/server/acme/autocert/autocert_test.go index 570769df..8a4aed20 100644 --- a/api/server/acme/autocert/autocert_test.go +++ b/api/server/acme/autocert/autocert_test.go @@ -5,9 +5,9 @@ import ( ) func TestAutocert(t *testing.T) { - l := New() + l := NewProvider() if _, ok := l.(*autocertProvider); !ok { - t.Error("New() didn't return an autocertProvider") + t.Error("NewProvider() didn't return an autocertProvider") } // TODO: Travis CI doesn't let us bind :443 // if _, err := l.NewListener(); err != nil { diff --git a/api/server/acme/autocert/cache.go b/api/server/acme/autocert/cache.go new file mode 100644 index 00000000..c898c449 --- /dev/null +++ b/api/server/acme/autocert/cache.go @@ -0,0 +1,37 @@ +package autocert + +import ( + "os" + "path/filepath" + "runtime" +) + +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) +} diff --git a/api/server/acme/certmagic/certmagic.go b/api/server/acme/certmagic/certmagic.go index febe56a7..81e485fc 100644 --- a/api/server/acme/certmagic/certmagic.go +++ b/api/server/acme/certmagic/certmagic.go @@ -2,6 +2,7 @@ package certmagic import ( + "crypto/tls" "log" "math/rand" "net" @@ -16,7 +17,8 @@ type certmagicProvider struct { opts acme.Options } -func (c *certmagicProvider) NewListener(ACMEHosts ...string) (net.Listener, error) { +// TODO: set self-contained options +func (c *certmagicProvider) setup() { certmagic.Default.CA = c.opts.CA if c.opts.ChallengeProvider != nil { // Enabling DNS Challenge disables the other challenges @@ -34,12 +36,20 @@ func (c *certmagicProvider) NewListener(ACMEHosts ...string) (net.Listener, erro rand.Seed(time.Now().UnixNano()) randomDuration := (7 * 24 * time.Hour) + (time.Duration(rand.Intn(504)) * time.Hour) certmagic.Default.RenewDurationBefore = randomDuration +} - return certmagic.Listen(ACMEHosts) +func (c *certmagicProvider) Listen(hosts ...string) (net.Listener, error) { + c.setup() + return certmagic.Listen(hosts) +} + +func (c *certmagicProvider) TLSConfig(hosts ...string) (*tls.Config, error) { + c.setup() + return certmagic.TLS(hosts) } // New returns a certmagic provider -func New(options ...acme.Option) acme.Provider { +func NewProvider(options ...acme.Option) acme.Provider { opts := acme.DefaultOptions() for _, o := range options { diff --git a/api/server/acme/certmagic/certmagic_test.go b/api/server/acme/certmagic/certmagic_test.go index cf3c515a..f91566b9 100644 --- a/api/server/acme/certmagic/certmagic_test.go +++ b/api/server/acme/certmagic/certmagic_test.go @@ -19,7 +19,7 @@ func TestCertMagic(t *testing.T) { if len(os.Getenv("IN_TRAVIS_CI")) != 0 { t.Skip("Travis doesn't let us bind :443") } - l, err := New().NewListener() + l, err := NewProvider().Listen() if err != nil { t.Fatal(err.Error()) } @@ -36,10 +36,10 @@ func TestCertMagic(t *testing.T) { t.Fatal(err.Error()) } - l, err = New(acme.AcceptToS(true), + l, err = NewProvider(acme.AcceptToS(true), acme.CA(acme.LetsEncryptStagingCA), acme.ChallengeProvider(p), - ).NewListener() + ).Listen() if err != nil { t.Fatal(err.Error()) @@ -180,7 +180,7 @@ func TestStorageImplementation(t *testing.T) { // New interface doesn't return an error, so call it in case any log.Fatal // happens - New(acme.Cache(s)) + NewProvider(acme.Cache(s)) } // Full test with a real zone, with against LE staging @@ -207,7 +207,7 @@ func TestE2e(t *testing.T) { t.Fatal(err.Error()) } - testProvider := New( + testProvider := NewProvider( acme.AcceptToS(true), acme.Cache(testStorage), acme.CA(acme.LetsEncryptStagingCA), @@ -215,7 +215,7 @@ func TestE2e(t *testing.T) { acme.OnDemand(false), ) - listener, err := testProvider.NewListener("*.micro.mu", "micro.mu") + listener, err := testProvider.Listen("*.micro.mu", "micro.mu") if err != nil { t.Fatal(err.Error()) } diff --git a/api/server/http/http.go b/api/server/http/http.go index ecf3d499..f5240cda 100644 --- a/api/server/http/http.go +++ b/api/server/http/http.go @@ -54,7 +54,7 @@ func (s *httpServer) Start() error { if s.opts.EnableACME && s.opts.ACMEProvider != nil { // should we check the address to make sure its using :443? - l, err = s.opts.ACMEProvider.NewListener(s.opts.ACMEHosts...) + l, err = s.opts.ACMEProvider.Listen(s.opts.ACMEHosts...) } else if s.opts.EnableTLS && s.opts.TLSConfig != nil { l, err = tls.Listen("tcp", s.address, s.opts.TLSConfig) } else { diff --git a/server/grpc/grpc.go b/server/grpc/grpc.go index 67be638b..b21c58e0 100644 --- a/server/grpc/grpc.go +++ b/server/grpc/grpc.go @@ -805,7 +805,14 @@ func (g *grpcServer) Start() error { ts = l } else { var err error - ts, err = net.Listen("tcp", config.Address) + + // check the tls config for secure connect + if tc := config.TLSConfig; tc != nil { + ts, err = tls.Listen("tcp", config.Address, tc) + // otherwise just plain tcp listener + } else { + ts, err = net.Listen("tcp", config.Address) + } if err != nil { return err } diff --git a/server/options.go b/server/options.go index 53424548..6a54620c 100644 --- a/server/options.go +++ b/server/options.go @@ -2,6 +2,7 @@ package server import ( "context" + "crypto/tls" "sync" "time" @@ -39,6 +40,9 @@ type Options struct { // The router for requests Router Router + // TLSConfig specifies tls.Config for secure serving + TLSConfig *tls.Config + // Other options for implementations of the interface // can be stored in a context Context context.Context @@ -205,6 +209,19 @@ func RegisterInterval(t time.Duration) Option { } } +// TLSConfig specifies a *tls.Config +func TLSConfig(t *tls.Config) Option { + return func(o *Options) { + // set the internal tls + o.TLSConfig = t + // set the transport tls + o.Transport.Init( + transport.Secure(true), + transport.TLSConfig(t), + ) + } +} + // WithRouter sets the request router func WithRouter(r Router) Option { return func(o *Options) {