diff --git a/go.mod b/go.mod index 58806d1..9bd7d98 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.1 go.opencensus.io v0.18.0 + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect google.golang.org/appengine v1.3.0 // indirect google.golang.org/grpc v1.14.0 diff --git a/go.sum b/go.sum index 2d4a4e9..bd0c069 100644 --- a/go.sum +++ b/go.sum @@ -48,14 +48,22 @@ github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7 github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= diff --git a/httputil/autocert.go b/httputil/autocert.go new file mode 100644 index 0000000..15e3be6 --- /dev/null +++ b/httputil/autocert.go @@ -0,0 +1,57 @@ +package httputil + +import ( + "net/http" + "time" + + "github.com/pkg/errors" + "golang.org/x/crypto/acme" + "golang.org/x/crypto/acme/autocert" +) + +type AcmOpt func(*autocert.Manager) error + +func WithLetsEncryptStaging() AcmOpt { + return func(m *autocert.Manager) error { + m.Client.DirectoryURL = "https://acme-staging.api.letsencrypt.org/directory" + return nil + } +} + +func WithEmail(e string) AcmOpt { + return func(m *autocert.Manager) error { + m.Email = e + return nil + } +} + +func WithRenewBefore(t time.Duration) AcmOpt { + return func(m *autocert.Manager) error { + m.RenewBefore = t + return nil + } +} + +func WithHttpClient(c *http.Client) AcmOpt { + return func(m *autocert.Manager) error { + m.Client.HTTPClient = c + return nil + } +} + +func NewAutocertManager(cache autocert.Cache, allowedHosts []string, opts ...AcmOpt) (*autocert.Manager, error) { + m := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(allowedHosts...), + Cache: cache, + Client: &acme.Client{}, + } + + for _, opt := range opts { + if err := opt(m); err != nil { + return nil, errors.Wrap(err, "applying option to autocert manager") + } + } + + return m, nil +} diff --git a/httputil/handlers.go b/httputil/handlers.go new file mode 100644 index 0000000..b7ec4fd --- /dev/null +++ b/httputil/handlers.go @@ -0,0 +1,39 @@ +package httputil + +import ( + "crypto/subtle" + "net/http" +) + +// BasicAuthMiddleware is http middleware to authenticate based on a +// predefined map of usernames and passwords. +func BasicAuthMiddleware(basicauthPairs map[string][]byte, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if !ok || username == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // username and password must match + expectedPassword, ok := basicauthPairs[username] + if !ok || subtle.ConstantTimeCompare([]byte(password), expectedPassword) != 1 { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // handoff to the next handler + next.ServeHTTP(w, r) + }) +} + +// RedirectToSecureHandler is a simple handler to redirect to the secure URL. +func RedirectToSecureHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Connection", "close") + url := r.URL + url.Scheme = "https" + url.Host = r.Host + http.Redirect(w, r, url.String(), http.StatusMovedPermanently) + }) +} diff --git a/httputil/httputil.go b/httputil/httputil.go index 9bb09e6..299e392 100644 --- a/httputil/httputil.go +++ b/httputil/httputil.go @@ -19,6 +19,18 @@ func WithTLSConfig(cfg *tls.Config) Option { } } +func WithReadTimeout(t time.Duration) Option { + return func(s *http.Server) { + s.ReadTimeout = t + } +} + +func WithWriteTimeout(t time.Duration) Option { + return func(s *http.Server) { + s.WriteTimeout = t + } +} + // NewServer creates an HTTP Server with pre-configured timeouts and a secure TLS Config. func NewServer(addr string, h http.Handler, opts ...Option) *http.Server { srv := http.Server{ diff --git a/tlsutil/tlsutil.go b/tlsutil/tlsutil.go index bfdd180..c62faa8 100644 --- a/tlsutil/tlsutil.go +++ b/tlsutil/tlsutil.go @@ -61,6 +61,15 @@ func WithCertificates(certs []tls.Certificate) Option { } } +// WithGetCertificate sets the GetCertificate hook on +// tls.Config.GetCertificate. It's the usual integration point for +// autocert. +func WithGetCertificate(getCertFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error)) Option { + return func(config *tls.Config) { + config.GetCertificate = getCertFunc + } +} + // NewConfig returns a configured *tls.Config. By default, the TLS Config is set to // MinVersion of TLS 1.2 and a Modern Profile. //