Replaced one-pager with multiple pages and fixed security bugs
This commit is contained in:
@@ -6,7 +6,9 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -142,9 +144,11 @@ func main() {
|
||||
registerRuleRoutes(mux)
|
||||
registerWSRoutes(mux)
|
||||
|
||||
handler := withSecurityHeaders(withLog(withCSRF(withCORS(mux))))
|
||||
|
||||
server := &http.Server{
|
||||
Addr: cfg.Addr,
|
||||
Handler: withLog(withCORS(mux)),
|
||||
Handler: handler,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
@@ -171,6 +175,22 @@ func withLog(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func withSecurityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
h.Set("Referrer-Policy", "no-referrer")
|
||||
h.Set("Permissions-Policy", "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()")
|
||||
h.Set("Cross-Origin-Resource-Policy", "same-site")
|
||||
h.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'")
|
||||
if r.TLS != nil || strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") {
|
||||
h.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func originAllowed(origin string) bool {
|
||||
if origin == "" {
|
||||
return false
|
||||
@@ -186,6 +206,65 @@ func originAllowed(origin string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func sameOriginRequest(r *http.Request) bool {
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
// Fall back to Referer
|
||||
ref := r.Header.Get("Referer")
|
||||
if ref == "" {
|
||||
// No origin info: only safe methods allowed. State-changing must have Origin.
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(ref)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
origin = u.Scheme + "://" + u.Host
|
||||
}
|
||||
u, err := url.Parse(origin)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
originHost, _, _ := net.SplitHostPort(u.Host)
|
||||
if originHost == "" {
|
||||
originHost = u.Host
|
||||
}
|
||||
reqHost, _, _ := net.SplitHostPort(r.Host)
|
||||
if reqHost == "" {
|
||||
reqHost = r.Host
|
||||
}
|
||||
return strings.EqualFold(originHost, reqHost)
|
||||
}
|
||||
|
||||
func isStateChanging(method string) bool {
|
||||
switch method {
|
||||
case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func withCSRF(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !isStateChanging(r.Method) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
origin := r.Header.Get("Origin")
|
||||
// Allow if Origin matches a configured CORS origin.
|
||||
if origin != "" && originAllowed(origin) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// Allow same-origin requests (Origin or Referer host matches request Host).
|
||||
if sameOriginRequest(r) {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
writeError(w, http.StatusForbidden, "csrf_forbidden")
|
||||
})
|
||||
}
|
||||
|
||||
func withCORS(next http.Handler) http.Handler {
|
||||
if len(corsOrigins) == 0 {
|
||||
return next
|
||||
|
||||
Reference in New Issue
Block a user