-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/GPX-685 inline prom-label-proxy (#42)
- removed the need for the prom label proxy + cleanedup the flow + longterm query logs + more debugging + added promql enforcer + added tests + added support for loki tenant label
- Loading branch information
Showing
14 changed files
with
755 additions
and
353 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
logqlv2 "github.com/observatorium/api/logql/v2" | ||
"github.com/prometheus/prometheus/model/labels" | ||
"go.uber.org/zap" | ||
"strings" | ||
"time" | ||
) | ||
|
||
func logqlEnforcer(query string, tenantLabels map[string]bool) (string, error) { | ||
currentTime := time.Now() | ||
if query == "" { | ||
query = "{__name__=~\".+\"}" | ||
} | ||
|
||
expr, err := logqlv2.ParseExpr(query) | ||
if err != nil { | ||
return "", err | ||
} | ||
Logger.Info("long term query collection", zap.String("ltqc", expr.String()), zap.Time("time", currentTime)) | ||
|
||
errMsg := error(nil) | ||
|
||
expr.Walk(func(expr interface{}) { | ||
switch labelExpression := expr.(type) { | ||
case *logqlv2.StreamMatcherExpr: | ||
matchers, err := matchNamespaceMatchers(labelExpression.Matchers(), tenantLabels) | ||
if err != nil { | ||
errMsg = err | ||
return | ||
} | ||
labelExpression.SetMatchers(matchers) | ||
default: | ||
// Do nothing | ||
} | ||
}) | ||
if errMsg != nil { | ||
Logger.Debug("error", zap.Error(errMsg), zap.Int("line", 164)) | ||
return "", errMsg | ||
} | ||
Logger.Debug("expr", zap.String("expr", expr.String()), zap.Any("tl", tenantLabels)) | ||
Logger.Info("long term query collection processed", zap.String("ltqcp", expr.String()), zap.Any("tl", tenantLabels), zap.Time("time", currentTime)) | ||
return expr.String(), nil | ||
} | ||
|
||
func matchNamespaceMatchers(queryMatches []*labels.Matcher, tenantLabels map[string]bool) ([]*labels.Matcher, error) { | ||
foundNamespace := false | ||
for _, match := range queryMatches { | ||
if match.Name == Cfg.Proxy.TenantLabels.Loki { | ||
foundNamespace = true | ||
queryLabels := strings.Split(match.Value, "|") | ||
for _, queryLabel := range queryLabels { | ||
_, ok := tenantLabels[queryLabel] | ||
if !ok { | ||
return nil, fmt.Errorf("unauthorized namespace %s", queryLabel) | ||
} | ||
} | ||
} | ||
} | ||
if !foundNamespace { | ||
matchType := labels.MatchEqual | ||
if len(tenantLabels) > 1 { | ||
matchType = labels.MatchRegexp | ||
} | ||
|
||
queryMatches = append(queryMatches, &labels.Matcher{ | ||
Type: matchType, | ||
Name: Cfg.Proxy.TenantLabels.Loki, | ||
Value: strings.Join(MapKeysToArray(tenantLabels), "|"), | ||
}) | ||
} | ||
return queryMatches, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/prometheus/prometheus/model/labels" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestLogqlEnforcer(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
query string | ||
tenantLabels map[string]bool | ||
expectedResult string | ||
expectErr bool | ||
}{ | ||
{ | ||
name: "Valid query and tenant labels", | ||
query: "{kubernetes_namespace_name=\"test\"}", | ||
tenantLabels: map[string]bool{"test": true}, | ||
expectedResult: "{kubernetes_namespace_name=\"test\"}", | ||
expectErr: false, | ||
}, | ||
{ | ||
name: "Empty query and valid tenant labels", | ||
query: "", | ||
tenantLabels: map[string]bool{"test": true}, | ||
expectedResult: "{__name__=~\".+\", kubernetes_namespace_name=\"test\"}", | ||
expectErr: false, | ||
}, | ||
{ | ||
name: "Valid query and invalid tenant labels", | ||
query: "{kubernetes_namespace_name=\"test\"}", | ||
tenantLabels: map[string]bool{"invalid": true}, | ||
expectErr: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
result, err := logqlEnforcer(tt.query, tt.tenantLabels) | ||
if tt.expectErr { | ||
assert.Error(t, err) | ||
} else { | ||
assert.NoError(t, err) | ||
assert.Equal(t, tt.expectedResult, result) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestMatchNamespaceMatchers(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
matchers []*labels.Matcher | ||
tenantLabels map[string]bool | ||
expectErr bool | ||
}{ | ||
{ | ||
name: "Valid matchers and tenant labels", | ||
matchers: []*labels.Matcher{ | ||
{ | ||
Type: labels.MatchEqual, | ||
Name: "kubernetes_namespace_name", | ||
Value: "test", | ||
}, | ||
}, | ||
tenantLabels: map[string]bool{"test": true}, | ||
expectErr: false, | ||
}, | ||
{ | ||
name: "Invalid matchers and valid tenant labels", | ||
matchers: []*labels.Matcher{ | ||
{ | ||
Type: labels.MatchEqual, | ||
Name: "kubernetes_namespace_name", | ||
Value: "invalid", | ||
}, | ||
}, | ||
tenantLabels: map[string]bool{"test": true}, | ||
expectErr: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
_, err := matchNamespaceMatchers(tt.matchers, tt.tenantLabels) | ||
if tt.expectErr { | ||
assert.Error(t, err) | ||
} else { | ||
assert.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
enforcer "github.com/prometheus-community/prom-label-proxy/injectproxy" | ||
"github.com/prometheus/prometheus/model/labels" | ||
"github.com/prometheus/prometheus/promql/parser" | ||
"go.uber.org/zap" | ||
"strings" | ||
"time" | ||
) | ||
|
||
func promqlEnforcer(query string, allowedTenantLabels map[string]bool) (string, error) { | ||
currentTime := time.Now() | ||
expr, err := parser.ParseExpr(query) | ||
if err != nil { | ||
Logger.Error("error", zap.Error(err), zap.String("info", "parsing query")) | ||
return "", err | ||
} | ||
Logger.Info("long term query collection", zap.String("ltqc", expr.String()), zap.Time("time", currentTime)) | ||
|
||
queryLabels, err := extractLabelsAndValues(expr) | ||
if err != nil { | ||
Logger.Error("error", zap.Error(err), zap.String("info", "extracting labels")) | ||
return "", err | ||
} | ||
|
||
tenantLabels, err := enforceLabels(queryLabels, allowedTenantLabels) | ||
if err != nil { | ||
Logger.Error("error", zap.Error(err), zap.String("info", "enforcing labels")) | ||
return "", err | ||
} | ||
|
||
labelEnforcer := createEnforcer(tenantLabels) | ||
err = labelEnforcer.EnforceNode(expr) | ||
if err != nil { | ||
Logger.Debug("error", zap.Error(err)) | ||
return "", err | ||
} | ||
|
||
Logger.Debug("expr", zap.String("expr", expr.String()), zap.String("tl", strings.Join(tenantLabels, "|"))) | ||
Logger.Info("long term query collection processed", zap.String("ltqcp", expr.String()), zap.Time("time", currentTime)) | ||
return expr.String(), nil | ||
} | ||
|
||
func extractLabelsAndValues(expr parser.Expr) (map[string]string, error) { | ||
l := make(map[string]string) | ||
parser.Inspect(expr, func(node parser.Node, path []parser.Node) error { | ||
if vector, ok := node.(*parser.VectorSelector); ok { | ||
for _, matcher := range vector.LabelMatchers { | ||
l[matcher.Name] = matcher.Value | ||
} | ||
} | ||
return nil | ||
}) | ||
return l, nil | ||
} | ||
|
||
func enforceLabels(queryLabels map[string]string, allowedTenantLabels map[string]bool) ([]string, error) { | ||
if _, ok := queryLabels[Cfg.Proxy.TenantLabels.Thanos]; ok { | ||
ok, tenantLabels := checkLabels(queryLabels, allowedTenantLabels) | ||
if !ok { | ||
return nil, fmt.Errorf("user not allowed with namespace %s", tenantLabels[0]) | ||
} | ||
return tenantLabels, nil | ||
} | ||
|
||
return MapKeysToArray(allowedTenantLabels), nil | ||
} | ||
|
||
func checkLabels(queryLabels map[string]string, allowedTenantLabels map[string]bool) (bool, []string) { | ||
splitQueryLabels := strings.Split(queryLabels[Cfg.Proxy.TenantLabels.Thanos], "|") | ||
for _, queryLabel := range splitQueryLabels { | ||
_, ok := allowedTenantLabels[queryLabel] | ||
if !ok { | ||
return false, []string{queryLabel} | ||
} | ||
} | ||
return true, splitQueryLabels | ||
} | ||
|
||
func createEnforcer(tenantLabels []string) *enforcer.Enforcer { | ||
var matchType labels.MatchType | ||
if len(tenantLabels) > 1 { | ||
matchType = labels.MatchRegexp | ||
} else { | ||
matchType = labels.MatchEqual | ||
} | ||
|
||
return enforcer.NewEnforcer(true, &labels.Matcher{ | ||
Name: Cfg.Proxy.TenantLabels.Thanos, | ||
Type: matchType, | ||
Value: strings.Join(tenantLabels, "|"), | ||
}) | ||
} |
Oops, something went wrong.