|
|
|
@ -0,0 +1,103 @@
|
|
|
|
|
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
|
|
|
|
|
// haven’t come across them before. But, in our case, the header tells the browser that
|
|
|
|
|
//it’s 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, we’ll 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
|
|
|
|
|
// don’t 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 you’re 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)
|
|
|
|
|
})
|
|
|
|
|
}
|