diff --git a/pkg/registry/apis/ofrep/proxy.go b/pkg/registry/apis/ofrep/proxy.go index efb5cc51930..b5791925b26 100644 --- a/pkg/registry/apis/ofrep/proxy.go +++ b/pkg/registry/apis/ofrep/proxy.go @@ -2,6 +2,7 @@ package ofrep import ( "bytes" + "context" "crypto/tls" "crypto/x509" "encoding/json" @@ -14,14 +15,21 @@ import ( "strconv" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/util/proxyutil" goffmodel "github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/model" ) -func (b *APIBuilder) proxyAllFlagReq(isAuthedUser bool, w http.ResponseWriter, r *http.Request) { +func (b *APIBuilder) proxyAllFlagReq(ctx context.Context, isAuthedUser bool, w http.ResponseWriter, r *http.Request) { + ctx, span := tracer.Start(ctx, "ofrep.proxy.evalAllFlags") + defer span.End() + + r = r.WithContext(ctx) + proxy, err := b.newProxy(ofrepPath) if err != nil { + err = tracing.Error(span, err) http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -61,9 +69,15 @@ func (b *APIBuilder) proxyAllFlagReq(isAuthedUser bool, w http.ResponseWriter, r proxy.ServeHTTP(w, r) } -func (b *APIBuilder) proxyFlagReq(flagKey string, isAuthedUser bool, w http.ResponseWriter, r *http.Request) { +func (b *APIBuilder) proxyFlagReq(ctx context.Context, flagKey string, isAuthedUser bool, w http.ResponseWriter, r *http.Request) { + ctx, span := tracer.Start(ctx, "ofrep.proxy.evalFlag") + defer span.End() + + r = r.WithContext(ctx) + proxy, err := b.newProxy(path.Join(ofrepPath, flagKey)) if err != nil { + err = tracing.Error(span, err) b.logger.Error("Failed to create proxy", "key", flagKey, "error", err) http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/pkg/registry/apis/ofrep/register.go b/pkg/registry/apis/ofrep/register.go index 154e3f6b6fc..b765bbadc32 100644 --- a/pkg/registry/apis/ofrep/register.go +++ b/pkg/registry/apis/ofrep/register.go @@ -10,6 +10,9 @@ import ( "net/url" "github.com/gorilla/mux" + "github.com/grafana/grafana/pkg/infra/tracing" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -31,6 +34,8 @@ var _ builder.APIGroupBuilder = (*APIBuilder)(nil) var _ builder.APIGroupRouteProvider = (*APIBuilder)(nil) var _ builder.APIGroupVersionProvider = (*APIBuilder)(nil) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/registry/apis/ofrep") + const ofrepPath = "/ofrep/v1/evaluate/flags" const namespaceMismatchMsg = "rejecting request with namespace mismatch" @@ -240,7 +245,13 @@ func (b *APIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes { } func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) { + ctx, span := tracer.Start(r.Context(), "ofrep.handler.evalFlag") + defer span.End() + + r = r.WithContext(ctx) + if !b.validateNamespace(r) { + _ = tracing.Errorf(span, namespaceMismatchMsg) b.logger.Error(namespaceMismatchMsg) http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized) return @@ -248,42 +259,54 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) { flagKey := mux.Vars(r)["flagKey"] if flagKey == "" { + _ = tracing.Errorf(span, "flagKey parameter is required") http.Error(w, "flagKey parameter is required", http.StatusBadRequest) return } + span.SetAttributes(attribute.String("flag_key", flagKey)) + isAuthedReq := b.isAuthenticatedRequest(r) + span.SetAttributes(attribute.Bool("authenticated", isAuthedReq)) // Unless the request is authenticated, we only allow public flags evaluations if !isAuthedReq && !isPublicFlag(flagKey) { + _ = tracing.Errorf(span, "unauthorized to evaluate flag: %s", flagKey) b.logger.Error("Unauthorized to evaluate flag", "flagKey", flagKey) http.Error(w, "unauthorized to evaluate flag", http.StatusUnauthorized) return } if b.providerType == setting.GOFFProviderType { - b.proxyFlagReq(flagKey, isAuthedReq, w, r) + b.proxyFlagReq(ctx, flagKey, isAuthedReq, w, r) return } - b.evalFlagStatic(flagKey, w, r) + b.evalFlagStatic(ctx, flagKey, w) } func (b *APIBuilder) allFlagsHandler(w http.ResponseWriter, r *http.Request) { + ctx, span := tracer.Start(r.Context(), "ofrep.handler.evalAllFlags") + defer span.End() + + r = r.WithContext(ctx) + if !b.validateNamespace(r) { + _ = tracing.Errorf(span, namespaceMismatchMsg) b.logger.Error(namespaceMismatchMsg) http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized) return } isAuthedReq := b.isAuthenticatedRequest(r) + span.SetAttributes(attribute.Bool("authenticated", isAuthedReq)) if b.providerType == setting.GOFFProviderType { - b.proxyAllFlagReq(isAuthedReq, w, r) + b.proxyAllFlagReq(ctx, isAuthedReq, w, r) return } - b.evalAllFlagsStatic(isAuthedReq, w, r) + b.evalAllFlagsStatic(ctx, isAuthedReq, w) } func writeResponse(statusCode int, result any, logger log.Logger, w http.ResponseWriter) { diff --git a/pkg/registry/apis/ofrep/static.go b/pkg/registry/apis/ofrep/static.go index 8d56104649d..18ff794349d 100644 --- a/pkg/registry/apis/ofrep/static.go +++ b/pkg/registry/apis/ofrep/static.go @@ -1,19 +1,28 @@ package ofrep import ( + "context" "net/http" + "github.com/grafana/grafana/pkg/infra/tracing" goffmodel "github.com/thomaspoignant/go-feature-flag/cmd/relayproxy/model" + "go.opentelemetry.io/otel/attribute" ) -func (b *APIBuilder) evalAllFlagsStatic(isAuthedUser bool, w http.ResponseWriter, r *http.Request) { - result, err := b.staticEvaluator.EvalAllFlags(r.Context()) +func (b *APIBuilder) evalAllFlagsStatic(ctx context.Context, isAuthedUser bool, w http.ResponseWriter) { + _, span := tracer.Start(ctx, "ofrep.static.evalAllFlags") + defer span.End() + + result, err := b.staticEvaluator.EvalAllFlags(ctx) if err != nil { + err = tracing.Error(span, err) b.logger.Error("Failed to evaluate all static flags", "error", err) http.Error(w, "failed to evaluate flags", http.StatusInternalServerError) return } + span.SetAttributes(attribute.Int("total_flags_count", len(result.Flags))) + if !isAuthedUser { var publicOnly []goffmodel.OFREPFlagBulkEvaluateSuccessResponse @@ -24,14 +33,21 @@ func (b *APIBuilder) evalAllFlagsStatic(isAuthedUser bool, w http.ResponseWriter } result.Flags = publicOnly + span.SetAttributes(attribute.Int("public_flags_count", len(publicOnly))) } writeResponse(http.StatusOK, result, b.logger, w) } -func (b *APIBuilder) evalFlagStatic(flagKey string, w http.ResponseWriter, r *http.Request) { - result, err := b.staticEvaluator.EvalFlag(r.Context(), flagKey) +func (b *APIBuilder) evalFlagStatic(ctx context.Context, flagKey string, w http.ResponseWriter) { + _, span := tracer.Start(ctx, "ofrep.static.evalFlag") + defer span.End() + + span.SetAttributes(attribute.String("flag_key", flagKey)) + + result, err := b.staticEvaluator.EvalFlag(ctx, flagKey) if err != nil { + err = tracing.Error(span, err) b.logger.Error("Failed to evaluate static flag", "key", flagKey, "error", err) http.Error(w, "failed to evaluate flag", http.StatusInternalServerError) return