diff --git a/.gitignore b/.gitignore index 72e42e6..8a1879a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # directories .idea/ +.vscode/ # files .DS_Store diff --git a/README.md b/README.md index 2ff1785..79ff2b3 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,10 @@ Helmet helps you secure your Golang web applications by setting various HTTP sec You can see more in the [documentation](https://pkg.go.dev/github.com/goddtriffin/helmet). - `go get github.com/goddtriffin/helmet` +### With net/http + ```go package main @@ -44,6 +45,36 @@ func main() { This code sample can be found in [`/examples/01-quick-start/`](https://github.com/goddtriffin/helmet/blob/master/examples/01-quick-start/main.go). +### With fasthttp + +```go +package main + +import ( + "github.com/fasthttp/router" + "github.com/goddtriffin/helmet" + "github.com/valyala/fasthttp" +) + +func main() { + r := router.New() + + r.GET("/", func(ctx *fasthttp.RequestCtx) { + ctx.WriteString("I love HelmetJS, I just wish there was a Go(lang) equivalent...") + }) + + h := helmet.Default() + + httpServer := fasthttp.Server{ + Handler: h.SecureFastHTTP(r.Handler), + } + + httpServer.ListenAndServe(":8080") +} +``` + +This code sample can be found in [`/examples/01-quick-start-fasthttp/`](https://github.com/goddtriffin/helmet/blob/master/examples/01-quick-start-fasthttp/main.go). + ## How It Works Helmet is a collection of 12 smaller middleware functions that set HTTP security response headers. Initializing via `helmet.Default()` will not include all of these middleware functions by default. diff --git a/content-security-policy.go b/content-security-policy.go index cf1ac3c..74fa811 100644 --- a/content-security-policy.go +++ b/content-security-policy.go @@ -4,6 +4,8 @@ import ( "fmt" "net/http" "strings" + + "github.com/valyala/fasthttp" ) // HeaderContentSecurityPolicy is the Content-Security-Policy HTTP security header. @@ -193,7 +195,7 @@ func (csp *ContentSecurityPolicy) String() string { return csp.cache } - var policies = []string{} + policies := []string{} for directive, sources := range csp.policies { if len(sources) == 0 { policies = append(policies, fmt.Sprintf("%s", directive)) @@ -222,3 +224,10 @@ func (csp *ContentSecurityPolicy) Header(w http.ResponseWriter) { w.Header().Set(HeaderContentSecurityPolicy, csp.String()) } } + +// HeaderFastHTTP adds the Content-Security-Policy HTTP security header to the given *fasthttp.RequestCtx. +func (csp *ContentSecurityPolicy) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !csp.Empty() { + ctx.Response.Header.Set(HeaderContentSecurityPolicy, csp.String()) + } +} diff --git a/examples/01-quick-start-fasthttp/main.go b/examples/01-quick-start-fasthttp/main.go new file mode 100644 index 0000000..4838a08 --- /dev/null +++ b/examples/01-quick-start-fasthttp/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/fasthttp/router" + "github.com/goddtriffin/helmet" + "github.com/valyala/fasthttp" +) + +func main() { + r := router.New() + + r.GET("/", func(ctx *fasthttp.RequestCtx) { + ctx.WriteString("I love HelmetJS, I just wish there was a Go(lang) equivalent...") + }) + + h := helmet.Default() + + httpServer := fasthttp.Server{ + Handler: h.SecureFastHTTP(r.Handler), + } + + httpServer.ListenAndServe(":8080") +} diff --git a/expect-ct.go b/expect-ct.go index e48fffb..4378cc1 100644 --- a/expect-ct.go +++ b/expect-ct.go @@ -4,6 +4,8 @@ import ( "fmt" "net/http" "strings" + + "github.com/valyala/fasthttp" ) // HeaderExpectCT is the Expect-CT HTTP security header. @@ -95,3 +97,10 @@ func (ect *ExpectCT) Header(w http.ResponseWriter) { w.Header().Set(HeaderExpectCT, ect.String()) } } + +// HeaderFastHTTP adds the Expect-CT HTTP security header to the given *fasthttp.RequestCtx. +func (ect *ExpectCT) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !ect.Empty() { + ctx.Response.Header.Set(HeaderExpectCT, ect.String()) + } +} diff --git a/feature-policy.go b/feature-policy.go index 2342a70..95dff06 100644 --- a/feature-policy.go +++ b/feature-policy.go @@ -4,6 +4,8 @@ import ( "fmt" "net/http" "strings" + + "github.com/valyala/fasthttp" ) // HeaderFeaturePolicy is the Feature-Policy HTTP security header. @@ -135,7 +137,7 @@ func (fp *FeaturePolicy) String() string { return fp.cache } - var policies = []string{} + policies := []string{} for directive, origins := range fp.policies { originsAsStrings := []string{} for _, origin := range origins { @@ -160,3 +162,10 @@ func (fp *FeaturePolicy) Header(w http.ResponseWriter) { w.Header().Set(HeaderFeaturePolicy, fp.String()) } } + +// HeaderFastHTTP adds the Feature-Policy HTTP security header to the given *fasthttp.RequestCtx. +func (fp *FeaturePolicy) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !fp.Empty() { + ctx.Response.Header.Set(HeaderFeaturePolicy, fp.String()) + } +} diff --git a/go.mod b/go.mod index ee70644..5a7f123 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,15 @@ module github.com/goddtriffin/helmet go 1.18 + +require ( + github.com/fasthttp/router v1.4.10 + github.com/valyala/fasthttp v1.38.0 +) + +require ( + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/klauspost/compress v1.15.0 // indirect + github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect +) diff --git a/helmet.go b/helmet.go index a6a1081..518d612 100644 --- a/helmet.go +++ b/helmet.go @@ -2,6 +2,8 @@ package helmet import ( "net/http" + + "github.com/valyala/fasthttp" ) // Helmet is a HTTP security middleware for Go(lang) inspired by HelmetJS for Express.js. @@ -51,7 +53,7 @@ func Empty() *Helmet { } } -// Secure is the middleware handler. +// Secure is the net/http middleware handler. func (h *Helmet) Secure(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h.ContentSecurityPolicy.Header(w) @@ -70,3 +72,23 @@ func (h *Helmet) Secure(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +// SecureFastHTTP is the fasthttp middleware handler. +func (h *Helmet) SecureFastHTTP(next fasthttp.RequestHandler) fasthttp.RequestHandler { + return func(ctx *fasthttp.RequestCtx) { + h.ContentSecurityPolicy.HeaderFastHTTP(ctx) + h.XContentTypeOptions.HeaderFastHTTP(ctx) + h.XDNSPrefetchControl.HeaderFastHTTP(ctx) + h.XDownloadOptions.HeaderFastHTTP(ctx) + h.ExpectCT.HeaderFastHTTP(ctx) + h.FeaturePolicy.HeaderFastHTTP(ctx) + h.XFrameOptions.HeaderFastHTTP(ctx) + h.XPermittedCrossDomainPolicies.HeaderFastHTTP(ctx) + h.XPoweredBy.HeaderFastHTTP(ctx) + h.ReferrerPolicy.HeaderFastHTTP(ctx) + h.StrictTransportSecurity.HeaderFastHTTP(ctx) + h.XXSSProtection.HeaderFastHTTP(ctx) + + next(ctx) + } +} diff --git a/referrer-policy.go b/referrer-policy.go index ecf51e7..069ac62 100644 --- a/referrer-policy.go +++ b/referrer-policy.go @@ -3,6 +3,8 @@ package helmet import ( "net/http" "strings" + + "github.com/valyala/fasthttp" ) // HeaderReferrerPolicy is the Referrer-Policy HTTP security header. @@ -73,3 +75,10 @@ func (rp *ReferrerPolicy) Header(w http.ResponseWriter) { w.Header().Set(HeaderReferrerPolicy, rp.String()) } } + +// HeaderFastHTTP adds the Referrer-Policy HTTP header to the given *fasthttp.RequestCtx. +func (rp *ReferrerPolicy) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !rp.Empty() { + ctx.Response.Header.Set(HeaderReferrerPolicy, rp.String()) + } +} diff --git a/strict-transport-security.go b/strict-transport-security.go index d724617..8dcd5c0 100644 --- a/strict-transport-security.go +++ b/strict-transport-security.go @@ -4,6 +4,8 @@ import ( "fmt" "net/http" "strings" + + "github.com/valyala/fasthttp" ) // HeaderStrictTransportSecurity is the Strict-Transport-Security HTTP security header. @@ -95,3 +97,10 @@ func (hsts *StrictTransportSecurity) Header(w http.ResponseWriter) { w.Header().Set(HeaderStrictTransportSecurity, hsts.String()) } } + +// HeaderFastHTTP adds the Strict-Transport-Security HTTP security header to the given *fasthttp.RequestCtx. +func (hsts *StrictTransportSecurity) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !hsts.Empty() { + ctx.Response.Header.Set(HeaderStrictTransportSecurity, hsts.String()) + } +} diff --git a/x-content-type-options.go b/x-content-type-options.go index 67f58d7..f0c7158 100644 --- a/x-content-type-options.go +++ b/x-content-type-options.go @@ -1,6 +1,10 @@ package helmet -import "net/http" +import ( + "net/http" + + "github.com/valyala/fasthttp" +) // HeaderXContentTypeOptions is the X-Content-Type-Options HTTP header. const HeaderXContentTypeOptions = "X-Content-Type-Options" @@ -26,3 +30,10 @@ func (xcto XContentTypeOptions) Header(w http.ResponseWriter) { w.Header().Set(HeaderXContentTypeOptions, xcto.String()) } } + +// HeaderFastHTTP adds the X-Content-Type-Options HTTP security header to the given *fasthttp.RequestCtx. +func (xcto XContentTypeOptions) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !xcto.Empty() { + ctx.Response.Header.Set(HeaderXContentTypeOptions, xcto.String()) + } +} diff --git a/x-dns-prefetch-control.go b/x-dns-prefetch-control.go index 90b9062..dbd2f87 100644 --- a/x-dns-prefetch-control.go +++ b/x-dns-prefetch-control.go @@ -1,6 +1,10 @@ package helmet -import "net/http" +import ( + "net/http" + + "github.com/valyala/fasthttp" +) // HeaderXDNSPrefetchControl is the X-DNS-Prefetch-Control HTTP header. const HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" @@ -29,3 +33,10 @@ func (dns XDNSPrefetchControl) Header(w http.ResponseWriter) { w.Header().Set(HeaderXDNSPrefetchControl, dns.String()) } } + +// HeaderFastHTTP adds the X-DNS-Prefetch-Control HTTP security header to the given *fasthttp.RequestCtx. +func (dns XDNSPrefetchControl) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !dns.Empty() { + ctx.Response.Header.Set(HeaderXDNSPrefetchControl, dns.String()) + } +} diff --git a/x-download-options.go b/x-download-options.go index 812ace4..214c875 100644 --- a/x-download-options.go +++ b/x-download-options.go @@ -1,6 +1,10 @@ package helmet -import "net/http" +import ( + "net/http" + + "github.com/valyala/fasthttp" +) // HeaderXDownloadOptions is the X-Download-Options HTTP header. const HeaderXDownloadOptions = "X-Download-Options" @@ -26,3 +30,10 @@ func (xdo XDownloadOptions) Header(w http.ResponseWriter) { w.Header().Set(HeaderXDownloadOptions, xdo.String()) } } + +// HeaderFastHTTP adds the X-Download-Options HTTP security header to the given *fasthttp.RequestCtx. +func (xdo XDownloadOptions) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !xdo.Empty() { + ctx.Response.Header.Set(HeaderXDownloadOptions, xdo.String()) + } +} diff --git a/x-frame-options.go b/x-frame-options.go index b1d4c06..d711f80 100644 --- a/x-frame-options.go +++ b/x-frame-options.go @@ -1,6 +1,10 @@ package helmet -import "net/http" +import ( + "net/http" + + "github.com/valyala/fasthttp" +) // HeaderXFrameOptions is the X-Frame-Options HTTP security header. const HeaderXFrameOptions = "X-Frame-Options" @@ -29,3 +33,10 @@ func (xfo XFrameOptions) Header(w http.ResponseWriter) { w.Header().Set(HeaderXFrameOptions, xfo.String()) } } + +// HeaderFastHTTP adds the X-Frame-Options HTTP header to the given *fasthttp.RequestCtx. +func (xfo XFrameOptions) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !xfo.Empty() { + ctx.Response.Header.Set(HeaderXFrameOptions, xfo.String()) + } +} diff --git a/x-permitted-cross-domain-policies.go b/x-permitted-cross-domain-policies.go index b75ab4e..e9058b8 100644 --- a/x-permitted-cross-domain-policies.go +++ b/x-permitted-cross-domain-policies.go @@ -1,6 +1,10 @@ package helmet -import "net/http" +import ( + "net/http" + + "github.com/valyala/fasthttp" +) // HeaderXPermittedCrossDomainPolicies is the X-Permitted-Cross-Domain-Policies HTTP security header. const HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" @@ -32,3 +36,10 @@ func (cdp XPermittedCrossDomainPolicies) Header(w http.ResponseWriter) { w.Header().Set(HeaderXPermittedCrossDomainPolicies, cdp.String()) } } + +// HeaderFastHTTP adds the X-DNS-Prefetch-Control HTTP security header to the given *fasthttp.RequestCtx. +func (cdp XPermittedCrossDomainPolicies) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !cdp.Empty() { + ctx.Response.Header.Set(HeaderXPermittedCrossDomainPolicies, cdp.String()) + } +} diff --git a/x-powered-by.go b/x-powered-by.go index a02846e..7069679 100644 --- a/x-powered-by.go +++ b/x-powered-by.go @@ -1,6 +1,10 @@ package helmet -import "net/http" +import ( + "net/http" + + "github.com/valyala/fasthttp" +) // HeaderXPoweredBy is the X-Powered-By HTTP security header. const HeaderXPoweredBy = "X-Powered-By" @@ -44,3 +48,19 @@ func (xpb XPoweredBy) Header(w http.ResponseWriter) { w.Header().Set(HeaderXPoweredBy, xpb.Replacement) } } + +// HeaderFastHTTP adds the X-Powered-By HTTP security header to the given *fasthttp.RequestCtx. +func (xpb XPoweredBy) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if xpb.Empty() { + return + } + + if xpb.Hide { + ctx.Response.Header.Del(HeaderXPoweredBy) + return + } + + if xpb.Replacement != "" { + ctx.Response.Header.Set(HeaderXPoweredBy, xpb.Replacement) + } +} diff --git a/x-xss-protection.go b/x-xss-protection.go index 0fa35ae..e07d9b2 100644 --- a/x-xss-protection.go +++ b/x-xss-protection.go @@ -4,6 +4,8 @@ import ( "fmt" "net/http" "strings" + + "github.com/valyala/fasthttp" ) // HeaderXXSSProtection is the X-XSS-Protection HTTP security header. @@ -90,3 +92,10 @@ func (xssp *XXSSProtection) Header(w http.ResponseWriter) { w.Header().Set(HeaderXXSSProtection, xssp.String()) } } + +// HeaderFastHTTP adds the X-XSS-Protection HTTP security header to the given *fasthttp.RequestCtx. +func (xssp *XXSSProtection) HeaderFastHTTP(ctx *fasthttp.RequestCtx) { + if !xssp.Empty() { + ctx.Response.Header.Set(HeaderXXSSProtection, xssp.String()) + } +}