diff --git a/pkg/api/frontendsettings_test.go b/pkg/api/frontendsettings_test.go index b80a1108499..84be97b1da2 100644 --- a/pkg/api/frontendsettings_test.go +++ b/pkg/api/frontendsettings_test.go @@ -23,7 +23,6 @@ import ( "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" - pluginassets2 "github.com/grafana/grafana/pkg/plugins/pluginassets" "github.com/grafana/grafana/pkg/plugins/pluginassets/modulehash" "github.com/grafana/grafana/pkg/plugins/pluginscdn" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 9a45f42e95d..86156b0f4a7 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -30,7 +30,6 @@ import ( "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" - pluginassets2 "github.com/grafana/grafana/pkg/plugins/pluginassets" "github.com/grafana/grafana/pkg/plugins/pluginassets/modulehash" "github.com/grafana/grafana/pkg/plugins/pluginerrs" "github.com/grafana/grafana/pkg/plugins/pluginscdn" diff --git a/pkg/services/pluginsintegration/pluginassets/pluginassets_test.go b/pkg/services/pluginsintegration/pluginassets/pluginassets_test.go index 192717c34ff..91b8b515bb1 100644 --- a/pkg/services/pluginsintegration/pluginassets/pluginassets_test.go +++ b/pkg/services/pluginsintegration/pluginassets/pluginassets_test.go @@ -2,19 +2,14 @@ package pluginassets import ( "context" - "fmt" - "path/filepath" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/config" "github.com/grafana/grafana/pkg/plugins/manager/pluginfakes" - "github.com/grafana/grafana/pkg/plugins/manager/signature" - "github.com/grafana/grafana/pkg/plugins/manager/signature/statickey" "github.com/grafana/grafana/pkg/plugins/pluginscdn" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" ) @@ -179,349 +174,6 @@ func TestService_Calculate(t *testing.T) { } } -func TestService_ModuleHash(t *testing.T) { - const ( - pluginID = "grafana-test-datasource" - parentPluginID = "grafana-test-app" - ) - for _, tc := range []struct { - name string - features *config.Features - store []pluginstore.Plugin - - // Can be used to configure plugin's fs - // fs cdn type = loaded from CDN with no files on disk - // fs local type = files on disk but served from CDN only if cdn=true - plugin pluginstore.Plugin - - // When true, set cdn=true in config - cdn bool - expModuleHash string - }{ - { - name: "unsigned should not return module hash", - plugin: newPlugin(pluginID, withSignatureStatus(plugins.SignatureStatusUnsigned)), - cdn: false, - features: &config.Features{SriChecksEnabled: false}, - expModuleHash: "", - }, - { - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid"))), - withClass(plugins.ClassExternal), - ), - cdn: true, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: newSRIHash(t, "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"), - }, - { - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid"))), - withClass(plugins.ClassExternal), - ), - cdn: true, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: newSRIHash(t, "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"), - }, - { - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid"))), - ), - cdn: false, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: "", - }, - { - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid"))), - ), - cdn: true, - features: &config.Features{SriChecksEnabled: false}, - expModuleHash: "", - }, - { - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid"))), - ), - cdn: false, - features: &config.Features{SriChecksEnabled: false}, - expModuleHash: "", - }, - { - // parentPluginID (/) - // └── pluginID (/datasource) - name: "nested plugin should return module hash from parent MANIFEST.txt", - store: []pluginstore.Plugin{ - newPlugin( - parentPluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-nested"))), - ), - }, - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-nested", "datasource"))), - withParent(parentPluginID), - ), - cdn: true, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: newSRIHash(t, "04d70db091d96c4775fb32ba5a8f84cc22893eb43afdb649726661d4425c6711"), - }, - { - // parentPluginID (/) - // └── pluginID (/panels/one) - name: "nested plugin deeper than one subfolder should return module hash from parent MANIFEST.txt", - store: []pluginstore.Plugin{ - newPlugin( - parentPluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-nested"))), - ), - }, - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-nested", "panels", "one"))), - withParent(parentPluginID), - ), - cdn: true, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: newSRIHash(t, "cbd1ac2284645a0e1e9a8722a729f5bcdd2b831222728709c6360beecdd6143f"), - }, - { - // grand-parent-app (/) - // ├── parent-datasource (/datasource) - // │ └── child-panel (/datasource/panels/one) - name: "nested plugin of a nested plugin should return module hash from parent MANIFEST.txt", - store: []pluginstore.Plugin{ - newPlugin( - "grand-parent-app", - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-deeply-nested"))), - ), - newPlugin( - "parent-datasource", - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-deeply-nested", "datasource"))), - withParent("grand-parent-app"), - ), - }, - plugin: newPlugin( - "child-panel", - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-deeply-nested", "datasource", "panels", "one"))), - withParent("parent-datasource"), - ), - cdn: true, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: newSRIHash(t, "cbd1ac2284645a0e1e9a8722a729f5bcdd2b831222728709c6360beecdd6143f"), - }, - { - name: "nested plugin should not return module hash from parent if it's not registered in the store", - store: []pluginstore.Plugin{}, - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-nested", "panels", "one"))), - withParent(parentPluginID), - ), - cdn: false, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: "", - }, - { - name: "missing module.js entry from MANIFEST.txt should not return module hash", - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-no-module-js"))), - ), - cdn: false, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: "", - }, - { - name: "signed status but missing MANIFEST.txt should not return module hash", - plugin: newPlugin( - pluginID, - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-no-manifest-txt"))), - ), - cdn: false, - features: &config.Features{SriChecksEnabled: true}, - expModuleHash: "", - }, - } { - if tc.name == "" { - var expS string - if tc.expModuleHash == "" { - expS = "should not return module hash" - } else { - expS = "should return module hash" - } - tc.name = fmt.Sprintf("feature=%v, cdn_config=%v, class=%v %s", tc.features.SriChecksEnabled, tc.cdn, tc.plugin.Class, expS) - } - - t.Run(tc.name, func(t *testing.T) { - var pluginSettings config.PluginSettings - if tc.cdn { - pluginSettings = config.PluginSettings{ - pluginID: { - "cdn": "true", - }, - parentPluginID: map[string]string{ - "cdn": "true", - }, - "grand-parent-app": map[string]string{ - "cdn": "true", - }, - } - } - features := tc.features - if features == nil { - features = &config.Features{} - } - pCfg := &config.PluginManagementCfg{ - PluginsCDNURLTemplate: "http://cdn.example.com", - PluginSettings: pluginSettings, - Features: *features, - } - svc := ProvideService( - pCfg, - pluginscdn.ProvideService(pCfg), - signature.ProvideService(pCfg, statickey.New()), - pluginstore.NewFakePluginStore(tc.store...), - ) - mh := svc.ModuleHash(context.Background(), tc.plugin) - require.Equal(t, tc.expModuleHash, mh) - }) - } -} - -func TestService_ModuleHash_Cache(t *testing.T) { - pCfg := &config.PluginManagementCfg{ - PluginSettings: config.PluginSettings{}, - Features: config.Features{SriChecksEnabled: true}, - } - svc := ProvideService( - pCfg, - pluginscdn.ProvideService(pCfg), - signature.ProvideService(pCfg, statickey.New()), - pluginstore.NewFakePluginStore(), - ) - const pluginID = "grafana-test-datasource" - - t.Run("cache key", func(t *testing.T) { - t.Run("with version", func(t *testing.T) { - const pluginVersion = "1.0.0" - p := newPlugin(pluginID, withInfo(plugins.Info{Version: pluginVersion})) - k := svc.moduleHashCacheKey(p) - require.Equal(t, pluginID+":"+pluginVersion, k, "cache key should be correct") - }) - - t.Run("without version", func(t *testing.T) { - p := newPlugin(pluginID) - k := svc.moduleHashCacheKey(p) - require.Equal(t, pluginID+":", k, "cache key should be correct") - }) - }) - - t.Run("ModuleHash usage", func(t *testing.T) { - pV1 := newPlugin( - pluginID, - withInfo(plugins.Info{Version: "1.0.0"}), - withSignatureStatus(plugins.SignatureStatusValid), - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid"))), - ) - - pCfg = &config.PluginManagementCfg{ - PluginsCDNURLTemplate: "https://cdn.grafana.com", - PluginSettings: config.PluginSettings{ - pluginID: { - "cdn": "true", - }, - }, - Features: config.Features{SriChecksEnabled: true}, - } - svc = ProvideService( - pCfg, - pluginscdn.ProvideService(pCfg), - signature.ProvideService(pCfg, statickey.New()), - pluginstore.NewFakePluginStore(), - ) - - k := svc.moduleHashCacheKey(pV1) - - _, ok := svc.moduleHashCache.Load(k) - require.False(t, ok, "cache should initially be empty") - - mhV1 := svc.ModuleHash(context.Background(), pV1) - pV1Exp := newSRIHash(t, "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03") - require.Equal(t, pV1Exp, mhV1, "returned value should be correct") - - cachedMh, ok := svc.moduleHashCache.Load(k) - require.True(t, ok) - require.Equal(t, pV1Exp, cachedMh, "cache should contain the returned value") - - t.Run("different version uses different cache key", func(t *testing.T) { - pV2 := newPlugin( - pluginID, - withInfo(plugins.Info{Version: "2.0.0"}), - withSignatureStatus(plugins.SignatureStatusValid), - // different fs for different hash - withFS(plugins.NewLocalFS(filepath.Join("testdata", "module-hash-valid-nested"))), - ) - mhV2 := svc.ModuleHash(context.Background(), pV2) - require.NotEqual(t, mhV2, mhV1, "different version should have different hash") - require.Equal(t, newSRIHash(t, "266c19bc148b22ddef2a288fc5f8f40855bda22ccf60be53340b4931e469ae2a"), mhV2) - }) - - t.Run("cache should be used", func(t *testing.T) { - // edit cache directly - svc.moduleHashCache.Store(k, "hax") - require.Equal(t, "hax", svc.ModuleHash(context.Background(), pV1)) - }) - }) -} - -func TestConvertHashFromSRI(t *testing.T) { - for _, tc := range []struct { - hash string - expHash string - expErr bool - }{ - { - hash: "ddfcb449445064e6c39f0c20b15be3cb6a55837cf4781df23d02de005f436811", - expHash: "sha256-3fy0SURQZObDnwwgsVvjy2pVg3z0eB3yPQLeAF9DaBE=", - }, - { - hash: "not-a-valid-hash", - expErr: true, - }, - } { - t.Run(tc.hash, func(t *testing.T) { - r, err := convertHashForSRI(tc.hash) - if tc.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.Equal(t, tc.expHash, r) - } - }) - } -} - func newPlugin(pluginID string, cbs ...func(p pluginstore.Plugin) pluginstore.Plugin) pluginstore.Plugin { p := pluginstore.Plugin{ JSONData: plugins.JSONData{ @@ -534,13 +186,6 @@ func newPlugin(pluginID string, cbs ...func(p pluginstore.Plugin) pluginstore.Pl return p } -func withInfo(info plugins.Info) func(p pluginstore.Plugin) pluginstore.Plugin { - return func(p pluginstore.Plugin) pluginstore.Plugin { - p.Info = info - return p - } -} - func withFS(fs plugins.FS) func(p pluginstore.Plugin) pluginstore.Plugin { return func(p pluginstore.Plugin) pluginstore.Plugin { p.FS = fs @@ -548,13 +193,6 @@ func withFS(fs plugins.FS) func(p pluginstore.Plugin) pluginstore.Plugin { } } -func withSignatureStatus(status plugins.SignatureStatus) func(p pluginstore.Plugin) pluginstore.Plugin { - return func(p pluginstore.Plugin) pluginstore.Plugin { - p.Signature = status - return p - } -} - func withAngular(angular bool) func(p pluginstore.Plugin) pluginstore.Plugin { return func(p pluginstore.Plugin) pluginstore.Plugin { p.Angular = plugins.AngularMeta{Detected: angular} @@ -562,13 +200,6 @@ func withAngular(angular bool) func(p pluginstore.Plugin) pluginstore.Plugin { } } -func withParent(parentID string) func(p pluginstore.Plugin) pluginstore.Plugin { - return func(p pluginstore.Plugin) pluginstore.Plugin { - p.Parent = &pluginstore.ParentPlugin{ID: parentID} - return p - } -} - func withClass(class plugins.Class) func(p pluginstore.Plugin) pluginstore.Plugin { return func(p pluginstore.Plugin) pluginstore.Plugin { p.Class = class @@ -587,9 +218,3 @@ func newPluginSettings(pluginID string, kv map[string]string) config.PluginSetti pluginID: kv, } } - -func newSRIHash(t *testing.T, s string) string { - r, err := convertHashForSRI(s) - require.NoError(t, err) - return r -}