mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Shared Azure middleware between Azure Monitor and Prometheus datasources (#46002)
* Scopes in Azure middleware * Enable Azure middleware without feature flag * Use common Azure middleware in Azure Monitor * Apply feature flag to JsonData configuration of Azure auth * Enforce feature flag in Prometheus datasource * Prometheus provider tests * Datasource service tests * Fix http client provider tests * Pass sdkhttpclient.Options by reference * Add middleware to httpclient.Options * Remove dependency on Grafana settings * Unit-tests updated * Fix ds_proxy_test * Fix service_test
This commit is contained in:
41
pkg/tsdb/azuremonitor/azhttpclient/middleware.go
Normal file
41
pkg/tsdb/azuremonitor/azhttpclient/middleware.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package azhttpclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/aztokenprovider"
|
||||
)
|
||||
|
||||
const azureMiddlewareName = "AzureAuthentication"
|
||||
|
||||
func AzureMiddleware(settings *azsettings.AzureSettings, credentials azcredentials.AzureCredentials, scopes []string) httpclient.Middleware {
|
||||
return httpclient.NamedMiddlewareFunc(azureMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
|
||||
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(settings, credentials)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
return ApplyAzureAuth(tokenProvider, scopes, next)
|
||||
})
|
||||
}
|
||||
|
||||
func ApplyAzureAuth(tokenProvider aztokenprovider.AzureTokenProvider, scopes []string, next http.RoundTripper) http.RoundTripper {
|
||||
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
token, err := tokenProvider.GetAccessToken(req.Context(), scopes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve Azure access token: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
return next.RoundTrip(req)
|
||||
})
|
||||
}
|
||||
|
||||
func errorResponse(err error) http.RoundTripper {
|
||||
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return nil, fmt.Errorf("invalid Azure configuration: %s", err)
|
||||
})
|
||||
}
|
||||
11
pkg/tsdb/azuremonitor/azhttpclient/options.go
Normal file
11
pkg/tsdb/azuremonitor/azhttpclient/options.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package azhttpclient
|
||||
|
||||
import (
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
)
|
||||
|
||||
func AddAzureAuthentication(opts *sdkhttpclient.Options, settings *azsettings.AzureSettings, credentials azcredentials.AzureCredentials, scopes []string) {
|
||||
opts.Middlewares = append(opts.Middlewares, AzureMiddleware(settings, credentials, scopes))
|
||||
}
|
||||
55
pkg/tsdb/azuremonitor/azsettings/clouds.go
Normal file
55
pkg/tsdb/azuremonitor/azsettings/clouds.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package azsettings
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
AzurePublic = "AzureCloud"
|
||||
AzureChina = "AzureChinaCloud"
|
||||
AzureUSGovernment = "AzureUSGovernment"
|
||||
AzureGermany = "AzureGermanCloud"
|
||||
)
|
||||
|
||||
func NormalizeAzureCloud(cloudName string) string {
|
||||
switch strings.ToLower(cloudName) {
|
||||
// Public
|
||||
case "azurecloud":
|
||||
fallthrough
|
||||
case "azurepublic":
|
||||
fallthrough
|
||||
case "azurepubliccloud":
|
||||
fallthrough
|
||||
case "public":
|
||||
return AzurePublic
|
||||
|
||||
// China
|
||||
case "azurechina":
|
||||
fallthrough
|
||||
case "azurechinacloud":
|
||||
fallthrough
|
||||
case "china":
|
||||
return AzureChina
|
||||
|
||||
// US Government
|
||||
case "azureusgovernment":
|
||||
fallthrough
|
||||
case "azureusgovernmentcloud":
|
||||
fallthrough
|
||||
case "usgov":
|
||||
fallthrough
|
||||
case "usgovernment":
|
||||
return AzureUSGovernment
|
||||
|
||||
// Germany
|
||||
case "azuregermancloud":
|
||||
fallthrough
|
||||
case "azuregermany":
|
||||
fallthrough
|
||||
case "german":
|
||||
fallthrough
|
||||
case "germany":
|
||||
return AzureGermany
|
||||
}
|
||||
|
||||
// Pass the name unchanged if it's not known
|
||||
return cloudName
|
||||
}
|
||||
7
pkg/tsdb/azuremonitor/azsettings/settings.go
Normal file
7
pkg/tsdb/azuremonitor/azsettings/settings.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package azsettings
|
||||
|
||||
type AzureSettings struct {
|
||||
Cloud string
|
||||
ManagedIdentityEnabled bool
|
||||
ManagedIdentityClientId string
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package aztokenprovider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
)
|
||||
|
||||
const authenticationMiddlewareName = "AzureAuthentication"
|
||||
|
||||
func AuthMiddleware(tokenProvider AzureTokenProvider, scopes []string) httpclient.Middleware {
|
||||
return httpclient.NamedMiddlewareFunc(authenticationMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
|
||||
return ApplyAuth(tokenProvider, scopes, next)
|
||||
})
|
||||
}
|
||||
|
||||
func ApplyAuth(tokenProvider AzureTokenProvider, scopes []string, next http.RoundTripper) http.RoundTripper {
|
||||
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
token, err := tokenProvider.GetAccessToken(req.Context(), scopes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve Azure access token: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
return next.RoundTrip(req)
|
||||
})
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -24,9 +24,9 @@ type tokenProviderImpl struct {
|
||||
tokenRetriever TokenRetriever
|
||||
}
|
||||
|
||||
func NewAzureAccessTokenProvider(cfg *setting.Cfg, credentials azcredentials.AzureCredentials) (AzureTokenProvider, error) {
|
||||
if cfg == nil {
|
||||
err := fmt.Errorf("parameter 'cfg' cannot be nil")
|
||||
func NewAzureAccessTokenProvider(settings *azsettings.AzureSettings, credentials azcredentials.AzureCredentials) (AzureTokenProvider, error) {
|
||||
if settings == nil {
|
||||
err := fmt.Errorf("parameter 'settings' cannot be nil")
|
||||
return nil, err
|
||||
}
|
||||
if credentials == nil {
|
||||
@@ -38,11 +38,11 @@ func NewAzureAccessTokenProvider(cfg *setting.Cfg, credentials azcredentials.Azu
|
||||
|
||||
switch c := credentials.(type) {
|
||||
case *azcredentials.AzureManagedIdentityCredentials:
|
||||
if !cfg.Azure.ManagedIdentityEnabled {
|
||||
if !settings.ManagedIdentityEnabled {
|
||||
err := fmt.Errorf("managed identity authentication is not enabled in Grafana config")
|
||||
return nil, err
|
||||
} else {
|
||||
tokenRetriever = getManagedIdentityTokenRetriever(cfg, c)
|
||||
tokenRetriever = getManagedIdentityTokenRetriever(settings, c)
|
||||
}
|
||||
case *azcredentials.AzureClientSecretCredentials:
|
||||
tokenRetriever = getClientSecretTokenRetriever(c)
|
||||
@@ -75,12 +75,12 @@ func (provider *tokenProviderImpl) GetAccessToken(ctx context.Context, scopes []
|
||||
return accessToken, nil
|
||||
}
|
||||
|
||||
func getManagedIdentityTokenRetriever(cfg *setting.Cfg, credentials *azcredentials.AzureManagedIdentityCredentials) TokenRetriever {
|
||||
func getManagedIdentityTokenRetriever(settings *azsettings.AzureSettings, credentials *azcredentials.AzureManagedIdentityCredentials) TokenRetriever {
|
||||
var clientId string
|
||||
if credentials.ClientId != "" {
|
||||
clientId = credentials.ClientId
|
||||
} else {
|
||||
clientId = cfg.Azure.ManagedIdentityClientId
|
||||
clientId = settings.ManagedIdentityClientId
|
||||
}
|
||||
return &managedIdentityTokenRetriever{
|
||||
clientId: clientId,
|
||||
@@ -105,13 +105,13 @@ func getClientSecretTokenRetriever(credentials *azcredentials.AzureClientSecretC
|
||||
func resolveAuthorityForCloud(cloudName string) string {
|
||||
// Known Azure clouds
|
||||
switch cloudName {
|
||||
case setting.AzurePublic:
|
||||
case azsettings.AzurePublic:
|
||||
return azidentity.AzurePublicCloud
|
||||
case setting.AzureChina:
|
||||
case azsettings.AzureChina:
|
||||
return azidentity.AzureChina
|
||||
case setting.AzureUSGovernment:
|
||||
case azsettings.AzureUSGovernment:
|
||||
return azidentity.AzureGovernment
|
||||
case setting.AzureGermany:
|
||||
case azsettings.AzureGermany:
|
||||
return azidentity.AzureGermany
|
||||
default:
|
||||
return ""
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -22,7 +22,7 @@ func (c *tokenCacheFake) GetAccessToken(_ context.Context, credential TokenRetri
|
||||
func TestAzureTokenProvider_GetAccessToken(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
cfg := &setting.Cfg{}
|
||||
settings := &azsettings.AzureSettings{}
|
||||
|
||||
scopes := []string{
|
||||
"https://management.azure.com/.default",
|
||||
@@ -33,12 +33,12 @@ func TestAzureTokenProvider_GetAccessToken(t *testing.T) {
|
||||
t.Cleanup(func() { azureTokenCache = original })
|
||||
|
||||
t.Run("when managed identities enabled", func(t *testing.T) {
|
||||
cfg.Azure.ManagedIdentityEnabled = true
|
||||
settings.ManagedIdentityEnabled = true
|
||||
|
||||
t.Run("should resolve managed identity retriever if auth type is managed identity", func(t *testing.T) {
|
||||
credentials := &azcredentials.AzureManagedIdentityCredentials{}
|
||||
|
||||
provider, err := NewAzureAccessTokenProvider(cfg, credentials)
|
||||
provider, err := NewAzureAccessTokenProvider(settings, credentials)
|
||||
require.NoError(t, err)
|
||||
|
||||
getAccessTokenFunc = func(credential TokenRetriever, scopes []string) {
|
||||
@@ -52,7 +52,7 @@ func TestAzureTokenProvider_GetAccessToken(t *testing.T) {
|
||||
t.Run("should resolve client secret retriever if auth type is client secret", func(t *testing.T) {
|
||||
credentials := &azcredentials.AzureClientSecretCredentials{}
|
||||
|
||||
provider, err := NewAzureAccessTokenProvider(cfg, credentials)
|
||||
provider, err := NewAzureAccessTokenProvider(settings, credentials)
|
||||
require.NoError(t, err)
|
||||
|
||||
getAccessTokenFunc = func(credential TokenRetriever, scopes []string) {
|
||||
@@ -65,12 +65,12 @@ func TestAzureTokenProvider_GetAccessToken(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("when managed identities disabled", func(t *testing.T) {
|
||||
cfg.Azure.ManagedIdentityEnabled = false
|
||||
settings.ManagedIdentityEnabled = false
|
||||
|
||||
t.Run("should return error if auth type is managed identity", func(t *testing.T) {
|
||||
credentials := &azcredentials.AzureManagedIdentityCredentials{}
|
||||
|
||||
_, err := NewAzureAccessTokenProvider(cfg, credentials)
|
||||
_, err := NewAzureAccessTokenProvider(settings, credentials)
|
||||
assert.Error(t, err, "managed identity authentication is not enabled in Grafana config")
|
||||
})
|
||||
})
|
||||
@@ -78,7 +78,7 @@ func TestAzureTokenProvider_GetAccessToken(t *testing.T) {
|
||||
|
||||
func TestAzureTokenProvider_getClientSecretCredential(t *testing.T) {
|
||||
credentials := &azcredentials.AzureClientSecretCredentials{
|
||||
AzureCloud: setting.AzurePublic,
|
||||
AzureCloud: azsettings.AzurePublic,
|
||||
Authority: "",
|
||||
TenantId: "7dcf1d1a-4ec0-41f2-ac29-c1538a698bc4",
|
||||
ClientId: "1af7c188-e5b6-4f96-81b8-911761bdd459",
|
||||
@@ -101,7 +101,7 @@ func TestAzureTokenProvider_getClientSecretCredential(t *testing.T) {
|
||||
originalCloud := credentials.AzureCloud
|
||||
defer func() { credentials.AzureCloud = originalCloud }()
|
||||
|
||||
credentials.AzureCloud = setting.AzureChina
|
||||
credentials.AzureCloud = azsettings.AzureChina
|
||||
|
||||
result := getClientSecretTokenRetriever(credentials)
|
||||
assert.IsType(t, &clientSecretTokenRetriever{}, result)
|
||||
@@ -115,7 +115,7 @@ func TestAzureTokenProvider_getClientSecretCredential(t *testing.T) {
|
||||
originalCloud := credentials.AzureCloud
|
||||
defer func() { credentials.AzureCloud = originalCloud }()
|
||||
|
||||
credentials.AzureCloud = setting.AzureChina
|
||||
credentials.AzureCloud = azsettings.AzureChina
|
||||
credentials.Authority = "https://another.com/"
|
||||
|
||||
result := getClientSecretTokenRetriever(credentials)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/metrics"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -99,7 +99,7 @@ func Test_handleResourceReq(t *testing.T) {
|
||||
im: &fakeInstance{
|
||||
services: map[string]types.DatasourceService{
|
||||
azureMonitor: {
|
||||
URL: routes[setting.AzurePublic][azureMonitor].URL,
|
||||
URL: routes[azsettings.AzurePublic][azureMonitor].URL,
|
||||
HTTPClient: &http.Client{},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -37,7 +37,7 @@ func ProvideService(cfg *setting.Cfg, httpClientProvider *httpclient.Provider, t
|
||||
executors[deprecated.AppInsights] = &deprecated.ApplicationInsightsDatasource{Proxy: proxy}
|
||||
}
|
||||
|
||||
im := datasource.NewInstanceManager(NewInstanceSettings(cfg, *httpClientProvider, executors))
|
||||
im := datasource.NewInstanceManager(NewInstanceSettings(cfg, httpClientProvider, executors))
|
||||
|
||||
s := &Service{
|
||||
im: im,
|
||||
@@ -68,7 +68,7 @@ type Service struct {
|
||||
tracer tracing.Tracer
|
||||
}
|
||||
|
||||
func getDatasourceService(cfg *setting.Cfg, clientProvider httpclient.Provider, dsInfo types.DatasourceInfo, routeName string) (types.DatasourceService, error) {
|
||||
func getDatasourceService(cfg *setting.Cfg, clientProvider *httpclient.Provider, dsInfo types.DatasourceInfo, routeName string) (types.DatasourceService, error) {
|
||||
route := dsInfo.Routes[routeName]
|
||||
client, err := newHTTPClient(route, dsInfo, cfg, clientProvider)
|
||||
if err != nil {
|
||||
@@ -80,7 +80,7 @@ func getDatasourceService(cfg *setting.Cfg, clientProvider httpclient.Provider,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
||||
jsonData, err := simplejson.NewJson(settings.JSONData)
|
||||
if err != nil {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/deprecated"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -61,10 +62,10 @@ func TestNewInstanceSettings(t *testing.T) {
|
||||
ID: 40,
|
||||
},
|
||||
expectedModel: types.DatasourceInfo{
|
||||
Cloud: setting.AzurePublic,
|
||||
Cloud: azsettings.AzurePublic,
|
||||
Credentials: &azcredentials.AzureManagedIdentityCredentials{},
|
||||
Settings: types.AzureMonitorSettings{},
|
||||
Routes: routes[setting.AzurePublic],
|
||||
Routes: routes[azsettings.AzurePublic],
|
||||
JSONData: map[string]interface{}{"azureAuthType": "msi"},
|
||||
DatasourceID: 40,
|
||||
DecryptedSecureJSONData: map[string]string{"key": "value"},
|
||||
@@ -75,14 +76,14 @@ func TestNewInstanceSettings(t *testing.T) {
|
||||
}
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
Azure: setting.AzureSettings{
|
||||
Cloud: setting.AzurePublic,
|
||||
Azure: &azsettings.AzureSettings{
|
||||
Cloud: azsettings.AzurePublic,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
factory := NewInstanceSettings(cfg, httpclient.Provider{}, map[string]azDatasourceExecutor{})
|
||||
factory := NewInstanceSettings(cfg, &httpclient.Provider{}, map[string]azDatasourceExecutor{})
|
||||
instance, err := factory(tt.settings)
|
||||
tt.Err(t, err)
|
||||
if !cmp.Equal(instance, tt.expectedModel) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
)
|
||||
|
||||
// Azure cloud names specific to Azure Monitor
|
||||
@@ -43,17 +44,17 @@ func getDefaultAzureCloud(cfg *setting.Cfg) (string, error) {
|
||||
// Allow only known cloud names
|
||||
cloudName := cfg.Azure.Cloud
|
||||
switch cloudName {
|
||||
case setting.AzurePublic:
|
||||
return setting.AzurePublic, nil
|
||||
case setting.AzureChina:
|
||||
return setting.AzureChina, nil
|
||||
case setting.AzureUSGovernment:
|
||||
return setting.AzureUSGovernment, nil
|
||||
case setting.AzureGermany:
|
||||
return setting.AzureGermany, nil
|
||||
case azsettings.AzurePublic:
|
||||
return azsettings.AzurePublic, nil
|
||||
case azsettings.AzureChina:
|
||||
return azsettings.AzureChina, nil
|
||||
case azsettings.AzureUSGovernment:
|
||||
return azsettings.AzureUSGovernment, nil
|
||||
case azsettings.AzureGermany:
|
||||
return azsettings.AzureGermany, nil
|
||||
case "":
|
||||
// Not set cloud defaults to public
|
||||
return setting.AzurePublic, nil
|
||||
return azsettings.AzurePublic, nil
|
||||
default:
|
||||
err := fmt.Errorf("the cloud '%s' not supported", cloudName)
|
||||
return "", err
|
||||
@@ -63,13 +64,13 @@ func getDefaultAzureCloud(cfg *setting.Cfg) (string, error) {
|
||||
func normalizeAzureCloud(cloudName string) (string, error) {
|
||||
switch cloudName {
|
||||
case azureMonitorPublic:
|
||||
return setting.AzurePublic, nil
|
||||
return azsettings.AzurePublic, nil
|
||||
case azureMonitorChina:
|
||||
return setting.AzureChina, nil
|
||||
return azsettings.AzureChina, nil
|
||||
case azureMonitorUSGovernment:
|
||||
return setting.AzureUSGovernment, nil
|
||||
return azsettings.AzureUSGovernment, nil
|
||||
case azureMonitorGermany:
|
||||
return setting.AzureGermany, nil
|
||||
return azsettings.AzureGermany, nil
|
||||
default:
|
||||
err := fmt.Errorf("the cloud '%s' not supported", cloudName)
|
||||
return "", err
|
||||
|
||||
@@ -6,12 +6,15 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCredentials_getAuthType(t *testing.T) {
|
||||
cfg := &setting.Cfg{}
|
||||
cfg := &setting.Cfg{
|
||||
Azure: &azsettings.AzureSettings{},
|
||||
}
|
||||
|
||||
t.Run("when managed identities enabled", func(t *testing.T) {
|
||||
cfg.Azure.ManagedIdentityEnabled = true
|
||||
@@ -76,8 +79,8 @@ func TestCredentials_getAuthType(t *testing.T) {
|
||||
|
||||
func TestCredentials_getAzureCloud(t *testing.T) {
|
||||
cfg := &setting.Cfg{
|
||||
Azure: setting.AzureSettings{
|
||||
Cloud: setting.AzureChina,
|
||||
Azure: &azsettings.AzureSettings{
|
||||
Cloud: azsettings.AzureChina,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -91,12 +94,12 @@ func TestCredentials_getAzureCloud(t *testing.T) {
|
||||
cloud, err := getAzureCloud(cfg, jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, setting.AzureChina, cloud)
|
||||
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: setting.AzureSettings{
|
||||
Azure: &azsettings.AzureSettings{
|
||||
Cloud: "",
|
||||
},
|
||||
}
|
||||
@@ -104,7 +107,7 @@ func TestCredentials_getAzureCloud(t *testing.T) {
|
||||
cloud, err := getAzureCloud(cfg, jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, setting.AzurePublic, cloud)
|
||||
assert.Equal(t, azsettings.AzurePublic, cloud)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -118,7 +121,7 @@ func TestCredentials_getAzureCloud(t *testing.T) {
|
||||
cloud, err := getAzureCloud(cfg, jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, setting.AzureGermany, cloud)
|
||||
assert.Equal(t, azsettings.AzureGermany, cloud)
|
||||
})
|
||||
|
||||
t.Run("should be from server configuration if not set in datasource", func(t *testing.T) {
|
||||
@@ -130,15 +133,15 @@ func TestCredentials_getAzureCloud(t *testing.T) {
|
||||
cloud, err := getAzureCloud(cfg, jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, setting.AzureChina, cloud)
|
||||
assert.Equal(t, azsettings.AzureChina, cloud)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCredentials_getAzureCredentials(t *testing.T) {
|
||||
cfg := &setting.Cfg{
|
||||
Azure: setting.AzureSettings{
|
||||
Cloud: setting.AzureChina,
|
||||
Azure: &azsettings.AzureSettings{
|
||||
Cloud: azsettings.AzureChina,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -175,8 +178,8 @@ func TestCredentials_getAzureCredentials(t *testing.T) {
|
||||
|
||||
t.Run("should return client secret credentials", func(t *testing.T) {
|
||||
cfg := &setting.Cfg{
|
||||
Azure: setting.AzureSettings{
|
||||
Cloud: setting.AzureChina,
|
||||
Azure: &azsettings.AzureSettings{
|
||||
Cloud: azsettings.AzureChina,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -185,7 +188,7 @@ func TestCredentials_getAzureCredentials(t *testing.T) {
|
||||
require.IsType(t, &azcredentials.AzureClientSecretCredentials{}, credentials)
|
||||
clientSecretCredentials := credentials.(*azcredentials.AzureClientSecretCredentials)
|
||||
|
||||
assert.Equal(t, setting.AzureGermany, clientSecretCredentials.AzureCloud)
|
||||
assert.Equal(t, azsettings.AzureGermany, 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)
|
||||
|
||||
@@ -3,23 +3,16 @@ package azuremonitor
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/aztokenprovider"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azhttpclient"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/deprecated"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
||||
)
|
||||
|
||||
func getMiddlewares(route types.AzRoute, model types.DatasourceInfo, cfg *setting.Cfg) ([]httpclient.Middleware, error) {
|
||||
middlewares := []httpclient.Middleware{}
|
||||
|
||||
if len(route.Scopes) > 0 {
|
||||
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg, model.Credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
middlewares = append(middlewares, aztokenprovider.AuthMiddleware(tokenProvider, route.Scopes))
|
||||
}
|
||||
func getMiddlewares(route types.AzRoute, model types.DatasourceInfo) ([]sdkhttpclient.Middleware, error) {
|
||||
var middlewares []sdkhttpclient.Middleware
|
||||
|
||||
// Remove with Grafana 9
|
||||
if apiKeyMiddleware := deprecated.GetAppInsightsMiddleware(route.URL, model.DecryptedSecureJSONData["appInsightsApiKey"]); apiKeyMiddleware != nil {
|
||||
@@ -30,13 +23,20 @@ func getMiddlewares(route types.AzRoute, model types.DatasourceInfo, cfg *settin
|
||||
}
|
||||
|
||||
func newHTTPClient(route types.AzRoute, model types.DatasourceInfo, cfg *setting.Cfg, clientProvider httpclient.Provider) (*http.Client, error) {
|
||||
m, err := getMiddlewares(route, model, cfg)
|
||||
m, err := getMiddlewares(route, model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clientProvider.New(httpclient.Options{
|
||||
opts := sdkhttpclient.Options{
|
||||
Headers: route.Headers,
|
||||
Middlewares: m,
|
||||
})
|
||||
}
|
||||
|
||||
// Use Azure credentials if the route has OAuth scopes configured
|
||||
if len(route.Scopes) > 0 {
|
||||
azhttpclient.AddAzureAuthentication(&opts, cfg.Azure, model.Credentials, route.Scopes)
|
||||
}
|
||||
|
||||
return clientProvider.New(opts)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package azuremonitor
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/deprecated"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_httpCliProvider(t *testing.T) {
|
||||
cfg := &setting.Cfg{}
|
||||
func TestHttpClient_Middlewares(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
route types.AzRoute
|
||||
@@ -19,18 +23,6 @@ func Test_httpCliProvider(t *testing.T) {
|
||||
expectedMiddlewares int
|
||||
Err require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "creates an HTTP client with a middleware due to the scope",
|
||||
route: types.AzRoute{
|
||||
URL: "http://route",
|
||||
Scopes: []string{"http://route/.default"},
|
||||
},
|
||||
model: types.DatasourceInfo{
|
||||
Credentials: &azcredentials.AzureClientSecretCredentials{},
|
||||
},
|
||||
expectedMiddlewares: 1,
|
||||
Err: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "creates an HTTP client with a middleware due to an app key",
|
||||
route: types.AzRoute{
|
||||
@@ -61,7 +53,7 @@ func Test_httpCliProvider(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m, err := getMiddlewares(tt.route, tt.model, cfg)
|
||||
m, err := getMiddlewares(tt.route, tt.model)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cannot test that the cli middleware works properly since the azcore sdk
|
||||
@@ -72,3 +64,63 @@ func Test_httpCliProvider(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpClient_AzureCredentials(t *testing.T) {
|
||||
model := types.DatasourceInfo{
|
||||
Credentials: &azcredentials.AzureManagedIdentityCredentials{},
|
||||
}
|
||||
|
||||
cfg := &setting.Cfg{}
|
||||
provider := &fakeHttpClientProvider{}
|
||||
|
||||
t.Run("should have Azure middleware when scopes provided", func(t *testing.T) {
|
||||
route := types.AzRoute{
|
||||
URL: deprecated.AzAppInsights.URL,
|
||||
Scopes: []string{"https://management.azure.com/.default"},
|
||||
}
|
||||
|
||||
_, err := newHTTPClient(route, model, cfg, provider)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, provider.opts)
|
||||
require.NotNil(t, provider.opts.Middlewares)
|
||||
assert.Len(t, provider.opts.Middlewares, 1)
|
||||
})
|
||||
|
||||
t.Run("should not have Azure middleware when scopes are not provided", func(t *testing.T) {
|
||||
route := types.AzRoute{
|
||||
URL: deprecated.AzAppInsights.URL,
|
||||
Scopes: []string{},
|
||||
}
|
||||
|
||||
_, err := newHTTPClient(route, model, cfg, provider)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, provider.opts)
|
||||
|
||||
if provider.opts.Middlewares != nil {
|
||||
assert.Len(t, provider.opts.Middlewares, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type fakeHttpClientProvider struct {
|
||||
httpclient.Provider
|
||||
|
||||
opts sdkhttpclient.Options
|
||||
}
|
||||
|
||||
func (p *fakeHttpClientProvider) New(opts ...sdkhttpclient.Options) (*http.Client, error) {
|
||||
p.opts = opts[0]
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *fakeHttpClientProvider) GetTransport(opts ...sdkhttpclient.Options) (http.RoundTripper, error) {
|
||||
p.opts = opts[0]
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *fakeHttpClientProvider) GetTLSConfig(opts ...sdkhttpclient.Options) (*tls.Config, error) {
|
||||
p.opts = opts[0]
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package resourcegraph
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -11,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azlog"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/macros"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
||||
@@ -268,13 +268,13 @@ func (e *AzureResourceGraphDatasource) unmarshalResponse(res *http.Response) (Az
|
||||
|
||||
func GetAzurePortalUrl(azureCloud string) (string, error) {
|
||||
switch azureCloud {
|
||||
case setting.AzurePublic:
|
||||
case azsettings.AzurePublic:
|
||||
return "https://portal.azure.com", nil
|
||||
case setting.AzureChina:
|
||||
case azsettings.AzureChina:
|
||||
return "https://portal.azure.cn", nil
|
||||
case setting.AzureUSGovernment:
|
||||
case azsettings.AzureUSGovernment:
|
||||
return "https://portal.azure.us", nil
|
||||
case setting.AzureGermany:
|
||||
case azsettings.AzureGermany:
|
||||
return "https://portal.microsoftazure.de", nil
|
||||
default:
|
||||
return "", fmt.Errorf("the cloud is not supported")
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -137,12 +137,12 @@ func TestAddConfigData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetAzurePortalUrl(t *testing.T) {
|
||||
clouds := []string{setting.AzurePublic, setting.AzureChina, setting.AzureUSGovernment, setting.AzureGermany}
|
||||
clouds := []string{azsettings.AzurePublic, azsettings.AzureChina, azsettings.AzureUSGovernment, azsettings.AzureGermany}
|
||||
expectedAzurePortalUrl := map[string]interface{}{
|
||||
setting.AzurePublic: "https://portal.azure.com",
|
||||
setting.AzureChina: "https://portal.azure.cn",
|
||||
setting.AzureUSGovernment: "https://portal.azure.us",
|
||||
setting.AzureGermany: "https://portal.microsoftazure.de",
|
||||
azsettings.AzurePublic: "https://portal.azure.com",
|
||||
azsettings.AzureChina: "https://portal.azure.cn",
|
||||
azsettings.AzureUSGovernment: "https://portal.azure.us",
|
||||
azsettings.AzureGermany: "https://portal.microsoftazure.de",
|
||||
}
|
||||
|
||||
for _, cloud := range clouds {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package azuremonitor
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/deprecated"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
|
||||
)
|
||||
@@ -59,22 +59,22 @@ var (
|
||||
// The different Azure routes are identified by its cloud (e.g. public or gov)
|
||||
// and the service to query (e.g. Azure Monitor or Azure Log Analytics)
|
||||
routes = map[string]map[string]types.AzRoute{
|
||||
setting.AzurePublic: {
|
||||
azsettings.AzurePublic: {
|
||||
azureMonitor: azManagement,
|
||||
azureLogAnalytics: azLogAnalytics,
|
||||
azureResourceGraph: azManagement,
|
||||
deprecated.AppInsights: deprecated.AzAppInsights,
|
||||
deprecated.InsightsAnalytics: deprecated.AzAppInsights,
|
||||
},
|
||||
setting.AzureUSGovernment: {
|
||||
azsettings.AzureUSGovernment: {
|
||||
azureMonitor: azUSGovManagement,
|
||||
azureLogAnalytics: azUSGovLogAnalytics,
|
||||
azureResourceGraph: azUSGovManagement,
|
||||
},
|
||||
setting.AzureGermany: {
|
||||
azsettings.AzureGermany: {
|
||||
azureMonitor: azGermanyManagement,
|
||||
},
|
||||
setting.AzureChina: {
|
||||
azsettings.AzureChina: {
|
||||
azureMonitor: azChinaManagement,
|
||||
azureLogAnalytics: azChinaLogAnalytics,
|
||||
azureResourceGraph: azChinaManagement,
|
||||
|
||||
Reference in New Issue
Block a user