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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 729 additions and 434 deletions

View File

@ -34,6 +34,7 @@ import (
)
func TestDataSourceProxy_routeRule(t *testing.T) {
cfg := &setting.Cfg{}
httpClientProvider := httpclient.NewProvider()
tracer, err := tracing.InitializeTracerForTest()
require.NoError(t, err)
@ -126,11 +127,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
return ctx, req
}
cfg := &setting.Cfg{}
t.Run("When matching route path", func(t *testing.T) {
ctx, req := setUp()
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/v4/some/method", cfg, httpClientProvider,
&oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -143,7 +142,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("When matching route path and has dynamic url", func(t *testing.T) {
ctx, req := setUp()
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/common/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
proxy.matchedRoute = routes[3]
@ -155,7 +154,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("When matching route path with no url", func(t *testing.T) {
ctx, req := setUp()
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
proxy.matchedRoute = routes[4]
@ -166,7 +165,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("When matching route path and has dynamic body", func(t *testing.T) {
ctx, req := setUp()
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/body", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
proxy.matchedRoute = routes[5]
@ -180,7 +179,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("Validating request", func(t *testing.T) {
t.Run("plugin route with valid role", func(t *testing.T) {
ctx, _ := setUp()
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
err = proxy.validateRequest()
@ -189,7 +188,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("plugin route with admin role and user is editor", func(t *testing.T) {
ctx, _ := setUp()
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
err = proxy.validateRequest()
@ -199,7 +198,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("plugin route with admin role and user is admin", func(t *testing.T) {
ctx, _ := setUp()
ctx.SignedInUser.OrgRole = models.ROLE_ADMIN
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
err = proxy.validateRequest()
@ -277,8 +276,6 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
client = newFakeHTTPClient(t, json)
defer func() { client = originalClient }()
cfg := &setting.Cfg{}
jd, err := ds.JsonData.Map()
require.NoError(t, err)
dsInfo := DSInfo{
@ -290,7 +287,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
},
}
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[0], dsInfo, cfg)
@ -306,7 +303,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
require.NoError(t, err)
client = newFakeHTTPClient(t, json2)
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken2", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[1], dsInfo, cfg)
@ -323,7 +320,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
require.NoError(t, err)
client = newFakeHTTPClient(t, []byte{})
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[0], dsInfo, cfg)
@ -345,7 +342,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
ctx := &models.ReqContext{}
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{BuildVersion: "5.3.0"}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
@ -371,7 +368,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
ctx := &models.ReqContext{}
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -395,7 +392,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
ctx := &models.ReqContext{}
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -423,7 +420,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
ctx := &models.ReqContext{}
var pluginRoutes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, pluginRoutes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -446,7 +443,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
ctx := &models.ReqContext{}
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
@ -510,7 +507,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &mockAuthToken, dsService, tracer, secretsService)
require.NoError(t, err)
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
@ -579,13 +576,14 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
createAuthTest(t, secretsService, models.DS_ES, "http://localhost:9200", authTypeBasic, authCheckHeader, true),
}
for _, test := range tests {
runDatasourceAuthTest(t, secretsService, test)
runDatasourceAuthTest(t, secretsService, cfg, test)
}
})
}
// test DataSourceProxy request handling.
func TestDataSourceProxy_requestHandling(t *testing.T) {
cfg := &setting.Cfg{}
httpClientProvider := httpclient.NewProvider()
var writeErr error
@ -643,7 +641,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
ctx, ds := setUp(t)
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -661,7 +659,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
})
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -675,7 +673,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
ctx, ds := setUp(t)
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -697,7 +695,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
})
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -722,7 +720,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
ctx.Req = httptest.NewRequest("GET", "/api/datasources/proxy/1/path/%2Ftest%2Ftest%2F?query=%2Ftest%2Ftest%2F", nil)
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -746,7 +744,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
ctx.Req = httptest.NewRequest("GET", "/api/datasources/proxy/1/path/%2Ftest%2Ftest%2F?query=%2Ftest%2Ftest%2F", nil)
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -766,13 +764,13 @@ func TestNewDataSourceProxy_InvalidURL(t *testing.T) {
Type: "test",
Url: "://host/root",
}
cfg := setting.Cfg{}
cfg := &setting.Cfg{}
tracer, err := tracing.InitializeTracerForTest()
require.NoError(t, err)
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
require.Error(t, err)
assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`))
}
@ -786,14 +784,14 @@ func TestNewDataSourceProxy_ProtocolLessURL(t *testing.T) {
Type: "test",
Url: "127.0.01:5432",
}
cfg := setting.Cfg{}
cfg := &setting.Cfg{}
tracer, err := tracing.InitializeTracerForTest()
require.NoError(t, err)
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
}
@ -827,7 +825,7 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
}
for _, tc := range tcs {
t.Run(tc.description, func(t *testing.T) {
cfg := setting.Cfg{}
cfg := &setting.Cfg{}
ds := models.DataSource{
Type: "mssql",
Url: tc.url,
@ -835,8 +833,8 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
p, err := NewDataSourceProxy(&ds, routes, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
p, err := NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
if tc.err == nil {
require.NoError(t, err)
assert.Equal(t, &url.URL{
@ -862,7 +860,7 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *sett
var routes []*plugins.Route
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
@ -980,13 +978,13 @@ func createAuthTest(t *testing.T, secretsService secrets.Service, dsType string,
return test
}
func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, test *testCase) {
func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, cfg *setting.Cfg, test *testCase) {
ctx := &models.ReqContext{}
tracer, err := tracing.InitializeTracerForTest()
require.NoError(t, err)
var routes []*plugins.Route
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(test.datasource, routes, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)
@ -999,6 +997,7 @@ func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, test *t
}
func Test_PathCheck(t *testing.T) {
cfg := &setting.Cfg{}
// Ensure that we test routes appropriately. This test reproduces a historical bug where two routes were defined with different role requirements but the same method and the more privileged route was tested first. Here we ensure auth checks are applied based on the correct route, not just the method.
routes := []*plugins.Route{
{
@ -1028,7 +1027,7 @@ func Test_PathCheck(t *testing.T) {
}
ctx, _ := setUp()
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
proxy, err := NewDataSourceProxy(&models.DataSource{}, routes, ctx, "b", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
require.NoError(t, err)

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"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/aztokenprovider"
)
@ -17,8 +18,8 @@ type azureAccessTokenProvider struct {
}
func newAzureAccessTokenProvider(ctx context.Context, cfg *setting.Cfg, authParams *plugins.JWTTokenAuth) (*azureAccessTokenProvider, error) {
credentials := getAzureCredentials(cfg, authParams)
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg, credentials)
credentials := getAzureCredentials(cfg.Azure, authParams)
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg.Azure, credentials)
if err != nil {
return nil, err
}
@ -33,7 +34,7 @@ func (provider *azureAccessTokenProvider) GetAccessToken() (string, error) {
return provider.tokenProvider.GetAccessToken(provider.ctx, provider.scopes)
}
func getAzureCredentials(cfg *setting.Cfg, authParams *plugins.JWTTokenAuth) azcredentials.AzureCredentials {
func getAzureCredentials(settings *azsettings.AzureSettings, authParams *plugins.JWTTokenAuth) azcredentials.AzureCredentials {
authType := strings.ToLower(authParams.Params["azure_auth_type"])
clientId := authParams.Params["client_id"]
@ -43,7 +44,7 @@ func getAzureCredentials(cfg *setting.Cfg, authParams *plugins.JWTTokenAuth) azc
// before managed identities where introduced, therefore use client secret authentication
// * If authType and other fields aren't set then it means the datasource never been configured
// and managed identity is the default authentication choice as long as managed identities are enabled
isManagedIdentity := authType == "msi" || (authType == "" && clientId == "" && cfg.Azure.ManagedIdentityEnabled)
isManagedIdentity := authType == "msi" || (authType == "" && clientId == "" && settings.ManagedIdentityEnabled)
if isManagedIdentity {
return &azcredentials.AzureManagedIdentityCredentials{}

View File

@ -1,86 +0,0 @@
package httpclientprovider
import (
"fmt"
"net/http"
"net/url"
"path"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/aztokenprovider"
)
const azureMiddlewareName = "AzureAuthentication.Provider"
func AzureMiddleware(cfg *setting.Cfg) httpclient.Middleware {
return httpclient.NamedMiddlewareFunc(azureMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
credentials, err := getAzureCredentials(opts.CustomOptions)
if err != nil {
return errorResponse(err)
} else if credentials == nil {
return next
}
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg, credentials)
if err != nil {
return errorResponse(err)
}
scopes, err := getAzureEndpointScopes(opts.CustomOptions)
if err != nil {
return errorResponse(err)
}
return aztokenprovider.ApplyAuth(tokenProvider, scopes, next)
})
}
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)
})
}
func getAzureCredentials(customOptions map[string]interface{}) (azcredentials.AzureCredentials, error) {
if untypedValue, ok := customOptions["_azureCredentials"]; !ok {
return nil, nil
} else if value, ok := untypedValue.(azcredentials.AzureCredentials); !ok {
err := fmt.Errorf("the field 'azureCredentials' should be a valid credentials object")
return nil, err
} else {
return value, nil
}
}
func getAzureEndpointResourceId(customOptions map[string]interface{}) (*url.URL, error) {
var value string
if untypedValue, ok := customOptions["azureEndpointResourceId"]; !ok {
err := fmt.Errorf("the field 'azureEndpointResourceId' should be set")
return nil, err
} else if value, ok = untypedValue.(string); !ok {
err := fmt.Errorf("the field 'azureEndpointResourceId' should be a string")
return nil, err
}
resourceId, err := url.Parse(value)
if err != nil || resourceId.Scheme == "" || resourceId.Host == "" {
err := fmt.Errorf("invalid endpoint Resource ID URL '%s'", value)
return nil, err
}
return resourceId, nil
}
func getAzureEndpointScopes(customOptions map[string]interface{}) ([]string, error) {
resourceId, err := getAzureEndpointResourceId(customOptions)
if err != nil {
return nil, err
}
resourceId.Path = path.Join(resourceId.Path, ".default")
scopes := []string{resourceId.String()}
return scopes, nil
}

View File

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics/metricutil"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/mwitkow/go-conntrack"
)
@ -17,7 +16,7 @@ import (
var newProviderFunc = sdkhttpclient.NewProvider
// New creates a new HTTP client provider with pre-configured middlewares.
func New(cfg *setting.Cfg, tracer tracing.Tracer, features featuremgmt.FeatureToggles) *sdkhttpclient.Provider {
func New(cfg *setting.Cfg, tracer tracing.Tracer) *sdkhttpclient.Provider {
logger := log.New("httpclient")
userAgent := fmt.Sprintf("Grafana/%s", cfg.BuildVersion)
@ -36,10 +35,6 @@ func New(cfg *setting.Cfg, tracer tracing.Tracer, features featuremgmt.FeatureTo
setDefaultTimeoutOptions(cfg)
if features.IsEnabled(featuremgmt.FlagHttpclientproviderAzureAuth) {
middlewares = append(middlewares, AzureMiddleware(cfg))
}
return newProviderFunc(sdkhttpclient.ProviderOptions{
Middlewares: middlewares,
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {

View File

@ -5,7 +5,6 @@ import (
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
@ -23,7 +22,7 @@ func TestHTTPClientProvider(t *testing.T) {
})
tracer, err := tracing.InitializeTracerForTest()
require.NoError(t, err)
_ = New(&setting.Cfg{SigV4AuthEnabled: false}, tracer, featuremgmt.WithFeatures())
_ = New(&setting.Cfg{SigV4AuthEnabled: false}, tracer)
require.Len(t, providerOpts, 1)
o := providerOpts[0]
require.Len(t, o.Middlewares, 6)
@ -47,7 +46,7 @@ func TestHTTPClientProvider(t *testing.T) {
})
tracer, err := tracing.InitializeTracerForTest()
require.NoError(t, err)
_ = New(&setting.Cfg{SigV4AuthEnabled: true}, tracer, featuremgmt.WithFeatures())
_ = New(&setting.Cfg{SigV4AuthEnabled: true}, tracer)
require.Len(t, providerOpts, 1)
o := providerOpts[0]
require.Len(t, o.Middlewares, 7)

View File

@ -2,6 +2,7 @@ package plugins
import (
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
)
type Cfg struct {
@ -19,7 +20,7 @@ type Cfg struct {
AWSAssumeRoleEnabled bool
// Azure Cloud settings
Azure setting.AzureSettings
Azure *azsettings.AzureSettings
BuildVersion string // TODO Remove
}

View File

@ -84,14 +84,17 @@ func (i *Initializer) awsEnvVars() []string {
func (i *Initializer) azureEnvVars() []string {
var variables []string
if i.cfg.Azure.Cloud != "" {
variables = append(variables, "AZURE_CLOUD="+i.cfg.Azure.Cloud)
}
if i.cfg.Azure.ManagedIdentityClientId != "" {
variables = append(variables, "AZURE_MANAGED_IDENTITY_CLIENT_ID="+i.cfg.Azure.ManagedIdentityClientId)
}
if i.cfg.Azure.ManagedIdentityEnabled {
variables = append(variables, "AZURE_MANAGED_IDENTITY_ENABLED=true")
if i.cfg.Azure != nil {
if i.cfg.Azure.Cloud != "" {
variables = append(variables, "AZURE_CLOUD="+i.cfg.Azure.Cloud)
}
if i.cfg.Azure.ManagedIdentityClientId != "" {
variables = append(variables, "AZURE_MANAGED_IDENTITY_CLIENT_ID="+i.cfg.Azure.ManagedIdentityClientId)
}
if i.cfg.Azure.ManagedIdentityEnabled {
variables = append(variables, "AZURE_MANAGED_IDENTITY_ENABLED=true")
}
}
return variables

View File

@ -80,7 +80,7 @@ func TestPluginManager_int_init(t *testing.T) {
idb := influxdb.ProvideService(hcp)
lk := loki.ProvideService(hcp, tracer)
otsdb := opentsdb.ProvideService(hcp)
pr := prometheus.ProvideService(hcp, tracer)
pr := prometheus.ProvideService(hcp, cfg, features, tracer)
tmpo := tempo.ProvideService(hcp)
td := testdatasource.ProvideService(cfg, features)
pg := postgres.ProvideService(cfg)

View File

@ -8,6 +8,7 @@ import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
@ -574,9 +575,11 @@ func newScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerS
cfg.AWSAllowedAuthProviders = []string{"keys", "credentials"}
cfg.AWSAssumeRoleEnabled = true
cfg.Azure.ManagedIdentityEnabled = true
cfg.Azure.Cloud = "AzureCloud"
cfg.Azure.ManagedIdentityClientId = "client-id"
cfg.Azure = &azsettings.AzureSettings{
ManagedIdentityEnabled: true,
Cloud: "AzureCloud",
ManagedIdentityClientId: "client-id",
}
loader := &fakeLoader{}
manager := New(cfg, nil, loader)

View File

@ -5,6 +5,8 @@ import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"sync"
@ -23,12 +25,14 @@ import (
"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/azhttpclient"
)
type Service struct {
Bus bus.Bus
SQLStore *sqlstore.SQLStore
SecretsService secrets.Service
cfg *setting.Cfg
features featuremgmt.FeatureToggles
permissionsService accesscontrol.PermissionsService
@ -57,7 +61,7 @@ type cachedDecryptedJSON struct {
}
func ProvideService(
bus bus.Bus, store *sqlstore.SQLStore, secretsService secrets.Service, features featuremgmt.FeatureToggles,
bus bus.Bus, store *sqlstore.SQLStore, secretsService secrets.Service, cfg *setting.Cfg, features featuremgmt.FeatureToggles,
ac accesscontrol.AccessControl, permissionsServices accesscontrol.PermissionsServices,
) *Service {
s := &Service{
@ -70,6 +74,7 @@ func ProvideService(
dsDecryptionCache: secureJSONDecryptionCache{
cache: make(map[int64]cachedDecryptedJSON),
},
cfg: cfg,
features: features,
permissionsService: permissionsServices.GetDataSourceService(),
}
@ -233,7 +238,7 @@ func (s *Service) GetHTTPTransport(ds *models.DataSource, provider httpclient.Pr
return nil, err
}
opts.Middlewares = customMiddlewares
opts.Middlewares = append(opts.Middlewares, customMiddlewares...)
rt, err := provider.GetTransport(*opts)
if err != nil {
@ -337,7 +342,8 @@ func (s *Service) httpClientOptions(ds *models.DataSource) (*sdkhttpclient.Optio
}
}
if ds.JsonData != nil {
// Azure authentication
if ds.JsonData != nil && s.features.IsEnabled(featuremgmt.FlagHttpclientproviderAzureAuth) {
credentials, err := azcredentials.FromDatasourceData(ds.JsonData.MustMap(), s.DecryptedValues(ds))
if err != nil {
err = fmt.Errorf("invalid Azure credentials: %s", err)
@ -345,7 +351,22 @@ func (s *Service) httpClientOptions(ds *models.DataSource) (*sdkhttpclient.Optio
}
if credentials != nil {
opts.CustomOptions["_azureCredentials"] = credentials
resourceIdStr := ds.JsonData.Get("azureEndpointResourceId").MustString()
if resourceIdStr == "" {
err := fmt.Errorf("endpoint resource ID (audience) not provided")
return nil, err
}
resourceId, err := url.Parse(resourceIdStr)
if err != nil || resourceId.Scheme == "" || resourceId.Host == "" {
err := fmt.Errorf("endpoint resource ID (audience) '%s' invalid", resourceIdStr)
return nil, err
}
resourceId.Path = path.Join(resourceId.Path, ".default")
scopes := []string{resourceId.String()}
azhttpclient.AddAzureAuthentication(opts, s.cfg.Azure, credentials, scopes)
}
}

View File

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

View File

@ -20,7 +20,7 @@ var (
},
{
Name: "httpclientprovider_azure_auth",
Description: "use http client for azure auth",
Description: "Experimental. Allow datasources to configure Azure authentication directly via JsonData",
State: FeatureStateBeta,
},
{
@ -112,7 +112,7 @@ var (
},
{
Name: "prometheus_azure_auth",
Description: "Use azure authentication for prometheus datasource",
Description: "Experimental. Azure authentication for Prometheus datasource",
State: FeatureStateBeta,
},
{

View File

@ -16,7 +16,7 @@ const (
FlagEnvelopeEncryption = "envelopeEncryption"
// FlagHttpclientproviderAzureAuth
// use http client for azure auth
// Experimental. Allow datasources to configure Azure authentication directly via JsonData
FlagHttpclientproviderAzureAuth = "httpclientprovider_azure_auth"
// FlagServiceAccounts
@ -84,7 +84,7 @@ const (
FlagAccesscontrolBuiltins = "accesscontrol-builtins"
// FlagPrometheusAzureAuth
// Use azure authentication for prometheus datasource
// Experimental. Azure authentication for Prometheus datasource
FlagPrometheusAzureAuth = "prometheus_azure_auth"
// FlagInfluxdbBackendMigration

View File

@ -21,6 +21,7 @@ import (
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/util"
@ -302,7 +303,7 @@ type Cfg struct {
AWSListMetricsPageLimit int
// Azure Cloud settings
Azure AzureSettings
Azure *azsettings.AzureSettings
// Auth proxy settings
AuthProxyEnabled bool
@ -824,6 +825,7 @@ func NewCfg() *Cfg {
return &Cfg{
Logger: log.New("settings"),
Raw: ini.Empty(),
Azure: &azsettings.AzureSettings{},
}
}

View File

@ -1,73 +1,19 @@
package setting
import "strings"
const (
AzurePublic = "AzureCloud"
AzureChina = "AzureChinaCloud"
AzureUSGovernment = "AzureUSGovernment"
AzureGermany = "AzureGermanCloud"
)
type AzureSettings struct {
Cloud string
ManagedIdentityEnabled bool
ManagedIdentityClientId string
}
import "github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
func (cfg *Cfg) readAzureSettings() {
azureSettings := &azsettings.AzureSettings{}
azureSection := cfg.Raw.Section("azure")
// Cloud
cloudName := azureSection.Key("cloud").MustString(AzurePublic)
cfg.Azure.Cloud = normalizeAzureCloud(cloudName)
cloudName := azureSection.Key("cloud").MustString(azsettings.AzurePublic)
azureSettings.Cloud = azsettings.NormalizeAzureCloud(cloudName)
// Managed Identity
cfg.Azure.ManagedIdentityEnabled = azureSection.Key("managed_identity_enabled").MustBool(false)
cfg.Azure.ManagedIdentityClientId = azureSection.Key("managed_identity_client_id").String()
}
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
azureSettings.ManagedIdentityEnabled = azureSection.Key("managed_identity_enabled").MustBool(false)
azureSettings.ManagedIdentityClientId = azureSection.Key("managed_identity_client_id").String()
cfg.Azure = azureSettings
}

View File

@ -3,6 +3,7 @@ package setting
import (
"testing"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azsettings"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -17,27 +18,27 @@ func TestAzureSettings(t *testing.T) {
{
name: "should be Public if not set",
configuredValue: "",
resolvedValue: AzurePublic,
resolvedValue: azsettings.AzurePublic,
},
{
name: "should be Public if set to Public",
configuredValue: AzurePublic,
resolvedValue: AzurePublic,
configuredValue: azsettings.AzurePublic,
resolvedValue: azsettings.AzurePublic,
},
{
name: "should be Public if set to Public using alternative name",
configuredValue: "AzurePublicCloud",
resolvedValue: AzurePublic,
resolvedValue: azsettings.AzurePublic,
},
{
name: "should be China if set to China",
configuredValue: AzureChina,
resolvedValue: AzureChina,
configuredValue: azsettings.AzureChina,
resolvedValue: azsettings.AzureChina,
},
{
name: "should be US Government if set to US Government using alternative name",
configuredValue: "usgov",
resolvedValue: AzureUSGovernment,
resolvedValue: azsettings.AzureUSGovernment,
},
{
name: "should be same as set if not known",

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,

View File

@ -15,11 +15,14 @@ import (
"github.com/grafana/grafana/pkg/services/oauthtoken"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/legacydata"
"github.com/stretchr/testify/require"
)
func TestHandleRequest(t *testing.T) {
cfg := &setting.Cfg{}
t.Run("Should invoke plugin manager QueryData when handling request for query", func(t *testing.T) {
origOAuthIsOAuthPassThruEnabledFunc := oAuthIsOAuthPassThruEnabledFunc
oAuthIsOAuthPassThruEnabledFunc = func(oAuthTokenService oauthtoken.OAuthTokenService, ds *models.DataSource) bool {
@ -37,7 +40,7 @@ func TestHandleRequest(t *testing.T) {
return backend.NewQueryDataResponse(), nil
}
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsService, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
s := ProvideService(client, nil, dsService)
ds := &models.DataSource{Id: 12, Type: "unregisteredType", JsonData: simplejson.New()}

View File

@ -4,6 +4,8 @@ import (
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/prometheus/middleware"
"github.com/grafana/grafana/pkg/util/maputil"
@ -19,6 +21,8 @@ type Provider struct {
jsonData map[string]interface{}
httpMethod string
clientProvider httpclient.Provider
cfg *setting.Cfg
features featuremgmt.FeatureToggles
log log.Logger
}
@ -26,6 +30,8 @@ func NewProvider(
settings backend.DataSourceInstanceSettings,
jsonData map[string]interface{},
clientProvider httpclient.Provider,
cfg *setting.Cfg,
features featuremgmt.FeatureToggles,
log log.Logger,
) *Provider {
httpMethod, _ := maputil.GetStringOptional(jsonData, "httpMethod")
@ -34,6 +40,8 @@ func NewProvider(
jsonData: jsonData,
httpMethod: httpMethod,
clientProvider: clientProvider,
cfg: cfg,
features: features,
log: log,
}
}
@ -53,7 +61,7 @@ func (p *Provider) GetClient(headers map[string]string) (apiv1.API, error) {
}
// Azure authentication
err = p.configureAzureAuthentication(opts)
err = p.configureAzureAuthentication(&opts)
if err != nil {
return nil, err
}

View File

@ -2,13 +2,22 @@ package promclient
import (
"fmt"
"net/url"
"path"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azhttpclient"
"github.com/grafana/grafana/pkg/util/maputil"
)
func (p *Provider) configureAzureAuthentication(opts sdkhttpclient.Options) error {
func (p *Provider) configureAzureAuthentication(opts *sdkhttpclient.Options) error {
// Azure authentication is experimental (#35857)
if !p.features.IsEnabled(featuremgmt.FlagPrometheusAzureAuth) {
return nil
}
credentials, err := azcredentials.FromDatasourceData(p.jsonData, p.settings.DecryptedSecureJSONData)
if err != nil {
err = fmt.Errorf("invalid Azure credentials: %s", err)
@ -16,16 +25,24 @@ func (p *Provider) configureAzureAuthentication(opts sdkhttpclient.Options) erro
}
if credentials != nil {
opts.CustomOptions["_azureCredentials"] = credentials
resourceId, err := maputil.GetStringOptional(p.jsonData, "azureEndpointResourceId")
resourceIdStr, err := maputil.GetStringOptional(p.jsonData, "azureEndpointResourceId")
if err != nil {
return err
} else if resourceIdStr == "" {
err := fmt.Errorf("endpoint resource ID (audience) not provided")
return err
}
if resourceId != "" {
opts.CustomOptions["azureEndpointResourceId"] = resourceId
resourceId, err := url.Parse(resourceIdStr)
if err != nil || resourceId.Scheme == "" || resourceId.Host == "" {
err := fmt.Errorf("endpoint resource ID (audience) '%s' invalid", resourceIdStr)
return err
}
resourceId.Path = path.Join(resourceId.Path, ".default")
scopes := []string{resourceId.String()}
azhttpclient.AddAzureAuthentication(opts, p.cfg.Azure, credentials, scopes)
}
return nil

View File

@ -0,0 +1,150 @@
package promclient
import (
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfigureAzureAuthentication(t *testing.T) {
cfg := &setting.Cfg{}
settings := backend.DataSourceInstanceSettings{}
t.Run("given feature flag enabled", func(t *testing.T) {
features := featuremgmt.WithFeatures(featuremgmt.FlagPrometheusAzureAuth)
t.Run("should set Azure middleware when JsonData contains valid credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
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 valid credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
require.NoError(t, err)
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
t.Run("should return error when JsonData contains invalid credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": "invalid",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
assert.Error(t, err)
})
t.Run("should set Azure middleware when JsonData contains credentials and valid audience", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
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) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
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) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "invalid",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
assert.Error(t, err)
})
})
t.Run("given feature flag not enabled", func(t *testing.T) {
features := featuremgmt.WithFeatures()
t.Run("should not set Azure Credentials even when JsonData contains credentials", func(t *testing.T) {
jsonData := map[string]interface{}{
"httpMethod": "POST",
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
}
var p = NewProvider(settings, jsonData, nil, cfg, features, nil)
var opts = &sdkhttpclient.Options{CustomOptions: map[string]interface{}{}}
err := p.configureAzureAuthentication(opts)
require.NoError(t, err)
if opts.Middlewares != nil {
assert.Len(t, opts.Middlewares, 0)
}
})
})
}

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/tsdb/prometheus/promclient"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana-plugin-sdk-go/backend"
@ -138,9 +139,11 @@ func setup(jsonData ...string) *testContext {
var jd map[string]interface{}
_ = json.Unmarshal(rawData, &jd)
cfg := &setting.Cfg{}
settings := backend.DataSourceInstanceSettings{URL: "test-url", JSONData: rawData}
features := featuremgmt.WithFeatures()
hp := &fakeHttpClientProvider{}
p := promclient.NewProvider(settings, jd, hp, nil)
p := promclient.NewProvider(settings, jd, hp, cfg, features, nil)
return &testContext{
httpProvider: hp,

View File

@ -7,15 +7,16 @@ import (
"fmt"
"regexp"
"github.com/grafana/grafana/pkg/tsdb/prometheus/promclient"
"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/instancemgmt"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
"github.com/grafana/grafana/pkg/tsdb/prometheus/promclient"
"github.com/grafana/grafana/pkg/util/maputil"
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
)
@ -32,16 +33,16 @@ type Service struct {
tracer tracing.Tracer
}
func ProvideService(httpClientProvider httpclient.Provider, tracer tracing.Tracer) *Service {
func ProvideService(httpClientProvider httpclient.Provider, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer) *Service {
plog.Debug("initializing")
return &Service{
intervalCalculator: intervalv2.NewCalculator(),
im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)),
im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider, cfg, features)),
tracer: tracer,
}
}
func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc {
func newInstanceSettings(httpClientProvider httpclient.Provider, cfg *setting.Cfg, features featuremgmt.FeatureToggles) datasource.InstanceFactoryFunc {
return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
var jsonData map[string]interface{}
err := json.Unmarshal(settings.JSONData, &jsonData)
@ -49,7 +50,7 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
return nil, fmt.Errorf("error reading settings: %w", err)
}
p := promclient.NewProvider(settings, jsonData, httpClientProvider, plog)
p := promclient.NewProvider(settings, jsonData, httpClientProvider, cfg, features, plog)
pc, err := promclient.NewProviderCache(p)
if err != nil {
return nil, err