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>
This commit is contained in:
Arve Knudsen 2020-10-05 13:28:18 +02:00 committed by GitHub
parent 65d0468806
commit 531c579341
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 182 additions and 88 deletions

View File

@ -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
} }

View File

@ -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")
} }

View File

@ -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
} }
// Everything OK // Everything OK
log.Debug("Plugin signature valid", "id", plugin.Id)
return PluginSignatureValid return PluginSignatureValid
} }

View File

@ -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 {
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 {
} }
} }
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
} }

View File

@ -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
} }

View File

@ -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 {
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 {
} }
} }
// 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 {
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 {
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 {
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 {
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
return nil return nil
} }
if f.Name() == "plugin.json" { if f.Name() != "plugin.json" {
err := scanner.loadPlugin(currentPath) return nil
if err != nil {
scanner.log.Error("Failed to load plugin", "error", err, "pluginPath", filepath.Dir(currentPath))
scanner.errors = append(scanner.errors, err)
}
} }
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 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,79 +343,89 @@ 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)
// For the time being, we choose to only require back-end plugins to be signed s.plugins[currentDir] = &pluginCommon
// 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)
}
}
}
}
pluginGoType, exists := PluginTypes[pluginCommon.Type] return nil
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)
} }
func (scanner *PluginScanner) IsBackendOnlyPlugin(pluginType string) bool { func (scanner *PluginScanner) IsBackendOnlyPlugin(pluginType string) bool {
return pluginType == "renderer" || pluginType == "transform" 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) { func GetPluginMarkdown(pluginId string, name string) ([]byte, error) {
plug, exists := Plugins[pluginId] plug, exists := Plugins[pluginId]
if !exists { if !exists {

View File

@ -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
} }

View File

@ -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
} }