You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

104 lines
4.4 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package server
import (
"fmt"
"log/slog"
"net/http"
)
// https://owasp.org/www-project-secure-headers/ guidance
// - Headers to Add: https://owasp.org/www-project-secure-headers/ci/headers_add.json
// - Headers to Remove: https://owasp.org/www-project-secure-headers/ci/headers_remove.json
// - See also testing tools like https://github.com/ovh/venom for security testing
// Content-Security-Policy (often abbreviated to CSP) headers are used to restrict where the
// resources for your web page (e.g. JavaScript, images, fonts etc) can be loaded from. Setting
// a strict CSP policy helps prevent a variety of cross-site scripting, clickjacking, and
// other code-injection attacks.
// CSP headers and how they work is a big topic, and I recommend reading this primer if you
// havent come across them before. But, in our case, the header tells the browser that
//its OK to load fonts from fonts.gstatic.com, stylesheets from fonts.googleapis.com and
// self (our own origin), and then everything else only from self. Inline JavaScript is blocked
// by default.
// Referrer-Policy is used to control what information is included in a Referer header when
// a user navigates away from your web page. In our case, well set the value to
// origin-when-cross-origin, which means that the full URL will be included for same-origin
// requests, but for all other requests information like the URL path and any query string
// values will be stripped out.
// X-Content-Type-Options: nosniff instructs browsers to not MIME-type sniff the content-type
// of the response, which in turn helps to prevent content-sniffing attacks.
// X-Frame-Options: deny is used to help prevent clickjacking attacks in older browsers that
// dont support CSP headers.
// X-XSS-Protection: 0 is used to disable the blocking of cross-site scripting attacks.
// Previously it was good practice to set this header to X-XSS-Protection: 1; mode=block,
// but when youre using CSP headers like we are the recommendation is to disable this feature
// altogether.
// CommonHeaderMiddleware adds common headers and secure headers following OWASP guidance.
func CommonHeaderMiddleware(next http.Handler) http.Handler {
// Technically the guidance is to remove "Server" header
headers := map[string]string{"Server": "Go"}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for k, v := range headers {
// know the diff between Add and Set
w.Header().Set(k, v)
}
w.Header().Set("Content-Security-Policy",
"default-src 'self'; style-src 'self' fonts.googleapis.com; font-src fonts.gstatic.com")
w.Header().Set("Referrer-Policy", "origin-when-cross-origin")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "deny")
w.Header().Set("X-XSS-Protection", "0")
next.ServeHTTP(w, r)
})
}
func RequestLoggingMiddleware(next http.Handler, logger *slog.Logger) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
ip = r.RemoteAddr
proto = r.Proto
method = r.Method
uri = r.URL.RequestURI()
)
logger.Info("received request", "ip", ip, "proto", proto, "method", method, "uri", uri)
next.ServeHTTP(w, r)
})
}
// RecoveryMiddleware recovers from panics that occur within http handler functions
//
// Go's HTTP server assumes that any panic is isolated to the goroutine serving the
// active http request. Following a panic the server will log a stack trace to the
// server error log, unwind the stack of the affected goroutine, calling defered functions
// along the way, and closing the underlying http connection. This doesn't terminate the application.
//
// Important this only will recover panics raised within the same goroutine that was
// throwing the panic. This means that if the handler is spinning off it's own goroutine
// you need to add a defer recover function like this into that goroutine or your server
// will crash since the http Server will not be handling the panic in the request go routine
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// Go's HTTP server will close the connection for us after the response with
// this header has been sent. http/2 it will strip it and send a GOAWAY frame
// instead
w.Header().Set("Connection", "close")
serverError(w, r, fmt.Errorf("%s", err))
}
}()
next.ServeHTTP(w, r)
})
}