Infra: Azure authentication in HttpClientProvider (#36932) (#37124)

* Azure middleware in HttpClientProxy

* Azure authentication under feature flag

* Minor fixes

* Add prefixes to not clash with JsonData

* Return error if JsonData cannot be parsed

* Return original string if URL invalid

* Tests for datasource_cache

(cherry picked from commit c1963024ec)

Co-authored-by: Sergey Kostrukov <sergey@kostrukov.com>
This commit is contained in:
Grot (@grafanabot)
2021-07-22 23:12:31 +02:00
committed by GitHub
co-authored by Sergey Kostrukov
parent 154231a58d
commit fda235a862
6 changed files with 344 additions and 14 deletions
@@ -0,0 +1,83 @@
package azcredentials
import (
"fmt"
)
func FromDatasourceData(data map[string]interface{}, secureData map[string]string) (AzureCredentials, error) {
if credentialsObj, err := getMapOptional(data, "azureCredentials"); err != nil {
return nil, err
} else if credentialsObj == nil {
return nil, nil
} else {
return getFromCredentialsObject(credentialsObj, secureData)
}
}
func getFromCredentialsObject(credentialsObj map[string]interface{}, secureData map[string]string) (AzureCredentials, error) {
authType, err := getStringValue(credentialsObj, "authType")
if err != nil {
return nil, err
}
switch authType {
case AzureAuthManagedIdentity:
credentials := &AzureManagedIdentityCredentials{}
return credentials, nil
case AzureAuthClientSecret:
cloud, err := getStringValue(credentialsObj, "azureCloud")
if err != nil {
return nil, err
}
tenantId, err := getStringValue(credentialsObj, "tenantId")
if err != nil {
return nil, err
}
clientId, err := getStringValue(credentialsObj, "clientId")
if err != nil {
return nil, err
}
clientSecret := secureData["azureClientSecret"]
credentials := &AzureClientSecretCredentials{
AzureCloud: cloud,
TenantId: tenantId,
ClientId: clientId,
ClientSecret: clientSecret,
}
return credentials, nil
default:
err := fmt.Errorf("the authentication type '%s' not supported", authType)
return nil, err
}
}
func getMapOptional(obj map[string]interface{}, key string) (map[string]interface{}, error) {
if untypedValue, ok := obj[key]; ok {
if value, ok := untypedValue.(map[string]interface{}); ok {
return value, nil
} else {
err := fmt.Errorf("the field '%s' should be an object", key)
return nil, err
}
} else {
// Value optional, not error
return nil, nil
}
}
func getStringValue(obj map[string]interface{}, key string) (string, error) {
if untypedValue, ok := obj[key]; ok {
if value, ok := untypedValue.(string); ok {
return value, nil
} else {
err := fmt.Errorf("the field '%s' should be a string", key)
return "", err
}
} else {
err := fmt.Errorf("the field '%s' should be set", key)
return "", err
}
}
@@ -11,13 +11,17 @@ 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 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)
})
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)
})
}