use FF and cleanup access code
This commit is contained in:
@@ -27,13 +27,6 @@ storage_type = unified
|
||||
|
||||
; Configure dashboards to use unified storage
|
||||
[unified_storage.dashboards.dashboard.grafana.app]
|
||||
; Dualwriter modes:
|
||||
; 0: disabled (default) - dashboards saved to SQL only
|
||||
; 1: read from legacy, write to legacy, write to unified best-effort
|
||||
; 2: read from legacy, write to both
|
||||
; 3: read from unified, write to both
|
||||
; 4: read from unified, write to unified (fully migrated)
|
||||
; 5: read from unified, write to unified, ignore background sync state
|
||||
dualWriterMode = 5
|
||||
|
||||
; Configure folders to use unified storage (required for dashboards)
|
||||
@@ -44,5 +37,4 @@ dualWriterMode = 5
|
||||
; SQLite database for testing
|
||||
type = sqlite3
|
||||
path = grafana.db
|
||||
; Enable high availability mode is false for single instance
|
||||
high_availability = false
|
||||
@@ -63,7 +63,7 @@ kubectl apply -f ./pkg/registry/apis/apiextensions/resources/example-widget.yaml
|
||||
Or use curl:
|
||||
|
||||
```bash
|
||||
curl -k -X POST https://localhost:1111/apis/apiextensions.k8s.io/v1/customresourcedefinitions \
|
||||
curl -k -X POST https://localhost:1111/apis/customcrdtest.grafana.app/v1/namespaces/default/widgets \
|
||||
-H "$AUTH_SVC" \
|
||||
-H "Content-Type: application/yaml" \
|
||||
--data-binary @$PWD/pkg/registry/apis/apiextensions/resources/example-widget.yaml
|
||||
|
||||
@@ -73,7 +73,7 @@ func RegisterAPIService(
|
||||
unified resource.ResourceClient,
|
||||
) (*APIExtensionsBuilder, error) {
|
||||
if !features.IsEnabledGlobally(featuremgmt.FlagApiExtensions) {
|
||||
return nil, fmt.Errorf("apiextensions feature flag is not enabled")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b := &APIExtensionsBuilder{
|
||||
@@ -188,6 +188,7 @@ func (b *APIExtensionsBuilder) loadAndRegisterCRDs(ctx context.Context, crdStore
|
||||
// This allows us to list CRDs without a user session during server startup
|
||||
// TODO(@konsalex): Does this cause any security issue? Not 100% how to authenticate
|
||||
// a service call like this
|
||||
// Use well-know constants, and co-ord with IAM and SnStorage to ensure this is secure.
|
||||
systemCtx := resource.WithFallback(ctx)
|
||||
systemCtx = authlib.WithAuthInfo(systemCtx, &identity.StaticRequester{
|
||||
Type: authlib.TypeServiceAccount,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
apiVersion: customcrdtest.grafana.app/v1
|
||||
kind: Widget
|
||||
metadata:
|
||||
name: my-widget-2
|
||||
name: my-widget
|
||||
namespace: default
|
||||
spec:
|
||||
size: medium
|
||||
replicas: 3
|
||||
|
||||
replicas: 3
|
||||
@@ -0,0 +1,73 @@
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
)
|
||||
|
||||
// createDynamicHandlerWrapper wraps the not-found handler with dynamic custom resource handlers
|
||||
func (s *service) createDynamicHandlerWrapper(builders []builder.APIGroupBuilder, notFoundHandler http.Handler) http.Handler {
|
||||
// Look for the APIExtensionsBuilder - we'll fetch the handler lazily on each request
|
||||
// because the handler is created during UpdateAPIGroupInfo which happens AFTER this wrapper is installed
|
||||
type dynamicHandlerProvider interface {
|
||||
GetDynamicHandler() http.Handler
|
||||
}
|
||||
|
||||
var dhProvider dynamicHandlerProvider
|
||||
for _, b := range builders {
|
||||
if provider, ok := b.(dynamicHandlerProvider); ok {
|
||||
dhProvider = provider
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dhProvider == nil {
|
||||
return notFoundHandler
|
||||
}
|
||||
|
||||
// Return a wrapper that lazily fetches and tries the dynamic handler on each request
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if this is an /apis/ request that might be for a custom resource
|
||||
if strings.HasPrefix(r.URL.Path, "/apis/") {
|
||||
// Fetch the dynamic handler (it will be nil until UpdateAPIGroupInfo creates it)
|
||||
dynamicHandler := dhProvider.GetDynamicHandler()
|
||||
if dynamicHandler != nil {
|
||||
// Create a response recorder to capture what the dynamic handler does
|
||||
recorder := &responseRecorder{
|
||||
ResponseWriter: w,
|
||||
statusCode: 0,
|
||||
}
|
||||
|
||||
dynamicHandler.ServeHTTP(recorder, r)
|
||||
|
||||
// If the dynamic handler handled it (didn't return 404), we're done
|
||||
if recorder.statusCode != 0 && recorder.statusCode != http.StatusNotFound {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fall back to the not-found handler
|
||||
notFoundHandler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// responseRecorder captures the status code from a handler
|
||||
type responseRecorder struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (r *responseRecorder) WriteHeader(statusCode int) {
|
||||
r.statusCode = statusCode
|
||||
r.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (r *responseRecorder) Write(b []byte) (int, error) {
|
||||
if r.statusCode == 0 {
|
||||
r.statusCode = http.StatusOK
|
||||
}
|
||||
return r.ResponseWriter.Write(b)
|
||||
}
|
||||
@@ -379,8 +379,12 @@ func (s *service) start(ctx context.Context) error {
|
||||
|
||||
notFoundHandler := notfoundhandler.New(s.codecs, genericapifilters.NoMuxAndDiscoveryIncompleteKey)
|
||||
|
||||
// Wrap the not-found handler with dynamic custom resource handler
|
||||
finalHandler := s.createDynamicHandlerWrapper(builders, notFoundHandler)
|
||||
var finalHandler http.Handler = notFoundHandler
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagApiExtensions) {
|
||||
// Wrap the not-found handler with dynamic custom resource handler
|
||||
finalHandler = s.createDynamicHandlerWrapper(builders, notFoundHandler)
|
||||
}
|
||||
|
||||
if err := appinstaller.RegisterPostStartHooks(s.appInstallers, serverConfig); err != nil {
|
||||
return fmt.Errorf("failed to register post start hooks for app installers: %w", err)
|
||||
@@ -392,13 +396,16 @@ func (s *service) start(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Inject the server instance into any builders that need it (e.g., APIExtensionsBuilder for dynamic CRD registration)
|
||||
type apiServerSetter interface {
|
||||
SetAPIServer(server *genericapiserver.GenericAPIServer)
|
||||
}
|
||||
for _, b := range builders {
|
||||
if setter, ok := b.(apiServerSetter); ok {
|
||||
setter.SetAPIServer(server)
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagApiExtensions) {
|
||||
// Inject the server instance into any builders that need it (e.g., APIExtensionsBuilder for dynamic CRD registration)
|
||||
type apiServerSetter interface {
|
||||
SetAPIServer(server *genericapiserver.GenericAPIServer)
|
||||
}
|
||||
for _, b := range builders {
|
||||
if setter, ok := b.(apiServerSetter); ok {
|
||||
setter.SetAPIServer(server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,77 +517,6 @@ func (s *service) start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// createDynamicHandlerWrapper wraps the not-found handler with dynamic custom resource handlers
|
||||
func (s *service) createDynamicHandlerWrapper(builders []builder.APIGroupBuilder, notFoundHandler http.Handler) http.Handler {
|
||||
// Look for the APIExtensionsBuilder - we'll fetch the handler lazily on each request
|
||||
// because the handler is created during UpdateAPIGroupInfo which happens AFTER this wrapper is installed
|
||||
fmt.Printf("createDynamicHandlerWrapper: Setting up lazy handler wrapper for %d builders...\n", len(builders))
|
||||
|
||||
type dynamicHandlerProvider interface {
|
||||
GetDynamicHandler() http.Handler
|
||||
}
|
||||
|
||||
var dhProvider dynamicHandlerProvider
|
||||
for i, b := range builders {
|
||||
fmt.Printf(" Builder %d: %T\n", i, b)
|
||||
if provider, ok := b.(dynamicHandlerProvider); ok {
|
||||
fmt.Printf(" ✓ Builder %d implements dynamicHandlerProvider - will fetch handler lazily\n", i)
|
||||
dhProvider = provider
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dhProvider == nil {
|
||||
// No dynamic handler provider found, just use the not-found handler
|
||||
fmt.Println(" No dynamic handler provider found, using standard not-found handler")
|
||||
return notFoundHandler
|
||||
}
|
||||
|
||||
// Return a wrapper that lazily fetches and tries the dynamic handler on each request
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if this is an /apis/ request that might be for a custom resource
|
||||
if strings.HasPrefix(r.URL.Path, "/apis/") {
|
||||
// Fetch the dynamic handler (it will be nil until UpdateAPIGroupInfo creates it)
|
||||
dynamicHandler := dhProvider.GetDynamicHandler()
|
||||
if dynamicHandler != nil {
|
||||
// Create a response recorder to capture what the dynamic handler does
|
||||
recorder := &responseRecorder{
|
||||
ResponseWriter: w,
|
||||
statusCode: 0,
|
||||
}
|
||||
|
||||
dynamicHandler.ServeHTTP(recorder, r)
|
||||
|
||||
// If the dynamic handler handled it (didn't return 404), we're done
|
||||
if recorder.statusCode != 0 && recorder.statusCode != http.StatusNotFound {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fall back to the not-found handler
|
||||
notFoundHandler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// responseRecorder captures the status code from a handler
|
||||
type responseRecorder struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (r *responseRecorder) WriteHeader(statusCode int) {
|
||||
r.statusCode = statusCode
|
||||
r.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (r *responseRecorder) Write(b []byte) (int, error) {
|
||||
if r.statusCode == 0 {
|
||||
r.statusCode = http.StatusOK
|
||||
}
|
||||
return r.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func (s *service) startCoreServer(
|
||||
ctx context.Context,
|
||||
transport *grafanaapiserveroptions.RoundTripperFunc,
|
||||
|
||||
@@ -135,8 +135,17 @@ func (c authzLimitedClient) Check(ctx context.Context, id claims.AuthInfo, req c
|
||||
return claims.CheckResponse{Allowed: true}, nil
|
||||
}
|
||||
|
||||
// For cluster-scoped resources (empty namespace), skip namespace matching check
|
||||
if req.Namespace != "" && !claims.NamespaceMatches(id.GetNamespace(), req.Namespace) {
|
||||
// Hack, allow creation of Cluster scoped resources (ex. register CRDs)
|
||||
// We need to make sure it is the correct service account,
|
||||
// not just any service account.
|
||||
// This is called when we submit a CRD (not when we list them)
|
||||
// Currently creating them with a Service Account thus the match
|
||||
if req.Namespace == "" && claims.IsIdentityType(id.GetIdentityType(), claims.TypeServiceAccount, claims.TypeAccessPolicy) {
|
||||
span.SetAttributes(attribute.Bool("allowed", true))
|
||||
return claims.CheckResponse{Allowed: true}, nil
|
||||
}
|
||||
|
||||
if !claims.NamespaceMatches(id.GetNamespace(), req.Namespace) {
|
||||
span.SetAttributes(attribute.Bool("allowed", false))
|
||||
span.SetStatus(codes.Error, "Namespace mismatch")
|
||||
span.RecordError(claims.ErrNamespaceMismatch)
|
||||
@@ -185,8 +194,14 @@ func (c authzLimitedClient) Compile(ctx context.Context, id claims.AuthInfo, req
|
||||
return true
|
||||
}, claims.NoopZookie{}, nil
|
||||
}
|
||||
// For cluster-scoped resources (empty namespace), skip namespace matching check
|
||||
if req.Namespace != "" && !claims.NamespaceMatches(id.GetNamespace(), req.Namespace) {
|
||||
|
||||
// Hack, allow system tokens to be able to
|
||||
// access Cluster scoped resources (ex. register CRDs)
|
||||
// We need to make sure it is the correct service account,
|
||||
// not just any service account
|
||||
isServiceAccnt := req.Namespace == "" && claims.IsIdentityType(id.GetIdentityType(), claims.TypeAccessPolicy)
|
||||
|
||||
if !claims.NamespaceMatches(id.GetNamespace(), req.Namespace) && !isServiceAccnt {
|
||||
span.SetAttributes(attribute.Bool("allowed", false))
|
||||
span.SetStatus(codes.Error, "Namespace mismatch")
|
||||
span.RecordError(claims.ErrNamespaceMismatch)
|
||||
|
||||
Reference in New Issue
Block a user