SaveExternalService (OAuth) on plugin load (#69764)

pull/70692/head
Andres Martinez Gotor 2 years ago committed by GitHub
parent f436364f9b
commit 4ff0abd0d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      pkg/api/plugin_resource_test.go
  2. 5
      pkg/plugins/config/config.go
  3. 16
      pkg/plugins/envvars/envvars.go
  4. 38
      pkg/plugins/envvars/envvars_test.go
  5. 9
      pkg/plugins/manager/fakes/fakes.go
  6. 5
      pkg/plugins/manager/loader/initializer/initializer.go
  7. 6
      pkg/plugins/manager/loader/initializer/initializer_test.go
  8. 63
      pkg/plugins/manager/loader/loader.go
  9. 2
      pkg/plugins/manager/loader/loader_test.go
  10. 2
      pkg/plugins/manager/manager_integration_test.go
  11. 37
      pkg/plugins/oauth/models.go
  12. 6
      pkg/plugins/plugins.go
  13. 3
      pkg/services/oauthserver/oasimpl/service.go
  14. 1
      pkg/services/pluginsintegration/config/config.go
  15. 4
      pkg/services/pluginsintegration/pluginsintegration.go
  16. 62
      pkg/services/pluginsintegration/serviceregistration/serviceregistration.go

@ -72,7 +72,7 @@ func TestCallResource(t *testing.T) {
l := loader.ProvideService(pCfg, fakes.NewFakeLicensingService(), signature.NewUnsignedAuthorizer(pCfg),
reg, provider.ProvideService(coreRegistry), finder.NewLocalFinder(pCfg), fakes.NewFakeRoleRegistry(),
assetpath.ProvideService(pluginscdn.ProvideService(pCfg)), signature.ProvideService(pCfg, statickey.New()),
angularInspector)
angularInspector, &fakes.FakeOauthService{})
srcs := sources.ProvideService(cfg, pCfg)
ps, err := store.ProvideService(reg, srcs, l)
require.NoError(t, err)

@ -38,6 +38,8 @@ type Cfg struct {
GrafanaComURL string
GrafanaAppURL string
Features plugins.FeatureToggles
AngularSupportEnabled bool
@ -45,7 +47,7 @@ type Cfg struct {
func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string,
awsAllowedAuthProviders []string, awsAssumeRoleEnabled bool, azure *azsettings.AzureSettings, secureSocksDSProxy setting.SecureSocksDSProxySettings,
grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, tracing Tracing, features plugins.FeatureToggles, angularSupportEnabled bool,
grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, appURL string, tracing Tracing, features plugins.FeatureToggles, angularSupportEnabled bool,
grafanaComURL string) *Cfg {
return &Cfg{
log: log.New("plugin.cfg"),
@ -62,6 +64,7 @@ func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSetti
PluginsCDNURLTemplate: pluginsCDNURLTemplate,
Tracing: tracing,
GrafanaComURL: grafanaComURL,
GrafanaAppURL: appURL,
Features: features,
AngularSupportEnabled: angularSupportEnabled,
}

@ -16,7 +16,7 @@ import (
)
type Provider interface {
Get(ctx context.Context, p *plugins.Plugin) []string
Get(ctx context.Context, p *plugins.Plugin) ([]string, error)
}
type Service struct {
@ -31,7 +31,7 @@ func NewProvider(cfg *config.Cfg, license plugins.Licensing) *Service {
}
}
func (s *Service) Get(_ context.Context, p *plugins.Plugin) []string {
func (s *Service) Get(ctx context.Context, p *plugins.Plugin) ([]string, error) {
hostEnv := []string{
fmt.Sprintf("GF_VERSION=%s", s.cfg.BuildVersion),
}
@ -46,13 +46,23 @@ func (s *Service) Get(_ context.Context, p *plugins.Plugin) []string {
hostEnv = append(hostEnv, s.license.Environment()...)
}
if p.ExternalService != nil {
hostEnv = append(
hostEnv,
fmt.Sprintf("GF_APP_URL=%s", s.cfg.GrafanaAppURL),
fmt.Sprintf("GF_PLUGIN_APP_CLIENT_ID=%s", p.ExternalService.ClientID),
fmt.Sprintf("GF_PLUGIN_APP_CLIENT_SECRET=%s", p.ExternalService.ClientSecret),
fmt.Sprintf("GF_PLUGIN_APP_PRIVATE_KEY=%s", p.ExternalService.PrivateKey),
)
}
hostEnv = append(hostEnv, s.awsEnvVars()...)
hostEnv = append(hostEnv, s.secureSocksProxyEnvVars()...)
hostEnv = append(hostEnv, azsettings.WriteToEnvStr(s.cfg.Azure)...)
hostEnv = append(hostEnv, s.tracingEnvVars(p)...)
ev := getPluginSettings(p.ID, s.cfg).asEnvVar("GF_PLUGIN", hostEnv)
return ev
return ev, nil
}
func (s *Service) tracingEnvVars(plugin *plugins.Plugin) []string {

@ -11,6 +11,8 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"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/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
)
@ -37,7 +39,8 @@ func TestInitializer_envVars(t *testing.T) {
},
}, licensing)
envVars := envVarsProvider.Get(context.Background(), p)
envVars, err := envVarsProvider.Get(context.Background(), p)
require.NoError(t, err)
assert.Len(t, envVars, 6)
assert.Equal(t, "GF_PLUGIN_CUSTOM_ENV_VAR=customVal", envVars[0])
assert.Equal(t, "GF_VERSION=", envVars[1])
@ -297,8 +300,39 @@ func TestInitializer_tracingEnvironmentVariables(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
envVarsProvider := NewProvider(tc.cfg, nil)
envVars := envVarsProvider.Get(context.Background(), tc.plugin)
envVars, err := envVarsProvider.Get(context.Background(), tc.plugin)
require.NoError(t, err)
tc.exp(t, envVars)
})
}
}
func TestInitializer_oauthEnvVars(t *testing.T) {
t.Run("backend datasource with oauth registration", func(t *testing.T) {
p := &plugins.Plugin{
JSONData: plugins.JSONData{
ID: "test",
ExternalServiceRegistration: &oauth.ExternalServiceRegistration{},
},
ExternalService: &oauth.ExternalService{
ClientID: "clientID",
ClientSecret: "clientSecret",
PrivateKey: "privatePem",
},
}
envVarsProvider := NewProvider(&config.Cfg{
GrafanaAppURL: "https://myorg.com/",
Features: featuremgmt.WithFeatures(featuremgmt.FlagExternalServiceAuth),
}, nil)
envVars, err := envVarsProvider.Get(context.Background(), p)
require.NoError(t, err)
assert.Len(t, envVars, 5)
assert.Equal(t, "GF_VERSION=", envVars[0])
assert.Equal(t, "GF_APP_URL=https://myorg.com/", envVars[1])
assert.Equal(t, "GF_PLUGIN_APP_CLIENT_ID=clientID", envVars[2])
assert.Equal(t, "GF_PLUGIN_APP_CLIENT_SECRET=clientSecret", envVars[3])
assert.Equal(t, "GF_PLUGIN_APP_PRIVATE_KEY=privatePem", envVars[4])
})
}

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"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/repo"
"github.com/grafana/grafana/pkg/plugins/storage"
)
@ -422,3 +423,11 @@ func (f *FakePluginFileStore) File(ctx context.Context, pluginID, filename strin
}
return nil, nil
}
type FakeOauthService struct {
Result *oauth.ExternalService
}
func (f *FakeOauthService) RegisterExternalService(ctx context.Context, name string, svc *oauth.ExternalServiceRegistration) (*oauth.ExternalService, error) {
return f.Result, nil
}

@ -28,7 +28,10 @@ func (i *Initializer) Initialize(ctx context.Context, p *plugins.Plugin) error {
return errors.New("could not find backend factory for plugin")
}
env := i.envVarProvider.Get(ctx, p)
env, err := i.envVarProvider.Get(ctx, p)
if err != nil {
return err
}
if backendClient, err := backendFactory(p.ID, p.Logger(), env); err != nil {
return err
} else {

@ -138,9 +138,9 @@ type fakeEnvVarsProvider struct {
GetFunc func(ctx context.Context, p *plugins.Plugin) []string
}
func (f *fakeEnvVarsProvider) Get(ctx context.Context, p *plugins.Plugin) []string {
func (f *fakeEnvVarsProvider) Get(ctx context.Context, p *plugins.Plugin) ([]string, error) {
if f.GetFunc != nil {
return f.GetFunc(ctx, p)
return f.GetFunc(ctx, p), nil
}
return nil
return nil, nil
}

@ -20,22 +20,25 @@ import (
"github.com/grafana/grafana/pkg/plugins/manager/process"
"github.com/grafana/grafana/pkg/plugins/manager/registry"
"github.com/grafana/grafana/pkg/plugins/manager/signature"
"github.com/grafana/grafana/pkg/plugins/oauth"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/util"
)
var _ plugins.ErrorResolver = (*Loader)(nil)
type Loader struct {
pluginFinder finder.Finder
processManager process.Service
pluginRegistry registry.Service
roleRegistry plugins.RoleRegistry
pluginInitializer initializer.Initializer
signatureValidator signature.Validator
signatureCalculator plugins.SignatureCalculator
assetPath *assetpath.Service
log log.Logger
cfg *config.Cfg
pluginFinder finder.Finder
processManager process.Service
pluginRegistry registry.Service
roleRegistry plugins.RoleRegistry
pluginInitializer initializer.Initializer
signatureValidator signature.Validator
signatureCalculator plugins.SignatureCalculator
externalServiceRegistry oauth.ExternalServiceRegistry
assetPath *assetpath.Service
log log.Logger
cfg *config.Cfg
angularInspector angularinspector.Inspector
@ -45,29 +48,30 @@ type Loader struct {
func ProvideService(cfg *config.Cfg, license plugins.Licensing, authorizer plugins.PluginLoaderAuthorizer,
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider, pluginFinder finder.Finder,
roleRegistry plugins.RoleRegistry, assetPath *assetpath.Service, signatureCalculator plugins.SignatureCalculator,
angularInspector angularinspector.Inspector) *Loader {
angularInspector angularinspector.Inspector, externalServiceRegistry oauth.ExternalServiceRegistry) *Loader {
return New(cfg, license, authorizer, pluginRegistry, backendProvider, process.NewManager(pluginRegistry),
roleRegistry, assetPath, pluginFinder, signatureCalculator, angularInspector)
roleRegistry, assetPath, pluginFinder, signatureCalculator, angularInspector, externalServiceRegistry)
}
func New(cfg *config.Cfg, license plugins.Licensing, authorizer plugins.PluginLoaderAuthorizer,
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider,
processManager process.Service, roleRegistry plugins.RoleRegistry,
assetPath *assetpath.Service, pluginFinder finder.Finder, signatureCalculator plugins.SignatureCalculator,
angularInspector angularinspector.Inspector) *Loader {
angularInspector angularinspector.Inspector, externalServiceRegistry oauth.ExternalServiceRegistry) *Loader {
return &Loader{
pluginFinder: pluginFinder,
pluginRegistry: pluginRegistry,
pluginInitializer: initializer.New(cfg, backendProvider, license),
signatureValidator: signature.NewValidator(authorizer),
signatureCalculator: signatureCalculator,
processManager: processManager,
errs: make(map[string]*plugins.SignatureError),
log: log.New("plugin.loader"),
roleRegistry: roleRegistry,
cfg: cfg,
assetPath: assetPath,
angularInspector: angularInspector,
pluginFinder: pluginFinder,
pluginRegistry: pluginRegistry,
pluginInitializer: initializer.New(cfg, backendProvider, license),
signatureValidator: signature.NewValidator(authorizer),
signatureCalculator: signatureCalculator,
processManager: processManager,
errs: make(map[string]*plugins.SignatureError),
log: log.New("plugin.loader"),
roleRegistry: roleRegistry,
cfg: cfg,
assetPath: assetPath,
angularInspector: angularInspector,
externalServiceRegistry: externalServiceRegistry,
}
}
@ -202,6 +206,15 @@ func (l *Loader) loadPlugins(ctx context.Context, src plugins.PluginSource, foun
}
}
if p.ExternalServiceRegistration != nil && l.cfg.Features.IsEnabled(featuremgmt.FlagExternalServiceAuth) {
s, err := l.externalServiceRegistry.RegisterExternalService(ctx, p.ID, p.ExternalServiceRegistration)
if err != nil {
l.log.Error("Could not register an external service. Initialization skipped", "pluginID", p.ID, "err", err)
continue
}
p.ExternalService = s
}
err := l.pluginInitializer.Initialize(ctx, p)
if err != nil {
l.log.Error("Could not initialize plugin", "pluginId", p.ID, "err", err)

@ -1443,7 +1443,7 @@ func newLoader(t *testing.T, cfg *config.Cfg, cbs ...func(loader *Loader)) *Load
l := New(cfg, &fakes.FakeLicensingService{}, signature.NewUnsignedAuthorizer(cfg), fakes.NewFakePluginRegistry(),
fakes.NewFakeBackendProcessProvider(), fakes.NewFakeProcessManager(), fakes.NewFakeRoleRegistry(),
assetpath.ProvideService(pluginscdn.ProvideService(cfg)), finder.NewLocalFinder(cfg),
signature.ProvideService(cfg, statickey.New()), angularInspector)
signature.ProvideService(cfg, statickey.New()), angularInspector, &fakes.FakeOauthService{})
for _, cb := range cbs {
cb(l)

@ -123,7 +123,7 @@ func TestIntegrationPluginManager(t *testing.T) {
l := loader.ProvideService(pCfg, lic, signature.NewUnsignedAuthorizer(pCfg),
reg, provider.ProvideService(coreRegistry), finder.NewLocalFinder(pCfg), fakes.NewFakeRoleRegistry(),
assetpath.ProvideService(pluginscdn.ProvideService(pCfg)), signature.ProvideService(pCfg, statickey.New()),
angularInspector)
angularInspector, &fakes.FakeOauthService{})
srcs := sources.ProvideService(cfg, pCfg)
ps, err := store.ProvideService(reg, srcs, l)
require.NoError(t, err)

@ -0,0 +1,37 @@
package oauth
import (
"context"
"github.com/grafana/grafana/pkg/services/accesscontrol"
)
// 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"`
PrivateKey string `json:"privateKey"`
}
type ExternalServiceRegistry interface {
RegisterExternalService(ctx context.Context, name string, svc *ExternalServiceRegistration) (*ExternalService, error)
}

@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
"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/services/org"
"github.com/grafana/grafana/pkg/util"
)
@ -53,6 +54,8 @@ type Plugin struct {
AngularDetected bool
ExternalService *oauth.ExternalService
Renderer pluginextensionv2.RendererPlugin
SecretsManager secretsmanagerplugin.SecretsManagerPlugin
client backendplugin.Plugin
@ -150,6 +153,9 @@ type JSONData struct {
// Backend (Datasource + Renderer + SecretsManager)
Executable string `json:"executable,omitempty"`
// Oauth App Service Registration
ExternalServiceRegistration *oauth.ExternalServiceRegistration `json:"externalServiceRegistration,omitempty"`
}
func ReadPluginJSON(reader io.Reader) (JSONData, error) {

@ -33,7 +33,6 @@ import (
"github.com/grafana/grafana/pkg/services/oauthserver/store"
"github.com/grafana/grafana/pkg/services/oauthserver/utils"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/signingkeys"
"github.com/grafana/grafana/pkg/services/team"
@ -62,7 +61,7 @@ type OAuth2ServiceImpl struct {
publicKey interface{}
}
func ProvideService(router routing.RouteRegister, db db.DB, cfg *setting.Cfg, skv kvstore.SecretsKVStore,
func ProvideService(router routing.RouteRegister, db db.DB, cfg *setting.Cfg,
svcAccSvc serviceaccounts.Service, accessControl ac.AccessControl, acSvc ac.Service, userSvc user.Service,
teamSvc team.Service, keySvc signingkeys.Service, fmgmt *featuremgmt.FeatureManager) (*OAuth2ServiceImpl, error) {
if !fmgmt.IsEnabled(featuremgmt.FlagExternalServiceAuth) {

@ -39,6 +39,7 @@ func ProvideConfig(settingProvider setting.Provider, grafanaCfg *setting.Cfg, fe
grafanaCfg.BuildVersion,
grafanaCfg.PluginLogBackendRequests,
grafanaCfg.PluginsCDNURLTemplate,
grafanaCfg.AppURL,
tracingCfg,
featuremgmt.ProvideToggles(features),
grafanaCfg.AngularSupportEnabled,

@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/plugins/manager/signature"
"github.com/grafana/grafana/pkg/plugins/manager/sources"
"github.com/grafana/grafana/pkg/plugins/manager/store"
"github.com/grafana/grafana/pkg/plugins/oauth"
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
"github.com/grafana/grafana/pkg/plugins/repo"
"github.com/grafana/grafana/pkg/services/caching"
@ -35,6 +36,7 @@ import (
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service"
"github.com/grafana/grafana/pkg/services/pluginsintegration/serviceregistration"
"github.com/grafana/grafana/pkg/setting"
)
@ -80,6 +82,8 @@ var WireSet = wire.NewSet(
wire.Bind(new(plugins.KeyRetriever), new(*keyretriever.Service)),
keyretriever.ProvideService,
dynamic.ProvideService,
serviceregistration.ProvideService,
wire.Bind(new(oauth.ExternalServiceRegistry), new(*serviceregistration.Service)),
)
// WireExtensionSet provides a wire.ProviderSet of plugin providers that can be

@ -0,0 +1,62 @@
package serviceregistration
import (
"context"
"github.com/grafana/grafana/pkg/plugins/oauth"
"github.com/grafana/grafana/pkg/services/oauthserver"
)
type Service struct {
os oauthserver.OAuth2Server
}
func ProvideService(os oauthserver.OAuth2Server) *Service {
s := &Service{
os: os,
}
return s
}
// 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) {
impersonation := oauthserver.ImpersonationCfg{}
if svc.Impersonation != nil {
impersonation.Permissions = svc.Impersonation.Permissions
if svc.Impersonation.Enabled != nil {
impersonation.Enabled = *svc.Impersonation.Enabled
} else {
impersonation.Enabled = true
}
if svc.Impersonation.Groups != nil {
impersonation.Groups = *svc.Impersonation.Groups
} else {
impersonation.Groups = true
}
}
self := oauthserver.SelfCfg{}
if svc.Self != nil {
self.Permissions = svc.Self.Permissions
if svc.Self.Enabled != nil {
self.Enabled = *svc.Self.Enabled
} else {
self.Enabled = true
}
}
extSvc, err := s.os.SaveExternalService(ctx, &oauthserver.ExternalServiceRegistration{
Name: svcName,
Impersonation: impersonation,
Self: self,
Key: &oauthserver.KeyOption{Generate: true},
})
if err != nil {
return nil, err
}
return &oauth.ExternalService{
ClientID: extSvc.ID,
ClientSecret: extSvc.Secret,
PrivateKey: extSvc.KeyResult.PrivatePem,
}, nil
}
Loading…
Cancel
Save