Skip to content

Commit 9e0c37b

Browse files
committed
app/vmauth: properly proxy requests to backend paths ending with /
Previously the traling / was incorrectly removed when proxying requests from http://vmauth/ While at it, add more tests for requestHandler()
1 parent add2db1 commit 9e0c37b

File tree

4 files changed

+176
-17
lines changed

4 files changed

+176
-17
lines changed

app/vmauth/auth_config.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,14 @@ func getAuthTokensFromRequest(r *http.Request) []string {
10091009
}
10101010
}
10111011

1012+
// Authorization via http://user:pass@hosname/path
1013+
if u := r.URL.User; u != nil && u.Username() != "" {
1014+
username := u.Username()
1015+
password, _ := u.Password()
1016+
at := getHTTPAuthBasicToken(username, password)
1017+
ats = append(ats, at)
1018+
}
1019+
10121020
return ats
10131021
}
10141022

@@ -1034,10 +1042,6 @@ func (up *URLPrefix) sanitizeAndInitialize() error {
10341042
}
10351043

10361044
func sanitizeURLPrefix(urlPrefix *url.URL) (*url.URL, error) {
1037-
// Remove trailing '/' from urlPrefix
1038-
for strings.HasSuffix(urlPrefix.Path, "/") {
1039-
urlPrefix.Path = urlPrefix.Path[:len(urlPrefix.Path)-1]
1040-
}
10411045
// Validate urlPrefix
10421046
if urlPrefix.Scheme != "http" && urlPrefix.Scheme != "https" {
10431047
return nil, fmt.Errorf("unsupported scheme for `url_prefix: %q`: %q; must be `http` or `https`", urlPrefix, urlPrefix.Scheme)

app/vmauth/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
118118
}
119119

120120
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
121-
http.Error(w, "missing `Authorization` request header", http.StatusUnauthorized)
121+
http.Error(w, "missing 'Authorization' request header", http.StatusUnauthorized)
122122
return true
123123
}
124124

app/vmauth/main_test.go

Lines changed: 166 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ func TestRequestHandler(t *testing.T) {
4646
}
4747

4848
response := w.getResponse()
49+
response = strings.ReplaceAll(response, "\r\n", "\n")
4950
response = strings.TrimSpace(response)
5051
responseExpected = strings.TrimSpace(responseExpected)
5152
if response != responseExpected {
52-
t.Fatalf("unexpected response\ngot\n%v\nwant\n%v", response, responseExpected)
53+
t.Fatalf("unexpected response\ngot\n%s\nwant\n%s", response, responseExpected)
5354
}
5455
}
5556

@@ -62,7 +63,8 @@ unauthorized_user:
6263
backendHandler := func(w http.ResponseWriter, r *http.Request) {
6364
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
6465
}
65-
responseExpected := `statusCode=200
66+
responseExpected := `
67+
statusCode=200
6668
requested_url={BACKEND}/foo/abc/def?bar=baz&some_arg=some_value
6769
`
6870
f(cfgStr, requestURL, backendHandler, responseExpected)
@@ -73,12 +75,13 @@ unauthorized_user:
7375
url_prefix: "{BACKEND}/foo?bar=baz"
7476
keep_original_host: true
7577
`
76-
requestURL = "http://some-host.com/abc/def?some_arg=some_value"
78+
requestURL = "http://some-host.com/abc/def"
7779
backendHandler = func(w http.ResponseWriter, r *http.Request) {
7880
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
7981
}
80-
responseExpected = `statusCode=200
81-
requested_url=http://some-host.com/foo/abc/def?bar=baz&some_arg=some_value
82+
responseExpected = `
83+
statusCode=200
84+
requested_url=http://some-host.com/foo/abc/def?bar=baz
8285
`
8386
f(cfgStr, requestURL, backendHandler, responseExpected)
8487

@@ -89,15 +92,165 @@ unauthorized_user:
8992
headers:
9093
- "Host: other-host:12345"
9194
`
92-
requestURL = "http://some-host.com/abc/def?some_arg=some_value"
95+
requestURL = "http://some-host.com/abc/def"
9396
backendHandler = func(w http.ResponseWriter, r *http.Request) {
9497
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
9598
}
96-
responseExpected = `statusCode=200
97-
requested_url=http://other-host:12345/foo/abc/def?bar=baz&some_arg=some_value
99+
responseExpected = `
100+
statusCode=200
101+
requested_url=http://other-host:12345/foo/abc/def?bar=baz
98102
`
99103
f(cfgStr, requestURL, backendHandler, responseExpected)
100104

105+
// /-/reload handler failure
106+
origAuthKey := reloadAuthKey.Get()
107+
if err := reloadAuthKey.Set("secret"); err != nil {
108+
t.Fatalf("unexpected error: %s", err)
109+
}
110+
cfgStr = `
111+
unauthorized_user:
112+
url_prefix: "{BACKEND}/foo"
113+
`
114+
requestURL = "http://some-host.com/-/reload"
115+
backendHandler = func(_ http.ResponseWriter, _ *http.Request) {
116+
panic(fmt.Errorf("backend handler shouldn't be called"))
117+
}
118+
responseExpected = `
119+
statusCode=401
120+
The provided authKey doesn't match -reloadAuthKey
121+
`
122+
f(cfgStr, requestURL, backendHandler, responseExpected)
123+
if err := reloadAuthKey.Set(origAuthKey); err != nil {
124+
t.Fatalf("unexpected error: %s", err)
125+
}
126+
127+
// missing authorization
128+
cfgStr = `
129+
users:
130+
- username: foo
131+
url_prefix: "{BACKEND}/bar"
132+
`
133+
requestURL = "http://some-host.com/a/b"
134+
backendHandler = func(_ http.ResponseWriter, _ *http.Request) {
135+
panic(fmt.Errorf("backend handler shouldn't be called"))
136+
}
137+
responseExpected = `
138+
statusCode=401
139+
Www-Authenticate: Basic realm="Restricted"
140+
missing 'Authorization' request header
141+
`
142+
f(cfgStr, requestURL, backendHandler, responseExpected)
143+
144+
// incorrect authorization
145+
cfgStr = `
146+
users:
147+
- username: foo
148+
password: secret
149+
url_prefix: "{BACKEND}/bar"
150+
`
151+
requestURL = "http://foo:invalid-secret@some-host.com/a/b"
152+
backendHandler = func(_ http.ResponseWriter, _ *http.Request) {
153+
panic(fmt.Errorf("backend handler shouldn't be called"))
154+
}
155+
responseExpected = `
156+
statusCode=401
157+
Unauthorized
158+
`
159+
f(cfgStr, requestURL, backendHandler, responseExpected)
160+
161+
// correct authorization
162+
cfgStr = `
163+
users:
164+
- username: foo
165+
password: secret
166+
url_prefix: "{BACKEND}/bar"
167+
`
168+
requestURL = "http://foo:secret@some-host.com/a/b"
169+
backendHandler = func(w http.ResponseWriter, r *http.Request) {
170+
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
171+
}
172+
responseExpected = `
173+
statusCode=200
174+
requested_url={BACKEND}/bar/a/b
175+
`
176+
f(cfgStr, requestURL, backendHandler, responseExpected)
177+
178+
// verify how path cleanup works
179+
cfgStr = `
180+
unauthorized_user:
181+
url_prefix: {BACKEND}/foo?bar=baz
182+
`
183+
requestURL = "http://some-host.com/../../a//.///bar/"
184+
backendHandler = func(w http.ResponseWriter, r *http.Request) {
185+
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
186+
}
187+
responseExpected = `
188+
statusCode=200
189+
requested_url={BACKEND}/foo/a/bar/?bar=baz
190+
`
191+
f(cfgStr, requestURL, backendHandler, responseExpected)
192+
193+
// verify how path cleanup works for url without path
194+
cfgStr = `
195+
unauthorized_user:
196+
url_prefix: {BACKEND}/foo?bar=baz
197+
`
198+
requestURL = "http://some-host.com/"
199+
backendHandler = func(w http.ResponseWriter, r *http.Request) {
200+
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
201+
}
202+
responseExpected = `
203+
statusCode=200
204+
requested_url={BACKEND}/foo?bar=baz
205+
`
206+
f(cfgStr, requestURL, backendHandler, responseExpected)
207+
208+
// verify how path cleanup works for url without path if url_prefix path ends with /
209+
cfgStr = `
210+
unauthorized_user:
211+
url_prefix: {BACKEND}/foo/?bar=baz
212+
`
213+
requestURL = "http://some-host.com/"
214+
backendHandler = func(w http.ResponseWriter, r *http.Request) {
215+
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
216+
}
217+
responseExpected = `
218+
statusCode=200
219+
requested_url={BACKEND}/foo/?bar=baz
220+
`
221+
f(cfgStr, requestURL, backendHandler, responseExpected)
222+
// verify how path cleanup works for url without path and the url_prefix without path prefix
223+
cfgStr = `
224+
unauthorized_user:
225+
url_prefix: {BACKEND}/?bar=baz
226+
`
227+
requestURL = "http://some-host.com/"
228+
backendHandler = func(w http.ResponseWriter, r *http.Request) {
229+
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
230+
}
231+
responseExpected = `
232+
statusCode=200
233+
requested_url={BACKEND}/?bar=baz
234+
`
235+
f(cfgStr, requestURL, backendHandler, responseExpected)
236+
237+
// verify routing to default_url
238+
cfgStr = `
239+
unauthorized_user:
240+
url_map:
241+
- src_paths: ["/foo/.+"]
242+
url_prefix: {BACKEND}/x-foo/
243+
default_url: {BACKEND}/404.html
244+
`
245+
requestURL = "http://some-host.com/abc?de=fg"
246+
backendHandler = func(w http.ResponseWriter, r *http.Request) {
247+
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
248+
}
249+
responseExpected = `
250+
statusCode=200
251+
requested_url={BACKEND}/404.html?request_path=http%3A%2F%2Fsome-host.com%2Fabc%3Fde%3Dfg
252+
`
253+
f(cfgStr, requestURL, backendHandler, responseExpected)
101254
}
102255

103256
type fakeResponseWriter struct {
@@ -127,12 +280,13 @@ func (w *fakeResponseWriter) WriteHeader(statusCode int) {
127280
return
128281
}
129282
err := w.h.WriteSubset(&w.bb, map[string]bool{
130-
"Content-Length": true,
131-
"Content-Type": true,
132-
"Date": true,
283+
"Content-Length": true,
284+
"Content-Type": true,
285+
"Date": true,
286+
"X-Content-Type-Options": true,
133287
})
134288
if err != nil {
135-
panic(fmt.Errorf("BUG: cannot marshal headers: %s", err))
289+
panic(fmt.Errorf("cannot marshal headers: %s", err))
136290
}
137291
}
138292

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
3131

3232
## tip
3333

34+
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): properly proxy requests to backend urls ending with `/` if the original request path equals to `/`. Previously the trailing `/` at the backend path was incorrectly removed. For example, if the request to `http://vmauth/` is configured to be proxied to `url_prefix=http://backend/foo/`, then it was proxied to `http://backend/foo`, while it should go to `http://backend/foo/`.
3435
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): fix `cannot read data after closing the reader` error when proxying HTTP requests without body (aka `GET` requests). The issue has been introduced in [v1.102.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.0) in [this commit](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/7ee57974935a662896f2de40fdf613156630617d).
3536

3637
## [v1.102.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.0)

0 commit comments

Comments
 (0)