Commit 531c5793 by Arve Knudsen Committed by GitHub

Plugins: Let descendant plugins inherit their root's signature (#27970)

* plugins: Let descendant plugins inherit their root's signature

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
parent 65d04688
...@@ -58,12 +58,12 @@ type JwtTokenAuth struct { ...@@ -58,12 +58,12 @@ type JwtTokenAuth struct {
Params map[string]string `json:"params"` 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 { if err := decoder.Decode(app); err != nil {
return err return err
} }
if err := app.registerPlugin(pluginDir); err != nil { if err := app.registerPlugin(base); err != nil {
return err return err
} }
......
...@@ -34,12 +34,12 @@ type DataSourcePlugin struct { ...@@ -34,12 +34,12 @@ type DataSourcePlugin struct {
SDK bool `json:"sdk,omitempty"` 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 { if err := decoder.Decode(p); err != nil {
return errutil.Wrapf(err, "Failed to decode datasource plugin") 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") return errutil.Wrapf(err, "Failed to register plugin")
} }
......
...@@ -84,16 +84,18 @@ func readPluginManifest(body []byte) (*pluginManifest, error) { ...@@ -84,16 +84,18 @@ func readPluginManifest(body []byte) (*pluginManifest, error) {
// getPluginSignatureState returns the signature state for a plugin. // getPluginSignatureState returns the signature state for a plugin.
func getPluginSignatureState(log log.Logger, plugin *PluginBase) PluginSignature { 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") manifestPath := filepath.Join(plugin.PluginDir, "MANIFEST.txt")
byteValue, err := ioutil.ReadFile(manifestPath) byteValue, err := ioutil.ReadFile(manifestPath)
if err != nil || len(byteValue) < 10 { if err != nil || len(byteValue) < 10 {
log.Debug("Plugin is unsigned", "id", plugin.Id)
return PluginSignatureUnsigned return PluginSignatureUnsigned
} }
manifest, err := readPluginManifest(byteValue) manifest, err := readPluginManifest(byteValue)
if err != nil { if err != nil {
log.Debug("Plugin signature invalid", "id", plugin.Id)
return PluginSignatureInvalid return PluginSignatureInvalid
} }
...@@ -126,5 +128,6 @@ func getPluginSignatureState(log log.Logger, plugin *PluginBase) PluginSignature ...@@ -126,5 +128,6 @@ func getPluginSignatureState(log log.Logger, plugin *PluginBase) PluginSignature
} }
// Everything OK // Everything OK
log.Debug("Plugin signature valid", "id", plugin.Id)
return PluginSignatureValid return PluginSignatureValid
} }
...@@ -42,10 +42,13 @@ func (e PluginNotFoundError) Error() string { ...@@ -42,10 +42,13 @@ func (e PluginNotFoundError) Error() string {
return fmt.Sprintf("Plugin with id %s not found", e.PluginId) return fmt.Sprintf("Plugin with id %s not found", e.PluginId)
} }
// PluginLoader can load a plugin.
type PluginLoader interface { 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 PluginBase struct {
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
...@@ -69,14 +72,16 @@ type PluginBase struct { ...@@ -69,14 +72,16 @@ type PluginBase struct {
GrafanaNetVersion string `json:"-"` GrafanaNetVersion string `json:"-"`
GrafanaNetHasUpdate bool `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 { if _, exists := Plugins[pb.Id]; exists {
return fmt.Errorf("Plugin with ID %q already exists", pb.Id) 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) plog.Info("Registering plugin", "name", pb.Name)
} }
...@@ -94,7 +99,10 @@ func (pb *PluginBase) registerPlugin(pluginDir string) error { ...@@ -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 Plugins[pb.Id] = pb
return nil return nil
} }
......
...@@ -11,12 +11,12 @@ type PanelPlugin struct { ...@@ -11,12 +11,12 @@ type PanelPlugin struct {
SkipDataQuery bool `json:"skipDataQuery"` 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 { if err := decoder.Decode(p); err != nil {
return err return err
} }
if err := p.registerPlugin(pluginDir); err != nil { if err := p.registerPlugin(base); err != nil {
return err return err
} }
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime"
"strings" "strings"
"time" "time"
...@@ -45,6 +46,7 @@ type PluginScanner struct { ...@@ -45,6 +46,7 @@ type PluginScanner struct {
cfg *setting.Cfg cfg *setting.Cfg
requireSigned bool requireSigned bool
log log.Logger log log.Logger
plugins map[string]*PluginBase
} }
type PluginManager struct { type PluginManager struct {
...@@ -114,8 +116,7 @@ func (pm *PluginManager) Init() error { ...@@ -114,8 +116,7 @@ func (pm *PluginManager) Init() error {
} }
} }
// check plugin paths defined in config if err := pm.scanPluginPaths(); err != nil {
if err := pm.checkPluginPaths(); err != nil {
return err return err
} }
...@@ -139,7 +140,6 @@ func (pm *PluginManager) Init() error { ...@@ -139,7 +140,6 @@ func (pm *PluginManager) Init() error {
if p.IsCorePlugin { if p.IsCorePlugin {
p.Signature = PluginSignatureInternal p.Signature = PluginSignatureInternal
} else { } else {
p.Signature = getPluginSignatureState(pm.log, p)
metrics.SetPluginBuildInformation(p.Id, p.Type, p.Info.Version) metrics.SetPluginBuildInformation(p.Id, p.Type, p.Info.Version)
} }
} }
...@@ -166,7 +166,8 @@ func (pm *PluginManager) Run(ctx context.Context) error { ...@@ -166,7 +166,8 @@ func (pm *PluginManager) Run(ctx context.Context) error {
return ctx.Err() 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 { for pluginID, settings := range pm.Cfg.PluginSettings {
path, exists := settings["path"] path, exists := settings["path"]
if !exists || path == "" { if !exists || path == "" {
...@@ -189,8 +190,10 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error { ...@@ -189,8 +190,10 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
cfg: pm.Cfg, cfg: pm.Cfg,
requireSigned: requireSigned, requireSigned: requireSigned,
log: pm.log, 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 err := util.Walk(pluginDir, true, true, scanner.walker); err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
pm.log.Debug("Couldn't scan directory since it doesn't exist", "pluginDir", pluginDir) 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 { ...@@ -206,6 +209,74 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
return err 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 { if len(scanner.errors) > 0 {
pm.log.Warn("Some plugins failed to load", "errors", scanner.errors) pm.log.Warn("Some plugins failed to load", "errors", scanner.errors)
pm.scanningErrors = scanner.errors pm.scanningErrors = scanner.errors
...@@ -239,23 +310,25 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro ...@@ -239,23 +310,25 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro
return nil return nil
} }
if f.Name() == "plugin.json" { if f.Name() != "plugin.json" {
err := scanner.loadPlugin(currentPath) return nil
if err != nil { }
if err := scanner.loadPlugin(currentPath); err != nil {
scanner.log.Error("Failed to load plugin", "error", err, "pluginPath", filepath.Dir(currentPath)) scanner.log.Error("Failed to load plugin", "error", err, "pluginPath", filepath.Dir(currentPath))
scanner.errors = append(scanner.errors, err) scanner.errors = append(scanner.errors, err)
} }
}
return nil return nil
} }
func (scanner *PluginScanner) loadPlugin(pluginJsonFilePath string) error { func (s *PluginScanner) loadPlugin(pluginJSONFilePath string) error {
currentDir := filepath.Dir(pluginJsonFilePath) s.log.Debug("Loading plugin", "path", pluginJSONFilePath)
reader, err := os.Open(pluginJsonFilePath) currentDir := filepath.Dir(pluginJSONFilePath)
reader, err := os.Open(pluginJSONFilePath)
if err != nil { if err != nil {
return err return err
} }
defer reader.Close() defer reader.Close()
jsonParser := json.NewDecoder(reader) jsonParser := json.NewDecoder(reader)
...@@ -270,77 +343,87 @@ func (scanner *PluginScanner) loadPlugin(pluginJsonFilePath string) error { ...@@ -270,77 +343,87 @@ func (scanner *PluginScanner) loadPlugin(pluginJsonFilePath string) error {
// The expressions feature toggle corresponds to transform plug-ins. // The expressions feature toggle corresponds to transform plug-ins.
if pluginCommon.Type == "transform" { if pluginCommon.Type == "transform" {
isEnabled := scanner.cfg.IsExpressionsEnabled() isEnabled := s.cfg.IsExpressionsEnabled()
if !isEnabled { 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) "pluginID", pluginCommon.Id)
return nil return nil
} }
} }
pluginCommon.PluginDir = filepath.Dir(pluginJsonFilePath) pluginCommon.PluginDir = filepath.Dir(pluginJSONFilePath)
pluginCommon.Signature = getPluginSignatureState(s.log, &pluginCommon)
s.plugins[currentDir] = &pluginCommon
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 // 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 // NOTE: the state is calculated again when setting metadata on the object
if pluginCommon.Backend && scanner.requireSigned { if !plugin.Backend || !s.requireSigned {
sig := getPluginSignatureState(scanner.log, &pluginCommon) return true
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 plugin.Signature == PluginSignatureValid {
s.log.Debug("Plugin has valid signature", "id", plugin.Id)
return true
} }
if setting.Env != setting.Dev && !allowUnsigned {
return fmt.Errorf("plugin %q is unsigned", pluginCommon.Id) if plugin.Root != nil {
} // If a descendant plugin with invalid signature, set signature to that of root
scanner.log.Warn("Running an unsigned backend plugin", "pluginID", pluginCommon.Id, "pluginDir", pluginCommon.PluginDir) 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 { } else {
switch sig { s.log.Debug("Setting descendant plugin's signature to that of root", "plugin", plugin.Id,
case PluginSignatureInvalid: "root", plugin.Root.Id, "signature", plugin.Signature, "rootSignature", plugin.Root.Signature)
return fmt.Errorf("plugin %q has an invalid signature", pluginCommon.Id) plugin.Signature = plugin.Root.Signature
case PluginSignatureModified: if plugin.Signature == PluginSignatureValid {
return fmt.Errorf("plugin %q's signature has been modified", pluginCommon.Id) s.log.Debug("Plugin has valid signature (inherited from root)", "id", plugin.Id)
default: return true
return fmt.Errorf("unrecognized plugin signature state %v", sig)
}
}
} }
} }
} else {
pluginGoType, exists := PluginTypes[pluginCommon.Type] s.log.Debug("Non-valid plugin Signature", "pluginID", plugin.Id, "pluginDir", plugin.PluginDir,
if !exists { "state", plugin.Signature)
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 switch plugin.Signature {
if !strings.HasPrefix(pluginJsonFilePath, setting.StaticRootPath) && !scanner.IsBackendOnlyPlugin(pluginCommon.Type) { case PluginSignatureUnsigned:
module := filepath.Join(filepath.Dir(pluginJsonFilePath), "module.js") allowUnsigned := false
exists, err := fs.Exists(module) for _, plug := range s.cfg.PluginsAllowUnsigned {
if err != nil { if plug == plugin.Id {
return err allowUnsigned = true
break
} }
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 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
} }
if _, err := reader.Seek(0, 0); err != nil { s.log.Warn("Running an unsigned backend plugin", "pluginID", plugin.Id, "pluginDir",
return err 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))
} }
return loader.Load(jsonParser, currentDir, scanner.backendPluginManager)
}
func (scanner *PluginScanner) IsBackendOnlyPlugin(pluginType string) bool {
return pluginType == "renderer" || pluginType == "transform"
} }
func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { func GetPluginMarkdown(pluginId string, name string) ([]byte, error) {
......
...@@ -22,12 +22,12 @@ type RendererPlugin struct { ...@@ -22,12 +22,12 @@ type RendererPlugin struct {
backendPluginManager backendplugin.Manager 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 { if err := decoder.Decode(r); err != nil {
return err return err
} }
if err := r.registerPlugin(pluginDir); err != nil { if err := r.registerPlugin(base); err != nil {
return err return err
} }
......
...@@ -28,12 +28,12 @@ type TransformPlugin struct { ...@@ -28,12 +28,12 @@ type TransformPlugin struct {
*TransformWrapper *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 { if err := decoder.Decode(p); err != nil {
return err return err
} }
if err := p.registerPlugin(pluginDir); err != nil { if err := p.registerPlugin(base); err != nil {
return err return err
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment