diff --git a/conf/defaults.ini b/conf/defaults.ini index d3deb04180f..9f682ffea15 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1986,7 +1986,7 @@ public_key_retrieval_disabled = false public_key_retrieval_on_startup = false # Enter a comma-separated list of plugin identifiers to avoid loading (including core plugins). These plugins will be hidden in the catalog. disable_plugins = -# Comma separated list of plugin ids for which environment variables should be forwarded. Used only when feature flag pluginsSkipHostEnvVars is enabled. +# Comma separated list of plugin ids for which all host environment variables should be forwarded to the plugin process. forward_host_env_vars = # Comma separated list of plugin ids to install as part of the startup process. # These will be installed, by default, asynchronously (in the background) while starting Grafana. diff --git a/pkg/services/pluginsintegration/pluginconfig/envvars.go b/pkg/services/pluginsintegration/pluginconfig/envvars.go index 8ae8c6cb648..565f6892cce 100644 --- a/pkg/services/pluginsintegration/pluginconfig/envvars.go +++ b/pkg/services/pluginsintegration/pluginconfig/envvars.go @@ -125,9 +125,34 @@ func (p *EnvVarsProvider) awsEnvVars(pluginID string) []string { variables = append(variables, p.envVar(awsds.ListMetricsPageLimitKeyName, p.cfg.AWSListMetricsPageLimit)) } + // Forward AWS SDK credential chain env vars from the host so that plugins can use + // EKS IRSA, ECS task roles, and other environment-based credential providers. + for _, envVarName := range awsHostEnvVarNames { + if v, ok := os.LookupEnv(envVarName); ok { + variables = append(variables, p.envVar(envVarName, v)) + } + } + return variables } +// awsHostEnvVarNames are the host environment variables forwarded to AWS plugins. +// These are needed for the AWS SDK default credential chain to resolve credentials +// in container environments (EKS with IRSA, ECS Fargate). +var awsHostEnvVarNames = []string{ + // EKS IRSA + "AWS_ROLE_ARN", + "AWS_WEB_IDENTITY_TOKEN_FILE", + // ECS (Fargate / EC2) + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", + "AWS_CONTAINER_CREDENTIALS_FULL_URI", + "AWS_CONTAINER_AUTHORIZATION_TOKEN", + "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", + // Region + "AWS_REGION", + "AWS_DEFAULT_REGION", +} + func (p *EnvVarsProvider) secureSocksProxyEnvVars() []string { if p.cfg.ProxySettings.Enabled { return []string{ diff --git a/pkg/services/pluginsintegration/pluginconfig/envvars_test.go b/pkg/services/pluginsintegration/pluginconfig/envvars_test.go index 66421f0b610..6eeea1bc4e3 100644 --- a/pkg/services/pluginsintegration/pluginconfig/envvars_test.go +++ b/pkg/services/pluginsintegration/pluginconfig/envvars_test.go @@ -484,28 +484,80 @@ func TestPluginEnvVarsProvider_authEnvVars(t *testing.T) { } func TestPluginEnvVarsProvider_awsEnvVars(t *testing.T) { - t.Run("backend datasource with aws settings", func(t *testing.T) { - tcs := []struct { - name string - pluginID string - forwardToPlugins []string - expected []string - }{ - { - name: "Will generate AWS env vars for plugin as long as is in the forwardToPlugins list", - forwardToPlugins: []string{"foobar-datasource", "cloudwatch", "prometheus"}, - pluginID: "cloudwatch", - expected: []string{"GF_VERSION=", "AWS_AUTH_AssumeRoleEnabled=false", "AWS_AUTH_AllowedAuthProviders=grafana_assume_role,keys", "AWS_AUTH_EXTERNAL_ID=mock_external_id", "AWS_AUTH_SESSION_DURATION=10m", "AWS_CW_LIST_METRICS_PAGE_LIMIT=100"}, + tcs := []struct { + name string + pluginID string + forwardToPlugins []string + hostEnvVars map[string]string + expected []string + unexpectedKeys []string + }{ + { + name: "generates AWS auth settings for whitelisted plugin", + forwardToPlugins: []string{"foobar-datasource", "cloudwatch", "prometheus"}, + pluginID: "cloudwatch", + expected: []string{"GF_VERSION=", "AWS_AUTH_AssumeRoleEnabled=false", "AWS_AUTH_AllowedAuthProviders=grafana_assume_role,keys", "AWS_AUTH_EXTERNAL_ID=mock_external_id", "AWS_AUTH_SESSION_DURATION=10m", "AWS_CW_LIST_METRICS_PAGE_LIMIT=100"}, + }, + { + name: "does not generate AWS env vars for non-whitelisted plugin", + forwardToPlugins: []string{"cloudwatch", "foobar-datasource"}, + pluginID: "prometheus", + expected: []string{"GF_VERSION="}, + }, + { + name: "forwards AWS SDK credential chain env vars for whitelisted plugin", + forwardToPlugins: []string{"cloudwatch"}, + pluginID: "cloudwatch", + hostEnvVars: map[string]string{ + "AWS_ROLE_ARN": "arn:aws:iam::123456789012:role/test-role", + "AWS_WEB_IDENTITY_TOKEN_FILE": "/var/run/secrets/token", + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/v2/credentials/uuid", + "AWS_REGION": "us-east-1", }, - { - name: "Will not generate AWS env vars for plugin as long as is in not the forwardToPlugins list", - forwardToPlugins: []string{"cloudwatch", "foobar-datasource"}, - pluginID: "prometheus", - expected: []string{"GF_VERSION="}, + expected: []string{ + "GF_VERSION=", + "AWS_AUTH_AssumeRoleEnabled=false", "AWS_AUTH_AllowedAuthProviders=grafana_assume_role,keys", + "AWS_AUTH_EXTERNAL_ID=mock_external_id", "AWS_AUTH_SESSION_DURATION=10m", "AWS_CW_LIST_METRICS_PAGE_LIMIT=100", + "AWS_ROLE_ARN=arn:aws:iam::123456789012:role/test-role", + "AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/token", + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/uuid", + "AWS_REGION=us-east-1", }, - } + }, + { + name: "does not forward AWS SDK credential chain env vars for non-whitelisted plugin", + forwardToPlugins: []string{"cloudwatch"}, + pluginID: "some-other-plugin", + hostEnvVars: map[string]string{ + "AWS_ROLE_ARN": "arn:aws:iam::123456789012:role/test-role", + "AWS_REGION": "us-east-1", + }, + expected: []string{"GF_VERSION="}, + unexpectedKeys: []string{"AWS_ROLE_ARN", "AWS_REGION"}, + }, + { + name: "only forwards AWS SDK env vars that are set in the host environment", + forwardToPlugins: []string{"cloudwatch"}, + pluginID: "cloudwatch", + hostEnvVars: map[string]string{ + "AWS_REGION": "eu-west-1", + }, + expected: []string{ + "GF_VERSION=", + "AWS_AUTH_AssumeRoleEnabled=false", "AWS_AUTH_AllowedAuthProviders=grafana_assume_role,keys", + "AWS_AUTH_EXTERNAL_ID=mock_external_id", "AWS_AUTH_SESSION_DURATION=10m", "AWS_CW_LIST_METRICS_PAGE_LIMIT=100", + "AWS_REGION=eu-west-1", + }, + unexpectedKeys: []string{"AWS_ROLE_ARN", "AWS_WEB_IDENTITY_TOKEN_FILE", "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"}, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.hostEnvVars { + t.Setenv(k, v) + } - for _, tc := range tcs { p := &plugins.Plugin{ JSONData: plugins.JSONData{ ID: tc.pluginID, @@ -524,8 +576,13 @@ func TestPluginEnvVarsProvider_awsEnvVars(t *testing.T) { provider := NewEnvVarsProvider(cfg, nil, &fakeSSOSettingsProvider{}) envVars := provider.PluginEnvVars(context.Background(), p) assert.ElementsMatch(t, tc.expected, envVars) - } - }) + + for _, key := range tc.unexpectedKeys { + _, ok := getEnvVarWithExists(envVars, key) + assert.False(t, ok, "env var %s should not be present", key) + } + }) + } } func TestPluginEnvVarsProvider_featureToggleEnvVar(t *testing.T) {