AzureMonitor: User authentication support (#81918)

* Stub out frontend user auth

* Stub out backend user auth

* Add context

* Reorganise files

* Refactor app registration form

* Alert for user auth service principal credentials

* AzureMonitor: Add flag for enabling/disabling fallback credentials for current user authentication (#82332)

* Rename field

* Add fallback setting

* Update tests and mock

* Remove duplicate setting line

* Update name of property

* Update frontend settings

* Update docs and default config files

* Update azure-sdk

* Fix lint

* Update test

* Bump dependency

* Update configuration

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>

* Docs review

* AzureMonitor: User authentication frontend updates (#83107)

* Rename field

* Add fallback setting

* Update tests and mock

* Remove duplicate setting line

* Update name of property

* Update frontend settings

* Update docs and default config files

* Add alerts to query editor

- Add authenticatedBy property to grafana/data
- Update mocks
- Update query editor to disable it under certain circumstances
- Update tests

* Add separate FallbackCredentials component

- Reset AppRegistrationCredentials component to only handle clientsecret credentials
- Update AzureCredentialsForm
- Update selectors
- Update tests
- Update credentials utility functions logic

* Alert when fallback credentials disabled

* Update condition

* Update azure-sdk

* Fix lint

* Update test

* Remove unneeded conditions

* Set auth type correctly

* Legacy cloud options

* Fix client secret

* Remove accidental import

* Bump dependency

* Add tests

* Don't use VerticalGroup component

* Remove unused import

* Fix lint

* Appropriately set oAuthPassThru and disableGrafanaCache properties

* Clear azureCredentials on authType change

* Correctly retrieve secret

* Fix bug in authTypeOptions

* Update public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx

Co-authored-by: Andrew Hackmann <5140848+bossinc@users.noreply.github.com>

* Update public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx

Co-authored-by: Andrew Hackmann <5140848+bossinc@users.noreply.github.com>

* Update public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx

Co-authored-by: Andrew Hackmann <5140848+bossinc@users.noreply.github.com>

* Add documentation links

* Fix broken link

---------

Co-authored-by: Andrew Hackmann <5140848+bossinc@users.noreply.github.com>

* AzureMonitor: Update docs for current user authentication (#83440)

* Rename field

* Add fallback setting

* Update tests and mock

* Remove duplicate setting line

* Update name of property

* Update frontend settings

* Update docs and default config files

* Add alerts to query editor

- Add authenticatedBy property to grafana/data
- Update mocks
- Update query editor to disable it under certain circumstances
- Update tests

* Add separate FallbackCredentials component

- Reset AppRegistrationCredentials component to only handle clientsecret credentials
- Update AzureCredentialsForm
- Update selectors
- Update tests
- Update credentials utility functions logic

* Alert when fallback credentials disabled

* Update condition

* Update azure-sdk

* Fix lint

* Update test

* Remove unneeded conditions

* Set auth type correctly

* Legacy cloud options

* Fix client secret

* Remove accidental import

* Bump dependency

* Add tests

* Don't use VerticalGroup component

* Remove unused import

* Update docs

* Fix lint

* Appropriately set oAuthPassThru and disableGrafanaCache properties

* Clear azureCredentials on authType change

* Correctly retrieve secret

* Feedback

* Spelling

* Update docs/sources/datasources/azure-monitor/_index.md

Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>

* Update docs/sources/datasources/azure-monitor/_index.md

Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>

* Update docs/sources/datasources/azure-monitor/_index.md

Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>

* Update docs/sources/datasources/azure-monitor/_index.md

Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>

---------

Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>

* Docs review

* Update docs with additional configuration information

* Fix to appropriately hide the query editor

* Typo

* Update isCredentialsComplete

* Update test

---------

Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
Co-authored-by: Andrew Hackmann <5140848+bossinc@users.noreply.github.com>
Co-authored-by: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com>
This commit is contained in:
Andreas Christou
2024-03-19 16:32:24 +00:00
committed by GitHub
co-authored by Larissa Wandzura Christopher Moyer Andrew Hackmann
parent 2a6a1fb3b3
commit 6bb7ab261a
55 changed files with 1058 additions and 328 deletions
@@ -62,6 +62,27 @@ func getFromLegacy(data map[string]interface{}, secureData map[string]string) (a
credentials := &azcredentials.AzureWorkloadIdentityCredentials{}
return credentials, nil
case azcredentials.AzureAuthCurrentUserIdentity:
legacyCloud, err := maputil.GetStringOptional(data, "cloudName")
if err != nil {
return nil, err
}
cloud, err := resolveLegacyCloudName(legacyCloud)
if err != nil {
return nil, err
}
clientSecret := secureData["clientSecret"]
credentials := &azcredentials.AadCurrentUserCredentials{
ServiceCredentials: &azcredentials.AzureClientSecretCredentials{
AzureCloud: cloud,
TenantId: tenantId,
ClientId: clientId,
ClientSecret: clientSecret,
},
}
return credentials, nil
case azcredentials.AzureAuthClientSecret:
legacyCloud, err := maputil.GetStringOptional(data, "cloudName")
if err != nil {
+20 -18
View File
@@ -11,6 +11,7 @@ import (
"strconv"
"github.com/grafana/grafana-azure-sdk-go/v2/azsettings"
"github.com/grafana/grafana-azure-sdk-go/v2/azusercontext"
"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/httpclient"
@@ -52,11 +53,11 @@ func ProvideService(httpClientProvider *httpclient.Provider) *Service {
}
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
return s.queryMux.QueryData(ctx, req)
return s.queryMux.QueryData(azusercontext.WithUserFromQueryReq(ctx, req), req)
}
func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return s.resourceHandler.CallResource(ctx, req, sender)
return s.resourceHandler.CallResource(azusercontext.WithUserFromResourceReq(ctx, req), req, sender)
}
type Service struct {
@@ -191,10 +192,10 @@ func (s *Service) getDSInfo(ctx context.Context, pluginCtx backend.PluginContext
return instance, nil
}
func queryMetricHealth(dsInfo types.DatasourceInfo) (*http.Response, error) {
func queryMetricHealth(ctx context.Context, dsInfo types.DatasourceInfo) (*http.Response, error) {
subscriptionsApiVersion := "2020-01-01"
url := fmt.Sprintf("%v/subscriptions?api-version=%v", dsInfo.Routes["Azure Monitor"].URL, subscriptionsApiVersion)
request, err := http.NewRequest(http.MethodGet, url, nil)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
@@ -207,9 +208,9 @@ func queryMetricHealth(dsInfo types.DatasourceInfo) (*http.Response, error) {
return res, nil
}
func checkAzureLogAnalyticsHealth(dsInfo types.DatasourceInfo, subscription string) (*http.Response, error) {
func checkAzureLogAnalyticsHealth(ctx context.Context, dsInfo types.DatasourceInfo, subscription string) (*http.Response, error) {
workspacesUrl := fmt.Sprintf("%v/subscriptions/%v/providers/Microsoft.OperationalInsights/workspaces?api-version=2017-04-26-preview", dsInfo.Routes["Azure Monitor"].URL, subscription)
workspacesReq, err := http.NewRequest(http.MethodGet, workspacesUrl, nil)
workspacesReq, err := http.NewRequestWithContext(ctx, http.MethodGet, workspacesUrl, nil)
if err != nil {
return nil, err
}
@@ -238,7 +239,7 @@ func checkAzureLogAnalyticsHealth(dsInfo types.DatasourceInfo, subscription stri
}
workspaceUrl := fmt.Sprintf("%v/v1/workspaces/%v/query", dsInfo.Routes["Azure Log Analytics"].URL, defaultWorkspaceId)
workspaceReq, err := http.NewRequest(http.MethodPost, workspaceUrl, bytes.NewBuffer(body))
workspaceReq, err := http.NewRequestWithContext(ctx, http.MethodPost, workspaceUrl, bytes.NewBuffer(body))
workspaceReq.Header.Set("Content-Type", "application/json")
if err != nil {
return nil, err
@@ -252,7 +253,7 @@ func checkAzureLogAnalyticsHealth(dsInfo types.DatasourceInfo, subscription stri
return res, nil
}
func checkAzureMonitorResourceGraphHealth(dsInfo types.DatasourceInfo, subscription string) (*http.Response, error) {
func checkAzureMonitorResourceGraphHealth(ctx context.Context, dsInfo types.DatasourceInfo, subscription string) (*http.Response, error) {
body, err := json.Marshal(map[string]any{
"query": "Resources | project id | limit 1",
"subscriptions": []string{subscription},
@@ -261,7 +262,7 @@ func checkAzureMonitorResourceGraphHealth(dsInfo types.DatasourceInfo, subscript
return nil, err
}
url := fmt.Sprintf("%v/providers/Microsoft.ResourceGraph/resources?api-version=%v", dsInfo.Routes["Azure Resource Graph"].URL, resourcegraph.ArgAPIVersion)
request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body))
request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body))
request.Header.Set("Content-Type", "application/json")
if err != nil {
return nil, err
@@ -275,9 +276,9 @@ func checkAzureMonitorResourceGraphHealth(dsInfo types.DatasourceInfo, subscript
return res, nil
}
func metricCheckHealth(dsInfo types.DatasourceInfo, logger log.Logger) (message string, defaultSubscription string, status backend.HealthStatus) {
func metricCheckHealth(ctx context.Context, dsInfo types.DatasourceInfo, logger log.Logger) (message string, defaultSubscription string, status backend.HealthStatus) {
defaultSubscription = dsInfo.Settings.SubscriptionId
metricsRes, err := queryMetricHealth(dsInfo)
metricsRes, err := queryMetricHealth(ctx, dsInfo)
if err != nil {
if ok := errors.Is(err, types.ErrorAzureHealthCheck); ok {
return fmt.Sprintf("Error connecting to Azure Monitor endpoint: %s", err.Error()), defaultSubscription, backend.HealthStatusError
@@ -309,8 +310,8 @@ func metricCheckHealth(dsInfo types.DatasourceInfo, logger log.Logger) (message
return "Successfully connected to Azure Monitor endpoint.", defaultSubscription, backend.HealthStatusOk
}
func logAnalyticsCheckHealth(dsInfo types.DatasourceInfo, defaultSubscription string) (message string, status backend.HealthStatus) {
logsRes, err := checkAzureLogAnalyticsHealth(dsInfo, defaultSubscription)
func logAnalyticsCheckHealth(ctx context.Context, dsInfo types.DatasourceInfo, defaultSubscription string) (message string, status backend.HealthStatus) {
logsRes, err := checkAzureLogAnalyticsHealth(ctx, dsInfo, defaultSubscription)
if err != nil {
if err.Error() == "no default workspace found" {
return "No Log Analytics workspaces found.", backend.HealthStatusUnknown
@@ -337,8 +338,8 @@ func logAnalyticsCheckHealth(dsInfo types.DatasourceInfo, defaultSubscription st
return "Successfully connected to Azure Log Analytics endpoint.", backend.HealthStatusOk
}
func graphLogHealthCheck(dsInfo types.DatasourceInfo, defaultSubscription string) (message string, status backend.HealthStatus) {
resourceGraphRes, err := checkAzureMonitorResourceGraphHealth(dsInfo, defaultSubscription)
func graphLogHealthCheck(ctx context.Context, dsInfo types.DatasourceInfo, defaultSubscription string) (message string, status backend.HealthStatus) {
resourceGraphRes, err := checkAzureMonitorResourceGraphHealth(ctx, dsInfo, defaultSubscription)
if err != nil {
if ok := errors.Is(err, types.ErrorAzureHealthCheck); ok {
return fmt.Sprintf("Error connecting to Azure Resource Graph endpoint: %s", err.Error()), backend.HealthStatusError
@@ -387,6 +388,7 @@ func parseSubscriptions(res *http.Response, logger log.Logger) ([]string, error)
}
func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
ctx = azusercontext.WithUserFromHealthCheckReq(ctx, req)
dsInfo, err := s.getDSInfo(ctx, req.PluginContext)
if err != nil {
return &backend.CheckHealthResult{
@@ -397,17 +399,17 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
status := backend.HealthStatusOk
metricsLog, defaultSubscription, metricsStatus := metricCheckHealth(dsInfo, s.logger)
metricsLog, defaultSubscription, metricsStatus := metricCheckHealth(ctx, dsInfo, s.logger)
if metricsStatus != backend.HealthStatusOk {
status = metricsStatus
}
logAnalyticsLog, logAnalyticsStatus := logAnalyticsCheckHealth(dsInfo, defaultSubscription)
logAnalyticsLog, logAnalyticsStatus := logAnalyticsCheckHealth(ctx, dsInfo, defaultSubscription)
if logAnalyticsStatus != backend.HealthStatusOk {
status = logAnalyticsStatus
}
graphLog, graphStatus := graphLogHealthCheck(dsInfo, defaultSubscription)
graphLog, graphStatus := graphLogHealthCheck(ctx, dsInfo, defaultSubscription)
if graphStatus != backend.HealthStatusOk {
status = graphStatus
}
+1
View File
@@ -38,6 +38,7 @@ func newHTTPClient(ctx context.Context, route types.AzRoute, model types.Datasou
}
authOpts := azhttpclient.NewAuthOptions(azureSettings)
authOpts.AllowUserIdentity()
authOpts.Scopes(route.Scopes)
azhttpclient.AddAzureAuthentication(&clientOpts, authOpts, model.Credentials)
}