Plugins: Add Subresource Integrity checks (#93024)

* Plugins: Pass hashes for SRI to frontend

* Add SRI hashes to frontendsettings DTOs

* Add docstring

* TestSriHashes

* Fix typo

* Changed SriHashes to ModuleHash

* update loader_test compareOpts

* update ModuleHash error message

* Add TestModuleHash/no_module.js

* Add omitEmpty to moduleHash

* Add ModuleHash to api/plugins/${pluginId}/settings

* moved ModuleHash field

* feat(plugins): add moduleHash to bootData and plugin types

* feat(plugins): if moduleHash is available apply it to systemjs importmap

* Calculate ModuleHash for CDN provisioned plugins

* Add ModuleHash tests for TestCalculate

* adjust test case name

* removed .envrc

* Fix signature verification failing for internal plugins

* fix tests

* Add pluginsFilesystemSriChecks feature togglemk

* renamed FilesystemSriChecksEnabled

* refactor(plugin_loader): prefer extending type declaration over ts-error

* added a couple more tests

* Removed unused features

* Removed unused argument from signature.DefaultCalculator call

* Removed unused argument from bootstrap.DefaultConstructFunc

* Moved ModuleHash to pluginassets service

* update docstring

* lint

* Removed cdn dependency from manifest.Signature

* add tests

* fix extra parameters in tests

* "fix" tests

* removed outdated test

* removed unused cdn dependency in signature.DefaultCalculator

* reduce diff

* Cache returned values

* Add support for deeply nested plugins (more than 1 hierarchy level)

* simplify cache usage

* refactor TestService_ModuleHash_Cache

* removed unused testdata

* re-generate feature toggles

* use version for module hash cache

* Renamed feature toggle to pluginsSriChecks and use it for both cdn and filesystem

* Removed app/types/system-integrity.d.ts

* re-generate feature toggles

* re-generate feature toggles

* feat(plugins): put systemjs integrity hash behind feature flag

---------

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
This commit is contained in:
Giuseppe Guerra
2024-10-04 14:55:09 +02:00
committed by GitHub
parent 153036be2e
commit 0db65d229e
46 changed files with 901 additions and 104 deletions

View File

@@ -30,6 +30,7 @@ type PluginSetting struct {
SignatureOrg string `json:"signatureOrg"`
AngularDetected bool `json:"angularDetected"`
LoadingStrategy plugins.LoadingStrategy `json:"loadingStrategy"`
ModuleHash string `json:"moduleHash,omitempty"`
}
type PluginListItem struct {

View File

@@ -145,6 +145,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
AliasIDs: panel.AliasIDs,
Info: panel.Info,
Module: panel.Module,
ModuleHash: hs.pluginAssets.ModuleHash(c.Req.Context(), panel),
BaseURL: panel.BaseURL,
SkipDataQuery: panel.SkipDataQuery,
HideFromList: panel.HideFromList,
@@ -453,6 +454,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
JSONData: plugin.JSONData,
Signature: plugin.Signature,
Module: plugin.Module,
ModuleHash: hs.pluginAssets.ModuleHash(c.Req.Context(), plugin),
BaseURL: plugin.BaseURL,
Angular: plugin.Angular,
MultiValueFilterOperators: plugin.MultiValueFilterOperators,
@@ -538,8 +540,9 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
JSONData: ds.JSONData,
Signature: ds.Signature,
Module: ds.Module,
BaseURL: ds.BaseURL,
Angular: ds.Angular,
// ModuleHash: hs.pluginAssets.ModuleHash(c.Req.Context(), ds),
BaseURL: ds.BaseURL,
Angular: ds.Angular,
},
}
if ds.Name == grafanads.DatasourceName {
@@ -563,6 +566,7 @@ func (hs *HTTPServer) newAppDTO(ctx context.Context, plugin pluginstore.Plugin,
LoadingStrategy: hs.pluginAssets.LoadingStrategy(ctx, plugin),
Extensions: plugin.Extensions,
Dependencies: plugin.Dependencies,
ModuleHash: hs.pluginAssets.ModuleHash(ctx, plugin),
}
if settings.Enabled {

View File

@@ -18,6 +18,8 @@ import (
"github.com/grafana/grafana/pkg/login/social/socialimpl"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/manager/signature"
"github.com/grafana/grafana/pkg/plugins/manager/signature/statickey"
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
@@ -51,10 +53,11 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
})
}
pluginsCDN := pluginscdn.ProvideService(&config.PluginManagementCfg{
pluginsCfg := &config.PluginManagementCfg{
PluginsCDNURLTemplate: cfg.PluginsCDNURLTemplate,
PluginSettings: cfg.PluginSettings,
})
}
pluginsCDN := pluginscdn.ProvideService(pluginsCfg)
var pluginStore = pstore
if pluginStore == nil {
@@ -68,7 +71,8 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
var pluginsAssets = passets
if pluginsAssets == nil {
pluginsAssets = pluginassets.ProvideService(cfg, pluginsCDN)
sig := signature.ProvideService(pluginsCfg, statickey.New())
pluginsAssets = pluginassets.ProvideService(pluginsCfg, pluginsCDN, sig, pluginStore)
}
hs := &HTTPServer{
@@ -240,6 +244,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
PluginList: []pluginstore.Plugin{
{
Module: fmt.Sprintf("/%s/module.js", "test-app"),
// ModuleHash: "sha256-test",
JSONData: plugins.JSONData{
ID: "test-app",
Info: plugins.Info{Version: "0.5.0"},
@@ -255,9 +260,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
Plugins: newAppSettings("test-app", false),
}
},
pluginAssets: func() *pluginassets.Service {
return pluginassets.ProvideService(setting.NewCfg(), pluginscdn.ProvideService(&config.PluginManagementCfg{}))
},
pluginAssets: newPluginAssets(),
expected: settings{
Apps: map[string]*plugins.AppDTO{
"test-app": {
@@ -266,6 +269,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
Path: "/test-app/module.js",
Version: "0.5.0",
LoadingStrategy: plugins.LoadingStrategyScript,
// ModuleHash: "sha256-test",
},
},
},
@@ -277,6 +281,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
PluginList: []pluginstore.Plugin{
{
Module: fmt.Sprintf("/%s/module.js", "test-app"),
// ModuleHash: "sha256-test",
JSONData: plugins.JSONData{
ID: "test-app",
Info: plugins.Info{Version: "0.5.0"},
@@ -292,9 +297,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
Plugins: newAppSettings("test-app", true),
}
},
pluginAssets: func() *pluginassets.Service {
return pluginassets.ProvideService(setting.NewCfg(), pluginscdn.ProvideService(&config.PluginManagementCfg{}))
},
pluginAssets: newPluginAssets(),
expected: settings{
Apps: map[string]*plugins.AppDTO{
"test-app": {
@@ -303,6 +306,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
Path: "/test-app/module.js",
Version: "0.5.0",
LoadingStrategy: plugins.LoadingStrategyScript,
// ModuleHash: "sha256-test",
},
},
},
@@ -330,9 +334,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
Plugins: newAppSettings("test-app", true),
}
},
pluginAssets: func() *pluginassets.Service {
return pluginassets.ProvideService(setting.NewCfg(), pluginscdn.ProvideService(&config.PluginManagementCfg{}))
},
pluginAssets: newPluginAssets(),
expected: settings{
Apps: map[string]*plugins.AppDTO{
"test-app": {
@@ -368,15 +370,13 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
Plugins: newAppSettings("test-app", true),
}
},
pluginAssets: func() *pluginassets.Service {
return pluginassets.ProvideService(&setting.Cfg{
PluginSettings: map[string]map[string]string{
"test-app": {
pluginassets.CreatePluginVersionCfgKey: pluginassets.CreatePluginVersionScriptSupportEnabled,
},
pluginAssets: newPluginAssetsWithConfig(&config.PluginManagementCfg{
PluginSettings: map[string]map[string]string{
"test-app": {
pluginassets.CreatePluginVersionCfgKey: pluginassets.CreatePluginVersionScriptSupportEnabled,
},
}, pluginscdn.ProvideService(&config.PluginManagementCfg{}))
},
},
}),
expected: settings{
Apps: map[string]*plugins.AppDTO{
"test-app": {
@@ -412,9 +412,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
Plugins: newAppSettings("test-app", true),
}
},
pluginAssets: func() *pluginassets.Service {
return pluginassets.ProvideService(setting.NewCfg(), pluginscdn.ProvideService(&config.PluginManagementCfg{}))
},
pluginAssets: newPluginAssets(),
expected: settings{
Apps: map[string]*plugins.AppDTO{
"test-app": {
@@ -456,3 +454,13 @@ func newAppSettings(id string, enabled bool) map[string]*pluginsettings.DTO {
},
}
}
func newPluginAssets() func() *pluginassets.Service {
return newPluginAssetsWithConfig(&config.PluginManagementCfg{})
}
func newPluginAssetsWithConfig(pCfg *config.PluginManagementCfg) func() *pluginassets.Service {
return func() *pluginassets.Service {
return pluginassets.ProvideService(pCfg, pluginscdn.ProvideService(pCfg), signature.ProvideService(pCfg, statickey.New()), &pluginstore.FakePluginStore{})
}
}

View File

@@ -201,6 +201,7 @@ func (hs *HTTPServer) GetPluginSettingByID(c *contextmodel.ReqContext) response.
Includes: plugin.Includes,
BaseUrl: plugin.BaseURL,
Module: plugin.Module,
ModuleHash: hs.pluginAssets.ModuleHash(c.Req.Context(), plugin),
DefaultNavUrl: path.Join(hs.Cfg.AppSubURL, plugin.DefaultNavURL),
State: plugin.State,
Signature: plugin.Signature,

View File

@@ -27,6 +27,8 @@ import (
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
"github.com/grafana/grafana/pkg/plugins/manager/filestore"
"github.com/grafana/grafana/pkg/plugins/manager/registry"
"github.com/grafana/grafana/pkg/plugins/manager/signature"
"github.com/grafana/grafana/pkg/plugins/manager/signature/statickey"
"github.com/grafana/grafana/pkg/plugins/pfs"
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -788,7 +790,6 @@ func Test_PluginsSettings(t *testing.T) {
Info: plugins.Info{
Version: "1.0.0",
}}, plugins.ClassExternal, plugins.NewFakeFS())
pluginRegistry := &fakes.FakePluginRegistry{
Store: map[string]*plugins.Plugin{
p1.ID: p1,
@@ -843,8 +844,10 @@ func Test_PluginsSettings(t *testing.T) {
ErrorCode: tc.errCode,
})
}
pluginCDN := pluginscdn.ProvideService(&config.PluginManagementCfg{})
hs.pluginAssets = pluginassets.ProvideService(hs.Cfg, pluginCDN)
pCfg := &config.PluginManagementCfg{}
pluginCDN := pluginscdn.ProvideService(pCfg)
sig := signature.ProvideService(pCfg, statickey.New())
hs.pluginAssets = pluginassets.ProvideService(pCfg, pluginCDN, sig, hs.pluginStore)
hs.pluginErrorResolver = pluginerrs.ProvideStore(errTracker)
var err error
hs.pluginsUpdateChecker, err = updatechecker.ProvidePluginsService(hs.Cfg, nil, tracing.InitializeTracerForTest())