diff --git a/app/vmauth/example_config.yml b/app/vmauth/example_config.yml index ba8e12217..5eae8e1b2 100644 --- a/app/vmauth/example_config.yml +++ b/app/vmauth/example_config.yml @@ -11,6 +11,14 @@ users: password: "***" url_prefix: "http://localhost:8428" + # The user for querying local single-node VictoriaMetrics with extra_label team=dev. + # All the requests to http://vmauth:8427 with the given Basic Auth (username:password) + # will be routed to http://localhost:8428 with extra_label=team=dev query arg. + # For example, http://vmauth:8427/api/v1/query is routed to http://localhost:8428/api/v1/query?extra_label=team=dev +- username: "local-single-node" + password: "***" + url_prefix: "http://localhost:8428?extra_label=team=dev" + # The user for querying account 123 in VictoriaMetrics cluster # See https://victoriametrics.github.io/Cluster-VictoriaMetrics.html#url-format # All the requests to http://vmauth:8427 with the given Basic Auth (username:password) @@ -28,4 +36,3 @@ users: - username: "cluster-insert-account-42" password: "***" url_prefix: "http://vminsert:8480/insert/42/prometheus" - diff --git a/app/vmauth/target_url.go b/app/vmauth/target_url.go index 9f4db4dcf..28fbdb47c 100644 --- a/app/vmauth/target_url.go +++ b/app/vmauth/target_url.go @@ -7,6 +7,32 @@ import ( "strings" ) +func mergeURLs(uiURL string, requestURI *url.URL) (string, error) { + prefixURL, err := url.Parse(uiURL) + if err != nil { + return "", fmt.Errorf("BUG - cannot parse userInfo url: %q, err: %w", uiURL, err) + } + prefixURL.Path += requestURI.Path + requestParams := requestURI.Query() + // fast path + if len(requestParams) == 0 { + return prefixURL.String(), nil + } + // merge query parameters from requests. + userInfoParams := prefixURL.Query() + for k, v := range requestParams { + // skip clashed query params from original request + if exist := userInfoParams.Get(k); len(exist) > 0 { + continue + } + for i := range v { + userInfoParams.Add(k, v[i]) + } + } + prefixURL.RawQuery = userInfoParams.Encode() + return prefixURL.String(), nil +} + func createTargetURL(ui *UserInfo, uOrig *url.URL) (string, error) { u, err := url.Parse(uOrig.String()) if err != nil { @@ -20,12 +46,12 @@ func createTargetURL(ui *UserInfo, uOrig *url.URL) (string, error) { for _, e := range ui.URLMap { for _, sp := range e.SrcPaths { if sp.match(u.Path) { - return e.URLPrefix + u.RequestURI(), nil + return mergeURLs(e.URLPrefix, u) } } } if len(ui.URLPrefix) > 0 { - return ui.URLPrefix + u.RequestURI(), nil + return mergeURLs(ui.URLPrefix, u) } return "", fmt.Errorf("missing route for %q", u) } diff --git a/app/vmauth/target_url_test.go b/app/vmauth/target_url_test.go index 34d2e6c33..9c8d454a5 100644 --- a/app/vmauth/target_url_test.go +++ b/app/vmauth/target_url_test.go @@ -38,7 +38,7 @@ func TestCreateTargetURLSuccess(t *testing.T) { }, "/../../aaa", "https://sss:3894/x/y/aaa") f(&UserInfo{ URLPrefix: "https://sss:3894/x/y", - }, "/./asd/../../aaa?a=d&s=s/../d", "https://sss:3894/x/y/aaa?a=d&s=s/../d") + }, "/./asd/../../aaa?a=d&s=s/../d", "https://sss:3894/x/y/aaa?a=d&s=s%2F..%2Fd") // Complex routing with `url_map` ui := &UserInfo{ @@ -77,6 +77,13 @@ func TestCreateTargetURLSuccess(t *testing.T) { f(ui, "/api/v1/label/foo/values", "http://vmselect/0/prometheus/api/v1/label/foo/values") f(ui, "/api/v1/write", "http://vminsert/0/prometheus/api/v1/write") f(ui, "/api/v1/foo/bar", "http://default-server/api/v1/foo/bar") + f(&UserInfo{ + URLPrefix: "http://foo.bar?extra_label=team=dev", + }, "/api/v1/query", "http://foo.bar/api/v1/query?extra_label=team=dev") + f(&UserInfo{ + URLPrefix: "http://foo.bar?extra_label=team=mobile", + }, "/api/v1/query?extra_label=team=dev", "http://foo.bar/api/v1/query?extra_label=team%3Dmobile") + } func TestCreateTargetURLFailure(t *testing.T) {