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:
Sergey Kostrukov
2022-04-01 04:26:49 -07:00
committed by GitHub
parent 16db1ad46d
commit 656ade9884
39 changed files with 729 additions and 434 deletions

View 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)
})
}

View 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))
}

View 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
}

View File

@@ -0,0 +1,7 @@
package azsettings
type AzureSettings struct {
Cloud string
ManagedIdentityEnabled bool
ManagedIdentityClientId string
}

View File

@@ -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)
})
}

View File

@@ -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 ""

View File

@@ -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)

View File

@@ -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{},
},
},

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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,