Plugins App: Switch to resource authorizer (#115019)

This commit is contained in:
Todd Treece
2025-12-10 09:12:26 -05:00
committed by GitHub
parent c4c1708e38
commit ac55fad1ba
6 changed files with 13 additions and 93 deletions
+2 -2
View File
@@ -10,11 +10,9 @@ replace github.com/grafana/grafana/pkg/apiserver => ../../pkg/apiserver
require (
github.com/emicklei/go-restful/v3 v3.13.0
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4
github.com/grafana/grafana v0.0.0-00010101000000-000000000000
github.com/grafana/grafana-app-sdk v0.48.5
github.com/grafana/grafana-app-sdk/logging v0.48.3
github.com/grafana/grafana/pkg/apimachinery v0.0.0
github.com/stretchr/testify v1.11.1
k8s.io/apimachinery v0.34.2
k8s.io/apiserver v0.34.2
@@ -78,11 +76,13 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/grafana/alerting v0.0.0-20251204145817-de8c2bbf9eba // indirect
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
github.com/grafana/grafana-aws-sdk v1.3.0 // indirect
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // indirect
github.com/grafana/grafana-plugin-sdk-go v0.284.0 // indirect
github.com/grafana/grafana/pkg/apimachinery v0.0.0 // indirect
github.com/grafana/grafana/pkg/apiserver v0.0.0 // indirect
github.com/grafana/otel-profiling-go v0.5.1 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
+4 -36
View File
@@ -70,6 +70,7 @@ type PluginAppConfig struct {
}
func ProvideAppInstaller(
authorizer authorizer.Authorizer,
metaProviderManager *meta.ProviderManager,
) (*PluginAppInstaller, error) {
specificConfig := &PluginAppConfig{
@@ -88,21 +89,17 @@ func ProvideAppInstaller(
appInstaller := &PluginAppInstaller{
AppInstaller: defaultInstaller,
authorizer: authorizer,
metaManager: metaProviderManager,
ready: make(chan struct{}),
}
return appInstaller, nil
}
func (p *PluginAppInstaller) WithAccessChecker(access authlib.AccessChecker) *PluginAppInstaller {
p.access = access
return p
}
type PluginAppInstaller struct {
appsdkapiserver.AppInstaller
metaManager *meta.ProviderManager
access authlib.AccessChecker
authorizer authorizer.Authorizer
// restConfig is set during InitializeApp and used by the client factory
restConfig *restclient.Config
@@ -153,34 +150,5 @@ func (p *PluginAppInstaller) InstallAPIs(
}
func (p *PluginAppInstaller) GetAuthorizer() authorizer.Authorizer {
if p.access == nil {
return nil
}
return authorizer.AuthorizerFunc(
func(ctx context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
info, ok := authlib.AuthInfoFrom(ctx)
if !ok {
return authorizer.DecisionDeny, "failed to get auth info", nil
}
res, err := p.access.Check(ctx, info, authlib.CheckRequest{
Verb: a.GetVerb(),
Group: a.GetAPIGroup(),
Resource: a.GetResource(),
Name: a.GetName(),
Namespace: a.GetNamespace(),
Subresource: a.GetSubresource(),
Path: a.GetPath(),
}, "")
if err != nil {
return authorizer.DecisionDeny, "failed to perform authorization", err
}
if !res.Allowed {
return authorizer.DecisionDeny, "permission denied", nil
}
return authorizer.DecisionAllow, "", nil
})
return p.authorizer
}
-32
View File
@@ -1,32 +0,0 @@
package app
import (
"context"
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/apimachinery/identity"
)
func GetAuthorizer() authorizer.Authorizer {
return authorizer.AuthorizerFunc(func(
ctx context.Context, attr authorizer.Attributes,
) (authorized authorizer.Decision, reason string, err error) {
if !attr.IsResourceRequest() {
return authorizer.DecisionNoOpinion, "", nil
}
// require a user
u, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "valid user is required", err
}
// check if is admin
if u.HasRole(identity.RoleAdmin) {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "forbidden", nil
})
}
+3 -8
View File
@@ -6,12 +6,12 @@ import (
authlib "github.com/grafana/authlib/types"
appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver"
"k8s.io/apiserver/pkg/authorization/authorizer"
pluginsapp "github.com/grafana/grafana/apps/plugins/pkg/app"
"github.com/grafana/grafana/apps/plugins/pkg/app/meta"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/appinstaller"
grafanaauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
)
var (
@@ -37,18 +37,13 @@ func ProvideAppInstaller(accessControlService accesscontrol.Service, accessClien
cloudProvider := meta.NewCatalogProvider(grafanaComAPIURL)
metaProviderManager := meta.NewProviderManager(coreProvider, cloudProvider)
i, err := pluginsapp.ProvideAppInstaller(metaProviderManager)
authorizer := grafanaauthorizer.NewResourceAuthorizer(accessClient)
i, err := pluginsapp.ProvideAppInstaller(authorizer, metaProviderManager)
if err != nil {
return nil, err
}
i.WithAccessChecker(accessClient)
return &AppInstaller{
PluginAppInstaller: i,
}, nil
}
func (a *AppInstaller) GetAuthorizer() authorizer.Authorizer {
return pluginsapp.GetAuthorizer()
}
@@ -55,15 +55,9 @@ func TestServiceAdapter_ErrorHandling(t *testing.T) {
adapter := asNamedService(mockSvc)
t.Cleanup(func() {
adapter.StopAsync()
err := adapter.AwaitTerminated(context.Background())
require.ErrorIs(t, err, expectedErr)
})
err := adapter.StartAsync(context.Background())
require.NoError(t, err)
err = adapter.AwaitRunning(context.Background())
err = adapter.AwaitTerminated(context.Background())
require.ErrorIs(t, err, expectedErr)
require.True(t, mockSvc.runCalled)
})
@@ -95,14 +89,9 @@ func TestServiceAdapter_ErrorHandling(t *testing.T) {
adapter := asNamedService(mockSvc)
t.Cleanup(func() {
adapter.StopAsync()
err := adapter.AwaitTerminated(context.Background())
require.ErrorIs(t, err, expectedErr)
})
err := adapter.StartAsync(context.Background())
require.NoError(t, err)
err = adapter.AwaitRunning(context.Background())
err = adapter.AwaitTerminated(context.Background())
require.ErrorIs(t, err, expectedErr)
require.True(t, mockSvc.runCalled)
})
@@ -9,13 +9,13 @@ import (
claims "github.com/grafana/authlib/types"
)
func NewResourceAuthorizer(c claims.AccessClient) authorizer.Authorizer {
func NewResourceAuthorizer(c claims.AccessChecker) authorizer.Authorizer {
return ResourceAuthorizer{c}
}
// ResourceAuthorizer is used to translate authorizer.Authorizer calls to claims.AccessClient calls
type ResourceAuthorizer struct {
c claims.AccessClient
c claims.AccessChecker
}
func (r ResourceAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {