Authentication/Authorization
Description
The commit re-enables forwarding of Azure managed identity related environment variables from the host to plugin processes, gated on the Azure settings and plugin allowlist. Specifically, when Azure managed identity or workload identity is enabled and a plugin is on the forward list, the provider now forwards environment variables such as IDENTIY_ENDPOINT, IDENTITY_HEADER, IDENTITY_SERVER_THUMBPRINT, IMDS_ENDPOINT, MSI_ENDPOINT, MSI_SECRET, AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, and AZURE_AUTHORITY_HOST to the plugin process. This restores proper credential discovery for Azure plugins by the azidentity SDK, which can rely on these env vars to locate the local managed identity endpoint. The fix addresses a regression introduced in 12.4.0 that stopped forwarding host env vars, which could cause plugins to fail authentication or fall back to less secure/default endpoints. While this improves correct authentication flow for plugins, it also expands the surface area exposed to plugins by handing them potentially sensitive identity-related environment data. The change is gated by an allowlist and enabled identity modes, aligning with a prior AWS fix that forwards similar host vars to plugins.
Proof of Concept
PoC steps (conceptual):
1) Environment setup: Run Grafana 12.4.0 with an Azure plugin (e.g., Grafana's Azure Data Explorer plugin) and enable managed identity or workload identity. Add the plugin to azure.forward_settings_to_plugins so it is allowed to receive host env vars.
2) Host env vars: Ensure the host exposes identity-related environment variables commonly used by Azure identity discovery, e.g. IDENTIY_ENDPOINT, IDENTITY_HEADER, MSI_ENDPOINT, MSI_SECRET, IMDS_ENDPOINT, AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, AZURE_AUTHORITY_HOST.
3) Malicious plugin scenario: A compromised plugin is executed by Grafana. Because the fix forwards the identity-related host env vars to the plugin, the plugin can leverage Azure SDK’s DefaultAzureCredential (or equivalent) to request tokens for a resource (e.g., https://management.azure.com/.default).
4) Token retrieval: The plugin calls into the Azure identity library to fetch a token using the forwarded environment endpoints/credentials. If the attacker plugin succeeds, it obtains a token scoped to target resources and can use it to access those resources, effectively acting with the host's managed identity.
5) Impact: Demonstrates a potential privilege-assignment path via a misbehaving or malicious plugin that has access to host identity data. This PoC does not require Grafana server compromise beyond enabling a plugin to read forwarded identity env vars; it relies on the plugin being able to call the Azure identity flow using those vars.
Code sketch (Go, illustrative):
package main
import (
"context"
"log"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)
func main(){
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil { log.Fatal(err) }
tok, err := cred.GetToken(context.Background(), azidentity.TokenRequestOptions{Scopes: []string{"https://management.azure.com/.default"}})
if err != nil { log.Fatal(err) }
fmt.Println("token=", tok.Token)
}
Commit Details
Author: Timur Olzhabayev
Date: 2026-06-10 17:08 UTC
Message:
fix(plugins): forward Azure managed identity env vars to plugin processes (#125170)
Azure platforms (Container Apps, App Service, AKS Workload Identity) inject
discovery env vars (IDENTITY_ENDPOINT, IDENTITY_HEADER, AZURE_FEDERATED_TOKEN_FILE,
etc.) that the azidentity SDK reads from the plugin process. PR #113412 stopped
forwarding host env vars by default in 12.4.0, which broke managed identity auth
for Azure plugins (e.g. Azure Data Explorer).
Mirror the AWS fix (#119772): forward these host env vars to plugins on the
existing azure.forward_settings_to_plugins allowlist, gated on
managed_identity_enabled / workload_identity_enabled.
Fixes #120377
Triage Assessment
Vulnerability Type: Authentication/Authorization
Confidence: HIGH
Reasoning:
Commit forwards Azure managed identity related environment variables to plugin processes so that plugins can authenticate using Azure managed identities. Without these vars, the credential discovery could fall back to non-restrictive endpoints (e.g., IMDS) or fail authentication, effectively weakening auth against Azure identity providers. This is a security fix to ensure proper authentication flow for plugins.
Verification Assessment
Vulnerability Type: Authentication/Authorization
Confidence: HIGH
Affected Versions: Grafana 12.4.0 (and the 12.4.x line prior to subsequent releases)
Code Diff
diff --git a/pkg/services/pluginsintegration/pluginconfig/envvars.go b/pkg/services/pluginsintegration/pluginconfig/envvars.go
index 565f6892cced3..34a0005ff9186 100644
--- a/pkg/services/pluginsintegration/pluginconfig/envvars.go
+++ b/pkg/services/pluginsintegration/pluginconfig/envvars.go
@@ -67,7 +67,9 @@ func (p *EnvVarsProvider) PluginEnvVars(ctx context.Context, plugin *plugins.Plu
hostEnv = append(hostEnv, p.featureToggleEnableVars(ctx)...)
hostEnv = append(hostEnv, p.awsEnvVars(plugin.PluginID())...)
hostEnv = append(hostEnv, p.secureSocksProxyEnvVars()...)
- hostEnv = append(hostEnv, azsettings.WriteToEnvStr(p.getAzureSettings())...)
+ 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())...)
@@ -153,6 +155,56 @@ var awsHostEnvVarNames = []string{
"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{
diff --git a/pkg/services/pluginsintegration/pluginconfig/envvars_test.go b/pkg/services/pluginsintegration/pluginconfig/envvars_test.go
index 51c023c35fa1f..aa1add7a766be 100644
--- a/pkg/services/pluginsintegration/pluginconfig/envvars_test.go
+++ b/pkg/services/pluginsintegration/pluginconfig/envvars_test.go
@@ -785,3 +785,160 @@ func TestPluginEnvVarsProvider_azureEnvVars(t *testing.T) {
}, envVars)
})
}
+
+func TestPluginEnvVarsProvider_azureHostEnvVars(t *testing.T) {
+ tcs := []struct {
+ name string
+ pluginID string
+ forwardSettingsPlugins []string
+ managedIdentityEnabled bool
+ workloadIdentityEnabled bool
+ hostEnvVars map[string]string
+ expectedKeys []string
+ unexpectedKeys []string
+ }{
+ {
+ name: "forwards managed identity host vars when MSI enabled and plugin allowlisted",
+ pluginID: "grafana-azure-data-explorer-datasource",
+ forwardSettingsPlugins: []string{"grafana-azure-data-explorer-datasource"},
+ managedIdentityEnabled: true,
+ hostEnvVars: map[string]string{
+ "IDENTITY_ENDPOINT": "http://localhost:42356/msi/token",
+ "IDENTITY_HEADER": "header-value",
+ },
+ expectedKeys: []string{
+ "IDENTITY_ENDPOINT=http://localhost:42356/msi/token",
+ "IDENTITY_HEADER=header-value",
+ },
+ },
+ {
+ name: "forwards workload identity host vars when WI enabled and plugin allowlisted",
+ pluginID: "grafana-azure-data-explorer-datasource",
+ forwardSettingsPlugins: []string{"grafana-azure-data-explorer-datasource"},
+ workloadIdentityEnabled: true,
+ hostEnvVars: map[string]string{
+ "AZURE_TENANT_ID": "tenant",
+ "AZURE_CLIENT_ID": "client",
+ "AZURE_FEDERATED_TOKEN_FILE": "/var/run/secrets/azure/tokens/azure-identity-token",
+ "AZURE_AUTHORITY_HOST": "https://login.microsoftonline.com/",
+ },
+ expectedKeys: []string{
+ "AZURE_TENANT_ID=tenant",
+ "AZURE_CLIENT_ID=client",
+ "AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token",
+ "AZURE_AUTHORITY_HOST=https://login.microsoftonline.com/",
+ },
+ },
+ {
+ name: "forwards both sets when both auth modes enabled",
+ pluginID: "grafana-azure-monitor-datasource",
+ forwardSettingsPlugins: []string{"grafana-azure-monitor-datasource"},
+ managedIdentityEnabled: true,
+ workloadIdentityEnabled: true,
+ hostEnvVars: map[string]string{
+ "IDENTITY_ENDPOINT": "http://localhost/msi",
+ "AZURE_FEDERATED_TOKEN_FILE": "/var/run/token",
+ },
+ expectedKeys: []string{
+ "IDENTITY_ENDPOINT=http://localhost/msi",
+ "AZURE_FEDERATED_TOKEN_FILE=/var/run/token",
+ },
+ },
+ {
+ name: "does not forward MSI host vars when MSI disabled even if plugin allowlisted",
+ pluginID: "grafana-azure-data-explorer-datasource",
+ forwardSettingsPlugins: []string{"grafana-azure-data-explorer-datasource"},
+ managedIdentityEnabled: false,
+ hostEnvVars: map[string]string{
+ "IDENTITY_ENDPOINT": "http://localhost/msi",
+ "IDENTITY_HEADER": "header",
+ },
+ unexpectedKeys: []string{"IDENTITY_ENDPOINT", "IDENTITY_HEADER"},
+ },
+ {
+ name: "does not forward workload identity host vars when WI disabled",
+ pluginID: "grafana-azure-data-explorer-datasource",
+ forwardSettingsPlugins: []string{"grafana-azure-data-explorer-datasource"},
+ workloadIdentityEnabled: false,
+ hostEnvVars: map[string]string{
+ "AZURE_FEDERATED_TOKEN_FILE": "/var/run/token",
+ "AZURE_CLIENT_ID": "client",
+ },
+ unexpectedKeys: []string{"AZURE_FEDERATED_TOKEN_FILE", "AZURE_CLIENT_ID"},
+ },
+ {
+ name: "does not forward to plugins not in azure forward_settings_to_plugins",
+ pluginID: "some-other-plugin",
+ forwardSettingsPlugins: []string{"grafana-azure-data-explorer-datasource"},
+ managedIdentityEnabled: true,
+ workloadIdentityEnabled: true,
+ hostEnvVars: map[string]string{
+ "IDENTITY_ENDPOINT": "http://localhost/msi",
+ "AZURE_FEDERATED_TOKEN_FILE": "/var/run/token",
+ },
+ unexpectedKeys: []string{"IDENTITY_ENDPOINT", "AZURE_FEDERATED_TOKEN_FILE"},
+ },
+ {
+ name: "only forwards host vars that are actually set",
+ pluginID: "grafana-azure-data-explorer-datasource",
+ forwardSettingsPlugins: []string{"grafana-azure-data-explorer-datasource"},
+ managedIdentityEnabled: true,
+ workloadIdentityEnabled: true,
+ hostEnvVars: map[string]string{
+ "IDENTITY_ENDPOINT": "http://localhost/msi",
+ "AZURE_CLIENT_ID": "client",
+ },
+ expectedKeys: []string{
+ "IDENTITY_ENDPOINT=http://localhost/msi",
+ "AZURE_CLIENT_ID=client",
+ },
+ unexpectedKeys: []string{
+ "IDENTITY_HEADER", "MSI_ENDPOINT", "MSI_SECRET", "IMDS_ENDPOINT",
+ "AZURE_TENANT_ID", "AZURE_FEDERATED_TOKEN_FILE", "AZURE_AUTHORITY_HOST",
+ },
+ },
+ }
+
+ for _, tc := range tcs {
+ t.Run(tc.name, func(t *testing.T) {
+ // Clear any pre-existing Azure host env vars (e.g., from CI) that aren't
+ // explicitly set by this test case, so they don't leak into results.
+ for _, envVarName := range append(append([]string{}, azureManagedIdentityHostEnvVarNames...), azureWorkloadIdentityHostEnvVarNames...) {
+ if _, ok := tc.hostEnvVars[envVarName]; !ok {
+ require.NoError(t, os.Unsetenv(envVarName))
+ }
+ }
+ for k, v := range tc.hostEnvVars {
+ t.Setenv(k, v)
+ }
+
+ p := &plugins.Plugin{
+ JSONData: plugins.JSONData{
+ ID: tc.pluginID,
+ },
+ }
+ cfg := &setting.Cfg{
+ Raw: ini.Empty(),
+ Azure: &azsettings.AzureSettings{
+ ManagedIdentityEnabled: tc.managedIdentityEnabled,
+ WorkloadIdentityEnabled: tc.workloadIdentityEnabled,
+ ForwardSettingsPlugins: tc.forwardSettingsPlugins,
+ },
+ }
+
+ pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures())
+ require.NoError(t, err)
+
+ provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{})
+ envVars := provider.PluginEnvVars(context.Background(), p)
+
+ for _, expected := range tc.expectedKeys {
+ assert.Contains(t, envVars, expected, "expected env var %s to be forwarded", expected)
+ }
+ for _, key := range tc.unexpectedKeys {
+ _, ok := getEnvVarWithExists(envVars, key)
+ assert.False(t, ok, "env var %s should not be forwarded", key)
+ }
+ })
+ }
+}