From 1a0618b0f407daa67140029f37783890bce1251e Mon Sep 17 00:00:00 2001 From: Lz Date: Sun, 5 Jan 2025 15:31:21 +0800 Subject: [PATCH] feat(autotls): added autotls extension (#17) --- README.md | 42 +++++++++++++++++++- ext/autotls/manager.go | 79 +++++++++++++++++++++++++++++++++++++ ext/autotls/manager_test.go | 75 +++++++++++++++++++++++++++++++++++ ext/autotls/option.go | 42 ++++++++++++++++++++ go.mod | 2 +- 5 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 ext/autotls/manager.go create mode 100644 ext/autotls/manager_test.go create mode 100644 ext/autotls/option.go diff --git a/README.md b/README.md index 2f3a844..f5cf0aa 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,12 @@ Xun [ʃʊn] (pronounced 'shoon'), derived from the Chinese character 迅, signif - Works with Go's built-in `html/template`. It is built-in support for Server-Side Rendering (SSR). - Built-in response compression support for `gzip` and `deflate`. - Built-in Form and Validate feature with i18n support. +- Built-in `AutoTLS` feature. It automatic SSL certificate issuance and renewal through Let's Encrypt and other ACME-based CAs - Support Page Router in `StaticViewEngine` and `HtmlViewEngine`. - Support multiple viewers by ViewEngines: `StaticViewEngine`, `JsonViewEngine` and `HtmlViewEngine`. You can feel free to add custom view engine, eg `XmlViewEngine`. - Support to reload changed static files automatically in development environment. - + + ## Getting Started > See full source code on [xun-examples](https://github.com/yaitoo/xun-examples) @@ -419,6 +421,44 @@ xun.AddValidator(ut.New(zh.New()).GetFallback(), trans.RegisterDefaultTranslatio > check more translations on [here](https://github.com/go-playground/validator/tree/master/translations) +### Extensions +#### GZip/Deflate handler +Set up the compression extension to interpret and respond to `Accept-Encoding` headers in client requests, supporting both GZip and Deflate compression methods. + +```go +app := xun.New(WithCompressor(&GzipCompressor{}, &DeflateCompressor{})) +``` + +#### AutoTLS +Use `autotls.Configure` to set up servers for automatic obtaining and renewing of TLS certificates from Let's Encrypt. + +```go + + mux := http.NewServeMux() + + app := xun.New(xun.WithMux(mux)) + + //... + + httpServer := &http.Server{ + Addr: ":http", + //... + } + + httpsServer := &http.Server{ + Addr: ":https", + //... + } + + autotls. + New(autotls.WithCache(autocert.DirCache("./certs")), + autotls.WithHosts("abc.com", "123.com")). + Configure(httpServer, httpsServer) + + go httpServer.ListenAndServe() + go httpsServer.ListenAndServeTLS("", "") +``` + ### Works with [tailwindcss](https://tailwindcss.com/docs/installation) #### Install Tailwind CSS Install tailwindcss via npm, and create your tailwind.config.js file. diff --git a/ext/autotls/manager.go b/ext/autotls/manager.go new file mode 100644 index 0000000..92b5071 --- /dev/null +++ b/ext/autotls/manager.go @@ -0,0 +1,79 @@ +package autotls + +import ( + "crypto/tls" + "net/http" + "time" + + "golang.org/x/crypto/acme/autocert" +) + +// Manager is a wrapper around autocert.Manager that provides automatic +// management of SSL/TLS certificates. It embeds autocert.Manager to +// inherit its methods and functionalities, allowing for seamless +// integration and usage of automatic certificate management in your +// application. +type Manager struct { + *autocert.Manager +} + +// New creates a new AutoSSL instance with the provided options. +// It initializes an autocert.Manager with the AcceptTOS prompt. +// If no cache is provided in the options, it defaults to using a directory cache. +// +// Parameters: +// +// opts - A variadic list of Option functions to configure the autocert.Manager. +// +// Returns: +// +// A pointer to an AutoSSL instance with the configured autocert.Manager. +func New(opts ...Option) *Manager { + cm := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + } + + for _, opt := range opts { + opt(cm) + } + + if cm.Cache == nil { + cm.Cache = autocert.DirCache(".") + } + + return &Manager{ + Manager: cm, + } +} + +// Configure sets up the HTTP and HTTPS servers with the necessary handlers and TLS configurations. +// It modifies the HTTP server to use the AutoSSL manager's HTTP handler and ensures the HTTPS server +// has a TLS configuration with at least TLS 1.2. It also sets the GetCertificate function for the +// HTTPS server's TLS configuration to use the AutoSSL manager's GetCertificate method. +// +// Parameters: +// - httpSrv: A pointer to the HTTP server to be configured. +// - httpsSrv: A pointer to the HTTPS server to be configured. +func (m *Manager) Configure(httpSrv *http.Server, httpsSrv *http.Server) { + if httpSrv != nil && httpsSrv != nil { + httpSrv.Handler = m.Manager.HTTPHandler(httpSrv.Handler) + + if httpSrv.ReadHeaderTimeout == 0 { + httpSrv.ReadHeaderTimeout = 3 * time.Second // prevent Potential slowloris attack + } + + if httpsSrv.ReadHeaderTimeout == 0 { + httpsSrv.ReadHeaderTimeout = 3 * time.Second // prevent Potential slowloris attack + } + + if httpsSrv.TLSConfig == nil { + httpsSrv.TLSConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + MaxVersion: 0, + } + } + + httpsSrv.TLSConfig.GetCertificate = m.Manager.GetCertificate + } + +} diff --git a/ext/autotls/manager_test.go b/ext/autotls/manager_test.go new file mode 100644 index 0000000..9b85d05 --- /dev/null +++ b/ext/autotls/manager_test.go @@ -0,0 +1,75 @@ +package autotls + +import ( + "context" + "crypto/tls" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/acme/autocert" +) + +func TestNew(t *testing.T) { + as := New() + + require.NotNil(t, as) + require.NotNil(t, as.Manager) + require.True(t, as.Prompt("")) + require.NotNil(t, as.Manager.Cache) +} + +func TestConfigure(t *testing.T) { + as := New() + require.NotNil(t, as) + + httpSrv := &http.Server{} // skipcq: GO-S2112 + httpsSrv := &http.Server{} // skipcq: GO-S2112,GSC-G402 + + as.Configure(httpSrv, httpsSrv) + + require.NotNil(t, httpSrv.Handler) + require.NotNil(t, httpsSrv.TLSConfig) + + require.Equal(t, 3*time.Second, httpSrv.ReadHeaderTimeout) + require.Equal(t, 3*time.Second, httpsSrv.ReadHeaderTimeout) + + require.Equal(t, uint16(tls.VersionTLS12), httpsSrv.TLSConfig.MinVersion) + require.Equal(t, uint16(0), httpsSrv.TLSConfig.MaxVersion) + + require.NotNil(t, httpsSrv.TLSConfig.GetCertificate) + + httpSrv = &http.Server{ + ReadHeaderTimeout: 1 * time.Second, + } + httpsSrv = &http.Server{ + ReadHeaderTimeout: 1 * time.Second, + TLSConfig: &tls.Config{ // skipcq: GSC-G402 + MinVersion: tls.VersionTLS10, + MaxVersion: tls.VersionTLS13, + }, + } + + as.Configure(httpSrv, httpsSrv) + require.NotNil(t, httpSrv.Handler) + require.NotNil(t, httpsSrv.TLSConfig) + + require.Equal(t, 1*time.Second, httpSrv.ReadHeaderTimeout) + require.Equal(t, 1*time.Second, httpsSrv.ReadHeaderTimeout) + + require.Equal(t, uint16(tls.VersionTLS10), httpsSrv.TLSConfig.MinVersion) + require.Equal(t, uint16(tls.VersionTLS13), httpsSrv.TLSConfig.MaxVersion) + + require.NotNil(t, httpsSrv.TLSConfig.GetCertificate) +} + +func TestOptions(t *testing.T) { + as := New(WithCache(autocert.DirCache(".")), WithHosts("abc.com")) + + require.NotNil(t, as) + require.IsType(t, autocert.DirCache("."), as.Manager.Cache) + require.Nil(t, as.Manager.HostPolicy(context.TODO(), "abc.com")) + require.NotNil(t, as.Manager.HostPolicy(context.TODO(), "123.com")) + +} diff --git a/ext/autotls/option.go b/ext/autotls/option.go new file mode 100644 index 0000000..b525bb1 --- /dev/null +++ b/ext/autotls/option.go @@ -0,0 +1,42 @@ +package autotls + +import "golang.org/x/crypto/acme/autocert" + +// Option is a function type that takes a pointer to autocert.Manager as an argument. +// It is used to configure the autocert.Manager with various options. +type Option func(*autocert.Manager) + +// WithCache sets the cache for the autocert.Manager. +// It takes an autocert.Cache as an argument and returns an Option +// that configures the autocert.Manager to use the provided cache. +// +// Example usage: +// +// cache := autocert.DirCache("/path/to/cache") +// manager := &autocert.Manager{ +// Prompt: autocert.AcceptTOS, +// Cache: cache, +// } +// option := WithCache(cache) +// option(manager) +// +// Parameters: +// - cache: The autocert.Cache to be used by the autocert.Manager. +func WithCache(cache autocert.Cache) Option { + return func(cm *autocert.Manager) { + cm.Cache = cache + } +} + +// WithHosts returns an Option that sets the HostPolicy of the autocert.Manager +// to whitelist the provided hosts. This ensures that the manager will only +// respond to requests for the specified hosts. +// +// Parameters: +// +// hosts - A variadic string parameter representing the hostnames to be whitelisted. +func WithHosts(hosts ...string) Option { + return func(cm *autocert.Manager) { + cm.HostPolicy = autocert.HostWhitelist(hosts...) + } +} diff --git a/go.mod b/go.mod index 7ca7055..702505f 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-playground/validator/v10 v10.23.0 github.com/json-iterator/go v1.1.12 github.com/stretchr/testify v1.10.0 + golang.org/x/crypto v0.31.0 ) require ( @@ -18,7 +19,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect