Azure Monitor: support AzureCredentials in common format on backend (#77424)

* Use GetDefaultCloud from SDK

* Use GetAzureCloud from SDK

* Credentials parser moved to azmoncredentials

* Refactor legacy credentials

* Tests

* Fix test description

Co-authored-by: Andreas Christou <andreas.christou@grafana.com>

---------

Co-authored-by: Andreas Christou <andreas.christou@grafana.com>
This commit is contained in:
Sergey Kostrukov 2023-11-15 06:59:23 -08:00 committed by GitHub
parent b8e8d84ef7
commit 40152922d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 371 additions and 431 deletions

View File

@ -0,0 +1,119 @@
package azmoncredentials
import (
"fmt"
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana-azure-sdk-go/util/maputil"
)
func FromDatasourceData(data map[string]interface{}, secureData map[string]string) (azcredentials.AzureCredentials, error) {
var credentials azcredentials.AzureCredentials
var err error
credentials, err = azcredentials.FromDatasourceData(data, secureData)
if err != nil {
return nil, err
}
// Fallback to legacy credentials format
if credentials == nil {
credentials, err = getFromLegacy(data, secureData)
if err != nil {
return nil, err
}
}
return credentials, err
}
func getFromLegacy(data map[string]interface{}, secureData map[string]string) (azcredentials.AzureCredentials, error) {
authType, err := maputil.GetStringOptional(data, "azureAuthType")
if err != nil {
return nil, err
}
tenantId, err := maputil.GetStringOptional(data, "tenantId")
if err != nil {
return nil, err
}
clientId, err := maputil.GetStringOptional(data, "clientId")
if err != nil {
return nil, err
}
if authType == "" {
// Some very old legacy datasources may not have explicit auth type specified,
// but they imply App Registration authentication
if tenantId != "" && clientId != "" {
authType = azcredentials.AzureAuthClientSecret
} else {
// No configuration present
return nil, nil
}
}
switch authType {
case azcredentials.AzureAuthManagedIdentity:
credentials := &azcredentials.AzureManagedIdentityCredentials{}
return credentials, nil
case azcredentials.AzureAuthWorkloadIdentity:
credentials := &azcredentials.AzureWorkloadIdentityCredentials{}
return credentials, nil
case azcredentials.AzureAuthClientSecret:
legacyCloud, err := maputil.GetStringOptional(data, "cloudName")
if err != nil {
return nil, err
}
cloud, err := resolveLegacyCloudName(legacyCloud)
if err != nil {
return nil, err
}
clientSecret := secureData["clientSecret"]
if secureData["clientSecret"] == "" {
return nil, fmt.Errorf("unable to instantiate credentials, clientSecret must be set")
}
credentials := &azcredentials.AzureClientSecretCredentials{
AzureCloud: cloud,
TenantId: tenantId,
ClientId: clientId,
ClientSecret: clientSecret,
}
return credentials, nil
default:
err := fmt.Errorf("the authentication type '%s' not supported", authType)
return nil, err
}
}
// Legacy Azure cloud names used by the Azure Monitor datasource
const (
azureMonitorPublic = "azuremonitor"
azureMonitorChina = "chinaazuremonitor"
azureMonitorUSGovernment = "govazuremonitor"
azureMonitorCustomized = "customizedazuremonitor"
)
func resolveLegacyCloudName(cloudName string) (string, error) {
switch cloudName {
case azureMonitorPublic:
return azsettings.AzurePublic, nil
case azureMonitorChina:
return azsettings.AzureChina, nil
case azureMonitorUSGovernment:
return azsettings.AzureUSGovernment, nil
case azureMonitorCustomized:
return azsettings.AzureCustomized, nil
case "":
return azsettings.AzurePublic, nil
default:
err := fmt.Errorf("the Azure cloud '%s' not supported by Azure Monitor datasource", cloudName)
return "", err
}
}

View File

@ -0,0 +1,215 @@
package azmoncredentials
import (
"testing"
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFromDatasourceData(t *testing.T) {
t.Run("should return nil when no credentials configured", func(t *testing.T) {
var data = map[string]interface{}{}
var secureData = map[string]string{}
result, err := FromDatasourceData(data, secureData)
require.NoError(t, err)
assert.Nil(t, result)
})
t.Run("should return managed identity credentials when auth type is managed identity", func(t *testing.T) {
data := map[string]interface{}{
"azureAuthType": "msi",
"cloudName": "chinaazuremonitor",
"tenantId": "LEGACY-TENANT-ID",
"clientId": "LEGACY-CLIENT-ID",
}
var secureData = map[string]string{
"clientSecret": "FAKE-LEGACY-SECRET",
}
credentials, err := FromDatasourceData(data, secureData)
require.NoError(t, err)
require.IsType(t, &azcredentials.AzureManagedIdentityCredentials{}, credentials)
msiCredentials := credentials.(*azcredentials.AzureManagedIdentityCredentials)
// Azure Monitor datasource doesn't support user-assigned managed identities (ClientId is always empty)
assert.Equal(t, "", msiCredentials.ClientId)
})
t.Run("should return workload identity credentials when auth type is workload identity", func(t *testing.T) {
data := map[string]interface{}{
"azureAuthType": azcredentials.AzureAuthWorkloadIdentity,
}
var secureData = map[string]string{}
credentials, err := FromDatasourceData(data, secureData)
require.NoError(t, err)
require.IsType(t, &azcredentials.AzureWorkloadIdentityCredentials{}, credentials)
})
t.Run("when legacy client secret configuration present", func(t *testing.T) {
t.Run("should return client secret credentials when auth type is client secret", func(t *testing.T) {
var data = map[string]interface{}{
"azureAuthType": "clientsecret",
"cloudName": "chinaazuremonitor",
"tenantId": "LEGACY-TENANT-ID",
"clientId": "LEGACY-CLIENT-ID",
}
var secureData = map[string]string{
"clientSecret": "FAKE-LEGACY-SECRET",
}
result, err := FromDatasourceData(data, secureData)
require.NoError(t, err)
require.NotNil(t, result)
assert.IsType(t, &azcredentials.AzureClientSecretCredentials{}, result)
credential := (result).(*azcredentials.AzureClientSecretCredentials)
assert.Equal(t, azsettings.AzureChina, credential.AzureCloud)
assert.Equal(t, "LEGACY-TENANT-ID", credential.TenantId)
assert.Equal(t, "LEGACY-CLIENT-ID", credential.ClientId)
assert.Equal(t, "FAKE-LEGACY-SECRET", credential.ClientSecret)
})
t.Run("should return client secret credentials when auth type is not specified but configuration present", func(t *testing.T) {
var data = map[string]interface{}{
"cloudName": "chinaazuremonitor",
"tenantId": "LEGACY-TENANT-ID",
"clientId": "LEGACY-CLIENT-ID",
}
var secureData = map[string]string{
"clientSecret": "FAKE-LEGACY-SECRET",
}
result, err := FromDatasourceData(data, secureData)
require.NoError(t, err)
require.NotNil(t, result)
assert.IsType(t, &azcredentials.AzureClientSecretCredentials{}, result)
credential := (result).(*azcredentials.AzureClientSecretCredentials)
assert.Equal(t, azsettings.AzureChina, credential.AzureCloud)
assert.Equal(t, "LEGACY-TENANT-ID", credential.TenantId)
assert.Equal(t, "LEGACY-CLIENT-ID", credential.ClientId)
assert.Equal(t, "FAKE-LEGACY-SECRET", credential.ClientSecret)
})
t.Run("should error if no client secret is set", func(t *testing.T) {
var data = map[string]interface{}{
"azureAuthType": "clientsecret",
"cloudName": "chinaazuremonitor",
"tenantId": "LEGACY-TENANT-ID",
"clientId": "LEGACY-CLIENT-ID",
}
var secureData = map[string]string{}
_, err := FromDatasourceData(data, secureData)
require.Error(t, err)
assert.ErrorContains(t, err, "clientSecret must be set")
})
})
t.Run("should return client secret credentials when client secret auth configured even if legacy configuration present", func(t *testing.T) {
var data = map[string]interface{}{
"azureCredentials": map[string]interface{}{
"authType": "clientsecret",
"azureCloud": "AzureChinaCloud",
"tenantId": "TENANT-ID",
"clientId": "CLIENT-TD",
},
"azureAuthType": "clientsecret",
"cloudName": "azuremonitor",
"tenantId": "LEGACY-TENANT-ID",
"clientId": "LEGACY-CLIENT-ID",
}
var secureData = map[string]string{
"azureClientSecret": "FAKE-SECRET",
"clientSecret": "FAKE-LEGACY-SECRET",
}
result, err := FromDatasourceData(data, secureData)
require.NoError(t, err)
require.NotNil(t, result)
assert.IsType(t, &azcredentials.AzureClientSecretCredentials{}, result)
credential := (result).(*azcredentials.AzureClientSecretCredentials)
assert.Equal(t, credential.AzureCloud, azsettings.AzureChina)
assert.Equal(t, credential.TenantId, "TENANT-ID")
assert.Equal(t, credential.ClientId, "CLIENT-TD")
assert.Equal(t, credential.ClientSecret, "FAKE-SECRET")
})
t.Run("should return error when credentials not supported even if legacy configuration present", func(t *testing.T) {
var data = map[string]interface{}{
"azureCredentials": map[string]interface{}{
"authType": "invalid",
"azureCloud": "AzureChinaCloud",
"tenantId": "TENANT-ID",
"clientId": "CLIENT-TD",
},
"cloudName": "azuremonitor",
"tenantId": "LEGACY-TENANT-ID",
"clientId": "LEGACY-CLIENT-ID",
"onBehalfOf": true,
"oauthPassThru": true,
}
var secureData = map[string]string{
"azureClientSecret": "FAKE-SECRET",
"clientSecret": "FAKE-LEGACY-SECRET",
}
_, err := FromDatasourceData(data, secureData)
assert.Error(t, err)
})
}
func TestNormalizedCloudName(t *testing.T) {
t.Run("should return normalized cloud name", func(t *testing.T) {
tests := []struct {
description string
legacyCloud string
normalizedCloud string
}{
{
legacyCloud: azureMonitorPublic,
normalizedCloud: azsettings.AzurePublic,
},
{
legacyCloud: azureMonitorChina,
normalizedCloud: azsettings.AzureChina,
},
{
legacyCloud: azureMonitorUSGovernment,
normalizedCloud: azsettings.AzureUSGovernment,
},
{
legacyCloud: "",
normalizedCloud: azsettings.AzurePublic,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
actualCloud, err := resolveLegacyCloudName(tt.legacyCloud)
require.NoError(t, err)
assert.Equal(t, tt.normalizedCloud, actualCloud)
})
}
})
t.Run("should fail when cloud is unknown", func(t *testing.T) {
legacyCloud := "unknown"
_, err := resolveLegacyCloudName(legacyCloud)
assert.Error(t, err)
})
}

View File

@ -0,0 +1,16 @@
package azmoncredentials
import (
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/azsettings"
)
func GetDefaultCredentials(settings *azsettings.AzureSettings) azcredentials.AzureCredentials {
if settings.ManagedIdentityEnabled {
return &azcredentials.AzureManagedIdentityCredentials{}
} else if settings.WorkloadIdentityEnabled {
return &azcredentials.AzureWorkloadIdentityCredentials{}
} else {
return &azcredentials.AzureClientSecretCredentials{AzureCloud: settings.GetDefaultCloud()}
}
}

View File

@ -10,6 +10,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/azsettings" "github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
@ -19,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azmoncredentials"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/metrics" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/metrics"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/resourcegraph" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/resourcegraph"
@ -77,19 +79,26 @@ func getDatasourceService(ctx context.Context, settings *backend.DataSourceInsta
func NewInstanceSettings(cfg *setting.Cfg, clientProvider *httpclient.Provider, executors map[string]azDatasourceExecutor) datasource.InstanceFactoryFunc { func NewInstanceSettings(cfg *setting.Cfg, clientProvider *httpclient.Provider, executors map[string]azDatasourceExecutor) datasource.InstanceFactoryFunc {
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
jsonDataObj := map[string]any{} jsonData := map[string]any{}
err := json.Unmarshal(settings.JSONData, &jsonDataObj) err := json.Unmarshal(settings.JSONData, &jsonData)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading settings: %w", err) return nil, fmt.Errorf("error reading settings: %w", err)
} }
azSettings := types.AzureSettings{} azMonitorSettings := types.AzureMonitorSettings{}
err = json.Unmarshal(settings.JSONData, &azSettings) err = json.Unmarshal(settings.JSONData, &azMonitorSettings)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading settings: %w", err) return nil, fmt.Errorf("error reading settings: %w", err)
} }
cloud, err := getAzureCloud(cfg, &azSettings.AzureClientSettings) credentials, err := azmoncredentials.FromDatasourceData(jsonData, settings.DecryptedSecureJSONData)
if err != nil {
return nil, fmt.Errorf("error getting credentials: %w", err)
} else if credentials == nil {
credentials = azmoncredentials.GetDefaultCredentials(cfg.Azure)
}
cloud, err := azcredentials.GetAzureCloud(cfg.Azure, credentials)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting credentials: %w", err) return nil, fmt.Errorf("error getting credentials: %w", err)
} }
@ -99,16 +108,11 @@ func NewInstanceSettings(cfg *setting.Cfg, clientProvider *httpclient.Provider,
return nil, err return nil, err
} }
credentials, err := getAzureCredentials(cfg, &azSettings.AzureClientSettings, settings.DecryptedSecureJSONData)
if err != nil {
return nil, fmt.Errorf("error getting credentials: %w", err)
}
model := types.DatasourceInfo{ model := types.DatasourceInfo{
Cloud: cloud, Cloud: cloud,
Credentials: credentials, Credentials: credentials,
Settings: azSettings.AzureMonitorSettings, Settings: azMonitorSettings,
JSONData: jsonDataObj, JSONData: jsonData,
DecryptedSecureJSONData: settings.DecryptedSecureJSONData, DecryptedSecureJSONData: settings.DecryptedSecureJSONData,
DatasourceID: settings.ID, DatasourceID: settings.ID,
Routes: routesForModel, Routes: routesForModel,

View File

@ -155,19 +155,19 @@ func Test_newMux(t *testing.T) {
{ {
name: "creates an Azure Monitor executor", name: "creates an Azure Monitor executor",
queryType: azureMonitor, queryType: azureMonitor,
expectedURL: routes[azureMonitorPublic][azureMonitor].URL, expectedURL: routes[azsettings.AzurePublic][azureMonitor].URL,
Err: require.NoError, Err: require.NoError,
}, },
{ {
name: "creates an Azure Log Analytics executor", name: "creates an Azure Log Analytics executor",
queryType: azureLogAnalytics, queryType: azureLogAnalytics,
expectedURL: routes[azureMonitorPublic][azureLogAnalytics].URL, expectedURL: routes[azsettings.AzurePublic][azureLogAnalytics].URL,
Err: require.NoError, Err: require.NoError,
}, },
{ {
name: "creates an Azure Traces executor", name: "creates an Azure Traces executor",
queryType: azureTraces, queryType: azureTraces,
expectedURL: routes[azureMonitorPublic][azureLogAnalytics].URL, expectedURL: routes[azsettings.AzurePublic][azureLogAnalytics].URL,
Err: require.NoError, Err: require.NoError,
}, },
} }
@ -176,10 +176,10 @@ func Test_newMux(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
s := &Service{ s := &Service{
im: &fakeInstance{ im: &fakeInstance{
routes: routes[azureMonitorPublic], routes: routes[azsettings.AzurePublic],
services: map[string]types.DatasourceService{ services: map[string]types.DatasourceService{
tt.queryType: { tt.queryType: {
URL: routes[azureMonitorPublic][tt.queryType].URL, URL: routes[azsettings.AzurePublic][tt.queryType].URL,
HTTPClient: &http.Client{}, HTTPClient: &http.Client{},
}, },
}, },

View File

@ -1,136 +0,0 @@
package azuremonitor
import (
"fmt"
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
)
// Azure cloud names specific to Azure Monitor
const (
azureMonitorPublic = "azuremonitor"
azureMonitorChina = "chinaazuremonitor"
azureMonitorUSGovernment = "govazuremonitor"
azureMonitorCustomized = "customizedazuremonitor"
)
func getAuthType(cfg *setting.Cfg, jsonData *types.AzureClientSettings) string {
if azureAuthType := jsonData.AzureAuthType; azureAuthType != "" {
return azureAuthType
} else {
tenantId := jsonData.TenantId
clientId := jsonData.ClientId
// If authentication type isn't explicitly specified and datasource has client credentials,
// then this is existing datasource which is configured for app registration (client secret)
if tenantId != "" && clientId != "" {
return azcredentials.AzureAuthClientSecret
}
// For newly created datasource with no configuration the order is as follows:
// Managed identity is the default if enabled
// Workload identity is the next option if enabled
// Client secret is the final fallback
if cfg.Azure.ManagedIdentityEnabled {
return azcredentials.AzureAuthManagedIdentity
} else if cfg.Azure.WorkloadIdentityEnabled {
return azcredentials.AzureAuthWorkloadIdentity
} else {
return azcredentials.AzureAuthClientSecret
}
}
}
func getDefaultAzureCloud(cfg *setting.Cfg) (string, error) {
// Allow only known cloud names
cloudName := ""
if cfg != nil && cfg.Azure != nil {
cloudName = cfg.Azure.Cloud
}
switch cloudName {
case azsettings.AzurePublic:
return azsettings.AzurePublic, nil
case azsettings.AzureChina:
return azsettings.AzureChina, nil
case azsettings.AzureUSGovernment:
return azsettings.AzureUSGovernment, nil
case azsettings.AzureCustomized:
return azsettings.AzureCustomized, nil
case "":
// Not set cloud defaults to public
return azsettings.AzurePublic, nil
default:
err := fmt.Errorf("the cloud '%s' not supported", cloudName)
return "", err
}
}
func normalizeAzureCloud(cloudName string) (string, error) {
switch cloudName {
case azureMonitorPublic:
return azsettings.AzurePublic, nil
case azureMonitorChina:
return azsettings.AzureChina, nil
case azureMonitorUSGovernment:
return azsettings.AzureUSGovernment, nil
case azureMonitorCustomized:
return azsettings.AzureCustomized, nil
default:
err := fmt.Errorf("the cloud '%s' not supported", cloudName)
return "", err
}
}
func getAzureCloud(cfg *setting.Cfg, jsonData *types.AzureClientSettings) (string, error) {
authType := getAuthType(cfg, jsonData)
switch authType {
case azcredentials.AzureAuthManagedIdentity, azcredentials.AzureAuthWorkloadIdentity:
// In case of managed identity and workload identity, the cloud is always same as where Grafana is hosted
return getDefaultAzureCloud(cfg)
case azcredentials.AzureAuthClientSecret:
if cloud := jsonData.CloudName; cloud != "" {
return normalizeAzureCloud(cloud)
} else {
return getDefaultAzureCloud(cfg)
}
default:
err := fmt.Errorf("the authentication type '%s' not supported", authType)
return "", err
}
}
func getAzureCredentials(cfg *setting.Cfg, jsonData *types.AzureClientSettings, secureJsonData map[string]string) (azcredentials.AzureCredentials, error) {
authType := getAuthType(cfg, jsonData)
switch authType {
case azcredentials.AzureAuthManagedIdentity:
credentials := &azcredentials.AzureManagedIdentityCredentials{}
return credentials, nil
case azcredentials.AzureAuthWorkloadIdentity:
credentials := &azcredentials.AzureWorkloadIdentityCredentials{}
return credentials, nil
case azcredentials.AzureAuthClientSecret:
cloud, err := getAzureCloud(cfg, jsonData)
if err != nil {
return nil, err
}
if secureJsonData["clientSecret"] == "" {
return nil, fmt.Errorf("unable to instantiate credentials, clientSecret must be set")
}
credentials := &azcredentials.AzureClientSecretCredentials{
AzureCloud: cloud,
TenantId: jsonData.TenantId,
ClientId: jsonData.ClientId,
ClientSecret: secureJsonData["clientSecret"],
}
return credentials, nil
default:
err := fmt.Errorf("the authentication type '%s' not supported", authType)
return nil, err
}
}

View File

@ -1,266 +0,0 @@
package azuremonitor
import (
"testing"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCredentials_getAuthType(t *testing.T) {
cfg := &setting.Cfg{
Azure: &azsettings.AzureSettings{},
}
t.Run("when managed identities enabled", func(t *testing.T) {
cfg.Azure.ManagedIdentityEnabled = true
t.Run("should be client secret if auth type is set to client secret", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthClientSecret,
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
})
t.Run("should be managed identity if datasource not configured", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: "",
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthManagedIdentity, authType)
})
t.Run("should be client secret if auth type not specified but credentials configured", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: "",
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
})
})
t.Run("when managed identities disabled", func(t *testing.T) {
cfg.Azure.ManagedIdentityEnabled = false
t.Run("should be managed identity if auth type is set to managed identity", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthManagedIdentity,
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthManagedIdentity, authType)
})
t.Run("should be client secret if datasource not configured", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: "",
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
})
})
t.Run("when workload identities enabled", func(t *testing.T) {
cfg.Azure.WorkloadIdentityEnabled = true
t.Run("should be client secret if auth type is set to client secret", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthClientSecret,
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
})
t.Run("should be workload identity if datasource not configured and managed identity is disabled", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: "",
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthWorkloadIdentity, authType)
})
t.Run("should be client secret if auth type not specified but credentials configured", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: "",
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
})
})
t.Run("when workload identities disabled", func(t *testing.T) {
cfg.Azure.WorkloadIdentityEnabled = false
t.Run("should be workload identity if auth type is set to workload identity", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthWorkloadIdentity,
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthWorkloadIdentity, authType)
})
t.Run("should be client secret if datasource not configured", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: "",
}
authType := getAuthType(cfg, jsonData)
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
})
})
}
func TestCredentials_getAzureCloud(t *testing.T) {
cfg := &setting.Cfg{
Azure: &azsettings.AzureSettings{
Cloud: azsettings.AzureChina,
},
}
t.Run("when auth type is managed identity", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthManagedIdentity,
CloudName: azureMonitorUSGovernment,
}
t.Run("should be from server configuration regardless of datasource value", func(t *testing.T) {
cloud, err := getAzureCloud(cfg, jsonData)
require.NoError(t, err)
assert.Equal(t, azsettings.AzureChina, cloud)
})
t.Run("should be public if not set in server configuration", func(t *testing.T) {
cfg := &setting.Cfg{
Azure: &azsettings.AzureSettings{
Cloud: "",
},
}
cloud, err := getAzureCloud(cfg, jsonData)
require.NoError(t, err)
assert.Equal(t, azsettings.AzurePublic, cloud)
})
})
t.Run("when auth type is client secret", func(t *testing.T) {
t.Run("should be from datasource value normalized to known cloud name", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthClientSecret,
CloudName: azureMonitorUSGovernment,
}
cloud, err := getAzureCloud(cfg, jsonData)
require.NoError(t, err)
assert.Equal(t, azsettings.AzureUSGovernment, cloud)
})
t.Run("should be from server configuration if not set in datasource", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthClientSecret,
CloudName: "",
}
cloud, err := getAzureCloud(cfg, jsonData)
require.NoError(t, err)
assert.Equal(t, azsettings.AzureChina, cloud)
})
})
}
func TestCredentials_getAzureCredentials(t *testing.T) {
cfg := &setting.Cfg{
Azure: &azsettings.AzureSettings{
Cloud: azsettings.AzureChina,
},
}
secureJsonData := map[string]string{
"clientSecret": "59e3498f-eb12-4943-b8f0-a5aa42640058",
}
t.Run("when auth type is managed identity", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthManagedIdentity,
CloudName: azureMonitorUSGovernment,
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
}
t.Run("should return managed identity credentials", func(t *testing.T) {
credentials, err := getAzureCredentials(cfg, jsonData, secureJsonData)
require.NoError(t, err)
require.IsType(t, &azcredentials.AzureManagedIdentityCredentials{}, credentials)
msiCredentials := credentials.(*azcredentials.AzureManagedIdentityCredentials)
// Azure Monitor datasource doesn't support user-assigned managed identities (ClientId is always empty)
assert.Equal(t, "", msiCredentials.ClientId)
})
})
t.Run("when auth type is client secret", func(t *testing.T) {
jsonData := &types.AzureClientSettings{
AzureAuthType: azcredentials.AzureAuthClientSecret,
CloudName: azureMonitorUSGovernment,
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
}
t.Run("should return client secret credentials", func(t *testing.T) {
cfg := &setting.Cfg{}
credentials, err := getAzureCredentials(cfg, jsonData, secureJsonData)
require.NoError(t, err)
require.IsType(t, &azcredentials.AzureClientSecretCredentials{}, credentials)
clientSecretCredentials := credentials.(*azcredentials.AzureClientSecretCredentials)
assert.Equal(t, azsettings.AzureUSGovernment, clientSecretCredentials.AzureCloud)
assert.Equal(t, "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c", clientSecretCredentials.TenantId)
assert.Equal(t, "849ccbb0-92eb-4226-b228-ef391abd8fe6", clientSecretCredentials.ClientId)
assert.Equal(t, "59e3498f-eb12-4943-b8f0-a5aa42640058", clientSecretCredentials.ClientSecret)
// Azure Monitor datasource doesn't support custom IdP authorities (Authority is always empty)
assert.Equal(t, "", clientSecretCredentials.Authority)
})
t.Run("should error if no client secret is set", func(t *testing.T) {
cfg := &setting.Cfg{}
_, err := getAzureCredentials(cfg, jsonData, map[string]string{
"clientSecret": "",
})
require.ErrorContains(t, err, "clientSecret must be set")
})
})
}

View File

@ -30,24 +30,12 @@ type AzRoute struct {
Headers map[string]string Headers map[string]string
} }
type AzureSettings struct {
AzureMonitorSettings
AzureClientSettings
}
type AzureMonitorSettings struct { type AzureMonitorSettings struct {
SubscriptionId string `json:"subscriptionId"` SubscriptionId string `json:"subscriptionId"`
LogAnalyticsDefaultWorkspace string `json:"logAnalyticsDefaultWorkspace"` LogAnalyticsDefaultWorkspace string `json:"logAnalyticsDefaultWorkspace"`
AppInsightsAppId string `json:"appInsightsAppId"` AppInsightsAppId string `json:"appInsightsAppId"`
} }
type AzureClientSettings struct {
AzureAuthType string
CloudName string
TenantId string
ClientId string
}
// AzureMonitorCustomizedCloudSettings is the extended Azure Monitor settings for customized cloud // AzureMonitorCustomizedCloudSettings is the extended Azure Monitor settings for customized cloud
type AzureMonitorCustomizedCloudSettings struct { type AzureMonitorCustomizedCloudSettings struct {
CustomizedRoutes map[string]AzRoute `json:"customizedRoutes"` CustomizedRoutes map[string]AzRoute `json:"customizedRoutes"`