Files
docker-agent/pkg/httpsec/host.go
T
David Gageot ca4254f2d8 fix: lock down REST session API against CSRF and DNS rebinding
Require --auth-token on non-loopback listeners, enforce JSON
content-type and origin checks on every method (not just mutations),
and add --remote-auth-token so `run --remote` can talk to a
token-protected listener.

Assisted-By: docker-agent
2026-10-18 19:31:55 +02:00

37 lines
1.2 KiB
Go

package httpsec
import (
"errors"
"net"
"net/http"
"net/url"
"strings"
)
// LoopbackHost rejects DNS-rebinding hosts on unauthenticated TCP listeners.
// Local sockets should not use it: their clients may use synthetic hostnames.
func LoopbackHost(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u, err := url.Parse("//" + r.Host)
if err != nil || u.Host != r.Host || u.User != nil || !isLoopbackHost(u.Hostname()) {
http.Error(w, "invalid Host header", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
func isLoopbackHost(host string) bool {
ip := net.ParseIP(host)
return strings.EqualFold(host, "localhost") || (ip != nil && ip.IsLoopback())
}
// ValidateListenerAuth requires authentication when a TCP listener is not local.
// Checking the bound address also covers inherited file-descriptor listeners.
func ValidateListenerAuth(addr net.Addr, token string) error {
if tcpAddr, ok := addr.(*net.TCPAddr); ok && !tcpAddr.IP.IsLoopback() && token == "" {
return errors.New("non-loopback TCP listeners require --auth-token; use a loopback address or local socket for unauthenticated access")
}
return nil
}