From 10e734e9d560684e8056231d2edfa78b8bfa1824 Mon Sep 17 00:00:00 2001 From: Artyom Misko Date: Mon, 15 May 2023 23:33:48 +0300 Subject: [PATCH 1/4] Add proxy headers: Forwarded, X-Forwarded-Host, X-Forwarded-Prefix --- echo.go | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/echo.go b/echo.go index 9028b7a71..ed7425a7b 100644 --- a/echo.go +++ b/echo.go @@ -205,24 +205,28 @@ const ( // advertised as supported by the target resource. Returning an Allow header is mandatory // for status 405 (method not found) and useful for the OPTIONS method in responses. // See RFC 7231: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1 - HeaderAllow = "Allow" - HeaderAuthorization = "Authorization" - HeaderContentDisposition = "Content-Disposition" - HeaderContentEncoding = "Content-Encoding" - HeaderContentLength = "Content-Length" - HeaderContentType = "Content-Type" - HeaderCookie = "Cookie" - HeaderSetCookie = "Set-Cookie" - HeaderIfModifiedSince = "If-Modified-Since" - HeaderLastModified = "Last-Modified" - HeaderLocation = "Location" - HeaderRetryAfter = "Retry-After" - HeaderUpgrade = "Upgrade" - HeaderVary = "Vary" - HeaderWWWAuthenticate = "WWW-Authenticate" - HeaderXForwardedFor = "X-Forwarded-For" - HeaderXForwardedProto = "X-Forwarded-Proto" - HeaderXForwardedProtocol = "X-Forwarded-Protocol" + HeaderAllow = "Allow" + HeaderAuthorization = "Authorization" + HeaderContentDisposition = "Content-Disposition" + HeaderContentEncoding = "Content-Encoding" + HeaderContentLength = "Content-Length" + HeaderContentType = "Content-Type" + HeaderCookie = "Cookie" + HeaderForwarded = "Forwarded" + HeaderSetCookie = "Set-Cookie" + HeaderIfModifiedSince = "If-Modified-Since" + HeaderLastModified = "Last-Modified" + HeaderLocation = "Location" + HeaderRetryAfter = "Retry-After" + HeaderUpgrade = "Upgrade" + HeaderVary = "Vary" + HeaderWWWAuthenticate = "WWW-Authenticate" + HeaderXForwardedFor = "X-Forwarded-For" + HeaderXForwardedHost = "X-Forwarded-Host" + HeaderXForwardedPrefix = "X-Forwarded-Prefix" + HeaderXForwardedProto = "X-Forwarded-Proto" + HeaderXForwardedProtocol = "X-Forwarded-Protocol" + HeaderXForwardedSsl = "X-Forwarded-Ssl" HeaderXUrlScheme = "X-Url-Scheme" HeaderXHTTPMethodOverride = "X-HTTP-Method-Override" From ef3bbb91f7963c80f87d77533a1a346433aa0ef6 Mon Sep 17 00:00:00 2001 From: Artyom Misko Date: Tue, 16 May 2023 00:00:36 +0300 Subject: [PATCH 2/4] Add middleware with support proxy headers --- middleware/proxy_headers.go | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 middleware/proxy_headers.go diff --git a/middleware/proxy_headers.go b/middleware/proxy_headers.go new file mode 100644 index 000000000..8aae1af51 --- /dev/null +++ b/middleware/proxy_headers.go @@ -0,0 +1,50 @@ +package middleware + +import ( + "net/url" + "regexp" + "strings" + + "github.com/labstack/echo/v4" +) + +var ( + protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`) +) + +func ProxyHeaders() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if fwd := c.RealIP(); fwd != "" { + c.Request().RemoteAddr = fwd + } + + if scheme := getScheme(c); scheme != "" { + c.Request().URL.Scheme = scheme + } + + if c.Request().Header.Get(echo.HeaderXForwardedHost) != "" { + c.Request().Host = c.Request().Header.Get(echo.HeaderXForwardedHost) + } + + if prefix := c.Request().Header.Get(echo.HeaderXForwardedPrefix); prefix != "" { + c.Request().RequestURI, _ = url.JoinPath(prefix, c.Request().RequestURI) + c.Request().URL.Path, _ = url.JoinPath(prefix, c.Request().URL.Path) + } + return next(c) + } + } +} + +func getScheme(c echo.Context) string { + var scheme string + + if proto := c.Request().Header.Get(echo.HeaderXForwardedProto); proto != "" { + scheme = strings.ToLower(proto) + } else if proto = c.Request().Header.Get(echo.HeaderForwarded); proto != "" { + if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 { + scheme = strings.ToLower(match[1]) + } + } + return scheme +} From a0b99c6a4815c83727fda2de13849218603ed37a Mon Sep 17 00:00:00 2001 From: Artyom Misko Date: Thu, 13 Jul 2023 00:42:07 +0300 Subject: [PATCH 3/4] Switch func getScheme from echo.Context to http.Request. Add unit-test for getScheme --- middleware/proxy_headers.go | 12 +++-- middleware/proxy_headers_test.go | 78 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 middleware/proxy_headers_test.go diff --git a/middleware/proxy_headers.go b/middleware/proxy_headers.go index 8aae1af51..6e86f1586 100644 --- a/middleware/proxy_headers.go +++ b/middleware/proxy_headers.go @@ -1,6 +1,7 @@ package middleware import ( + "net/http" "net/url" "regexp" "strings" @@ -10,6 +11,7 @@ import ( var ( protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`) + ipRegex = regexp.MustCompile("(?i)(?:for=)([^(;|,| )]+)") ) func ProxyHeaders() echo.MiddlewareFunc { @@ -19,7 +21,7 @@ func ProxyHeaders() echo.MiddlewareFunc { c.Request().RemoteAddr = fwd } - if scheme := getScheme(c); scheme != "" { + if scheme := getScheme(c.Request()); scheme != "" { c.Request().URL.Scheme = scheme } @@ -36,12 +38,14 @@ func ProxyHeaders() echo.MiddlewareFunc { } } -func getScheme(c echo.Context) string { +func getScheme(r *http.Request) string { var scheme string - if proto := c.Request().Header.Get(echo.HeaderXForwardedProto); proto != "" { + if proto := r.Header.Get(echo.HeaderXForwardedProto); proto != "" { scheme = strings.ToLower(proto) - } else if proto = c.Request().Header.Get(echo.HeaderForwarded); proto != "" { + } else if proto := r.Header.Get(echo.HeaderXForwardedProtocol); proto != "" { + scheme = strings.ToLower(proto) + } else if proto = r.Header.Get(echo.HeaderForwarded); proto != "" { if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 { scheme = strings.ToLower(match[1]) } diff --git a/middleware/proxy_headers_test.go b/middleware/proxy_headers_test.go new file mode 100644 index 000000000..3226a2eda --- /dev/null +++ b/middleware/proxy_headers_test.go @@ -0,0 +1,78 @@ +package middleware + +import ( + "net/http" + "testing" +) + +func Test_getScheme(t *testing.T) { + tests := []struct { + name string + r *http.Request + headerName string + whenHeader string + want string + }{ + { + name: "test only X-Forwarded-Proto: https", + headerName: "X-Forwarded-Proto", + whenHeader: "https", + want: "https", + }, + { + name: "test only X-Forwarded-Proto: http", + headerName: "X-Forwarded-Proto", + whenHeader: "http", + want: "http", + }, + { + name: "test only X-Forwarded-Proto: HTTP", + headerName: "X-Forwarded-Proto", + whenHeader: "HTTP", + want: "http", + }, + { + name: "test only X-Forwarded-Protocol: https", + headerName: "X-Forwarded-Protocol", + whenHeader: "https", + want: "https", + }, + { + name: "test only X-Forwarded-Protocol: http", + headerName: "X-Forwarded-Protocol", + whenHeader: "http", + want: "http", + }, + { + name: "test only X-Forwarded-Protocol: HTTP", + headerName: "X-Forwarded-Protocol", + whenHeader: "HTTP", + want: "http", + }, + { + name: "test only Forwarded https", + headerName: "Forwarded", + whenHeader: "proto=https", + want: "https", + }, + { + name: "test only Forwarded: http", + headerName: "Forwarded", + whenHeader: "proto=http", + want: "http", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := &http.Request{ + Header: http.Header{ + tt.headerName: []string{tt.whenHeader}, + }, + } + + if got := getScheme(req); got != tt.want { + t.Errorf("getScheme() = %v, want %v", got, tt.want) + } + }) + } +} From 431d0fbe6b4da4a8500312b821d5b1f66e35971e Mon Sep 17 00:00:00 2001 From: Artyom Misko Date: Thu, 13 Jul 2023 00:43:48 +0300 Subject: [PATCH 4/4] Add get ip address from header Forwarded (RFC-7239) --- middleware/proxy_headers.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/middleware/proxy_headers.go b/middleware/proxy_headers.go index 6e86f1586..1353171b2 100644 --- a/middleware/proxy_headers.go +++ b/middleware/proxy_headers.go @@ -17,7 +17,11 @@ var ( func ProxyHeaders() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { - if fwd := c.RealIP(); fwd != "" { + if fwd := c.Request().Header.Get(echo.HeaderForwarded); fwd != "" { + if match := ipRegex.FindStringSubmatch(fwd); len(match) > 1 { + c.Request().RemoteAddr = strings.Trim(match[1], `"`) + } + } else if fwd := c.RealIP(); fwd != "" { c.Request().RemoteAddr = fwd }