Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Azure authentication #785

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/serious-badgers-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'grafana-infinity-datasource': minor
---

Add native Azure authentication
6 changes: 6 additions & 0 deletions cspell.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@
"words": [
"ANSIC",
"azblob",
"azcredentials",
"azhttpclient",
"azsettings",
"basgys",
"Bauch",
"bigpanda",
"braintree",
"builtins",
"Chelsey",
"clientcredentials",
"clientsecret",
"clsx",
"cmdk",
"Collapsable",
Expand Down Expand Up @@ -85,6 +89,7 @@
"lucide",
"magefile",
"mainbg",
"maputil",
"maxif",
"microsocks",
"Milli",
Expand Down Expand Up @@ -170,6 +175,7 @@
"visualisation",
"vlookup",
"vuepress",
"workloadidentity",
"xinsnake",
"xmlframer",
"yesoreyeram",
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ services:
- GF_SECURITY_ANGULAR_SUPPORT_ENABLED=false
- GF_SECURITY_CSRF_ALWAYS_CHECK=true
- GF_ENTERPRISE_LICENSE_TEXT=$GF_ENTERPRISE_LICENSE_TEXT
- GF_AZURE_FORWARD_SETTINGS_TO_PLUGINS=
- GF_AZURE_WORKLOAD_IDENTITY_ENABLED=true
11 changes: 4 additions & 7 deletions docs/sources/examples/azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,12 @@ Here are the detailed steps on how to connect Microsoft Azure APIs
3. Note down the Client ID, Client Secret and Tenant ID
4. Give reader/monitoring reader access to the resources/subscriptions as necessary
5. Install the infinity plugin in Grafana and add data source for the same
1. Expand Authentication section and select "OAuth2"
2. Select "Client Credentials" as OAuth2 type
1. Expand Authentication section and select "Azure"
2. Select "Client Credentials" as Auth type
3. Specify the Client ID
4. Specify the Client Secret
5. Specify the Token URL `https://login.microsoftonline.com/<TENANT_ID>/oauth2/token`. Replace `<TENANT_ID>` with yours
6. Leave the Scopes section empty
7. Add the following Endpoint param
1. Key : `resource` Value: `https://management.azure.com/`
8. If you are using Infinity 1.0.0+, then also specify `https://management.azure.com/` as an allowed URL.
5. Specify the Tenant ID
6. If you are using Infinity 1.0.0+, then also specify `https://management.azure.com/` as an allowed URL.
6. Click Save and Test.
7. Click the `Explore` button
8. Configure the query
Expand Down
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.1
require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1
github.com/grafana/grafana-aws-sdk v0.24.0
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0
github.com/grafana/grafana-plugin-sdk-go v0.241.0
github.com/grafana/infinity-libs/lib/go/csvframer v1.0.0
github.com/grafana/infinity-libs/lib/go/gframer v1.0.0
Expand All @@ -22,7 +23,9 @@ require (

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/apache/arrow/go/v15 v15.0.2 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
Expand All @@ -44,6 +47,7 @@ require (
github.com/go-openapi/swag v0.23.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/flatbuffers v24.3.25+incompatible // indirect
github.com/google/go-cmp v0.6.0 // indirect
Expand All @@ -68,6 +72,7 @@ require (
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
Expand All @@ -89,6 +94,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI=
Expand All @@ -97,6 +97,8 @@ github.com/grafana/dataplane/sdata v0.0.7 h1:CImITypIyS1jxijCR6xqKx71JnYAxcwpH9C
github.com/grafana/dataplane/sdata v0.0.7/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU=
github.com/grafana/grafana-aws-sdk v0.24.0 h1:0RKCJTeIkpEUvLCTjGOK1+jYZpaE2nJaGghGLvtUsFs=
github.com/grafana/grafana-aws-sdk v0.24.0/go.mod h1:3zghFF6edrxn0d6k6X9HpGZXDH+VfA+MwD2Pc/9X0ec=
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0 h1:lajVqTWaE96MpbjZToj7EshvqgRWOfYNkD4MbIZizaY=
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0/go.mod h1:aKlFPE36IDa8qccRg3KbgZX3MQ5xymS3RelT4j6kkVU=
github.com/grafana/grafana-plugin-sdk-go v0.241.0 h1:zBcSW9xV9gA9hD8UN+HjJtD7tESMZcaQhA1BI76MTxM=
github.com/grafana/grafana-plugin-sdk-go v0.241.0/go.mod h1:2HjNwzGCfaFAyR2HGoECTwAmq8vSIn2L1/1yOt4XRS4=
github.com/grafana/infinity-libs/lib/go/csvframer v1.0.0 h1:PGM6BkwU1You9zFShVTtNvQcnQDabJ4jg9TLhqAdC/k=
Expand Down
87 changes: 87 additions & 0 deletions pkg/infinity/azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package infinity

import (
"context"
"fmt"
"net/http"

"github.com/grafana/grafana-azure-sdk-go/v2/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/v2/azhttpclient"
"github.com/grafana/grafana-azure-sdk-go/v2/azsettings"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
"github.com/grafana/grafana-plugin-sdk-go/data/utils/maputil"

"github.com/grafana/grafana-infinity-datasource/pkg/models"
)

func ApplyAzureAuth(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
_, span := tracing.DefaultTracer().Start(ctx, "ApplyAzureAuth")
defer span.End()

if !IsAzureAuthConfigured(settings) {
return httpClient, nil
}

credentialsObj, err := maputil.GetMapOptional(settings.RawData, "azureCredentials")
if err != nil {
return nil, err
} else if credentialsObj == nil {
return httpClient, nil
}

azSettings, err := azsettings.ReadFromEnv()
if err != nil {
return nil, err
}

// set authType if not set
// this can happen if the user manually provisions the datasource without setting the authType
if credentialsObj["authType"] == nil || credentialsObj["authType"] == "" {
credentialsObj["authType"] = azcredentials.AzureAuthClientSecret
}

// set azureCloud if not set
// this can happen if the user manually provisions the datasource without setting the azureCloud
if credentialsObj["azureCloud"] == nil || credentialsObj["azureCloud"] == "" {
credentialsObj["azureCloud"] = azSettings.GetDefaultCloud()
}

azCredentials, err := azcredentials.FromDatasourceData(settings.RawData, settings.RawSecureData)
if err != nil {
return nil, err
}

authOpts := azhttpclient.NewAuthOptions(azSettings)

// set scopes
if scopes, ok := credentialsObj["scopes"].([]any); ok {
stringScopes, err := toStringSlice(scopes)
if err != nil {
return nil, err
}

authOpts.Scopes(stringScopes)
}

httpClient.Transport = azhttpclient.AzureMiddleware(authOpts, azCredentials).
CreateMiddleware(httpclient.Options{}, httpClient.Transport)

return httpClient, nil
}

func IsAzureAuthConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodAzure
}

func toStringSlice(arr []any) ([]string, error) {
result := make([]string, len(arr))
for i, v := range arr {
if s, ok := v.(string); ok {
result[i] = s
} else {
return nil, fmt.Errorf("expected string, got %T", v)
}
}
return result, nil
}
6 changes: 5 additions & 1 deletion pkg/infinity/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C
httpClient = ApplyOAuthClientCredentials(ctx, httpClient, settings)
httpClient = ApplyOAuthJWT(ctx, httpClient, settings)
httpClient = ApplyAWSAuth(ctx, httpClient, settings)
httpClient, err = ApplyAzureAuth(ctx, httpClient, settings)
if err != nil {
return nil, err
}

httpClient, err = ApplySecureSocksProxyConfiguration(ctx, httpClient, settings)
if err != nil {
Expand Down Expand Up @@ -159,7 +163,7 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C

func ApplySecureSocksProxyConfiguration(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
logger := backend.Logger.FromContext(ctx)
if IsAwsAuthConfigured(settings) {
if IsAwsAuthConfigured(settings) || IsAzureAuthConfigured(settings) {
return httpClient, nil
}
t := httpClient.Transport
Expand Down
27 changes: 27 additions & 0 deletions pkg/models/settings.go
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"net/textproto"
"strings"

"github.com/grafana/grafana-azure-sdk-go/v2/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/v2/azsettings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"golang.org/x/oauth2"
Expand All @@ -22,6 +24,7 @@ const (
AuthenticationMethodDigestAuth = "digestAuth"
AuthenticationMethodOAuth = "oauth2"
AuthenticationMethodAWS = "aws"
AuthenticationMethodAzure = "azure"
AuthenticationMethodAzureBlob = "azureBlob"
)

Expand Down Expand Up @@ -119,6 +122,9 @@ type InfinitySettings struct {
PathEncodedURLsEnabled bool
// ProxyOpts is used for Secure Socks Proxy configuration
ProxyOpts httpclient.Options

RawData map[string]interface{}
RawSecureData map[string]string
}

func (s *InfinitySettings) Validate() error {
Expand Down Expand Up @@ -149,6 +155,17 @@ func (s *InfinitySettings) Validate() error {
}
return nil
}
if s.AuthenticationMethod == AuthenticationMethodAzure {
_, err := azsettings.ReadFromEnv()
if err != nil {
return err
}

_, err = azcredentials.FromDatasourceData(s.RawData, s.RawSecureData)
if err != nil {
return err
}
}
if s.AuthenticationMethod != AuthenticationMethodNone && len(s.AllowedHosts) < 1 {
return errors.New("configure allowed hosts in the authentication section")
}
Expand Down Expand Up @@ -256,6 +273,15 @@ func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings
if len(infJson.AllowedHosts) > 0 {
settings.AllowedHosts = infJson.AllowedHosts
}

var rawData map[string]interface{}

if err := json.Unmarshal(config.JSONData, &rawData); err != nil {
return settings, err
}

settings.RawData = rawData
settings.RawSecureData = config.DecryptedSecureJSONData
}
settings.ReferenceData = infJson.ReferenceData
settings.CustomHealthCheckEnabled = infJson.CustomHealthCheckEnabled
Expand Down Expand Up @@ -292,6 +318,7 @@ func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings
if val, ok := config.DecryptedSecureJSONData["azureBlobAccountKey"]; ok {
settings.AzureBlobAccountKey = val
}

settings.CustomHeaders = GetSecrets(config, "httpHeaderName", "httpHeaderValue")
settings.SecureQueryFields = GetSecrets(config, "secureQueryName", "secureQueryValue")
settings.OAuth2Settings.EndpointParams = GetSecrets(config, "oauth2EndPointParamsName", "oauth2EndPointParamsValue")
Expand Down
Loading