Skip to content

Commit 6d10723

Browse files
committed
query rejection - add API type
Signed-off-by: Erlan Zholdubai uulu <[email protected]>
1 parent 13169d1 commit 6d10723

File tree

5 files changed

+35
-5
lines changed

5 files changed

+35
-5
lines changed

docs/configuration/config-file-reference.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5367,6 +5367,10 @@ limits:
53675367
### `QueryAttribute`
53685368

53695369
```yaml
5370+
# API type for the query. Should be one of the query, query_range, series,
5371+
# labels, label_values. If not set, it won't be checked.
5372+
[api_type: <string> | default = ""]
5373+
53705374
# Regex that the query string (or at least one of the matchers in metadata
53715375
# query) should match. If not set, it won't be checked.
53725376
[regex: <string> | default = ""]

integration/query_frontend_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,8 @@ func TestQueryFrontendQueryRejection(t *testing.T) {
631631
query_rejection:
632632
enabled: true
633633
query_attributes:
634-
- regex: .*rate.*
634+
- api_type: "query"
635+
regex: .*rate.*
635636
query_step_limit:
636637
min: 6s
637638
max: 20m
@@ -659,13 +660,19 @@ func TestQueryFrontendQueryRejection(t *testing.T) {
659660
require.NoError(t, err)
660661

661662
now := time.Now()
662-
// We expect request to be rejected as it matches query_attribute of query_rejection (contains rate, contains dashboard header dash123). step limit is ignored for instant queries
663+
// We expect request to be rejected, as it matches query_attribute of query_rejection (contains rate, contains dashboard header dash123). step limit is ignored for instant queries
663664
// Query shouldn't be checked against attributes that is not provided in query_attribute config(time_window, time_range_limit, user_agent_regex, panel_id)
664665
resp, body, err := c.QueryRaw(`min_over_time( rate(http_requests_total[5m])[30m:5s] )`, now, map[string]string{"X-Dashboard-Uid": "dash123", "User-Agent": "grafana-agent/v0.19.0"})
665666
require.NoError(t, err)
666667
require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
667668
require.Contains(t, string(body), tripperware.QueryRejectErrorMessage)
668669

670+
// We expect request not to be rejected, as it doesn't match api_type
671+
resp, body, err = c.QueryRangeRaw(`min_over_time( rate(http_requests_total[5m])[30m:5s] )`, now.Add(-11*time.Hour), now.Add(-8*time.Hour), 25*time.Minute, map[string]string{"X-Dashboard-Uid": "dash123", "User-Agent": "grafana-agent/v0.19.0"})
672+
require.NoError(t, err)
673+
require.Equal(t, http.StatusOK, resp.StatusCode)
674+
require.NotContains(t, string(body), tripperware.QueryRejectErrorMessage)
675+
669676
// update runtime config
670677
newRuntimeConfig = []byte(`overrides:
671678
user-1:

pkg/querier/tripperware/query_attribute_matcher.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func rejectQueryOrSetPriority(r *http.Request, now time.Time, lookbackDelta time
5858

5959
if queryReject := limits.QueryRejection(userStr); queryReject.Enabled && (op == "series" || op == "labels" || op == "label_values") {
6060
for _, attribute := range queryReject.QueryAttributes {
61-
if matchAttributeForMetadataQuery(attribute, r, now) {
61+
if matchAttributeForMetadataQuery(attribute, op, r, now) {
6262
rejectedQueriesPerTenant.WithLabelValues(op, userStr).Inc()
6363
return httpgrpc.Errorf(http.StatusUnprocessableEntity, QueryRejectErrorMessage)
6464
}
@@ -86,6 +86,9 @@ func getOperation(r *http.Request) string {
8686
}
8787

8888
func matchAttributeForExpressionQuery(attribute validation.QueryAttribute, op string, r *http.Request, query string, now time.Time, minTime, maxTime int64) bool {
89+
if attribute.ApiType != "" && attribute.ApiType != op {
90+
return false
91+
}
8992
if attribute.Regex != "" && attribute.Regex != ".*" && attribute.Regex != ".+" {
9093
if attribute.CompiledRegex != nil && !attribute.CompiledRegex.MatchString(query) {
9194
return false
@@ -121,7 +124,10 @@ func matchAttributeForExpressionQuery(attribute validation.QueryAttribute, op st
121124
return true
122125
}
123126

124-
func matchAttributeForMetadataQuery(attribute validation.QueryAttribute, r *http.Request, now time.Time) bool {
127+
func matchAttributeForMetadataQuery(attribute validation.QueryAttribute, op string, r *http.Request, now time.Time) bool {
128+
if attribute.ApiType != "" && attribute.ApiType != op {
129+
return false
130+
}
125131
if err := r.ParseForm(); err != nil {
126132
return false
127133
}

pkg/querier/tripperware/query_attribute_matcher_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,12 @@ func Test_rejectQueryOrSetPriorityShouldRejectIfMatches(t *testing.T) {
186186
},
187187
},
188188

189-
"should reject if query rejection regex matches match[] of series request and time window": {
189+
"should reject if query rejection api matches and regex matches match[] of series request and time window": {
190190
queryRejectionEnabled: true,
191191
path: fmt.Sprintf("/api/v1/series?start=%d&end=%d&step=7s&match[]=%s", now.Add(-30*time.Minute).UnixMilli()/1000, now.Add(-20*time.Minute).UnixMilli()/1000, url.QueryEscape("count(sum(up))")),
192192
expectedError: httpgrpc.Errorf(http.StatusUnprocessableEntity, QueryRejectErrorMessage),
193193
rejectQueryAttribute: validation.QueryAttribute{
194+
ApiType: "series",
194195
Regex: ".*sum.*",
195196
CompiledRegex: regexp.MustCompile(".*sum.*"),
196197
TimeWindow: validation.TimeWindow{
@@ -199,6 +200,17 @@ func Test_rejectQueryOrSetPriorityShouldRejectIfMatches(t *testing.T) {
199200
},
200201
},
201202
},
203+
204+
"should not reject if query api_type doesn't match matches": {
205+
queryRejectionEnabled: true,
206+
path: fmt.Sprintf("/api/v1/series?start=%d&end=%d&step=7s&match[]=%s", now.Add(-30*time.Minute).UnixMilli()/1000, now.Add(-20*time.Minute).UnixMilli()/1000, url.QueryEscape("count(sum(up))")),
207+
expectedError: nil,
208+
rejectQueryAttribute: validation.QueryAttribute{
209+
ApiType: "query",
210+
Regex: ".*sum.*",
211+
CompiledRegex: regexp.MustCompile(".*sum.*"),
212+
},
213+
},
202214
}
203215

204216
for testName, testData := range tests {

pkg/util/validation/limits.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type QueryRejection struct {
7272
}
7373

7474
type QueryAttribute struct {
75+
ApiType string `yaml:"api_type" json:"api_type" doc:"nocli|description=API type for the query. Should be one of the query, query_range, series, labels, label_values. If not set, it won't be checked."`
7576
Regex string `yaml:"regex" json:"regex" doc:"nocli|description=Regex that the query string (or at least one of the matchers in metadata query) should match. If not set, it won't be checked."`
7677
TimeWindow TimeWindow `yaml:"time_window" json:"time_window" doc:"nocli|description=Overall data select time window (including range selectors, modifiers and lookback delta) that the query should be within. If not set, it won't be checked."`
7778
TimeRangeLimit TimeRangeLimit `yaml:"time_range_limit" json:"time_range_limit" doc:"nocli|description=Query time range should be within this limit to match. If not set, it won't be checked."`

0 commit comments

Comments
 (0)