Skip to content

Commit 272ca2f

Browse files
fix: correctly handle fine-grained authorization for the /series endpoint (#810)
1 parent 48fde49 commit 272ca2f

File tree

4 files changed

+59
-63
lines changed

4 files changed

+59
-63
lines changed

api/logs/v1/labels_enforcer.go

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type AuthzResponseData struct {
2222
const (
2323
logicalOr = "or"
2424
queryParam = "query"
25+
matchParam = "match"
2526
)
2627

2728
type matchersContextKey struct{}
@@ -70,80 +71,64 @@ func WithEnforceAuthorizationLabels() func(http.Handler) http.Handler {
7071
func enforceValues(mInfo AuthzResponseData, u *url.URL) (values string, err error) {
7172
switch {
7273
case strings.HasSuffix(u.Path, "/values"):
73-
return enforceValuesOnLabelValues(mInfo, u.Query())
74+
return enforceValuesOnLogQL(mInfo, u.Query(), queryParam, false)
75+
case strings.HasSuffix(u.Path, "/series"):
76+
return enforceValuesOnLogQL(mInfo, u.Query(), matchParam, false)
7477
default:
75-
return enforceValuesOnQuery(mInfo, u.Query())
78+
return enforceValuesOnLogQL(mInfo, u.Query(), queryParam, true)
7679
}
7780
}
7881

79-
func enforceValuesOnLabelValues(mInfo AuthzResponseData, v url.Values) (values string, err error) {
82+
func enforceValuesOnLogQL(mInfo AuthzResponseData, v url.Values, paramName string, queryEndpoint bool) (values string, err error) {
8083
lm, err := initAuthzMatchers(mInfo.Matchers)
8184
if err != nil {
8285
return "", err
8386
}
8487

85-
var expr logqlv2.Expr
86-
87-
if q := v.Get(queryParam); q != "" {
88-
expr, err = logqlv2.ParseExpr(q)
89-
if err != nil {
90-
return "", fmt.Errorf("failed parsing LogQL expression: %w", err)
88+
paramValue := v.Get(paramName)
89+
if paramValue == "" {
90+
// For query endpoints, we don't always to enforce the authorization
91+
// label matchers, so weskip it if the query is empty.
92+
if queryEndpoint {
93+
return v.Encode(), nil
9194
}
9295

93-
if le, ok := expr.(*logqlv2.LogQueryExpr); ok {
94-
matchers := combineLabelMatchers(le.Matchers(), lm)
95-
le.SetMatchers(matchers)
96-
}
97-
} else {
98-
expr = &logqlv2.StreamMatcherExpr{}
99-
expr.(*logqlv2.StreamMatcherExpr).SetMatchers(lm)
100-
}
101-
102-
v.Set(queryParam, expr.String())
103-
104-
return v.Encode(), nil
105-
}
106-
107-
func enforceValuesOnQuery(mInfo AuthzResponseData, v url.Values) (values string, err error) {
108-
if v.Get(queryParam) == "" {
96+
// For the other endpoints we want to enforce the authZ label matchers
97+
expr := &logqlv2.StreamMatcherExpr{}
98+
expr.SetMatchers(lm)
99+
v.Set(paramName, expr.String())
109100
return v.Encode(), nil
110101
}
111102

112-
lm, err := initAuthzMatchers(mInfo.Matchers)
113-
if err != nil {
114-
return "", err
115-
}
116-
117-
expr, err := logqlv2.ParseExpr(v.Get(queryParam))
103+
expr, err := logqlv2.ParseExpr(paramValue)
118104
if err != nil {
119105
return "", fmt.Errorf("failed parsing LogQL expression: %w", err)
120106
}
121107

122-
switch mInfo.MatcherOp {
123-
case logicalOr:
108+
// Logical "OR" only applies to query expressions, not for filter params
109+
if mInfo.MatcherOp == logicalOr && paramValue == queryParam {
124110
// Logical "OR" to combine multiple matchers needs to be done via LogQueryExpr > LogPipelineExpr
125111
expr.Walk(func(expr interface{}) {
126-
switch le := expr.(type) {
127-
case *logqlv2.LogQueryExpr:
112+
if le, ok := expr.(*logqlv2.LogQueryExpr); ok {
128113
le.AppendPipelineMatchers(mInfo.Matchers, logicalOr)
129-
default:
130-
// Do nothing
131-
}
132-
})
133-
default:
134-
expr.Walk(func(expr interface{}) {
135-
switch le := expr.(type) {
136-
case *logqlv2.StreamMatcherExpr:
137-
matchers := combineLabelMatchers(le.Matchers(), lm)
138-
le.SetMatchers(matchers)
139-
default:
140-
// Do nothing
141114
}
142115
})
116+
v.Set(paramName, expr.String())
117+
return v.Encode(), nil
143118
}
144119

145-
v.Set(queryParam, expr.String())
120+
expr.Walk(func(expr interface{}) {
121+
switch le := expr.(type) {
122+
case *logqlv2.LogQueryExpr:
123+
matchers := combineLabelMatchers(le.Matchers(), lm)
124+
le.SetMatchers(matchers)
125+
case *logqlv2.StreamMatcherExpr:
126+
matchers := combineLabelMatchers(le.Matchers(), lm)
127+
le.SetMatchers(matchers)
128+
}
129+
})
146130

131+
v.Set(paramName, expr.String())
147132
return v.Encode(), nil
148133
}
149134

authorization/http.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"net/http"
7+
"strings"
78

89
"github.com/go-kit/log"
910
"github.com/go-kit/log/level"
@@ -83,7 +84,19 @@ func WithLogsStreamSelectorsExtractor(logger log.Logger, selectorNames []string)
8384
return
8485
}
8586

86-
selectorsInfo, err := extractLogStreamSelectors(selectorNameMap, r.URL.Query())
87+
var (
88+
selectorsInfo *SelectorsInfo
89+
err error
90+
)
91+
92+
switch {
93+
case strings.HasSuffix(r.URL.Path, "/rules"):
94+
selectorsInfo = extractLogRulesSelectors(selectorNameMap, r.URL.Query())
95+
case strings.HasSuffix(r.URL.Path, "/series"):
96+
selectorsInfo, err = extractLogStreamSelectors(selectorNameMap, r.URL.Query(), "match")
97+
default:
98+
selectorsInfo, err = extractLogStreamSelectors(selectorNameMap, r.URL.Query(), "query")
99+
}
87100
if err != nil {
88101
// Don't error out, just warn about error and continue with empty selectorsInfo
89102
level.Warn(logger).Log("msg", err)

authorization/query.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,12 @@ import (
99
"github.com/prometheus/prometheus/model/labels"
1010
)
1111

12-
func extractLogStreamSelectors(selectorNames map[string]bool, values url.Values) (*SelectorsInfo, error) {
13-
query := values.Get("query")
14-
if query == "" {
15-
// If query is empty we will assume it's a possibly a rules request
16-
selectors := parseLogRulesSelectors(selectorNames, values)
12+
func extractLogStreamSelectors(selectorNames map[string]bool, values url.Values, param string) (*SelectorsInfo, error) {
13+
value := values.Get(param)
1714

18-
return &SelectorsInfo{
19-
Selectors: selectors,
20-
}, nil
21-
}
22-
23-
selectors, hasWildcard, err := parseLogStreamSelectors(selectorNames, query)
15+
selectors, hasWildcard, err := parseLogStreamSelectors(selectorNames, value)
2416
if err != nil {
25-
return nil, fmt.Errorf("error extracting selectors from query %#q: %w", query, err)
17+
return nil, fmt.Errorf("error extracting selectors from %s %#q: %w", param, value, err)
2618
}
2719

2820
return &SelectorsInfo{

authorization/rules.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import (
44
"net/url"
55
)
66

7-
func parseLogRulesSelectors(selectorNames map[string]bool, queryParameter url.Values) map[string][]string {
7+
func extractLogRulesSelectors(selectorNames map[string]bool, values url.Values) *SelectorsInfo {
8+
return &SelectorsInfo{
9+
Selectors: parseLogRulesSelectors(selectorNames, values),
10+
}
11+
}
12+
13+
func parseLogRulesSelectors(selectorNames map[string]bool, values url.Values) map[string][]string {
814
selectors := make(map[string][]string)
915
appendSelector := func(selector, value string) {
1016
values, ok := selectors[selector]
@@ -17,7 +23,7 @@ func parseLogRulesSelectors(selectorNames map[string]bool, queryParameter url.Va
1723
}
1824

1925
for selector := range selectorNames {
20-
values := queryParameter[selector]
26+
values := values[selector]
2127
for _, value := range values {
2228
appendSelector(selector, value)
2329
}

0 commit comments

Comments
 (0)