/
githubmirror
/
grafana
Обзор
Документация
Войти
/
githubmirror
/
grafana
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
pkg/services/pluginsintegration/pluginconfig/envvars.go
345 строк
12 KB
Giuseppe Guerra
Plugins marketplace: add service for providing marketplace env vars to plugins (#128800)
21 июл 2026, 17:01
Не верифицирован
21 июл 2026, 17:01
4ca1cfc
Код
Авторство
О чём код?
package pluginconfig import ( "context" "fmt" "os" "slices" "strconv" "strings" "github.com/grafana/grafana-aws-sdk/pkg/awsds" "github.com/grafana/grafana-azure-sdk-go/v2/azsettings" "github.com/grafana/grafana-plugin-sdk-go/backend/proxy" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/envvars" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/marketplacelicensing" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso" ) var _ envvars.Provider = (*EnvVarsProvider)(nil) type EnvVarsProvider struct { cfg *PluginInstanceCfg license plugins.Licensing marketplaceLicensing marketplacelicensing.Licensing logger log.Logger ssoSettings pluginsso.SettingsProvider } func NewEnvVarsProvider(cfg *PluginInstanceCfg, license plugins.Licensing, ssoSettings pluginsso.SettingsProvider, marketplace marketplacelicensing.Licensing) *EnvVarsProvider { return &EnvVarsProvider{ cfg: cfg, license: license, logger: log.New("plugins.envvars"), marketplaceLicensing: marketplace, ssoSettings: ssoSettings, } } func (p *EnvVarsProvider) PluginEnvVars(ctx context.Context, plugin *plugins.Plugin) []string { hostEnv := []string{ p.envVar("GF_VERSION", p.cfg.GrafanaVersion), } if p.license != nil { hostEnv = append( hostEnv, p.envVar("GF_EDITION", p.license.Edition()), p.envVar("GF_ENTERPRISE_LICENSE_PATH", p.license.Path()), p.envVar("GF_ENTERPRISE_APP_URL", p.license.AppURL()), ) hostEnv = append(hostEnv, p.license.Environment()...) } if plugin.ExternalService != nil { hostEnv = append( hostEnv, p.envVar("GF_APP_URL", p.cfg.GrafanaAppURL), p.envVar("GF_PLUGIN_APP_CLIENT_ID", plugin.ExternalService.ClientID), p.envVar("GF_PLUGIN_APP_CLIENT_SECRET", plugin.ExternalService.ClientSecret), ) if plugin.ExternalService.PrivateKey != "" { hostEnv = append(hostEnv, p.envVar("GF_PLUGIN_APP_PRIVATE_KEY", plugin.ExternalService.PrivateKey)) } } hostEnv = append(hostEnv, p.featureToggleEnableVars(ctx)...) marketplaceEnvVars := p.marketplaceLicenseEnvVars(ctx, plugin.PluginID()) p.logger.Debug("Providing marketplace env vars", "pluginId", plugin.PluginID(), "envVars", envVarNames(marketplaceEnvVars)) hostEnv = append(hostEnv, marketplaceEnvVars...) hostEnv = append(hostEnv, p.awsEnvVars(plugin.PluginID())...) hostEnv = append(hostEnv, p.secureSocksProxyEnvVars()...) azureSettings := p.getAzureSettings() hostEnv = append(hostEnv, azsettings.WriteToEnvStr(azureSettings)...) hostEnv = append(hostEnv, p.azureHostEnvVars(azureSettings, plugin.PluginID())...) hostEnv = append(hostEnv, p.tracingEnvVars(plugin)...) hostEnv = append(hostEnv, p.pluginSettingsEnvVars(plugin.PluginID())...) // If SkipHostEnvVars is enabled, get some allowed variables from the current process and pass // them down to the plugin. If the flag is not set, do not add anything else because ALL env vars // from the current process (os.Environ()) will be forwarded to the plugin's process by go-plugin if plugin.SkipHostEnvVars { hostEnv = append(hostEnv, envvars.PermittedHostEnvVars()...) } return hostEnv } func (p *EnvVarsProvider) marketplaceLicenseEnvVars(ctx context.Context, pluginID string) []string { // Marketplace plugins require feature toggle and a valid Enterprise license if p.cfg.Features == nil || !p.cfg.Features.GetEnabled(ctx)[featuremgmt.FlagPluginsMarketplaceLicensing] { return nil } if p.license == nil || !p.license.HasValidLicense() { return nil } // Try to get the license token, falling-back to the JWT path on disk if token is not available. token, err := p.marketplaceLicensing.LicenseToken(ctx, pluginID) if err != nil { p.logger.Warn("Failed to get marketplace license token, falling-back to disk license", "pluginId", pluginID, "error", err) token = "" } var licensePath string var hasPath bool if p.cfg.MarketplaceLicenseDirectory != "" { licensePath, hasPath = marketplacelicensing.LicensePath(p.cfg.MarketplaceLicenseDirectory, pluginID) } if token == "" && !hasPath { return nil } // Pass the most relevant marketplace license information to the plugin. // The SDK gives higher priority to the license token over the license path. variables := []string{p.envVar("GF_MARKETPLACE_APP_URL", p.marketplaceLicensing.AppURL())} if token != "" { variables = append(variables, p.envVar("GF_MARKETPLACE_LICENSE_TEXT", token)) } if hasPath { variables = append(variables, p.envVar("GF_MARKETPLACE_LICENSE_PATH", licensePath)) } return variables } func (p *EnvVarsProvider) featureToggleEnableVars(ctx context.Context) []string { var variables []string // an array is used for consistency and keep the logic simpler for no features case if p.cfg.Features == nil { return variables } enabledFeatures := p.cfg.Features.GetEnabled(ctx) if len(enabledFeatures) > 0 { features := make([]string, 0, len(enabledFeatures)) for feat := range enabledFeatures { features = append(features, feat) } variables = append(variables, p.envVar("GF_INSTANCE_FEATURE_TOGGLES_ENABLE", strings.Join(features, ","))) } return variables } func (p *EnvVarsProvider) awsEnvVars(pluginID string) []string { if !slices.Contains[[]string, string](p.cfg.AWSForwardSettingsPlugins, pluginID) { return []string{} } var variables []string if !p.cfg.AWSAssumeRoleEnabled { variables = append(variables, p.envVar(awsds.AssumeRoleEnabledEnvVarKeyName, "false")) } if p.cfg.AWSPerDatasourceHTTPProxyEnabled { variables = append(variables, p.envVar(awsds.PerDatasourceHTTPProxyEnabledEnvVarKeyName, "true")) } if len(p.cfg.AWSAllowedAuthProviders) > 0 { variables = append(variables, p.envVar(awsds.AllowedAuthProvidersEnvVarKeyName, strings.Join(p.cfg.AWSAllowedAuthProviders, ","))) } if p.cfg.AWSExternalId != "" { variables = append(variables, p.envVar(awsds.GrafanaAssumeRoleExternalIdKeyName, p.cfg.AWSExternalId)) } if p.cfg.AWSSessionDuration != "" { variables = append(variables, p.envVar(awsds.SessionDurationEnvVarKeyName, p.cfg.AWSSessionDuration)) } if p.cfg.AWSListMetricsPageLimit != "" { 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", } // azureManagedIdentityHostEnvVarNames are host vars injected by Azure // platforms (App Service, Container Apps, Service Fabric, Arc, Cloud Shell) // that the azidentity SDK reads to locate the local managed identity // token endpoint. Without them the SDK falls back to IMDS, which is not // reachable on platforms like Azure Container Apps. var azureManagedIdentityHostEnvVarNames = []string{ "IDENTITY_ENDPOINT", "IDENTITY_HEADER", "IDENTITY_SERVER_THUMBPRINT", "IMDS_ENDPOINT", "MSI_ENDPOINT", "MSI_SECRET", } // azureWorkloadIdentityHostEnvVarNames are host vars injected by the AKS // azure-workload-identity mutating webhook into pods that use Azure AD // Workload Identity federation. var azureWorkloadIdentityHostEnvVarNames = []string{ "AZURE_TENANT_ID", "AZURE_CLIENT_ID", "AZURE_FEDERATED_TOKEN_FILE", "AZURE_AUTHORITY_HOST", } func (p *EnvVarsProvider) azureHostEnvVars(azureSettings *azsettings.AzureSettings, pluginID string) []string { if azureSettings == nil { return nil } if !slices.Contains(azureSettings.ForwardSettingsPlugins, pluginID) { return nil } var variables []string if azureSettings.ManagedIdentityEnabled { for _, envVarName := range azureManagedIdentityHostEnvVarNames { if v, ok := os.LookupEnv(envVarName); ok { variables = append(variables, p.envVar(envVarName, v)) } } } if azureSettings.WorkloadIdentityEnabled { for _, envVarName := range azureWorkloadIdentityHostEnvVarNames { if v, ok := os.LookupEnv(envVarName); ok { variables = append(variables, p.envVar(envVarName, v)) } } } return variables } func (p *EnvVarsProvider) secureSocksProxyEnvVars() []string { if p.cfg.ProxySettings.Enabled { return []string{ // nolint:staticcheck p.envVar(proxy.PluginSecureSocksProxyClientCertFilePathEnvVarName, p.cfg.ProxySettings.ClientCertFilePath), // nolint:staticcheck p.envVar(proxy.PluginSecureSocksProxyClientKeyFilePathEnvVarName, p.cfg.ProxySettings.ClientKeyFilePath), // nolint:staticcheck p.envVar(proxy.PluginSecureSocksProxyRootCACertFilePathsEnvVarName, strings.Join(p.cfg.ProxySettings.RootCAFilePaths, " ")), // nolint:staticcheck p.envVar(proxy.PluginSecureSocksProxyAddressEnvVarName, p.cfg.ProxySettings.ProxyAddress), // nolint:staticcheck p.envVar(proxy.PluginSecureSocksProxyServerNameEnvVarName, p.cfg.ProxySettings.ServerName), // nolint:staticcheck p.envVar(proxy.PluginSecureSocksProxyEnabledEnvVarName, strconv.FormatBool(p.cfg.ProxySettings.Enabled)), // nolint:staticcheck p.envVar(proxy.PluginSecureSocksProxyAllowInsecureEnvVarName, strconv.FormatBool(p.cfg.ProxySettings.AllowInsecure)), } } return nil } func (p *EnvVarsProvider) tracingEnvVars(plugin *plugins.Plugin) []string { if !p.cfg.Tracing.IsEnabled() { return nil } vars := []string{ p.envVar("GF_INSTANCE_OTLP_ADDRESS", p.cfg.Tracing.OpenTelemetry.Address), p.envVar("GF_INSTANCE_OTLP_PROPAGATION", p.cfg.Tracing.OpenTelemetry.Propagation), p.envVar("GF_INSTANCE_OTLP_SAMPLER_TYPE", p.cfg.Tracing.OpenTelemetry.Sampler), fmt.Sprintf("GF_INSTANCE_OTLP_SAMPLER_PARAM=%.6f", p.cfg.Tracing.OpenTelemetry.SamplerParam), p.envVar("GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL", p.cfg.Tracing.OpenTelemetry.SamplerRemoteURL), } if plugin.Info.Version != "" { vars = append(vars, fmt.Sprintf("GF_PLUGIN_VERSION=%s", plugin.Info.Version)) } return vars } func (p *EnvVarsProvider) pluginSettingsEnvVars(pluginID string) []string { const customConfigPrefix = "GF_PLUGIN" pluginSettings := p.cfg.PluginSettings[pluginID] env := make([]string, 0, len(pluginSettings)) for k, v := range pluginSettings { if k == "path" || strings.ToLower(k) == "id" { continue } key := fmt.Sprintf("%s_%s", customConfigPrefix, strings.ToUpper(k)) if value := os.Getenv(key); value != "" { v = value } env = append(env, fmt.Sprintf("%s=%s", key, v)) } return env } // envVar returns a string in the format "key=value" for an environment variable. func (p *EnvVarsProvider) envVar(key, value string) string { if strings.Contains(value, "\x00") { p.logger.Error("Variable with key '%s' contains NUL", key) } return fmt.Sprintf("%s=%s", key, value) } // envVarNames returns the names of the environment variables from a list of "key=value" strings. func envVarNames(envVars []string) []string { names := make([]string, 0, len(envVars)) for _, envVar := range envVars { parts := strings.SplitN(envVar, "=", 2) if len(parts) > 0 { names = append(names, parts[0]) } } return names } func (p *EnvVarsProvider) getAzureSettings() *azsettings.AzureSettings { azureSettings := p.cfg.Azure if azureSettings == nil { azureSettings = &azsettings.AzureSettings{} } azureAdSettings, err := p.ssoSettings.GetForProvider(context.Background(), social.AzureADProviderName) if err != nil { p.logger.Error("Failed to get SSO settings", "error", err) } return mergeAzureSettings(azureSettings, azureAdSettings) }