diff --git a/internal/httpproxy/accept.go b/internal/httpproxy/accept.go new file mode 100644 index 00000000..c895e038 --- /dev/null +++ b/internal/httpproxy/accept.go @@ -0,0 +1,24 @@ +package httpproxy + +import ( + "fmt" + "net/http" +) + +func (h *handler) isAccepted(responseWriter http.ResponseWriter, request *http.Request) bool { + // Not compatible with HTTP < 1.0 or HTTP >= 2.0 (see https://github.com/golang/go/issues/14797#issuecomment-196103814) + const ( + minimalMajorVersion = 1 + minimalMinorVersion = 0 + maximumMajorVersion = 2 + maximumMinorVersion = 0 + ) + if !request.ProtoAtLeast(minimalMajorVersion, minimalMinorVersion) || + request.ProtoAtLeast(maximumMajorVersion, maximumMinorVersion) { + message := fmt.Sprintf("http version not supported: %s", request.Proto) + h.logger.Info("%s, from %s", message, request.RemoteAddr) + http.Error(responseWriter, message, http.StatusBadRequest) + return false + } + return true +} diff --git a/internal/httpproxy/auth.go b/internal/httpproxy/auth.go index 3b48e71f..796ed2d5 100644 --- a/internal/httpproxy/auth.go +++ b/internal/httpproxy/auth.go @@ -6,10 +6,13 @@ import ( "strings" ) -func isAuthorized(responseWriter http.ResponseWriter, request *http.Request, - username, password string) (authorized bool) { +func (h *handler) isAuthorized(responseWriter http.ResponseWriter, request *http.Request) (authorized bool) { + if len(h.username) == 0 || (request.Method != "CONNECT" && !request.URL.IsAbs()) { + return true + } basicAuth := request.Header.Get("Proxy-Authorization") if len(basicAuth) == 0 { + h.logger.Info("Proxy-Authorization header not found from %s", request.RemoteAddr) responseWriter.Header().Set("Proxy-Authenticate", `Basic realm="Access to Gluetun over HTTP"`) responseWriter.WriteHeader(http.StatusProxyAuthRequired) return false @@ -17,6 +20,8 @@ func isAuthorized(responseWriter http.ResponseWriter, request *http.Request, b64UsernamePassword := strings.TrimPrefix(basicAuth, "Basic ") b, err := base64.StdEncoding.DecodeString(b64UsernamePassword) if err != nil { + h.logger.Info("Cannot decode Proxy-Authorization header value from %s: %s", + request.RemoteAddr, err.Error()) responseWriter.WriteHeader(http.StatusUnauthorized) return false } @@ -26,7 +31,9 @@ func isAuthorized(responseWriter http.ResponseWriter, request *http.Request, responseWriter.WriteHeader(http.StatusBadRequest) return false } - if username != usernamePassword[0] && password != usernamePassword[1] { + if h.username != usernamePassword[0] || h.password != usernamePassword[1] { + h.logger.Info("Username or password mismatch from %s", request.RemoteAddr) + h.logger.Debug("username provided %q and password provided %q", usernamePassword[0], usernamePassword[1]) responseWriter.WriteHeader(http.StatusUnauthorized) return false } diff --git a/internal/httpproxy/handler.go b/internal/httpproxy/handler.go index 8ee3cdb9..9e846bef 100644 --- a/internal/httpproxy/handler.go +++ b/internal/httpproxy/handler.go @@ -34,10 +34,15 @@ type handler struct { } func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) { - if len(h.username) > 0 && !isAuthorized(responseWriter, request, h.username, h.password) { - h.logger.Info("%s unauthorized", request.RemoteAddr) + if !h.isAccepted(responseWriter, request) { return } + if !h.isAuthorized(responseWriter, request) { + return + } + request.Header.Del("Proxy-Connection") + request.Header.Del("Proxy-Authenticate") + request.Header.Del("Proxy-Authorization") switch request.Method { case http.MethodConnect: h.handleHTTPS(responseWriter, request)