Files
grafana/pkg/registry/apis/ofrep/proxy.go
T
Tania 67a952c34e Implement OFREP compatible feature flag service (#105632)
* Add ofrep pkg

* api server: Use namespace from request in case user is not authenticated

* Add handlers to ofrep api builder

* Add NewOpenFeatureService to initialize mt apiserver

* allow specifying CA and insecure

* Compare namespace with eval ctx stackID

* Organize ofrep package

* Implement AllowedV0Alpha1Resources

* Revert folderimpl changes

* Handle default namespace

* Fix extracting stack id from eval ctx

* Add more logs

* Update pkg/registry/apis/ofrep/register.go

Co-authored-by: Dave Henderson <dave.henderson@grafana.com>

* Update pkg/registry/apis/ofrep/register.go

Co-authored-by: Dave Henderson <dave.henderson@grafana.com>

* Apply review feedback

* Replace contexthandler with types

* Fix identifying authed request

* Refactor checks in the handlers

* Remove anonymous from isAuthenticatedRequest check

---------

Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com>
Co-authored-by: Gabriel Mabille <gabriel.mabille@grafana.com>
Co-authored-by: Charandas Batra <charandas.batra@grafana.com>
Co-authored-by: Dave Henderson <dave.henderson@grafana.com>
2025-06-27 18:30:38 +02:00

110 lines
2.5 KiB
Go

package ofrep
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net/http"
"net/http/httputil"
"os"
"path"
"github.com/grafana/grafana/pkg/util/proxyutil"
)
func (b *APIBuilder) proxyAllFlagReq(isAuthedUser bool, w http.ResponseWriter, r *http.Request) {
proxy, err := b.newProxy(ofrepPath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
proxy.ModifyResponse = func(resp *http.Response) error {
if resp.StatusCode == http.StatusOK && !isAuthedUser {
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
_ = resp.Body.Close()
filtered := make(map[string]any)
for k, v := range result {
if isPublicFlag(k) {
filtered[k] = v
}
}
writeResponse(http.StatusOK, filtered, b.logger, w)
}
return nil
}
proxy.ServeHTTP(w, r)
}
func (b *APIBuilder) proxyFlagReq(flagKey string, isAuthedUser bool, w http.ResponseWriter, r *http.Request) {
proxy, err := b.newProxy(path.Join(ofrepPath, flagKey))
if err != nil {
b.logger.Error("Failed to create proxy", "key", flagKey, "error", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
proxy.ModifyResponse = func(resp *http.Response) error {
if resp.StatusCode == http.StatusOK && !isAuthedUser && !isPublicFlag(flagKey) {
writeResponse(http.StatusUnauthorized, struct{}{}, b.logger, w)
}
return nil
}
proxy.ServeHTTP(w, r)
}
func (b *APIBuilder) newProxy(proxyPath string) (*httputil.ReverseProxy, error) {
if proxyPath == "" {
return nil, fmt.Errorf("proxy path is required")
}
if b.url == nil {
return nil, fmt.Errorf("OpenFeatureService provider URL is not set")
}
var caRoot *x509.CertPool
if b.caFile != "" {
var err error
caRoot, err = getCARoot(b.caFile)
if err != nil {
return nil, err
}
}
director := func(req *http.Request) {
req.URL.Scheme = b.url.Scheme
req.URL.Host = b.url.Host
req.URL.Path = proxyPath
}
proxy := proxyutil.NewReverseProxy(b.logger, director)
proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: b.insecure,
RootCAs: caRoot,
},
}
return proxy, nil
}
func getCARoot(caFile string) (*x509.CertPool, error) {
// It should be safe to ignore since caFile is passed as --internal.root-ca-file flag of apiserver
// nolint:gosec
caCert, err := os.ReadFile(caFile)
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
return caCertPool, nil
}