Add CSRF middleware to all pages

The frontend server now runs a CSRF middleware on all paths except
/assets/. To enable CSRF the -csrf_key flag must be set. Instead of
enforcing the flag is set, I opted to log that CSRF is disabled. Not
running CSRF means I can test with curl.

In the future I might enforce that the flag must be set in release builds.
This commit is contained in:
Victor Vrantchan
2020-07-22 23:22:03 -04:00
parent a9900916bf
commit a8dc606beb
5 changed files with 72 additions and 3 deletions

View File

@@ -9,9 +9,11 @@ import (
"net/http"
"path/filepath"
"runtime/debug"
"strings"
"sync"
"text/template"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
"micromdm.io/v2/pkg/log"
@@ -66,12 +68,20 @@ type Server struct {
templates map[string]*template.Template
siteName string
csrfKey []byte
csrfCookieName string
csrfFieldName string
}
// Config parameters to create a new Server.
type Config struct {
Logger log.Logger
SiteName string
CSRFKey []byte
CSRFCookieName string
CSRFFieldName string
}
// New creates a Server.
@@ -80,10 +90,15 @@ func New(config Config) (*Server, error) {
r: mux.NewRouter(),
templates: make(map[string]*template.Template),
siteName: config.SiteName,
csrfKey: config.CSRFKey,
csrfFieldName: config.CSRFFieldName,
csrfCookieName: config.CSRFCookieName,
}
srv.r.Use(
log.HTTP(config.Logger), // HTTP logging middleware.
srv.csrf, // CSRF protection.
srv.recoverPanic, // convert any panic into 500 errors.
)
@@ -221,6 +236,46 @@ func (srv *Server) recoverPanic(next http.Handler) http.Handler {
})
}
func csrfDisabled(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger := log.FromContext(r.Context())
log.Info(logger).Log("msg", "CSRF Protection disabled", "reason", "CSRF key not set.")
next.ServeHTTP(w, r)
})
}
func (srv *Server) csrf(next http.Handler) http.Handler {
mw := csrfDisabled
if string(srv.csrfKey) != "" {
mw = csrf.Protect(
srv.csrfKey,
csrf.CookieName(srv.csrfCookieName),
csrf.FieldName(srv.csrfFieldName),
csrf.ErrorHandler(http.HandlerFunc(srv.csrfErrorHandler)),
)
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Excluding assets from setting the cookie.
// Refactor into a switch or callback? if adding other paths.
if strings.HasPrefix(r.URL.Path, "/assets/") {
next.ServeHTTP(w, r)
return
}
mw(next).ServeHTTP(w, r)
})
}
func (srv *Server) csrfErrorHandler(w http.ResponseWriter, r *http.Request) {
switch err := csrf.FailureReason(r); {
// TODO: add 403 cases
default:
srv.Fail(r.Context(), w, err, "msg", "csrf.Protect encountered an error")
return
}
}
func (srv *Server) notFound(w http.ResponseWriter, r *http.Request) {
srv.RenderTemplate(r.Context(), w, "404.tmpl", Data{}.WithCode(http.StatusNotFound))
}