Skip to content

Commit

Permalink
Feature/GPX-685 inline prom-label-proxy (#42)
Browse files Browse the repository at this point in the history
- 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
Lucostus authored Jun 5, 2023
1 parent 1f4be69 commit 378fe6e
Show file tree
Hide file tree
Showing 14 changed files with 755 additions and 353 deletions.
13 changes: 8 additions & 5 deletions configs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ proxy:
provider: configmap
thanos_url: https://localhost:3000
loki_url: https://localhost:3000
prom_label_url: https://localhost:3000
jwks_cert_url: https://gp-sso-service.gp-sso.svc.cluster.local:8443/realms/internal/protocol/openid-connect/certs
tenant_label: namespace
jwks_cert_url: https://sso.example.com/realms/internal/protocol/openid-connect/certs
admin_group: gepardec-run-admins
port: 8080
tenant_labels:
thanos: namespace
loki: kubernetes_namespace_name
db:
user: multitenant
password_path: "."
host: localhost
port: 3306
dbName: blubb
dbName: example
query: "SELECT * FROM users WHERE username = ?"
dev:
enabled: false
enabled: true
username: example
ServiceAccountToken: "fake"
89 changes: 0 additions & 89 deletions enforcer.go

This file was deleted.

75 changes: 75 additions & 0 deletions enforcer_logql.go
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
}
95 changes: 95 additions & 0 deletions enforcer_logql_test.go
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)
}
})
}
}
95 changes: 95 additions & 0 deletions enforcer_promql.go
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, "|"),
})
}
Loading

0 comments on commit 378fe6e

Please sign in to comment.