Resolve real client IP behind trusted proxies (closes #94) #127
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/pixa/internal/clientip"
|
||||
"sneak.berlin/go/pixa/internal/encurl"
|
||||
"sneak.berlin/go/pixa/internal/imgcache"
|
||||
"sneak.berlin/go/pixa/internal/templates"
|
||||
@@ -47,7 +48,8 @@ func (s *Handlers) handleLoginPost(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Constant-time comparison to prevent timing attacks
|
||||
if subtle.ConstantTimeCompare([]byte(submittedKey), []byte(s.config.SigningKey)) != 1 {
|
||||
s.log.Warn("failed login attempt", "remote_addr", r.RemoteAddr)
|
||||
s.log.Warn("failed login attempt",
|
||||
"remote_addr", clientip.FromContext(r.Context()))
|
||||
s.renderLogin(w, r, "Invalid signing key")
|
||||
|
||||
return
|
||||
@@ -62,7 +64,8 @@ func (s *Handlers) handleLoginPost(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.log.Info("successful login", "remote_addr", r.RemoteAddr)
|
||||
s.log.Info("successful login",
|
||||
"remote_addr", clientip.FromContext(r.Context()))
|
||||
|
||||
// Redirect to generator page
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
|
||||
@@ -3,7 +3,6 @@ package middleware
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
ghmm "github.com/slok/go-http-metrics/middleware"
|
||||
"github.com/slok/go-http-metrics/middleware/std"
|
||||
"go.uber.org/fx"
|
||||
"sneak.berlin/go/pixa/internal/clientip"
|
||||
"sneak.berlin/go/pixa/internal/config"
|
||||
"sneak.berlin/go/pixa/internal/logger"
|
||||
)
|
||||
@@ -58,31 +58,34 @@ type Params struct {
|
||||
|
||||
// Middleware provides HTTP middleware functions.
|
||||
type Middleware struct {
|
||||
log *slog.Logger
|
||||
config *config.Config
|
||||
log *slog.Logger
|
||||
config *config.Config
|
||||
clientIP *clientip.Resolver
|
||||
}
|
||||
|
||||
// New creates a new Middleware instance.
|
||||
func New(_ fx.Lifecycle, params Params) (*Middleware, error) {
|
||||
s := &Middleware{
|
||||
log: params.Logger.Get(),
|
||||
config: params.Config,
|
||||
log: params.Logger.Get(),
|
||||
config: params.Config,
|
||||
clientIP: clientip.NewResolver(params.Config.TrustedProxies),
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func ipFromHostPort(hp string) string {
|
||||
h, _, err := net.SplitHostPort(hp)
|
||||
if err != nil {
|
||||
return ""
|
||||
// ClientIP returns a middleware that resolves the real client IP,
|
||||
// honoring X-Forwarded-For only from trusted proxies, and stores it in
|
||||
// the request context for the logging middleware and handlers to read.
|
||||
func (s *Middleware) ClientIP() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ip := s.clientIP.Resolve(
|
||||
r.RemoteAddr, r.Header.Values(clientip.ForwardedForHeader))
|
||||
ctx := clientip.WithClientIP(r.Context(), ip)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
if len(h) > 0 && h[0] == '[' {
|
||||
return h[1 : len(h)-1]
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type loggingResponseWriter struct {
|
||||
@@ -127,7 +130,7 @@ func (s *Middleware) Logging() func(http.Handler) http.Handler {
|
||||
"request_id", reqID,
|
||||
"referer", r.Referer(),
|
||||
"proto", r.Proto,
|
||||
"remoteIP", ipFromHostPort(r.RemoteAddr),
|
||||
"remoteIP", clientip.FromContext(ctx),
|
||||
"status", lrw.statusCode,
|
||||
"response_bytes", lrw.bytesWritten,
|
||||
"latency_ms", latency.Milliseconds(),
|
||||
|
||||
@@ -18,6 +18,7 @@ func (s *Server) SetupRoutes() {
|
||||
|
||||
s.router.Use(middleware.Recoverer)
|
||||
s.router.Use(middleware.RequestID)
|
||||
s.router.Use(s.mw.ClientIP())
|
||||
s.router.Use(s.mw.SecurityHeaders())
|
||||
s.router.Use(s.mw.Logging())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user