OpenFeature: Fix ofrep path and stack id check (#107884)
* OpenFeature: Fix ofrep path and stack id check * Use namespace instead of stackId in eval ctx * Apply review feedback * Update pkg/registry/apis/ofrep/register.go Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com> * Update pkg/registry/apis/ofrep/register.go Co-authored-by: Dave Henderson <dave.henderson@grafana.com> --------- Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com> Co-authored-by: Dave Henderson <dave.henderson@grafana.com>
This commit is contained in:
co-authored by
Stephanie Hingtgen
Dave Henderson
parent
2cd0be3cbd
commit
f9f04c2a55
+1
-2
@@ -1994,8 +1994,7 @@ provider = static
|
||||
|
||||
[feature_toggles.openfeature.context]
|
||||
# This is EXPERIMENTAL. Please, do not use this section
|
||||
# instance = "grafana"
|
||||
# version = 11.0.0
|
||||
# foo = bar
|
||||
|
||||
[date_formats]
|
||||
# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/
|
||||
|
||||
@@ -32,6 +32,8 @@ var _ builder.APIGroupVersionProvider = (*APIBuilder)(nil)
|
||||
|
||||
const ofrepPath = "/ofrep/v1/evaluate/flags"
|
||||
|
||||
const namespaceMismatchMsg = "rejecting request with namespace mismatch"
|
||||
|
||||
var groupVersion = schema.GroupVersion{
|
||||
Group: "features.grafana.app",
|
||||
Version: "v0alpha1",
|
||||
@@ -126,7 +128,7 @@ func (b *APIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes {
|
||||
return &builder.APIRoutes{
|
||||
Namespace: []builder.APIRouteHandler{
|
||||
{
|
||||
Path: "ofrep/v1/evaluate/flags/",
|
||||
Path: "ofrep/v1/evaluate/flags",
|
||||
Spec: &spec3.PathProps{
|
||||
Post: &spec3.Operation{
|
||||
OperationProps: spec3.OperationProps{
|
||||
@@ -225,8 +227,8 @@ func (b *APIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes {
|
||||
|
||||
func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !b.validateNamespace(r) {
|
||||
b.logger.Error("stackId in evaluation context does not match requested namespace")
|
||||
http.Error(w, "stackId in evaluation context does not match requested namespace", http.StatusUnauthorized)
|
||||
b.logger.Error(namespaceMismatchMsg)
|
||||
http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -255,8 +257,8 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (b *APIBuilder) allFlagsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !b.validateNamespace(r) {
|
||||
b.logger.Error("stackId in evaluation context does not match requested namespace")
|
||||
http.Error(w, "stackId in evaluation context does not match requested namespace", http.StatusUnauthorized)
|
||||
b.logger.Error(namespaceMismatchMsg)
|
||||
http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -279,25 +281,25 @@ func writeResponse(statusCode int, result any, logger log.Logger, w http.Respons
|
||||
}
|
||||
}
|
||||
|
||||
func (b *APIBuilder) stackIdFromEvalCtx(body []byte) int64 {
|
||||
// Extract stackID from request body without consuming it
|
||||
func (b *APIBuilder) namespaceFromEvalCtx(body []byte) string {
|
||||
// Extract namespace from request body without consuming it
|
||||
var evalCtx struct {
|
||||
Context struct {
|
||||
StackID int64 `json:"stackId"` // TODO -- replace with namespace "stackId" ONLY makes sense in cloud
|
||||
Namespace string `json:"namespace"`
|
||||
} `json:"context"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &evalCtx); err != nil {
|
||||
b.logger.Debug("Failed to unmarshal evaluation context", "error", err, "body", string(body))
|
||||
return 0
|
||||
return ""
|
||||
}
|
||||
|
||||
if evalCtx.Context.StackID <= 0 {
|
||||
b.logger.Debug("Invalid or missing stackId in evaluation context", "stackId", evalCtx.Context.StackID)
|
||||
return 0
|
||||
if evalCtx.Context.Namespace == "" {
|
||||
b.logger.Debug("namespace missing from evaluation context", "namespace", evalCtx.Context.Namespace)
|
||||
return ""
|
||||
}
|
||||
|
||||
return evalCtx.Context.StackID
|
||||
return evalCtx.Context.Namespace
|
||||
}
|
||||
|
||||
// isAuthenticatedRequest returns true if the request is authenticated
|
||||
@@ -309,7 +311,7 @@ func (b *APIBuilder) isAuthenticatedRequest(r *http.Request) bool {
|
||||
return user.GetIdentityType() != ""
|
||||
}
|
||||
|
||||
// validateNamespace checks if the stackId in the evaluation context matches the namespace in the request
|
||||
// validateNamespace checks if the namespace in the evaluation context matches the namespace in the request
|
||||
func (b *APIBuilder) validateNamespace(r *http.Request) bool {
|
||||
// Extract namespace from request context or URL path
|
||||
var namespace string
|
||||
@@ -324,13 +326,7 @@ func (b *APIBuilder) validateNamespace(r *http.Request) bool {
|
||||
namespace = mux.Vars(r)["namespace"]
|
||||
}
|
||||
|
||||
info, err := types.ParseNamespace(namespace)
|
||||
if err != nil {
|
||||
b.logger.Error("Error parsing namespace", "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Extract stackId from feature flag evaluation context
|
||||
// Extract namespace from feature flag evaluation context
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
b.logger.Error("Error reading evaluation request body", "error", err)
|
||||
@@ -338,8 +334,9 @@ func (b *APIBuilder) validateNamespace(r *http.Request) bool {
|
||||
}
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
|
||||
evalCtxNamespace := b.namespaceFromEvalCtx(body)
|
||||
// "default" namespace case can only occur in on-prem grafana
|
||||
if b.stackIdFromEvalCtx(body) == info.StackID {
|
||||
if (namespace == "default" && evalCtxNamespace == "") || (evalCtxNamespace == namespace) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user