diff --git a/pkg/plugins/app_plugin.go b/pkg/plugins/app_plugin.go index 66265fc3abc..a6cbc3e279e 100644 --- a/pkg/plugins/app_plugin.go +++ b/pkg/plugins/app_plugin.go @@ -58,12 +58,12 @@ type JwtTokenAuth struct { Params map[string]string `json:"params"` } -func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string, backendPluginManager backendplugin.Manager) error { +func (app *AppPlugin) Load(decoder *json.Decoder, base *PluginBase, backendPluginManager backendplugin.Manager) error { if err := decoder.Decode(app); err != nil { return err } - if err := app.registerPlugin(pluginDir); err != nil { + if err := app.registerPlugin(base); err != nil { return err } diff --git a/pkg/plugins/datasource_plugin.go b/pkg/plugins/datasource_plugin.go index 39a30c9cbf6..fbacf92f9f2 100644 --- a/pkg/plugins/datasource_plugin.go +++ b/pkg/plugins/datasource_plugin.go @@ -34,12 +34,12 @@ type DataSourcePlugin struct { SDK bool `json:"sdk,omitempty"` } -func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string, backendPluginManager backendplugin.Manager) error { +func (p *DataSourcePlugin) Load(decoder *json.Decoder, base *PluginBase, backendPluginManager backendplugin.Manager) error { if err := decoder.Decode(p); err != nil { return errutil.Wrapf(err, "Failed to decode datasource plugin") } - if err := p.registerPlugin(pluginDir); err != nil { + if err := p.registerPlugin(base); err != nil { return errutil.Wrapf(err, "Failed to register plugin") } diff --git a/pkg/plugins/manifest.go b/pkg/plugins/manifest.go index ed137ddcce5..9b6e2cb00c4 100644 --- a/pkg/plugins/manifest.go +++ b/pkg/plugins/manifest.go @@ -84,16 +84,18 @@ func readPluginManifest(body []byte) (*pluginManifest, error) { // getPluginSignatureState returns the signature state for a plugin. func getPluginSignatureState(log log.Logger, plugin *PluginBase) PluginSignature { - log.Debug("Getting signature state of plugin", "plugin", plugin.Id) + log.Debug("Getting signature state of plugin", "plugin", plugin.Id, "isBackend", plugin.Backend) manifestPath := filepath.Join(plugin.PluginDir, "MANIFEST.txt") byteValue, err := ioutil.ReadFile(manifestPath) if err != nil || len(byteValue) < 10 { + log.Debug("Plugin is unsigned", "id", plugin.Id) return PluginSignatureUnsigned } manifest, err := readPluginManifest(byteValue) if err != nil { + log.Debug("Plugin signature invalid", "id", plugin.Id) return PluginSignatureInvalid } @@ -126,5 +128,6 @@ func getPluginSignatureState(log log.Logger, plugin *PluginBase) PluginSignature } // Everything OK + log.Debug("Plugin signature valid", "id", plugin.Id) return PluginSignatureValid } diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index d1d47bb8a32..ce8057bd0a8 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -42,10 +42,13 @@ func (e PluginNotFoundError) Error() string { return fmt.Sprintf("Plugin with id %s not found", e.PluginId) } +// PluginLoader can load a plugin. type PluginLoader interface { - Load(decoder *json.Decoder, pluginDir string, backendPluginManager backendplugin.Manager) error + // Load loads a plugin and registers it with the manager. + Load(decoder *json.Decoder, base *PluginBase, backendPluginManager backendplugin.Manager) error } +// PluginBase is the base plugin type. type PluginBase struct { Type string `json:"type"` Name string `json:"name"` @@ -69,14 +72,16 @@ type PluginBase struct { GrafanaNetVersion string `json:"-"` GrafanaNetHasUpdate bool `json:"-"` + + Root *PluginBase } -func (pb *PluginBase) registerPlugin(pluginDir string) error { +func (pb *PluginBase) registerPlugin(base *PluginBase) error { if _, exists := Plugins[pb.Id]; exists { return fmt.Errorf("Plugin with ID %q already exists", pb.Id) } - if !strings.HasPrefix(pluginDir, setting.StaticRootPath) { + if !strings.HasPrefix(base.PluginDir, setting.StaticRootPath) { plog.Info("Registering plugin", "name", pb.Name) } @@ -94,7 +99,10 @@ func (pb *PluginBase) registerPlugin(pluginDir string) error { } } - pb.PluginDir = pluginDir + // Copy relevant fields from the base + pb.PluginDir = base.PluginDir + pb.Signature = base.Signature + Plugins[pb.Id] = pb return nil } diff --git a/pkg/plugins/panel_plugin.go b/pkg/plugins/panel_plugin.go index ecece8a78fc..4376dac18ee 100644 --- a/pkg/plugins/panel_plugin.go +++ b/pkg/plugins/panel_plugin.go @@ -11,12 +11,12 @@ type PanelPlugin struct { SkipDataQuery bool `json:"skipDataQuery"` } -func (p *PanelPlugin) Load(decoder *json.Decoder, pluginDir string, backendPluginManager backendplugin.Manager) error { +func (p *PanelPlugin) Load(decoder *json.Decoder, base *PluginBase, backendPluginManager backendplugin.Manager) error { if err := decoder.Decode(p); err != nil { return err } - if err := p.registerPlugin(pluginDir); err != nil { + if err := p.registerPlugin(base); err != nil { return err } diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 1ba4717b511..d43e91dda7d 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -10,6 +10,7 @@ import ( "path" "path/filepath" "reflect" + "runtime" "strings" "time" @@ -45,6 +46,7 @@ type PluginScanner struct { cfg *setting.Cfg requireSigned bool log log.Logger + plugins map[string]*PluginBase } type PluginManager struct { @@ -114,8 +116,7 @@ func (pm *PluginManager) Init() error { } } - // check plugin paths defined in config - if err := pm.checkPluginPaths(); err != nil { + if err := pm.scanPluginPaths(); err != nil { return err } @@ -139,7 +140,6 @@ func (pm *PluginManager) Init() error { if p.IsCorePlugin { p.Signature = PluginSignatureInternal } else { - p.Signature = getPluginSignatureState(pm.log, p) metrics.SetPluginBuildInformation(p.Id, p.Type, p.Info.Version) } } @@ -166,7 +166,8 @@ func (pm *PluginManager) Run(ctx context.Context) error { return ctx.Err() } -func (pm *PluginManager) checkPluginPaths() error { +// scanPluginPaths scans configured plugin paths. +func (pm *PluginManager) scanPluginPaths() error { for pluginID, settings := range pm.Cfg.PluginSettings { path, exists := settings["path"] if !exists || path == "" { @@ -189,8 +190,10 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error { cfg: pm.Cfg, requireSigned: requireSigned, log: pm.log, + plugins: map[string]*PluginBase{}, } + // 1st pass: Scan plugins, also mapping plugins to their respective directories if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil { if errors.Is(err, os.ErrNotExist) { pm.log.Debug("Couldn't scan directory since it doesn't exist", "pluginDir", pluginDir) @@ -206,6 +209,74 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error { return err } + pm.log.Debug("Initial plugin loading done") + + // 2nd pass: Validate and register plugins + for dpath, plugin := range scanner.plugins { + // Try to find any root plugin + ancestors := strings.Split(dpath, string(filepath.Separator)) + ancestors = ancestors[0 : len(ancestors)-1] + aPath := "" + if runtime.GOOS != "windows" && filepath.IsAbs(dpath) { + aPath = "/" + } + for _, a := range ancestors { + aPath = filepath.Join(aPath, a) + if root, ok := scanner.plugins[aPath]; ok { + plugin.Root = root + break + } + } + + pm.log.Debug("Found plugin", "id", plugin.Id, "signature", plugin.Signature, "hasRoot", plugin.Root != nil) + if !scanner.validateSignature(plugin) { + pm.log.Debug("Not adding plugin since it lacks a valid signature", "id", plugin.Id, + "signature", plugin.Signature) + continue + } + + pm.log.Debug("Attempting to add plugin", "id", plugin.Id) + + pluginGoType, exists := PluginTypes[plugin.Type] + if !exists { + return fmt.Errorf("unknown plugin type %q", plugin.Type) + } + + loader := reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader) + + jsonFPath := filepath.Join(plugin.PluginDir, "plugin.json") + + // External plugins need a module.js file for SystemJS to load + if !strings.HasPrefix(jsonFPath, setting.StaticRootPath) && !scanner.IsBackendOnlyPlugin(plugin.Type) { + module := filepath.Join(plugin.PluginDir, "module.js") + exists, err := fs.Exists(module) + if err != nil { + return err + } + if !exists { + scanner.log.Warn("Plugin missing module.js", + "name", plugin.Name, + "warning", "Missing module.js, If you loaded this plugin from git, make sure to compile it.", + "path", module) + } + } + + reader, err := os.Open(jsonFPath) + if err != nil { + return err + } + defer reader.Close() + + jsonParser := json.NewDecoder(reader) + + // Load the full plugin, and add it to manager + if err := loader.Load(jsonParser, plugin, scanner.backendPluginManager); err != nil { + return err + } + + pm.log.Debug("Successfully added plugin", "id", plugin.Id) + } + if len(scanner.errors) > 0 { pm.log.Warn("Some plugins failed to load", "errors", scanner.errors) pm.scanningErrors = scanner.errors @@ -239,23 +310,25 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro return nil } - if f.Name() == "plugin.json" { - err := scanner.loadPlugin(currentPath) - if err != nil { - scanner.log.Error("Failed to load plugin", "error", err, "pluginPath", filepath.Dir(currentPath)) - scanner.errors = append(scanner.errors, err) - } + if f.Name() != "plugin.json" { + return nil } + + if err := scanner.loadPlugin(currentPath); err != nil { + scanner.log.Error("Failed to load plugin", "error", err, "pluginPath", filepath.Dir(currentPath)) + scanner.errors = append(scanner.errors, err) + } + return nil } -func (scanner *PluginScanner) loadPlugin(pluginJsonFilePath string) error { - currentDir := filepath.Dir(pluginJsonFilePath) - reader, err := os.Open(pluginJsonFilePath) +func (s *PluginScanner) loadPlugin(pluginJSONFilePath string) error { + s.log.Debug("Loading plugin", "path", pluginJSONFilePath) + currentDir := filepath.Dir(pluginJSONFilePath) + reader, err := os.Open(pluginJSONFilePath) if err != nil { return err } - defer reader.Close() jsonParser := json.NewDecoder(reader) @@ -270,79 +343,89 @@ func (scanner *PluginScanner) loadPlugin(pluginJsonFilePath string) error { // The expressions feature toggle corresponds to transform plug-ins. if pluginCommon.Type == "transform" { - isEnabled := scanner.cfg.IsExpressionsEnabled() + isEnabled := s.cfg.IsExpressionsEnabled() if !isEnabled { - scanner.log.Debug("Transform plugin is disabled since the expressions feature toggle is not enabled", + s.log.Debug("Transform plugin is disabled since the expressions feature toggle is not enabled", "pluginID", pluginCommon.Id) return nil } } - pluginCommon.PluginDir = filepath.Dir(pluginJsonFilePath) + pluginCommon.PluginDir = filepath.Dir(pluginJSONFilePath) + pluginCommon.Signature = getPluginSignatureState(s.log, &pluginCommon) - // For the time being, we choose to only require back-end plugins to be signed - // NOTE: the state is calculated again when setting metadata on the object - if pluginCommon.Backend && scanner.requireSigned { - sig := getPluginSignatureState(scanner.log, &pluginCommon) - if sig != PluginSignatureValid { - scanner.log.Debug("Invalid Plugin Signature", "pluginID", pluginCommon.Id, "pluginDir", pluginCommon.PluginDir, "state", sig) - if sig == PluginSignatureUnsigned { - allowUnsigned := false - for _, plug := range scanner.cfg.PluginsAllowUnsigned { - if plug == pluginCommon.Id { - allowUnsigned = true - break - } - } - if setting.Env != setting.Dev && !allowUnsigned { - return fmt.Errorf("plugin %q is unsigned", pluginCommon.Id) - } - scanner.log.Warn("Running an unsigned backend plugin", "pluginID", pluginCommon.Id, "pluginDir", pluginCommon.PluginDir) - } else { - switch sig { - case PluginSignatureInvalid: - return fmt.Errorf("plugin %q has an invalid signature", pluginCommon.Id) - case PluginSignatureModified: - return fmt.Errorf("plugin %q's signature has been modified", pluginCommon.Id) - default: - return fmt.Errorf("unrecognized plugin signature state %v", sig) - } - } - } - } + s.plugins[currentDir] = &pluginCommon - pluginGoType, exists := PluginTypes[pluginCommon.Type] - if !exists { - return fmt.Errorf("unknown plugin type %q", pluginCommon.Type) - } - loader := reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader) - - // External plugins need a module.js file for SystemJS to load - if !strings.HasPrefix(pluginJsonFilePath, setting.StaticRootPath) && !scanner.IsBackendOnlyPlugin(pluginCommon.Type) { - module := filepath.Join(filepath.Dir(pluginJsonFilePath), "module.js") - exists, err := fs.Exists(module) - if err != nil { - return err - } - if !exists { - scanner.log.Warn("Plugin missing module.js", - "name", pluginCommon.Name, - "warning", "Missing module.js, If you loaded this plugin from git, make sure to compile it.", - "path", module) - } - } - - if _, err := reader.Seek(0, 0); err != nil { - return err - } - - return loader.Load(jsonParser, currentDir, scanner.backendPluginManager) + return nil } func (scanner *PluginScanner) IsBackendOnlyPlugin(pluginType string) bool { return pluginType == "renderer" || pluginType == "transform" } +// validateSignature validates a plugin's signature. +func (s *PluginScanner) validateSignature(plugin *PluginBase) bool { + // For the time being, we choose to only require back-end plugins to be signed + // NOTE: the state is calculated again when setting metadata on the object + if !plugin.Backend || !s.requireSigned { + return true + } + + if plugin.Signature == PluginSignatureValid { + s.log.Debug("Plugin has valid signature", "id", plugin.Id) + return true + } + + if plugin.Root != nil { + // If a descendant plugin with invalid signature, set signature to that of root + if plugin.IsCorePlugin || plugin.Signature == PluginSignatureInternal { + s.log.Debug("Not setting descendant plugin's signature to that of root since it's core or internal", + "plugin", plugin.Id, "signature", plugin.Signature, "isCore", plugin.IsCorePlugin) + } else { + s.log.Debug("Setting descendant plugin's signature to that of root", "plugin", plugin.Id, + "root", plugin.Root.Id, "signature", plugin.Signature, "rootSignature", plugin.Root.Signature) + plugin.Signature = plugin.Root.Signature + if plugin.Signature == PluginSignatureValid { + s.log.Debug("Plugin has valid signature (inherited from root)", "id", plugin.Id) + return true + } + } + } else { + s.log.Debug("Non-valid plugin Signature", "pluginID", plugin.Id, "pluginDir", plugin.PluginDir, + "state", plugin.Signature) + } + + switch plugin.Signature { + case PluginSignatureUnsigned: + allowUnsigned := false + for _, plug := range s.cfg.PluginsAllowUnsigned { + if plug == plugin.Id { + allowUnsigned = true + break + } + } + if setting.Env != setting.Dev && !allowUnsigned { + s.log.Debug("Plugin is unsigned", "id", plugin.Id) + s.errors = append(s.errors, fmt.Errorf("plugin %q is unsigned", plugin.Id)) + return false + } + + s.log.Warn("Running an unsigned backend plugin", "pluginID", plugin.Id, "pluginDir", + plugin.PluginDir) + return true + case PluginSignatureInvalid: + s.log.Debug("Plugin %q has an invalid signature", plugin.Id) + s.errors = append(s.errors, fmt.Errorf("plugin %q has an invalid signature", plugin.Id)) + return false + case PluginSignatureModified: + s.log.Debug("Plugin %q has a modified signature", plugin.Id) + s.errors = append(s.errors, fmt.Errorf("plugin %q's signature has been modified", plugin.Id)) + return false + default: + panic(fmt.Sprintf("Plugin %q has unrecognized plugin signature state %q", plugin.Id, plugin.Signature)) + } +} + func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { plug, exists := Plugins[pluginId] if !exists { diff --git a/pkg/plugins/renderer_plugin.go b/pkg/plugins/renderer_plugin.go index fcf7829ddde..a5d86f5db26 100644 --- a/pkg/plugins/renderer_plugin.go +++ b/pkg/plugins/renderer_plugin.go @@ -22,12 +22,12 @@ type RendererPlugin struct { backendPluginManager backendplugin.Manager } -func (r *RendererPlugin) Load(decoder *json.Decoder, pluginDir string, backendPluginManager backendplugin.Manager) error { +func (r *RendererPlugin) Load(decoder *json.Decoder, base *PluginBase, backendPluginManager backendplugin.Manager) error { if err := decoder.Decode(r); err != nil { return err } - if err := r.registerPlugin(pluginDir); err != nil { + if err := r.registerPlugin(base); err != nil { return err } diff --git a/pkg/plugins/transform_plugin.go b/pkg/plugins/transform_plugin.go index b62cc3e5258..a3a6477287b 100644 --- a/pkg/plugins/transform_plugin.go +++ b/pkg/plugins/transform_plugin.go @@ -28,12 +28,12 @@ type TransformPlugin struct { *TransformWrapper } -func (p *TransformPlugin) Load(decoder *json.Decoder, pluginDir string, backendPluginManager backendplugin.Manager) error { +func (p *TransformPlugin) Load(decoder *json.Decoder, base *PluginBase, backendPluginManager backendplugin.Manager) error { if err := decoder.Decode(p); err != nil { return err } - if err := p.registerPlugin(pluginDir); err != nil { + if err := p.registerPlugin(base); err != nil { return err }