From 5b89d3b807d06836a4d7e281bb37cbd1d5dae715 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:56:01 -0500 Subject: [PATCH] Plugins App: Add access control (#114869) --- pkg/registry/apps/plugins/accesscontrol.go | 127 ++++++++++++++++++ pkg/registry/apps/plugins/register.go | 21 ++- pkg/server/wire_gen.go | 4 +- pkg/services/accesscontrol/permreg/permreg.go | 2 + 4 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 pkg/registry/apps/plugins/accesscontrol.go diff --git a/pkg/registry/apps/plugins/accesscontrol.go b/pkg/registry/apps/plugins/accesscontrol.go new file mode 100644 index 00000000000..d41efa86f97 --- /dev/null +++ b/pkg/registry/apps/plugins/accesscontrol.go @@ -0,0 +1,127 @@ +package plugins + +import ( + "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/org" +) + +const ( + // Plugins + ActionPluginsPluginsCreate = "plugins.plugins:create" // CREATE. + ActionPluginsPluginsWrite = "plugins.plugins:write" // UPDATE. + ActionPluginsPluginsRead = "plugins.plugins:read" // GET + LIST. + ActionPluginsPluginsDelete = "plugins.plugins:delete" // DELETE. + + // PluginMetas + ActionPluginsPluginsMetaCreate = "plugins.pluginsmeta:create" // CREATE. + ActionPluginsPluginsMetaWrite = "plugins.pluginsmeta:write" // UPDATE. + ActionPluginsPluginsMetaRead = "plugins.pluginsmeta:read" // GET + LIST. + ActionPluginsPluginsMetaDelete = "plugins.pluginsmeta:delete" // DELETE. +) + +var ( + ScopeProviderPluginsPlugins = accesscontrol.NewScopeProvider("plugins.plugins") + ScopeProviderPluginsPluginsMeta = accesscontrol.NewScopeProvider("plugins.pluginsmeta") + + ScopeAllPluginsPlugins = ScopeProviderPluginsPlugins.GetResourceAllScope() + ScopeAllPluginsPluginsMeta = ScopeProviderPluginsPluginsMeta.GetResourceAllScope() +) + +func registerAccessControlRoles(service accesscontrol.Service) error { + // Plugins + pluginsReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:plugins.plugins:reader", + DisplayName: "Plugins Reader", + Description: "Read and list plugins.", + Group: "Plugins", + Permissions: []accesscontrol.Permission{ + { + Action: ActionPluginsPluginsRead, + Scope: ScopeAllPluginsPlugins, + }, + }, + }, + Grants: []string{string(org.RoleViewer), string(org.RoleEditor), string(org.RoleAdmin)}, + } + + pluginsWriter := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:plugins.plugins:writer", + DisplayName: "Plugins Writer", + Description: "Create, update and delete plugins.", + Group: "Plugins", + Permissions: []accesscontrol.Permission{ + { + Action: ActionPluginsPluginsCreate, + Scope: ScopeAllPluginsPlugins, + }, + { + Action: ActionPluginsPluginsRead, + Scope: ScopeAllPluginsPlugins, + }, + { + Action: ActionPluginsPluginsWrite, + Scope: ScopeAllPluginsPlugins, + }, + { + Action: ActionPluginsPluginsDelete, + Scope: ScopeAllPluginsPlugins, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + // PluginMetas + pluginsMetaReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:plugins.pluginsmeta:reader", + DisplayName: "Plugin Metas Reader", + Description: "Read and list plugin metadata.", + Group: "Plugins", + Permissions: []accesscontrol.Permission{ + { + Action: ActionPluginsPluginsMetaRead, + Scope: ScopeAllPluginsPluginsMeta, + }, + }, + }, + Grants: []string{string(org.RoleViewer), string(org.RoleEditor), string(org.RoleAdmin)}, + } + + pluginsMetaWriter := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:plugins.pluginsmeta:writer", + DisplayName: "Plugin Metas Writer", + Description: "Create, update and delete plugin metadata.", + Group: "Plugins", + Permissions: []accesscontrol.Permission{ + { + Action: ActionPluginsPluginsMetaCreate, + Scope: ScopeAllPluginsPluginsMeta, + }, + { + Action: ActionPluginsPluginsMetaRead, + Scope: ScopeAllPluginsPluginsMeta, + }, + { + Action: ActionPluginsPluginsMetaWrite, + Scope: ScopeAllPluginsPluginsMeta, + }, + { + Action: ActionPluginsPluginsMetaDelete, + Scope: ScopeAllPluginsPluginsMeta, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + return service.DeclareFixedRoles( + pluginsReader, + pluginsWriter, + pluginsMetaReader, + pluginsMetaWriter, + ) +} diff --git a/pkg/registry/apps/plugins/register.go b/pkg/registry/apps/plugins/register.go index 5d452cbe67c..6831d31ef9b 100644 --- a/pkg/registry/apps/plugins/register.go +++ b/pkg/registry/apps/plugins/register.go @@ -1,14 +1,16 @@ package plugins import ( + "fmt" "os" - "k8s.io/apiserver/pkg/authorization/authorizer" - + 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" ) @@ -18,10 +20,14 @@ var ( ) type AppInstaller struct { - appsdkapiserver.AppInstaller + *pluginsapp.PluginAppInstaller } -func ProvideAppInstaller() (*AppInstaller, error) { +func ProvideAppInstaller(accessControlService accesscontrol.Service, accessClient authlib.AccessClient) (*AppInstaller, error) { + if err := registerAccessControlRoles(accessControlService); err != nil { + return nil, fmt.Errorf("registering access control roles: %w", err) + } + grafanaComAPIURL := os.Getenv("GRAFANA_COM_API_URL") if grafanaComAPIURL == "" { grafanaComAPIURL = "https://grafana.com/api/plugins" @@ -36,12 +42,13 @@ func ProvideAppInstaller() (*AppInstaller, error) { return nil, err } + i.WithAccessChecker(accessClient) + return &AppInstaller{ - AppInstaller: i, + PluginAppInstaller: i, }, nil } -// GetAuthorizer returns the authorizer for the plugins app. -func (p *AppInstaller) GetAuthorizer() authorizer.Authorizer { +func (a *AppInstaller) GetAuthorizer() authorizer.Authorizer { return pluginsapp.GetAuthorizer() } diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index e920bdbec61..d1a2ecee64f 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -783,7 +783,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - appInstaller, err := plugins.ProvideAppInstaller() + appInstaller, err := plugins.ProvideAppInstaller(acimplService, accessClient) if err != nil { return nil, err } @@ -1436,7 +1436,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - appInstaller, err := plugins.ProvideAppInstaller() + appInstaller, err := plugins.ProvideAppInstaller(acimplService, accessClient) if err != nil { return nil, err } diff --git a/pkg/services/accesscontrol/permreg/permreg.go b/pkg/services/accesscontrol/permreg/permreg.go index 5d025a1258b..c9f010c2909 100644 --- a/pkg/services/accesscontrol/permreg/permreg.go +++ b/pkg/services/accesscontrol/permreg/permreg.go @@ -84,6 +84,8 @@ func newPermissionRegistry() *permissionRegistry { "annotations": "annotations:type:", "orgs": "orgs:id:", "plugins": "plugins:id:", + "plugins.plugins": "plugins.plugins:uid:", + "plugins.pluginsmeta": "plugins.pluginsmeta:uid:", "provisioners": "provisioners:", "reports": "reports:id:", "permissions": "permissions:type:",