mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
65d0468806
commit
531c579341
@ -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
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user