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 13:26:49 +02:00
committed by GitHub
parent 16db1ad46d
commit 656ade9884
39 changed files with 728 additions and 433 deletions
@@ -22,12 +22,13 @@ import (
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/services/sqlstore"
"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 TestService(t *testing.T) {
cfg := &setting.Cfg{}
sqlStore := sqlstore.InitTestDB(t)
origSecret := setting.SecretKey
@@ -37,7 +38,7 @@ func TestService(t *testing.T) {
})
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
s := ProvideService(bus.New(), sqlStore, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
s := ProvideService(bus.New(), sqlStore, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
var ds *models.DataSource
@@ -220,6 +221,8 @@ func TestService_IDScopeResolver(t *testing.T) {
//nolint:goconst
func TestService_GetHttpTransport(t *testing.T) {
cfg := &setting.Cfg{}
t.Run("Should use cached proxy", func(t *testing.T) {
var configuredTransport *http.Transport
provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
@@ -235,7 +238,7 @@ func TestService_GetHttpTransport(t *testing.T) {
}
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
rt1, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
@@ -268,7 +271,7 @@ func TestService_GetHttpTransport(t *testing.T) {
json.Set("tlsAuthWithCACert", true)
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
tlsCaCert, err := secretsService.Encrypt(context.Background(), []byte(caCert), secrets.WithoutScope())
require.NoError(t, err)
@@ -318,7 +321,7 @@ func TestService_GetHttpTransport(t *testing.T) {
json.Set("tlsAuth", true)
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
tlsClientCert, err := secretsService.Encrypt(context.Background(), []byte(clientCert), secrets.WithoutScope())
require.NoError(t, err)
@@ -361,7 +364,7 @@ func TestService_GetHttpTransport(t *testing.T) {
json.Set("serverName", "server-name")
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
tlsCaCert, err := secretsService.Encrypt(context.Background(), []byte(caCert), secrets.WithoutScope())
require.NoError(t, err)
@@ -398,7 +401,7 @@ func TestService_GetHttpTransport(t *testing.T) {
json.Set("tlsSkipVerify", true)
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
ds := models.DataSource{
Id: 1,
@@ -429,7 +432,7 @@ func TestService_GetHttpTransport(t *testing.T) {
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
encryptedData, err := secretsService.Encrypt(context.Background(), []byte(`Bearer xf5yhfkpsnmgo`), secrets.WithoutScope())
require.NoError(t, err)
@@ -488,7 +491,7 @@ func TestService_GetHttpTransport(t *testing.T) {
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
ds := models.DataSource{
Id: 1,
@@ -521,7 +524,7 @@ func TestService_GetHttpTransport(t *testing.T) {
require.NoError(t, err)
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
ds := models.DataSource{
Type: models.DS_ES,
@@ -537,6 +540,7 @@ func TestService_GetHttpTransport(t *testing.T) {
}
func TestService_getTimeout(t *testing.T) {
cfg := &setting.Cfg{}
originalTimeout := sdkhttpclient.DefaultTimeoutOptions.Timeout
sdkhttpclient.DefaultTimeoutOptions.Timeout = 60 * time.Second
t.Cleanup(func() {
@@ -555,7 +559,7 @@ func TestService_getTimeout(t *testing.T) {
}
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
for _, tc := range testCases {
ds := &models.DataSource{
@@ -566,9 +570,11 @@ func TestService_getTimeout(t *testing.T) {
}
func TestService_DecryptedValue(t *testing.T) {
cfg := &setting.Cfg{}
t.Run("When datasource hasn't been updated, encrypted JSON should be fetched from cache", func(t *testing.T) {
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
encryptedJsonData, err := secretsService.EncryptJsonData(
context.Background(),
@@ -622,7 +628,7 @@ func TestService_DecryptedValue(t *testing.T) {
SecureJsonData: encryptedJsonData,
}
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
// Populate cache
password, ok := dsService.DecryptedValue(&ds, "password")
@@ -644,6 +650,10 @@ func TestService_DecryptedValue(t *testing.T) {
}
func TestService_HTTPClientOptions(t *testing.T) {
cfg := &setting.Cfg{
Azure: &azsettings.AzureSettings{},
}
emptyJsonData := simplejson.New()
emptySecureJsonData := map[string][]byte{}
@@ -654,70 +664,144 @@ func TestService_HTTPClientOptions(t *testing.T) {
}
t.Run("Azure authentication", func(t *testing.T) {
t.Run("should be disabled if no Azure credentials configured", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
t.Run("given feature flag enabled", func(t *testing.T) {
features := featuremgmt.WithFeatures(featuremgmt.FlagHttpclientproviderAzureAuth)
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
t.Run("should set Azure middleware when JsonData contains valid credentials", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
})
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, features, acmock.New(), acmock.NewPermissionsServicesMock())
t.Run("should be enabled if Azure credentials configured", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
require.NotNil(t, opts.Middlewares)
assert.Len(t, opts.Middlewares, 1)
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
t.Run("should not set Azure middleware when JsonData doesn't contain valid credentials", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"httpMethod": "POST",
})
require.Contains(t, opts.CustomOptions, "_azureCredentials")
credentials := opts.CustomOptions["_azureCredentials"]
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, features, acmock.New(), acmock.NewPermissionsServicesMock())
assert.IsType(t, &azcredentials.AzureManagedIdentityCredentials{}, credentials)
})
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
t.Run("should fail if credentials are invalid", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureCredentials": "invalid",
if opts.Middlewares != nil {
assert.Len(t, opts.Middlewares, 0)
}
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
t.Run("should return error when JsonData contains invalid credentials", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
_, err := dsService.httpClientOptions(&ds)
assert.Error(t, err)
})
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": "invalid",
})
t.Run("should pass resourceId from JsonData", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, features, acmock.New(), acmock.NewPermissionsServicesMock())
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
_, err := dsService.httpClientOptions(&ds)
assert.Error(t, err)
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
t.Run("should set Azure middleware when JsonData contains credentials and valid audience", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
})
require.Contains(t, opts.CustomOptions, "azureEndpointResourceId")
azureEndpointResourceId := opts.CustomOptions["azureEndpointResourceId"]
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, features, acmock.New(), acmock.NewPermissionsServicesMock())
assert.Equal(t, "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5", azureEndpointResourceId)
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
require.NotNil(t, opts.Middlewares)
assert.Len(t, opts.Middlewares, 1)
})
t.Run("should not set Azure middleware when JsonData doesn't contain credentials", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"httpMethod": "POST",
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, features, acmock.New(), acmock.NewPermissionsServicesMock())
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
if opts.Middlewares != nil {
assert.Len(t, opts.Middlewares, 0)
}
})
t.Run("should return error when JsonData contains invalid audience", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "invalid",
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, features, acmock.New(), acmock.NewPermissionsServicesMock())
_, err := dsService.httpClientOptions(&ds)
assert.Error(t, err)
})
})
t.Run("given feature flag not enabled", func(t *testing.T) {
t.Run("should not set Azure middleware even when JsonData contains credentials", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
})
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
if opts.Middlewares != nil {
assert.Len(t, opts.Middlewares, 0)
}
})
})
})
}