diff --git a/go.mod b/go.mod index 781a260a03..d223265ff3 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/tinylib/msgp v1.2.4 github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/fasthttp v1.57.0 + golang.org/x/crypto v0.28.0 ) require ( diff --git a/go.sum b/go.sum index f3eb0e2468..0e17342468 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/listen.go b/listen.go index 0df4e1a060..4bb489069d 100644 --- a/listen.go +++ b/listen.go @@ -22,6 +22,7 @@ import ( "github.com/gofiber/fiber/v3/log" "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" + "golang.org/x/crypto/acme/autocert" ) // Figlet text to show Fiber ASCII art on startup message @@ -70,6 +71,14 @@ type ListenConfig struct { // // Default: nil OnShutdownSuccess func() + + // AutoCertManager manages TLS certificates automatically using the ACME protocol, + // enabling integration with Let's Encrypt or other ACME-compatible providers. + // Provide an *autocert.Manager instance to enable automatic certificate management. + // + // Default: nil + AutoCertManager *autocert.Manager + // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only) // WARNING: When prefork is set to true, only "tcp4" and "tcp6" can be chosen. // @@ -176,6 +185,12 @@ func (app *App) Listen(addr string, config ...ListenConfig) error { // Attach the tlsHandler to the config app.SetTLSHandler(tlsHandler) + } else if cfg.AutoCertManager != nil { + tlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + GetCertificate: cfg.AutoCertManager.GetCertificate, + NextProtos: []string{"http/1.1", "acme-tls/1"}, + } } if cfg.TLSConfigFunc != nil { diff --git a/listen_test.go b/listen_test.go index c828a911cb..d61422cfd7 100644 --- a/listen_test.go +++ b/listen_test.go @@ -19,6 +19,8 @@ import ( "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttputil" + "golang.org/x/crypto/acme" + "golang.org/x/crypto/acme/autocert" ) // go test -run Test_Listen @@ -145,6 +147,45 @@ func Test_Listen_TLS(t *testing.T) { })) } +// go test -run Test_Listen_Acme_TLS +func Test_Listen_Acme_TLS(t *testing.T) { + dir, _ := os.Getwd() + dir, err := os.MkdirTemp(dir, "certs") + require.NoError(t, err) + defer os.RemoveAll(dir) + + // Certificate manager + m := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + // Replace with your domain + HostPolicy: autocert.HostWhitelist("example.com"), + // Folder to store the certificates + Cache: autocert.DirCache(dir), + // Define the Client for test + Client: &acme.Client{ + DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory", + }, + } + + app := New() + + // invalid port + require.Error(t, app.Listen(":99999", ListenConfig{ + DisableStartupMessage: true, + AutoCertManager: m, + })) + + go func() { + time.Sleep(1000 * time.Millisecond) + assert.NoError(t, app.Shutdown()) + }() + + require.NoError(t, app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + AutoCertManager: m, + })) +} + // go test -run Test_Listen_TLS_Prefork func Test_Listen_TLS_Prefork(t *testing.T) { testPreforkMaster = true @@ -172,6 +213,45 @@ func Test_Listen_TLS_Prefork(t *testing.T) { })) } +// go test -run Test_Listen_Acme_TLS_Prefork +func Test_Listen_Acme_TLS_Prefork(t *testing.T) { + dir, _ := os.MkdirTemp(".", "./certs") + defer os.RemoveAll(dir) + + // Certificate manager + m := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + // Replace with your domain + HostPolicy: autocert.HostWhitelist("example.com"), + // Folder to store the certificates + Cache: autocert.DirCache(dir), + // Define the Client for test + Client: &acme.Client{ + DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory", + }, + } + + app := New() + + // invalid port + require.Error(t, app.Listen(":0", ListenConfig{ + DisableStartupMessage: true, + EnablePrefork: true, + AutoCertManager: m, + })) + + go func() { + time.Sleep(1000 * time.Millisecond) + assert.NoError(t, app.Shutdown()) + }() + + require.NoError(t, app.Listen(":99999", ListenConfig{ + DisableStartupMessage: true, + EnablePrefork: true, + AutoCertManager: m, + })) +} + // go test -run Test_Listen_MutualTLS func Test_Listen_MutualTLS(t *testing.T) { app := New()