Plugins: Require signing of external back-end plugins (#24075)

* PluginManager: Require signing of external plugins

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
This commit is contained in:
Arve Knudsen
2020-05-04 10:57:55 +02:00
committed by GitHub
parent 827f99f0cb
commit 96ffcaa134
12 changed files with 287 additions and 66 deletions

View File

@ -13,6 +13,7 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/infra/fs"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
@ -43,12 +44,15 @@ type PluginScanner struct {
errors []error
backendPluginManager backendplugin.Manager
cfg *setting.Cfg
requireSigned bool
log log.Logger
}
type PluginManager struct {
BackendPluginManager backendplugin.Manager `inject:""`
Cfg *setting.Cfg `inject:""`
log log.Logger
scanningErrors []error
}
func init() {
@ -73,33 +77,40 @@ func (pm *PluginManager) Init() error {
}
pm.log.Info("Starting plugin search")
plugDir := path.Join(setting.StaticRootPath, "app/plugins")
if err := pm.scan(plugDir); err != nil {
return errutil.Wrapf(err, "Failed to scan main plugin directory '%s'", plugDir)
pm.log.Debug("Scanning core plugin directory", "dir", plugDir)
if err := pm.scan(plugDir, false); err != nil {
return errutil.Wrapf(err, "failed to scan core plugin directory '%s'", plugDir)
}
pm.log.Info("Checking Bundled Plugins")
plugDir = path.Join(setting.HomePath, "plugins-bundled")
if _, err := os.Stat(plugDir); !os.IsNotExist(err) {
if err := pm.scan(plugDir); err != nil {
return errutil.Wrapf(err, "failed to scan bundled plugin directory '%s'", plugDir)
plugDir = pm.Cfg.BundledPluginsPath
pm.log.Debug("Scanning bundled plugins directory", "dir", plugDir)
exists, err := fs.Exists(plugDir)
if err != nil {
return err
}
if exists {
if err := pm.scan(plugDir, false); err != nil {
return errutil.Wrapf(err, "failed to scan bundled plugins directory '%s'", plugDir)
}
}
// check if plugins dir exists
if _, err := os.Stat(setting.PluginsPath); os.IsNotExist(err) {
exists, err = fs.Exists(setting.PluginsPath)
if err != nil {
return err
}
if !exists {
if err = os.MkdirAll(setting.PluginsPath, os.ModePerm); err != nil {
plog.Error("Failed to create plugin dir", "dir", setting.PluginsPath, "error", err)
pm.log.Error("failed to create external plugins directory", "dir", setting.PluginsPath, "error", err)
} else {
plog.Info("Plugin dir created", "dir", setting.PluginsPath)
if err := pm.scan(setting.PluginsPath); err != nil {
return errutil.Wrapf(err, "Failed to scan configured plugin directory '%s'",
setting.PluginsPath)
}
pm.log.Info("External plugins directory created", "directory", setting.PluginsPath)
}
} else {
if err := pm.scan(setting.PluginsPath); err != nil {
return errutil.Wrapf(err, "Failed to scan configured plugin directory '%s'",
pm.log.Debug("Scanning external plugins directory", "dir", setting.PluginsPath)
if err := pm.scan(setting.PluginsPath, true); err != nil {
return errutil.Wrapf(err, "failed to scan external plugins directory '%s'",
setting.PluginsPath)
}
}
@ -163,8 +174,8 @@ func (pm *PluginManager) checkPluginPaths() error {
continue
}
if err := pm.scan(path); err != nil {
return errutil.Wrapf(err, "Failed to scan directory configured for plugin '%s': '%s'", pluginID, path)
if err := pm.scan(path, false); err != nil {
return errutil.Wrapf(err, "failed to scan directory configured for plugin '%s': '%s'", pluginID, path)
}
}
@ -172,11 +183,13 @@ func (pm *PluginManager) checkPluginPaths() error {
}
// scan a directory for plugins.
func (pm *PluginManager) scan(pluginDir string) error {
func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
scanner := &PluginScanner{
pluginPath: pluginDir,
backendPluginManager: pm.BackendPluginManager,
cfg: pm.Cfg,
requireSigned: requireSigned,
log: pm.log,
}
if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil {
@ -196,6 +209,7 @@ func (pm *PluginManager) scan(pluginDir string) error {
if len(scanner.errors) > 0 {
pm.log.Warn("Some plugins failed to load", "errors", scanner.errors)
pm.scanningErrors = scanner.errors
}
return nil
@ -229,7 +243,7 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro
if f.Name() == "plugin.json" {
err := scanner.loadPluginJson(currentPath)
if err != nil {
log.Error(3, "Plugins: Failed to load plugin json file: %v, err: %v", currentPath, err)
scanner.log.Error("Failed to load plugin", "error", err, "pluginPath", filepath.Dir(currentPath))
scanner.errors = append(scanner.errors, err)
}
}
@ -252,21 +266,51 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
}
if pluginCommon.Id == "" || pluginCommon.Type == "" {
return errors.New("Did not find type and id property in plugin.json")
return errors.New("did not find type or id properties in plugin.json")
}
pluginCommon.PluginDir = filepath.Dir(pluginJsonFilePath)
// For the time being, we choose to only require back-end plugins to be signed
if pluginCommon.Backend && scanner.requireSigned {
scanner.log.Debug("Plugin signature required, validating", "pluginID", pluginCommon.Id,
"pluginDir", pluginCommon.PluginDir)
allowUnsigned := false
for _, plug := range scanner.cfg.PluginsAllowUnsigned {
if plug == pluginCommon.Id {
allowUnsigned = true
break
}
}
if sig := GetPluginSignatureState(&pluginCommon); sig != PluginSignatureValid && !allowUnsigned {
switch sig {
case PluginSignatureUnsigned:
return fmt.Errorf("plugin %q is unsigned", pluginCommon.Id)
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)
}
}
}
var loader PluginLoader
pluginGoType, exists := PluginTypes[pluginCommon.Type]
if !exists {
return errors.New("Unknown plugin type " + pluginCommon.Type)
return fmt.Errorf("unknown plugin type %q", pluginCommon.Type)
}
loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader)
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")
if _, err := os.Stat(module); os.IsNotExist(err) {
plog.Warn("Plugin missing 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)
@ -276,6 +320,7 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
if _, err := reader.Seek(0, 0); err != nil {
return err
}
return loader.Load(jsonParser, currentDir, scanner.backendPluginManager)
}
@ -290,11 +335,19 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) {
}
path := filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToUpper(name)))
if _, err := os.Stat(path); os.IsNotExist(err) {
exists, err := fs.Exists(path)
if err != nil {
return nil, err
}
if !exists {
path = filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToLower(name)))
}
if _, err := os.Stat(path); os.IsNotExist(err) {
exists, err = fs.Exists(path)
if err != nil {
return nil, err
}
if !exists {
return make([]byte, 0), nil
}