From 025465e61129ccf98cc6bc1605e3cf2257ab99ab Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Tue, 27 Jun 2023 08:47:25 +0200 Subject: [PATCH] Chore: Update plugin schema with service registration info (#70692) --- .../developers/plugins/plugin.schema.json | 62 +++++++++++++++++++ pkg/plugins/envvars/envvars_test.go | 3 +- pkg/plugins/manager/fakes/fakes.go | 3 +- pkg/plugins/oauth/models.go | 24 +------ pkg/plugins/plugindef/plugindef.cue | 31 ++++++++++ pkg/plugins/plugindef/plugindef_types_gen.go | 35 ++++++++++- pkg/plugins/plugins.go | 3 +- .../serviceregistration.go | 23 ++++++- 8 files changed, 155 insertions(+), 29 deletions(-) diff --git a/docs/sources/developers/plugins/plugin.schema.json b/docs/sources/developers/plugins/plugin.schema.json index b90c6addc15..55040253d0e 100644 --- a/docs/sources/developers/plugins/plugin.schema.json +++ b/docs/sources/developers/plugins/plugin.schema.json @@ -477,6 +477,68 @@ "tracing": { "type": "boolean", "description": "For data source plugins, if the plugin supports tracing. Used for example to link logs (e.g. Loki logs) with tracing plugins." + }, + "externalServiceRegistration": { + "type": "object", + "description": "Oauth App Service Registration.", + "properties": { + "impersonation": { + "type": "object", + "description": "Impersonation describes the permissions that the external service will have on behalf of the user.", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled allows the service to request access tokens to impersonate users using the jwtbearer grant" + }, + "groups": { + "type": "boolean", + "description": "Groups allows the service to list the impersonated user's teams." + }, + "permissions": { + "type": "array", + "description": "Permissions are the permissions that the external service needs when impersonating a user. The intersection of this set with the impersonated user's permission guarantees that the client will not gain more privileges than the impersonated user has.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "action": { + "type": "string" + }, + "scope": { + "type": "string" + } + } + } + } + } + }, + "self": { + "type": "object", + "description": "Self describes the permissions that the external service will have on behalf of itself", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enabled allows the service to request access tokens for itself using the client_credentials grant" + }, + "permissions": { + "type": "array", + "description": "Permissions are the permissions that the external service needs its associated service account to have", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "action": { + "type": "string" + }, + "scope": { + "type": "string" + } + } + } + } + } + } + } } } } diff --git a/pkg/plugins/envvars/envvars_test.go b/pkg/plugins/envvars/envvars_test.go index 5fdcd8ba34d..c50bf64790b 100644 --- a/pkg/plugins/envvars/envvars_test.go +++ b/pkg/plugins/envvars/envvars_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/plugins/oauth" + "github.com/grafana/grafana/pkg/plugins/plugindef" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" ) @@ -312,7 +313,7 @@ func TestInitializer_oauthEnvVars(t *testing.T) { p := &plugins.Plugin{ JSONData: plugins.JSONData{ ID: "test", - ExternalServiceRegistration: &oauth.ExternalServiceRegistration{}, + ExternalServiceRegistration: &plugindef.ExternalServiceRegistration{}, }, ExternalService: &oauth.ExternalService{ ClientID: "clientID", diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index 3efe42f2a7b..f099542360a 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/plugins/oauth" + "github.com/grafana/grafana/pkg/plugins/plugindef" "github.com/grafana/grafana/pkg/plugins/repo" "github.com/grafana/grafana/pkg/plugins/storage" ) @@ -428,6 +429,6 @@ type FakeOauthService struct { Result *oauth.ExternalService } -func (f *FakeOauthService) RegisterExternalService(ctx context.Context, name string, svc *oauth.ExternalServiceRegistration) (*oauth.ExternalService, error) { +func (f *FakeOauthService) RegisterExternalService(ctx context.Context, name string, svc *plugindef.ExternalServiceRegistration) (*oauth.ExternalService, error) { return f.Result, nil } diff --git a/pkg/plugins/oauth/models.go b/pkg/plugins/oauth/models.go index dbae05270b7..728035cd5de 100644 --- a/pkg/plugins/oauth/models.go +++ b/pkg/plugins/oauth/models.go @@ -3,29 +3,9 @@ package oauth import ( "context" - "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/plugins/plugindef" ) -// SelfCfg is a subset of oauthserver.SelfCfg making some fields optional -type SelfCfg struct { - Enabled *bool `json:"enabled,omitempty"` - Permissions []accesscontrol.Permission `json:"permissions,omitempty"` -} - -// ImpersonationCfg is a subset of oauthserver.ImpersonationCfg making some fields optional -type ImpersonationCfg struct { - Enabled *bool `json:"enabled,omitempty"` - Groups *bool `json:"groups,omitempty"` - Permissions []accesscontrol.Permission `json:"permissions,omitempty"` -} - -// PluginExternalServiceRegistration is a subset of oauthserver.ExternalServiceRegistration -// simplified for the plugin use case. -type ExternalServiceRegistration struct { - Impersonation *ImpersonationCfg `json:"impersonation,omitempty"` - Self *SelfCfg `json:"self,omitempty"` -} - type ExternalService struct { ClientID string `json:"clientId"` ClientSecret string `json:"clientSecret"` @@ -33,5 +13,5 @@ type ExternalService struct { } type ExternalServiceRegistry interface { - RegisterExternalService(ctx context.Context, name string, svc *ExternalServiceRegistration) (*ExternalService, error) + RegisterExternalService(ctx context.Context, name string, svc *plugindef.ExternalServiceRegistration) (*ExternalService, error) } diff --git a/pkg/plugins/plugindef/plugindef.cue b/pkg/plugins/plugindef/plugindef.cue index 79195b9182d..b2ec0f409a1 100644 --- a/pkg/plugins/plugindef/plugindef.cue +++ b/pkg/plugins/plugindef/plugindef.cue @@ -405,6 +405,37 @@ schemas: [{ // Parameters for the JWT token authentication request. params: [string]: string } + + // External service registration information + externalServiceRegistration: #ExternalServiceRegistration + + #ExternalServiceRegistration: { + // Impersonation describes the permissions that the external service will have on behalf of the user + impersonation?: #Impersonation + // Self describes the permissions that the external service will have on behalf of itself + self?: #Self + } + + #Impersonation: { + // Enabled allows the service to request access tokens to impersonate users using the jwtbearer grant + // Defaults to true. + enabled?: bool + // Groups allows the service to list the impersonated user's teams. + // Defaults to true. + groups?: bool + // Permissions are the permissions that the external service needs when impersonating a user. + // The intersection of this set with the impersonated user's permission guarantees that the client will not + // gain more privileges than the impersonated user has. + permissions?: [...#Permission] + } + + #Self: { + // Enabled allows the service to request access tokens for itself using the client_credentials grant + // Defaults to true. + enabled?: bool + // Permissions are the permissions that the external service needs its associated service account to have. + permissions?: [...#Permission] + } } }] lenses: [] diff --git a/pkg/plugins/plugindef/plugindef_types_gen.go b/pkg/plugins/plugindef/plugindef_types_gen.go index 067332b11ab..c9ad1b92786 100644 --- a/pkg/plugins/plugindef/plugindef_types_gen.go +++ b/pkg/plugins/plugindef/plugindef_types_gen.go @@ -122,6 +122,12 @@ type Dependency struct { // DependencyType defines model for Dependency.Type. type DependencyType string +// ExternalServiceRegistration defines model for ExternalServiceRegistration. +type ExternalServiceRegistration struct { + Impersonation *Impersonation `json:"impersonation,omitempty"` + Self *Self `json:"self,omitempty"` +} + // Header describes an HTTP header that is forwarded with a proxied request for // a plugin route. type Header struct { @@ -129,6 +135,22 @@ type Header struct { Name string `json:"name"` } +// Impersonation defines model for Impersonation. +type Impersonation struct { + // Enabled allows the service to request access tokens to impersonate users using the jwtbearer grant + // Defaults to true. + Enabled *bool `json:"enabled,omitempty"` + + // Groups allows the service to list the impersonated user's teams. + // Defaults to true. + Groups *bool `json:"groups,omitempty"` + + // Permissions are the permissions that the external service needs when impersonating a user. + // The intersection of this set with the impersonated user's permission guarantees that the client will not + // gain more privileges than the impersonated user has. + Permissions []Permission `json:"permissions,omitempty"` +} + // A resource to be included in a plugin. type Include struct { // RBAC action the user must have to access the route @@ -288,7 +310,8 @@ type PluginDef struct { // $GOARCH><.exe for Windows>`, e.g. `plugin_linux_amd64`. // Combination of $GOOS and $GOARCH can be found here: // https://golang.org/doc/install/source#environment. - Executable *string `json:"executable,omitempty"` + Executable *string `json:"executable,omitempty"` + ExternalServiceRegistration ExternalServiceRegistration `json:"externalServiceRegistration"` // [internal only] Excludes the plugin from listings in Grafana's UI. Only // allowed for `builtIn` plugins. @@ -445,6 +468,16 @@ type Route struct { UrlParams []URLParam `json:"urlParams,omitempty"` } +// Self defines model for Self. +type Self struct { + // Enabled allows the service to request access tokens for itself using the client_credentials grant + // Defaults to true. + Enabled *bool `json:"enabled,omitempty"` + + // Permissions are the permissions that the external service needs its associated service account to have. + Permissions []Permission `json:"permissions,omitempty"` +} + // TODO docs type TokenAuth struct { // Parameters for the token authentication request. diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 451d752e35e..7772c1fb40c 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/plugins/oauth" + "github.com/grafana/grafana/pkg/plugins/plugindef" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/util" ) @@ -155,7 +156,7 @@ type JSONData struct { Executable string `json:"executable,omitempty"` // Oauth App Service Registration - ExternalServiceRegistration *oauth.ExternalServiceRegistration `json:"externalServiceRegistration,omitempty"` + ExternalServiceRegistration *plugindef.ExternalServiceRegistration `json:"externalServiceRegistration,omitempty"` } func ReadPluginJSON(reader io.Reader) (JSONData, error) { diff --git a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go index 9912401cff3..a10f8cfa553 100644 --- a/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go +++ b/pkg/services/pluginsintegration/serviceregistration/serviceregistration.go @@ -4,6 +4,8 @@ import ( "context" "github.com/grafana/grafana/pkg/plugins/oauth" + "github.com/grafana/grafana/pkg/plugins/plugindef" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/oauthserver" ) @@ -19,10 +21,10 @@ func ProvideService(os oauthserver.OAuth2Server) *Service { } // RegisterExternalService is a simplified wrapper around SaveExternalService for the plugin use case. -func (s *Service) RegisterExternalService(ctx context.Context, svcName string, svc *oauth.ExternalServiceRegistration) (*oauth.ExternalService, error) { +func (s *Service) RegisterExternalService(ctx context.Context, svcName string, svc *plugindef.ExternalServiceRegistration) (*oauth.ExternalService, error) { impersonation := oauthserver.ImpersonationCfg{} if svc.Impersonation != nil { - impersonation.Permissions = svc.Impersonation.Permissions + impersonation.Permissions = toAccessControlPermissions(svc.Impersonation.Permissions) if svc.Impersonation.Enabled != nil { impersonation.Enabled = *svc.Impersonation.Enabled } else { @@ -37,7 +39,7 @@ func (s *Service) RegisterExternalService(ctx context.Context, svcName string, s self := oauthserver.SelfCfg{} if svc.Self != nil { - self.Permissions = svc.Self.Permissions + self.Permissions = toAccessControlPermissions(svc.Self.Permissions) if svc.Self.Enabled != nil { self.Enabled = *svc.Self.Enabled } else { @@ -60,3 +62,18 @@ func (s *Service) RegisterExternalService(ctx context.Context, svcName string, s PrivateKey: extSvc.KeyResult.PrivatePem, }, nil } + +func toAccessControlPermissions(ps []plugindef.Permission) []accesscontrol.Permission { + res := make([]accesscontrol.Permission, 0, len(ps)) + for _, p := range ps { + scope := "" + if p.Scope != nil { + scope = *p.Scope + } + res = append(res, accesscontrol.Permission{ + Action: p.Action, + Scope: scope, + }) + } + return res +}