Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugins: Include Azure settings as a part of Grafana config sent in plugin requests #79342

Merged
merged 11 commits into from
Dec 14, 2023
4 changes: 4 additions & 0 deletions conf/defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,10 @@ user_identity_client_id =
# By default is the same as used in AAD authentication or can be set to another application (for OBO flow)
user_identity_client_secret =

# Set the plugins that will receive Azure settings for each request (via plugin context)
# By default this will include all Grafana Labs owned Azure plugins, or those that make use of Azure settings (Azure Monitor, Azure Data Explorer, Prometheus, MSSQL).
forward_settings_to_plugins = grafana-azure-monitor-datasource, prometheus, grafana-azure-data-explorer-datasource, mssql

#################################### Role-based Access Control ###########
[rbac]
# If enabled, cache permissions in a in memory cache
Expand Down
4 changes: 4 additions & 0 deletions conf/sample.ini
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,10 @@
# By default is the same as used in AAD authentication or can be set to another application (for OBO flow)
;user_identity_client_secret =

# Set the plugins that will receive Azure settings for each request (via plugin context)
# By default this will include all Grafana Labs owned Azure plugins, or those that make use of Azure settings (Azure Monitor, Azure Data Explorer, Prometheus, MSSQL).
;forward_settings_to_plugins = grafana-azure-monitor-datasource, prometheus, grafana-azure-data-explorer-datasource, mssql

#################################### Role-based Access Control ###########
[rbac]
;permission_cache = true
Expand Down
6 changes: 6 additions & 0 deletions docs/sources/setup-grafana/configure-grafana/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,12 @@ Override the AAD application client secret.

By default is the same as used in AAD authentication or can be set to another application (for OBO flow).

### forward_settings_to_plugins

Set plugins that will receive Azure settings via plugin context.

By default this will include all Grafana Labs owned Azure plugins, or those that make use of Azure settings (Azure Monitor, Azure Data Explorer, Prometheus, MSSQL).
aangelisc marked this conversation as resolved.
Show resolved Hide resolved

## [auth.jwt]

Refer to [JWT authentication]({{< relref "../configure-security/configure-authentication/jwt" >}}) for more information.
Expand Down
87 changes: 52 additions & 35 deletions pkg/plugins/envvars/envvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"slices"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -98,7 +99,7 @@ func (s *Service) Get(ctx context.Context, p *plugins.Plugin) []string {
}

// GetConfigMap returns a map of configuration that should be passed in a plugin request.
func (s *Service) GetConfigMap(ctx context.Context, _ string, _ *auth.ExternalService) map[string]string {
func (s *Service) GetConfigMap(ctx context.Context, pluginID string, _ *auth.ExternalService) map[string]string {
m := make(map[string]string)

if s.cfg.GrafanaAppURL != "" {
Expand Down Expand Up @@ -144,40 +145,56 @@ func (s *Service) GetConfigMap(ctx context.Context, _ string, _ *auth.ExternalSe
m[proxy.PluginSecureSocksProxyServerName] = s.cfg.ProxySettings.ServerName
}

// TODO add support via plugin SDK
//azureSettings := s.cfg.Azure
//if azureSettings != nil {
// if azureSettings.Cloud != "" {
// m[azsettings.AzureCloud] = azureSettings.Cloud
// }
//
// if azureSettings.ManagedIdentityEnabled {
// m[azsettings.ManagedIdentityEnabled] = "true"
//
// if azureSettings.ManagedIdentityClientId != "" {
// m[azsettings.ManagedIdentityClientID] = azureSettings.ManagedIdentityClientId
// }
// }
//
// if azureSettings.UserIdentityEnabled {
// m[azsettings.UserIdentityEnabled] = "true"
//
// if azureSettings.UserIdentityTokenEndpoint != nil {
// if azureSettings.UserIdentityTokenEndpoint.TokenUrl != "" {
// m[azsettings.UserIdentityTokenURL] = azureSettings.UserIdentityTokenEndpoint.TokenUrl
// }
// if azureSettings.UserIdentityTokenEndpoint.ClientId != "" {
// m[azsettings.UserIdentityClientID] = azureSettings.UserIdentityTokenEndpoint.ClientId
// }
// if azureSettings.UserIdentityTokenEndpoint.ClientSecret != "" {
// m[azsettings.UserIdentityClientSecret] = azureSettings.UserIdentityTokenEndpoint.ClientSecret
// }
// if azureSettings.UserIdentityTokenEndpoint.UsernameAssertion {
// m[azsettings.UserIdentityAssertion] = "username"
// }
// }
// }
//}
// Settings here will be extracted by grafana-azure-sdk-go from the plugin context
azureSettings := s.cfg.Azure
if azureSettings != nil && slices.Contains[[]string, string](azureSettings.ForwardSettingsPlugins, pluginID) {
if azureSettings.Cloud != "" {
m[azsettings.AzureCloud] = azureSettings.Cloud
}

if azureSettings.ManagedIdentityEnabled {
m[azsettings.ManagedIdentityEnabled] = "true"

if azureSettings.ManagedIdentityClientId != "" {
m[azsettings.ManagedIdentityClientID] = azureSettings.ManagedIdentityClientId
}
}

if azureSettings.UserIdentityEnabled {
m[azsettings.UserIdentityEnabled] = "true"

if azureSettings.UserIdentityTokenEndpoint != nil {
if azureSettings.UserIdentityTokenEndpoint.TokenUrl != "" {
m[azsettings.UserIdentityTokenURL] = azureSettings.UserIdentityTokenEndpoint.TokenUrl
}
if azureSettings.UserIdentityTokenEndpoint.ClientId != "" {
m[azsettings.UserIdentityClientID] = azureSettings.UserIdentityTokenEndpoint.ClientId
}
if azureSettings.UserIdentityTokenEndpoint.ClientSecret != "" {
m[azsettings.UserIdentityClientSecret] = azureSettings.UserIdentityTokenEndpoint.ClientSecret
}
if azureSettings.UserIdentityTokenEndpoint.UsernameAssertion {
m[azsettings.UserIdentityAssertion] = "username"
}
}
}

if azureSettings.WorkloadIdentityEnabled {
m[azsettings.WorkloadIdentityEnabled] = "true"

if azureSettings.WorkloadIdentitySettings != nil {
if azureSettings.WorkloadIdentitySettings.ClientId != "" {
m[azsettings.WorkloadIdentityClientID] = azureSettings.WorkloadIdentitySettings.ClientId
}
if azureSettings.WorkloadIdentitySettings.TenantId != "" {
m[azsettings.WorkloadIdentityTenantID] = azureSettings.WorkloadIdentitySettings.TenantId
}
if azureSettings.WorkloadIdentitySettings.TokenFile != "" {
m[azsettings.WorkloadIdentityTokenFile] = azureSettings.WorkloadIdentitySettings.TokenFile
}
}
}
}

// TODO add support via plugin SDK
//ps := getPluginSettings(pluginID, s.cfg)
Expand Down
114 changes: 114 additions & 0 deletions pkg/plugins/envvars/envvars_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/auth"
"github.com/grafana/grafana/pkg/plugins/config"
Expand Down Expand Up @@ -598,6 +599,45 @@ func TestInitializer_featureToggleEnvVar(t *testing.T) {
})
}

func TestInitalizer_azureEnvVars(t *testing.T) {
t.Run("backend datasource with azure settings", func(t *testing.T) {
p := &plugins.Plugin{}
envVarsProvider := NewProvider(&config.Cfg{
Azure: &azsettings.AzureSettings{
Cloud: azsettings.AzurePublic,
ManagedIdentityEnabled: true,
ManagedIdentityClientId: "mock_managed_identity_client_id",
WorkloadIdentityEnabled: true,
WorkloadIdentitySettings: &azsettings.WorkloadIdentitySettings{
TenantId: "mock_workload_identity_tenant_id",
ClientId: "mock_workload_identity_client_id",
TokenFile: "mock_workload_identity_token_file",
},
UserIdentityEnabled: true,
UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{
TokenUrl: "mock_user_identity_token_url",
ClientId: "mock_user_identity_client_id",
ClientSecret: "mock_user_identity_client_secret",
UsernameAssertion: true,
},
},
}, nil)
envVars := envVarsProvider.Get(context.Background(), p)
assert.ElementsMatch(t, []string{"GF_VERSION=", "GFAZPL_AZURE_CLOUD=AzureCloud", "GFAZPL_MANAGED_IDENTITY_ENABLED=true",
"GFAZPL_MANAGED_IDENTITY_CLIENT_ID=mock_managed_identity_client_id",
"GFAZPL_WORKLOAD_IDENTITY_ENABLED=true",
"GFAZPL_WORKLOAD_IDENTITY_TENANT_ID=mock_workload_identity_tenant_id",
"GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID=mock_workload_identity_client_id",
"GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE=mock_workload_identity_token_file",
"GFAZPL_USER_IDENTITY_ENABLED=true",
"GFAZPL_USER_IDENTITY_TOKEN_URL=mock_user_identity_token_url",
"GFAZPL_USER_IDENTITY_CLIENT_ID=mock_user_identity_client_id",
"GFAZPL_USER_IDENTITY_CLIENT_SECRET=mock_user_identity_client_secret",
"GFAZPL_USER_IDENTITY_ASSERTION=username",
}, envVars)
})
}

func TestService_GetConfigMap(t *testing.T) {
tcs := []struct {
name string
Expand Down Expand Up @@ -730,3 +770,77 @@ func TestService_GetConfigMap_appURL(t *testing.T) {
require.Equal(t, map[string]string{"GF_APP_URL": "https://myorg.com/"}, s.GetConfigMap(context.Background(), "", nil))
})
}

func TestService_GetConfigMap_azure(t *testing.T) {
azSettings := &azsettings.AzureSettings{
Cloud: azsettings.AzurePublic,
ManagedIdentityEnabled: true,
ManagedIdentityClientId: "mock_managed_identity_client_id",
WorkloadIdentityEnabled: true,
WorkloadIdentitySettings: &azsettings.WorkloadIdentitySettings{
TenantId: "mock_workload_identity_tenant_id",
ClientId: "mock_workload_identity_client_id",
TokenFile: "mock_workload_identity_token_file",
},
UserIdentityEnabled: true,
UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{
TokenUrl: "mock_user_identity_token_url",
ClientId: "mock_user_identity_client_id",
ClientSecret: "mock_user_identity_client_secret",
UsernameAssertion: true,
},
ForwardSettingsPlugins: []string{"grafana-azure-monitor-datasource", "prometheus", "grafana-azure-data-explorer-datasource", "mssql"},
}

t.Run("uses the azure settings for an Azure plugin", func(t *testing.T) {
s := &Service{
cfg: &config.Cfg{
Azure: azSettings,
},
}
require.Equal(t, map[string]string{
"GFAZPL_AZURE_CLOUD": "AzureCloud", "GFAZPL_MANAGED_IDENTITY_ENABLED": "true",
"GFAZPL_MANAGED_IDENTITY_CLIENT_ID": "mock_managed_identity_client_id",
"GFAZPL_WORKLOAD_IDENTITY_ENABLED": "true",
"GFAZPL_WORKLOAD_IDENTITY_TENANT_ID": "mock_workload_identity_tenant_id",
"GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID": "mock_workload_identity_client_id",
"GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE": "mock_workload_identity_token_file",
"GFAZPL_USER_IDENTITY_ENABLED": "true",
"GFAZPL_USER_IDENTITY_TOKEN_URL": "mock_user_identity_token_url",
"GFAZPL_USER_IDENTITY_CLIENT_ID": "mock_user_identity_client_id",
"GFAZPL_USER_IDENTITY_CLIENT_SECRET": "mock_user_identity_client_secret",
"GFAZPL_USER_IDENTITY_ASSERTION": "username",
}, s.GetConfigMap(context.Background(), "grafana-azure-monitor-datasource", nil))
})

t.Run("does not use the azure settings for a non-Azure plugin", func(t *testing.T) {
s := &Service{
cfg: &config.Cfg{
Azure: azSettings,
},
}
require.Equal(t, map[string]string{}, s.GetConfigMap(context.Background(), "", nil))
})

t.Run("uses the azure settings for a non-Azure user-specified plugin", func(t *testing.T) {
azSettings.ForwardSettingsPlugins = append(azSettings.ForwardSettingsPlugins, "test-datasource")
s := &Service{
cfg: &config.Cfg{
Azure: azSettings,
},
}
require.Equal(t, map[string]string{
"GFAZPL_AZURE_CLOUD": "AzureCloud", "GFAZPL_MANAGED_IDENTITY_ENABLED": "true",
"GFAZPL_MANAGED_IDENTITY_CLIENT_ID": "mock_managed_identity_client_id",
"GFAZPL_WORKLOAD_IDENTITY_ENABLED": "true",
"GFAZPL_WORKLOAD_IDENTITY_TENANT_ID": "mock_workload_identity_tenant_id",
"GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID": "mock_workload_identity_client_id",
"GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE": "mock_workload_identity_token_file",
"GFAZPL_USER_IDENTITY_ENABLED": "true",
"GFAZPL_USER_IDENTITY_TOKEN_URL": "mock_user_identity_token_url",
"GFAZPL_USER_IDENTITY_CLIENT_ID": "mock_user_identity_client_id",
"GFAZPL_USER_IDENTITY_CLIENT_SECRET": "mock_user_identity_client_secret",
"GFAZPL_USER_IDENTITY_ASSERTION": "username",
}, s.GetConfigMap(context.Background(), "test-datasource", nil))
})
}
5 changes: 5 additions & 0 deletions pkg/setting/setting_azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package setting

import (
"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana/pkg/util"
)

func (cfg *Cfg) readAzureSettings() {
Expand Down Expand Up @@ -63,5 +64,9 @@ func (cfg *Cfg) readAzureSettings() {
azureSettings.UserIdentityTokenEndpoint = tokenEndpointSettings
}

if plugins := util.SplitString(azureSection.Key("forward_settings_to_plugins").String()); len(plugins) > 0 {
aangelisc marked this conversation as resolved.
Show resolved Hide resolved
azureSettings.ForwardSettingsPlugins = plugins
}

cfg.Azure = azureSettings
}
37 changes: 37 additions & 0 deletions pkg/setting/setting_azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,41 @@ func TestAzureSettings(t *testing.T) {
assert.Empty(t, cfg.Azure.UserIdentityTokenEndpoint.ClientSecret)
})
})

t.Run("forward settings to plugins", func(t *testing.T) {
defaultPlugins := []string{"grafana-azure-monitor-datasource", "prometheus", "grafana-azure-data-explorer-datasource", "mssql"}
testCases := []struct {
name string
configuredValue string
resolvedValue []string
}{
{
name: "should be Grafana Labs data sources if not set",
configuredValue: "",
resolvedValue: defaultPlugins,
},
{
name: "should append a user specified plugin if set",
configuredValue: "test-datasource",
resolvedValue: append(defaultPlugins, "test-datasource"),
},
}

for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
cfg := NewCfg()

azureSection, err := cfg.Raw.NewSection("azure")
require.NoError(t, err)
_, err = azureSection.NewKey("forward_settings_to_plugins", c.configuredValue)
require.NoError(t, err)

cfg.readAzureSettings()
require.NotNil(t, cfg.Azure)

assert.Equal(t, c.resolvedValue, cfg.Azure.ForwardSettingsPlugins)
})
}
})

}