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