diff --git a/conf/defaults.ini b/conf/defaults.ini index f14739c2d41..538e25a0b3d 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1535,6 +1535,8 @@ disable_plugins = install_token = # Comma separated list of plugin ids for which angular deprecation UI should be disabled hide_angular_deprecation = +# Comma separated list of plugin ids for which environment variables should be forwarded. Used only when feature flag pluginsSkipHostEnvVars is enabled. +forward_host_env_vars = #################################### Grafana Live ########################################## [live] diff --git a/pkg/plugins/config/config.go b/pkg/plugins/config/config.go index c7f3ac61742..f96aa769ad8 100644 --- a/pkg/plugins/config/config.go +++ b/pkg/plugins/config/config.go @@ -18,6 +18,7 @@ type Cfg struct { PluginSettings setting.PluginSettings PluginsAllowUnsigned []string DisablePlugins []string + ForwardHostEnvVars []string // AWS Plugin Auth AWSAllowedAuthProviders []string @@ -52,7 +53,7 @@ type Cfg struct { func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string, awsAllowedAuthProviders []string, awsAssumeRoleEnabled bool, awsExternalId string, azure *azsettings.AzureSettings, secureSocksDSProxy setting.SecureSocksDSProxySettings, grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, appURL string, appSubURL string, tracing Tracing, features plugins.FeatureToggles, angularSupportEnabled bool, - grafanaComURL string, disablePlugins []string, hideAngularDeprecation []string) *Cfg { + grafanaComURL string, disablePlugins []string, hideAngularDeprecation []string, forwardHostEnvVars []string) *Cfg { return &Cfg{ log: log.New("plugin.cfg"), PluginsPath: pluginsPath, @@ -75,5 +76,6 @@ func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSetti Features: features, AngularSupportEnabled: angularSupportEnabled, HideAngularDeprecation: hideAngularDeprecation, + ForwardHostEnvVars: forwardHostEnvVars, } } diff --git a/pkg/plugins/manager/pipeline/bootstrap/steps.go b/pkg/plugins/manager/pipeline/bootstrap/steps.go index 5dc2aa56e10..83e025b18cf 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/steps.go +++ b/pkg/plugins/manager/pipeline/bootstrap/steps.go @@ -3,6 +3,7 @@ package bootstrap import ( "context" "path" + "slices" "strings" "github.com/grafana/grafana/pkg/infra/slugify" @@ -157,12 +158,12 @@ func configureAppChildPlugin(cfg *config.Cfg, parent *plugins.Plugin, child *plu } // SkipHostEnvVarsDecorateFunc returns a DecorateFunc that configures the SkipHostEnvVars field of the plugin. -// It will be set to true if the FlagPluginsSkipHostEnvVars feature flag is set, and the plugin does not have -// forward_host_env_vars = true in its plugin settings. +// It will be set to true if the FlagPluginsSkipHostEnvVars feature flag is set, and the plugin is not present in the +// ForwardHostEnvVars plugin ids list. func SkipHostEnvVarsDecorateFunc(cfg *config.Cfg) DecorateFunc { return func(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) { p.SkipHostEnvVars = cfg.Features.IsEnabledGlobally(featuremgmt.FlagPluginsSkipHostEnvVars) && - cfg.PluginSettings[p.ID]["forward_host_env_vars"] != "true" + !slices.Contains(cfg.ForwardHostEnvVars, p.ID) return p, nil } } diff --git a/pkg/plugins/manager/pipeline/bootstrap/steps_test.go b/pkg/plugins/manager/pipeline/bootstrap/steps_test.go index 57457060db5..53302890df0 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/steps_test.go +++ b/pkg/plugins/manager/pipeline/bootstrap/steps_test.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/setting" ) func TestSetDefaultNavURL(t *testing.T) { @@ -172,34 +171,34 @@ func TestSkipEnvVarsDecorateFunc(t *testing.T) { t.Run("plugin setting", func(t *testing.T) { for _, tc := range []struct { name string - pluginSettings setting.PluginSettings + forwardHostEnvVars []string expSkipHostEnvVars bool }{ { - name: "forward_host_env_vars = false should set SkipHostEnvVars to true", - pluginSettings: setting.PluginSettings{pluginID: map[string]string{"forward_host_env_vars": "false"}}, + name: "plugin id not present in forwardHostEnvVars should set SkipHostEnvVars to true (empty)", + forwardHostEnvVars: []string{}, expSkipHostEnvVars: true, }, { - name: "forward_host_env_vars = true should set SkipHostEnvVars to false", - pluginSettings: setting.PluginSettings{pluginID: map[string]string{"forward_host_env_vars": "true"}}, + name: "plugin id not present in forwardHostEnvVars should set SkipHostEnvVars to true (other id)", + forwardHostEnvVars: []string{"other-id", "yet-another-id"}, + expSkipHostEnvVars: true, + }, + { + name: "plugin id in forwardHostEnvVars should set SkipHostEnvVars to false (only)", + forwardHostEnvVars: []string{pluginID}, expSkipHostEnvVars: false, }, { - name: "invalid forward_host_env_vars should set SkipHostEnvVars to true", - pluginSettings: setting.PluginSettings{pluginID: map[string]string{"forward_host_env_vars": "grilled cheese sandwich with bacon"}}, - expSkipHostEnvVars: true, - }, - { - name: "forward_host_env_vars absent should set SkipHostEnvVars to true", - pluginSettings: setting.PluginSettings{pluginID: nil}, - expSkipHostEnvVars: true, + name: "plugin id in forwardHostEnvVars should set SkipHostEnvVars to false (with other)", + forwardHostEnvVars: []string{"a-plugin", pluginID, "other-id"}, + expSkipHostEnvVars: false, }, } { t.Run(tc.name, func(t *testing.T) { f := SkipHostEnvVarsDecorateFunc(&config.Cfg{ - Features: featuremgmt.WithFeatures(featuremgmt.FlagPluginsSkipHostEnvVars), - PluginSettings: tc.pluginSettings, + Features: featuremgmt.WithFeatures(featuremgmt.FlagPluginsSkipHostEnvVars), + ForwardHostEnvVars: tc.forwardHostEnvVars, }) p, err := f(context.Background(), &plugins.Plugin{JSONData: plugins.JSONData{ID: pluginID}}) require.NoError(t, err) diff --git a/pkg/services/pluginsintegration/config/config.go b/pkg/services/pluginsintegration/config/config.go index bd3ebe14f00..cabd7d4a398 100644 --- a/pkg/services/pluginsintegration/config/config.go +++ b/pkg/services/pluginsintegration/config/config.go @@ -48,6 +48,7 @@ func ProvideConfig(settingProvider setting.Provider, grafanaCfg *setting.Cfg, fe grafanaCfg.GrafanaComURL, grafanaCfg.DisablePlugins, grafanaCfg.HideAngularDeprecation, + grafanaCfg.ForwardHostEnvVars, ), nil } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 0f11813c828..91621b4af2e 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -248,6 +248,7 @@ type Cfg struct { DisablePlugins []string HideAngularDeprecation []string PluginInstallToken string + ForwardHostEnvVars []string PluginsCDNURLTemplate string PluginLogBackendRequests bool diff --git a/pkg/setting/setting_plugins.go b/pkg/setting/setting_plugins.go index 0bf677d34c2..636ad5753f3 100644 --- a/pkg/setting/setting_plugins.go +++ b/pkg/setting/setting_plugins.go @@ -4,6 +4,8 @@ import ( "strings" "gopkg.in/ini.v1" + + "github.com/grafana/grafana/pkg/util" ) // PluginSettings maps plugin id to map of key/value settings. @@ -33,40 +35,17 @@ func (cfg *Cfg) readPluginSettings(iniFile *ini.File) error { cfg.PluginSkipPublicKeyDownload = pluginsSection.Key("public_key_retrieval_disabled").MustBool(false) cfg.PluginForcePublicKeyDownload = pluginsSection.Key("public_key_retrieval_on_startup").MustBool(false) - pluginsAllowUnsigned := pluginsSection.Key("allow_loading_unsigned_plugins").MustString("") - - for _, plug := range strings.Split(pluginsAllowUnsigned, ",") { - plug = strings.TrimSpace(plug) - cfg.PluginsAllowUnsigned = append(cfg.PluginsAllowUnsigned, plug) - } - - disablePlugins := pluginsSection.Key("disable_plugins").MustString("") - for _, plug := range strings.Split(disablePlugins, ",") { - plug = strings.TrimSpace(plug) - if plug != "" { - cfg.DisablePlugins = append(cfg.DisablePlugins, plug) - } - } - - hideAngularDeprecation := pluginsSection.Key("hide_angular_deprecation").MustString("") - for _, id := range strings.Split(hideAngularDeprecation, ",") { - id = strings.TrimSpace(id) - if id == "" { - continue - } - cfg.HideAngularDeprecation = append(cfg.HideAngularDeprecation, id) - } + cfg.PluginsAllowUnsigned = util.SplitString(pluginsSection.Key("allow_loading_unsigned_plugins").MustString("")) + cfg.DisablePlugins = util.SplitString(pluginsSection.Key("disable_plugins").MustString("")) + cfg.HideAngularDeprecation = util.SplitString(pluginsSection.Key("hide_angular_deprecation").MustString("")) + cfg.ForwardHostEnvVars = util.SplitString(pluginsSection.Key("forward_host_env_vars").MustString("")) cfg.PluginCatalogURL = pluginsSection.Key("plugin_catalog_url").MustString("https://grafana.com/grafana/plugins/") cfg.PluginAdminEnabled = pluginsSection.Key("plugin_admin_enabled").MustBool(true) cfg.PluginAdminExternalManageEnabled = pluginsSection.Key("plugin_admin_external_manage_enabled").MustBool(false) - catalogHiddenPlugins := pluginsSection.Key("plugin_catalog_hidden_plugins").MustString("") + cfg.PluginCatalogHiddenPlugins = util.SplitString(pluginsSection.Key("plugin_catalog_hidden_plugins").MustString("")) - for _, plug := range strings.Split(catalogHiddenPlugins, ",") { - plug = strings.TrimSpace(plug) - cfg.PluginCatalogHiddenPlugins = append(cfg.PluginCatalogHiddenPlugins, plug) - } - // Pull disablep plugins from the catalog + // Pull disabled plugins from the catalog cfg.PluginCatalogHiddenPlugins = append(cfg.PluginCatalogHiddenPlugins, cfg.DisablePlugins...) // Plugins CDN settings diff --git a/pkg/setting/setting_plugins_test.go b/pkg/setting/setting_plugins_test.go index 018e877da00..6ca5e0cc692 100644 --- a/pkg/setting/setting_plugins_test.go +++ b/pkg/setting/setting_plugins_test.go @@ -1,6 +1,7 @@ package setting import ( + "strings" "testing" "github.com/stretchr/testify/require" @@ -43,23 +44,55 @@ func TestPluginSettings(t *testing.T) { } func Test_readPluginSettings(t *testing.T) { - t.Run("should parse plugin ids", func(t *testing.T) { - cfg := NewCfg() - sec, err := cfg.Raw.NewSection("plugins") - require.NoError(t, err) - _, err = sec.NewKey("disable_plugins", "plugin1,plugin2") - require.NoError(t, err) + t.Run("should parse separated plugin ids", func(t *testing.T) { + for _, tc := range []struct { + name string + f func(ids ...string) string + }{ + { + name: "commas", + f: func(ids ...string) string { + return strings.Join(ids, ",") + }, + }, + { + name: "commas and a space", + f: func(ids ...string) string { + return strings.Join(ids, ", ") + }, + }, + { + name: "spaces", + f: func(ids ...string) string { + return strings.Join(ids, " ") + }, + }, + { + name: "json-like", + f: func(ids ...string) string { + return `["` + strings.Join(ids, `","`) + `"]` + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + cfg := NewCfg() + sec, err := cfg.Raw.NewSection("plugins") + require.NoError(t, err) + _, err = sec.NewKey("disable_plugins", tc.f("plugin1", "plugin2")) + require.NoError(t, err) - _, err = sec.NewKey("plugin_catalog_hidden_plugins", "plugin3") - require.NoError(t, err) + _, err = sec.NewKey("plugin_catalog_hidden_plugins", tc.f("plugin3")) + require.NoError(t, err) - _, err = sec.NewKey("hide_angular_deprecation", "a,b,c") - require.NoError(t, err) + _, err = sec.NewKey("hide_angular_deprecation", tc.f("a", "b", "c")) + require.NoError(t, err) - err = cfg.readPluginSettings(cfg.Raw) - require.NoError(t, err) - require.Equal(t, []string{"plugin1", "plugin2"}, cfg.DisablePlugins) - require.Equal(t, []string{"plugin3", "plugin1", "plugin2"}, cfg.PluginCatalogHiddenPlugins) - require.Equal(t, []string{"a", "b", "c"}, cfg.HideAngularDeprecation) + err = cfg.readPluginSettings(cfg.Raw) + require.NoError(t, err) + require.Equal(t, []string{"plugin1", "plugin2"}, cfg.DisablePlugins) + require.Equal(t, []string{"plugin3", "plugin1", "plugin2"}, cfg.PluginCatalogHiddenPlugins) + require.Equal(t, []string{"a", "b", "c"}, cfg.HideAngularDeprecation) + }) + } }) }