-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
do not set query parameter when forms are set #111
base: main
Are you sure you want to change the base?
Conversation
Would you please take a look at this pull request? |
I raised some related concerns a couple of years ago related to how we were not strictly enforcing both query parameters and form values. I think that anything that makes our proxy more strict in this regard (like this PR) will not only improve the predictability and compatibility but also the security. |
Is it possible to be reviewed or get some advice about this, please? |
Was having an issue yesterday with prom-label-proxy which was caused by this. This should be merged asap! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR introduces a serious regression in the enforcement code and is not what we want.
Currently, we always enforce labels on potential queries in the query portion of the URL because any request, whether or not it includes a form, can have a query in the query part of the URL. Then, if the request is a POST request, we will additionally enforce labels in the form. This ensures that all queries present in any part of the request will be protected.
This PR effectively allows a potential attacker to break this last point and enables an attacker to send a request with two queries: one malicious query in the query part of the URL and one dummy query in the request body; labels will only be enforced in the form body and will not be enforced in the query portion of the URL. Which query is actually parsed and processed by the API supplying metrics is entirely dependent on the language specification. Will the API read the non-enforced query from the URL or will it read the enforced query from the request body? There is no way to be certain.
This behavior was fixed in the prom-label-proxy two years ago and we should not regress on it.
TL;DR: we do not want to allow any queries that could potentially be read and processed by a backend API to pass without enforcement.
@squat: I agree indeed! But it's more complicated. Take you have a POST request with the following body: With the current code this seems to generate the following url parameter: And the correct POST body: And finally the Simple Proxy in Golang seems to drop the POST body and use the url parameter. I think we should somehow block match url parameters if it's a POST request? |
I read the discussion in #53. I understand the security risk that not injected requests are sent to the backend. I agree that both query parameters and form values must be modified by However, in a system like VictoriaMetrics, where query parameters are evaluated first, there is an issue with unexpected responses. I consider that values to be sent to backends should be consistent. Either the requested query parameter or the form value is modified by
How about this idea? |
@h-otter: If its a POST request its logic indeed to send all matchers via the Form Values. |
Yes, this would be a potential solution: we chose one data transfer format to prioritize, and if a query is detected there, then we eliminate queries from other transfer formats. In other words, we could say "forms take priority over query parameters" and then if we ever detect a form then we remove all query parameters that could include a Prometheus query. |
I tried to reflect the results of the discussion in the code. |
@simonpasquier I remember this PR. I'm sorry for my poor English. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @h-otter I think this is not exactly what we need. Firstly, form data can be sent in a body with two common content types, which Golang supports and automatically parses: application/x-www-form-urlencoded and multipart/form-data. Secondly form data can be sent in bodies with many different methods: POST, PUT, and PATCH.
To move this PR forward, I would suggest handling these cases. Otherwise we are opening ourselves up to real.vulnerabilities where we only inject labels (which projects use to separate tenant data) in queries when they should be protecting forms.
Sorry, I can't figure out how to fix it. 😢 What are real vulnerabilities? In that case, I need to decide what content type is set, from the following selections:
The current patch keeps the content type.
So, the current patch sends form data if POST method and How can I fix this PR, or are there any other problems? 😃 |
xref: observatorium/api#595 Here is an example of how easy it is to introduce vulnerabilities that allow tenants isolated by this proxy to view one another's data. Observatorium's isolation is built using prom-label-proxy code. The vulnerability they experience is something that we wanted to account for by always enforcing matchers no matter where they are, query or form |
First of all, if this PR is having a negative impact on the development of prom-label-proxy, please feel free to close it. The behavior of observatorium/api#595 looks like my first commit c118800. The difference is that matchers of the inbound query params and inbound form data are merged. |
I think we should be enforcing on both query params and form data, if both are present. Then it's up to the servers to decide which one they give priority to (i.e. Thanos prefers form data and VictoriaMetrics prefers query params). I'm open to working on the fix here, as long we agree on the solution. |
In that case, we can choose not to insert anything with injectMatcher when there is no |
Agreed. This was exactly the approach we took 2+ years ago when I made this PR: #53 In the PR, I mention that:
|
@h-otter I like your proposal:
Let's do exactly that; keep the enforcement logic as is to always enforce with the caveat that if there is no |
@squat @h-otter isn't this proposal a bit dangerous? If we do nothing when there's no _, _, _ := v1.NewAPI(apiTest).LabelNames(context.TODO(), []string{""}, now.Time().Add(-5*time.Minute), now.Time())
_, _, _ := v1.NewAPI(apiTest).LabelNames(context.TODO(), []string{}, now.Time().Add(-5*time.Minute), now.Time())
_, _, _ := v1.NewAPI(apiTest).LabelNames(context.TODO(), nil, now.Time().Add(-5*time.Minute), now.Time()) |
These 3 cases are even more problematic because So in the label names and values calls, we need to inject the matcher when if |
That's definitely a problem. There is a workaround to not inject based on The challenge is that prom-label-proxy doesn't know whether the user's request is stored in form data, query param, or both. There are these choices:
func injectMatcher(q url.Values, matcher *labels.Matcher) error {
if len(q) == 0 || (len(q) == 1 && q.Has(matcher.Name)) {
return nil
}
matchers := q[matchersParam]
if len(matchers) == 0 {
q.Set(matchersParam, matchersToString(matcher))
return nil
} Which would you choose? I think 5th choice is better. |
I think option 5 is the best indeed. I almost misunderstood what you meant because of the writing |
At route level though, we might have to go with option 1 because of the fact that different backends will prefer query params and others will prefer form params. If we want to have a generic middleware for everyone, we need to update both. On this regard, how this is bad for VictoriaMetrics? Injecting the matcher in both places won't cause any problem, no? VM prefers query params, so it ignores the one added in the form data and that's it. Can you give an example scenario of the trouble it could cause? But this "generic" |
I didn't have the confidence to express it in words, so I wrote the code as well. I'm glad you understood correctly. 😄
#134 details our problem. My example scenario has occurred on a set of prom-label-proxy, VictoriaMetrics, and Grafana. Grafana sends
Then prom-label-proxy sends a request to VictriaMetrics with injected
As a result, since VictoriaMetrics prefers query params, VictoriaMetrics returns the result of This behavior caused heavy performance issues, especially when sending a request to the labels endpoint. To assist the user, Grafana makes a request to the labels endpoint and displays label suggestions. At that time, VictoriaMetrics makes a response of |
@h-otter I guess the route level enforcer has to be configurable then. There could be a setting that specifies the merge logic whenever a parameter is find in both query and form body params. Options could be a bitmask representing:
Examples:
Analogous for favoring form params. For default value, I will suggest What do you think, @h-otter? |
I can't figure out what the route level enforcer is. Where will the route level enforcer be configured, the options are set as prom-label-proxy flags on the starting process, header or query params in user's requests, or hard-code into these lines? prom-label-proxy/injectproxy/routes.go Lines 286 to 292 in c1160c9
Your merge logic and options sound good because they cover all of the use cases to merge query params and form data. 👍 In my opinion, since prom-label-proxy is a component to enforce labels in a given PromQL, prom-label-proxy should not modify the user's request as much as possible. I don't know the behavior of other tools, although it is better if the upstream service decides whether to merge the query and form data or not. The 5th option is more precise in maintaining users' requests. An idea is to create a new component (something called prom-merge-proxy) to merge query params and form data. |
I disagree. I think it's fine that prom-label-proxy is taught how it should modify the request to do what it's meant to do (enforce labels). The issue is that different PromQL backends diverged on how they handle query params and form params. There are people out there building platforms on top of prom-label-proxy and these PromQL backends for internal and external customers. These customers are being able to punch through the enforcement of prom-label-proxy because of this logic divergence in the backends. The only way to make prom-label-proxy safe is to tell it what to do depending on the backend you run. I heavily disagree that we need yet another proxy for this. It's one more thing to maintain, run, scan for CVEs, update, monitor, alert on, etc etc. But I'm just someone from another project trying to upstream a fix. So I'm keen to listen to what maintainers and contributors believe this project should do and what should be another project. 😄 |
I think they could be cli flags on the prom-label-proxy binary and then passed down to the http handlers. |
I think so, too. I don't have a strong opinion about merge either. I follow the maintainer's decision. @squat, which option do you think is better in #111 (comment) ? And, configurable of the route level enforcer should be implemented? |
I agree that in a multi-tenant environment, there is a risk of unmerged results compromising other tenants. On the other hand, modifying user requests risks causing unexpected behavior when an unknown upstream like VictoriaMetrics appears. That is a trade-off. Your proposal realizes that we can configure that trade-off, so it sounds good. However, I think it would be better to discuss this in another issue. |
What is the benefit to making the prioritization logic configurable? Prometheus and Victoria metrics and Thanos all (implicitly) make decisions as to what to do when an ambiguous request arrives. Prometheus and Thanos implicitly do merge|favor_form and VictoriaMetrics either does favor_query or merge|favor_query. I think that this project could also simply make a final decision about how to handle that ambiguity and document how the proxy works and call it a day. IMO that solves everything and reduces complexity in the project:
I vote for picking one behavior and applying it always, whether it's option 5 or something like merge|favor_form. Adding more configurability to this behavior does not seem worth the effort to me. Who will benefit from this? The only use case that I can imagine would benefit from additional configurability is a user who has a faulty stack that introduces queries in two places and prefers to configure the proxy rather than fix the rogue component introducing an extra query, right? |
Signed-off-by: h-otter <[email protected]>
Signed-off-by: h-otter <[email protected]>
Signed-off-by: h-otter <[email protected]>
Signed-off-by: h-otter <[email protected]>
Thank you for your response! I see your point about not needing it configurable. Personally, I agree. In that case, should we further discuss which default behavior to adopt? Anyway, I applied option 5 to the changes on 6e70621. Since the following block removes the proxy label from the query parameters, so changes become very small. https://github.com/prometheus-community/prom-label-proxy/blob/main/injectproxy/routes.go#L187-L190 However, it seems difficult to fundamentally solve this problem. As you can see from the test failure, if query params and form data are not set, the matcher will be sent without injected. In addition, some endpoints (such as labels endpoint ) do not have the required fields, so option 5 does not cover all cases. The problem is the behavior when values are not set in query or form. If we respect the previous behavior of prom-label-proxy, it should inject matcher into only the query value. I would like to fix the behavior when the form value is only set. Summarized in a table, it looks like this:
Therefore, I created a37a998. The tests are passing, so we confirmed that much of the previous behavior is preserved. How about it? |
@squat Could you check the comment? |
Thank you for the useful project!
This PR changes to strictly determine whether to pass form values or query parameters to the backend.
In the current behavior, when prom-label-proxy receives form value
match[]=up&namespace=default
asPOST /api/v1/series
, prom-label-proxy sends request to the backend with query parametermatch[]=
and form valuesmatch[]={__name__="up",namespace="default"}
. At that time, the backend returns the result ofmatch[]=
which is set as the query parameter. Since we are using VictriaMetrics as a backend, the behavior in which query parameters take precedence over form values may be due to the implementation of VictoriaMetrics.