Plugins: Enforce signing for all plugins (#34364)

* enforce non-backend plugin signing

* fix tests

* add tests

* add signatures

* apply PR feedback

* update upgrading docs
This commit is contained in:
Will Browne
2021-05-19 15:42:50 +02:00
committed by GitHub
parent b987237c9b
commit c1ec13035d
9 changed files with 183 additions and 21 deletions

View File

@ -454,7 +454,11 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
}
if len(scanner.errors) > 0 {
pm.log.Warn("Some plugin scanning errors were found", "errors", scanner.errors)
var errStr []string
for _, err := range scanner.errors {
errStr = append(errStr, err.Error())
}
pm.log.Warn("Some plugin scanning errors were found", "errors", strings.Join(errStr, ", "))
pm.scanningErrors = scanner.errors
}
@ -623,9 +627,7 @@ func (s *PluginScanner) validateSignature(plugin *plugins.PluginBase) *plugins.P
"state", plugin.Signature)
}
// 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 {
if !s.requireSigned {
return nil
}
@ -633,28 +635,28 @@ func (s *PluginScanner) validateSignature(plugin *plugins.PluginBase) *plugins.P
case plugins.PluginSignatureUnsigned:
if allowed := s.allowUnsigned(plugin); !allowed {
s.log.Debug("Plugin is unsigned", "id", plugin.Id)
s.errors = append(s.errors, fmt.Errorf("plugin %q is unsigned", plugin.Id))
s.errors = append(s.errors, fmt.Errorf("plugin '%s' is unsigned", plugin.Id))
return &plugins.PluginError{
ErrorCode: signatureMissing,
}
}
s.log.Warn("Running an unsigned backend plugin", "pluginID", plugin.Id, "pluginDir",
s.log.Warn("Running an unsigned plugin", "pluginID", plugin.Id, "pluginDir",
plugin.PluginDir)
return nil
case plugins.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))
s.log.Debug("Plugin '%s' has an invalid signature", plugin.Id)
s.errors = append(s.errors, fmt.Errorf("plugin '%s' has an invalid signature", plugin.Id))
return &plugins.PluginError{
ErrorCode: signatureInvalid,
}
case plugins.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))
s.log.Debug("Plugin '%s' has a modified signature", plugin.Id)
s.errors = append(s.errors, fmt.Errorf("plugin '%s' has a modified signature", plugin.Id))
return &plugins.PluginError{
ErrorCode: signatureModified,
}
default:
panic(fmt.Sprintf("Plugin %q has unrecognized plugin signature state %q", plugin.Id, plugin.Signature))
panic(fmt.Sprintf("Plugin '%s' has an unrecognized plugin signature state '%s'", plugin.Id, plugin.Signature))
}
}

View File

@ -61,17 +61,71 @@ func TestPluginManager_Init(t *testing.T) {
assert.Equal(t, "public/plugins/test-app/img/screenshot2.png", pm.apps["test-app"].Info.Screenshots[1].Path)
})
t.Run("With external back-end plugin lacking signature", func(t *testing.T) {
t.Run("With external back-end plugin lacking signature (production)", func(t *testing.T) {
pm := createManager(t, func(pm *PluginManager) {
pm.Cfg.PluginsPath = "testdata/unsigned"
pm.Cfg.PluginsPath = "testdata/unsigned-datasource"
pm.Cfg.Env = setting.Prod
})
err := pm.Init()
require.NoError(t, err)
const pluginID = "test"
assert.Equal(t, []error{fmt.Errorf(`plugin '%s' is unsigned`, pluginID)}, pm.scanningErrors)
assert.Nil(t, pm.GetDataSource(pluginID))
assert.Nil(t, pm.GetPlugin(pluginID))
})
t.Run("With external back-end plugin lacking signature (development)", func(t *testing.T) {
pm := createManager(t, func(pm *PluginManager) {
pm.Cfg.PluginsPath = "testdata/unsigned-datasource"
pm.Cfg.Env = setting.Dev
})
err := pm.Init()
require.NoError(t, err)
const pluginID = "test"
assert.Empty(t, pm.scanningErrors)
assert.NotNil(t, pm.GetDataSource(pluginID))
plugin := pm.GetPlugin(pluginID)
assert.NotNil(t, plugin)
assert.Equal(t, plugins.PluginSignatureUnsigned, plugin.Signature)
})
t.Run("With external panel plugin lacking signature (production)", func(t *testing.T) {
pm := createManager(t, func(pm *PluginManager) {
pm.Cfg.PluginsPath = "testdata/unsigned-panel"
pm.Cfg.Env = setting.Prod
})
err := pm.Init()
require.NoError(t, err)
const pluginID = "test-panel"
assert.Equal(t, []error{fmt.Errorf(`plugin '%s' is unsigned`, pluginID)}, pm.scanningErrors)
assert.Nil(t, pm.panels[pluginID])
assert.Nil(t, pm.GetPlugin(pluginID))
})
t.Run("With external panel plugin lacking signature (development)", func(t *testing.T) {
pm := createManager(t, func(pm *PluginManager) {
pm.Cfg.PluginsPath = "testdata/unsigned-panel"
pm.Cfg.Env = setting.Dev
})
err := pm.Init()
require.NoError(t, err)
pluginID := "test-panel"
assert.Empty(t, pm.scanningErrors)
assert.NotNil(t, pm.panels[pluginID])
plugin := pm.GetPlugin(pluginID)
assert.NotNil(t, plugin)
assert.Equal(t, plugins.PluginSignatureUnsigned, plugin.Signature)
})
t.Run("With external unsigned back-end plugin and configuration disabling signature check of this plugin", func(t *testing.T) {
pm := createManager(t, func(pm *PluginManager) {
pm.Cfg.PluginsPath = "testdata/unsigned"
pm.Cfg.PluginsPath = "testdata/unsigned-datasource"
pm.Cfg.PluginsAllowUnsigned = []string{"test"}
})
err := pm.Init()
@ -87,7 +141,10 @@ func TestPluginManager_Init(t *testing.T) {
err := pm.Init()
require.NoError(t, err)
assert.Equal(t, []error{fmt.Errorf(`plugin "test" has an invalid signature`)}, pm.scanningErrors)
const pluginID = "test"
assert.Equal(t, []error{fmt.Errorf(`plugin '%s' has an invalid signature`, pluginID)}, pm.scanningErrors)
assert.Nil(t, pm.GetDataSource(pluginID))
assert.Nil(t, pm.GetPlugin(pluginID))
})
t.Run("With external back-end plugin lacking files listed in manifest", func(t *testing.T) {
@ -99,7 +156,7 @@ func TestPluginManager_Init(t *testing.T) {
err := pm.Init()
require.NoError(t, err)
assert.Equal(t, []error{fmt.Errorf(`plugin "test"'s signature has been modified`)}, pm.scanningErrors)
assert.Equal(t, []error{fmt.Errorf(`plugin 'test' has a modified signature`)}, pm.scanningErrors)
})
t.Run("Transform plugins should be ignored when expressions feature is off", func(t *testing.T) {
@ -221,7 +278,7 @@ func TestPluginManager_Init(t *testing.T) {
err := pm.Init()
require.NoError(t, err)
assert.Equal(t, []error{fmt.Errorf(`plugin "test" has an invalid signature`)}, pm.scanningErrors)
assert.Equal(t, []error{fmt.Errorf(`plugin 'test' has an invalid signature`)}, pm.scanningErrors)
assert.Nil(t, pm.plugins[("test")])
})
@ -263,7 +320,7 @@ func TestPluginManager_Init(t *testing.T) {
})
err := pm.Init()
require.NoError(t, err)
assert.Equal(t, []error{fmt.Errorf(`plugin "test"'s signature has been modified`)}, pm.scanningErrors)
assert.Equal(t, []error{fmt.Errorf(`plugin 'test' has a modified signature`)}, pm.scanningErrors)
assert.Nil(t, pm.plugins[("test")])
})
@ -279,7 +336,7 @@ func TestPluginManager_Init(t *testing.T) {
})
err := pm.Init()
require.NoError(t, err)
assert.Equal(t, []error{fmt.Errorf(`plugin "test"'s signature has been modified`)}, pm.scanningErrors)
assert.Equal(t, []error{fmt.Errorf(`plugin 'test' has a modified signature`)}, pm.scanningErrors)
assert.Nil(t, pm.plugins[("test")])
})
}

View File

@ -0,0 +1,28 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
{
"manifestVersion": "2.0.0",
"signatureType": "grafana",
"signedByOrg": "grafana",
"signedByOrgName": "Grafana Labs",
"plugin": "test-app",
"version": "1.0.0",
"time": 1621412405893,
"keyId": "7e4d0c6a708866e7",
"files": {
"plugin.json": "e2c9f711796252bdde63b19691b248aaabea361f521fff6de8ded8d95a333609",
"nested/plugin.json": "d4aee2052f5f9aaa3eecc90e5c5d9568efcd2d97595cd77fdcd1de0ada922638"
}
}
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.1
Comment: https://openpgpjs.org
wqIEARMKAAYFAmCkyjYACgkQfk0ManCIZufMrgIJAWouS5oNJixNsold48Jw
BuCFtAT7cP0rqxiyu/Z1c06IIVcmEJg/KngcUDhP8bEN4xAunP7KfZktmGp9
+8OqVbd/AgkBg9tWWgnMWln8XENca0ou1PTd/y24embsK3UNweqBAJPDL9el
nnmA5UWS7pFiHQTLp/daE08o2FGclRbgHcOtFBI=
=cSQ0
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,27 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
{
"manifestVersion": "2.0.0",
"signatureType": "grafana",
"signedByOrg": "grafana",
"signedByOrgName": "Grafana Labs",
"plugin": "test-app",
"version": "1.0.0",
"time": 1621411638406,
"keyId": "7e4d0c6a708866e7",
"files": {
"plugin.json": "d4aee2052f5f9aaa3eecc90e5c5d9568efcd2d97595cd77fdcd1de0ada922638"
}
}
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.1
Comment: https://openpgpjs.org
wqIEARMKAAYFAmCkxzYACgkQfk0ManCIZucKRAIJAUqsvNDA1GaHdMSQ4h+3
lOXkvN7xMbzOpRvC3Wu7agfsNgmaQtctL/502jUpH94J6aItg7Wmx+mtvVGj
5i456DitAgkBWDQU7KMAYRhAPNToRZhAdIBr0UXEOS6P9sM+xDuQ/gjZ2J+/
Iy8j85zhl//0hC/RLspYVxgbZFIHmEto/y+3bbs=
=1s40
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,30 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
{
"manifestVersion": "2.0.0",
"signatureType": "grafana",
"signedByOrg": "grafana",
"signedByOrgName": "Grafana Labs",
"plugin": "test-app",
"version": "1.0.0",
"time": 1621356785895,
"keyId": "7e4d0c6a708866e7",
"files": {
"plugin.json": "c59a51bf6d7ecd7a99608ccb99353390c8b973672a938a0247164324005c0caf",
"dashboards/connections.json": "bea86da4be970b98dc4681802ab55cdef3441dc3eb3c654cb207948d17b25303",
"dashboards/memory.json": "7c042464941084caa91d0a9a2f188b05315a9796308a652ccdee31ca4fbcbfee",
"dashboards/connections_result.json": "124d85c9c2e40214b83273f764574937a79909cfac3f925276fbb72543c224dc"
}
}
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.1
Comment: https://openpgpjs.org
wqAEARMKAAYFAmCj8PIACgkQfk0ManCIZueQJQII8f8za5CkX59o9A0uG/fH
imSSFN0YxGfDXSOC1VjNwVQQWSSQ7S08cOX16cQ6huNpxUKCNUGSQnRnQIGg
CcMMRU0CB3kZB1y8mcgq8Lcy2wiIjGhWRLmvckRSPotnyPM5hGkxQCIAX/k4
VXEh4FM7q7uTiktRjRgrUMNXnszq4FJInkU8
=DVFL
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,12 @@
{
"type": "panel",
"name": "Test",
"id": "test-panel",
"info": {
"description": "Test panel",
"author": {
"name": "Grafana Labs",
"url": "https://grafana.com"
}
}
}