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 4 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
12 changes: 5 additions & 7 deletions docs/sources/examples/azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,13 @@ 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 "Microsoft Entra ID"
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. Add the Scope `https://management.azure.com/.default`.
7. 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
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 v1.13.0
github.com/grafana/grafana-plugin-sdk-go v0.212.0
github.com/icholy/digest v0.1.22
github.com/stretchr/testify v1.9.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.0 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
Expand All @@ -47,6 +50,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.11.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/flatbuffers v23.5.26+incompatible // indirect
github.com/google/go-cmp v0.6.0 // indirect
Expand All @@ -72,6 +76,7 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.17.3 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand Down Expand Up @@ -99,6 +104,7 @@ require (
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.18 // 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.18.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,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 v1.13.0 h1:2II2kXyHsBOCWkSQBYXrhhzuZpAn+viaesz3y+AyOSM=
github.com/grafana/grafana-azure-sdk-go v1.13.0/go.mod h1:SAlwLdEuox4vw8ZaeQwnepYXnhznnQQdstJbcw8LH68=
github.com/grafana/grafana-plugin-sdk-go v0.212.0 h1:ohgMktFAasLTzAhKhcIzk81O60E29Za6ly02GhEqGIU=
github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs=
github.com/grafana/sqlds/v3 v3.2.0 h1:WXuYEaFfiCvgm8kK2ixx44/zAEjFzCylA2+RF3GBqZA=
Expand Down
81 changes: 81 additions & 0 deletions pkg/infinity/azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package infinity

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

"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/azhttpclient"
"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana-infinity-datasource/pkg/models"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
)

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

if IsAzureAuthConfigured(settings) {
azSettings := &azsettings.AzureSettings{
Cloud: string(settings.MicrosoftSettings.Cloud),
ManagedIdentityEnabled: true,
WorkloadIdentityEnabled: true,
}

tenantID := settings.MicrosoftSettings.TenantID
if tenantID == "" {
tenantID = os.Getenv("AZURE_TENANT_ID")
}
jkroepke marked this conversation as resolved.
Show resolved Hide resolved

clientID := settings.OAuth2Settings.ClientID
if clientID == "" {
clientID = os.Getenv("AZURE_CLIENT_ID")
}
jkroepke marked this conversation as resolved.
Show resolved Hide resolved

var credentials azcredentials.AzureCredentials

switch settings.MicrosoftSettings.AuthType {
case models.MicrosoftAuthTypeClientSecret:
clientSecret := settings.OAuth2Settings.ClientSecret
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
if clientSecret == "" {
clientSecret = os.Getenv("AZURE_CLIENT_SECRET")
}
jkroepke marked this conversation as resolved.
Show resolved Hide resolved

credentials = &azcredentials.AzureClientSecretCredentials{
AzureCloud: string(settings.MicrosoftSettings.Cloud),
TenantId: tenantID,
ClientId: clientID,
ClientSecret: clientSecret,
}
case models.MicrosoftAuthTypeManagedIdentity:
azSettings.ManagedIdentityClientId = clientID

credentials = &azcredentials.AzureManagedIdentityCredentials{
ClientId: clientID,
}
case models.MicrosoftAuthTypeWorkloadIdentity:
azSettings.WorkloadIdentitySettings = &azsettings.WorkloadIdentitySettings{
TenantId: tenantID,
ClientId: clientID,
}

jkroepke marked this conversation as resolved.
Show resolved Hide resolved
credentials = &azcredentials.AzureWorkloadIdentityCredentials{}
default:
panic(fmt.Errorf("invalid auth type '%s'", settings.MicrosoftSettings.AuthType))
}

authOpts := azhttpclient.NewAuthOptions(azSettings)
authOpts.Scopes(settings.OAuth2Settings.Scopes)
jkroepke marked this conversation as resolved.
Show resolved Hide resolved

httpClient.Transport = azhttpclient.AzureMiddleware(authOpts, credentials).
CreateMiddleware(httpclient.Options{}, httpClient.Transport)
}
return httpClient, nil
}

func IsAzureAuthConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodMicrosoft
}
17 changes: 12 additions & 5 deletions pkg/infinity/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,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(httpClient, settings)
if err != nil {
Expand Down Expand Up @@ -167,12 +171,15 @@ func ApplySecureSocksProxyConfiguration(httpClient *http.Client, settings models
t = t.(*oauth2.Transport).Base
}

// secure socks proxy configuration - checks if enabled inside the function
err := proxy.New(settings.ProxyOpts.ProxyOptions).ConfigureSecureSocksHTTPProxy(t.(*http.Transport))
if err != nil {
backend.Logger.Error("error configuring secure socks proxy", "err", err.Error())
return nil, fmt.Errorf("error configuring secure socks proxy. %s", err)
if t, ok := t.(*http.Transport); ok {
// secure socks proxy configuration - checks if enabled inside the function
err := proxy.New(settings.ProxyOpts.ProxyOptions).ConfigureSecureSocksHTTPProxy(t)
if err != nil {
backend.Logger.Error("error configuring secure socks proxy", "err", err.Error())
return nil, fmt.Errorf("error configuring secure socks proxy. %s", err)
}
}

return httpClient, nil
}

Expand Down
78 changes: 58 additions & 20 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/azcredentials"
"github.com/grafana/grafana-azure-sdk-go/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"
AuthenticationMethodMicrosoft = "microsoft"
AuthenticationMethodAzureBlob = "azureBlob"
)

Expand Down Expand Up @@ -62,6 +65,28 @@ type AWSSettings struct {
Service string `json:"service"`
}

type MicrosoftAuthType string

const (
MicrosoftAuthTypeManagedIdentity MicrosoftAuthType = azcredentials.AzureAuthManagedIdentity
MicrosoftAuthTypeWorkloadIdentity MicrosoftAuthType = azcredentials.AzureAuthWorkloadIdentity
MicrosoftAuthTypeClientSecret MicrosoftAuthType = azcredentials.AzureAuthClientSecret
)

type MicrosoftCloudType string

const (
MicrosoftCloudPublic MicrosoftCloudType = azsettings.AzurePublic
MicrosoftCloudChina MicrosoftCloudType = azsettings.AzureChina
MicrosoftCloudUSGovernment MicrosoftCloudType = azsettings.AzureUSGovernment
)

type MicrosoftSettings struct {
Cloud MicrosoftCloudType `json:"cloud"`
AuthType MicrosoftAuthType `json:"auth_type"`
TenantID string `json:"tenant_id"`
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
}

type ProxyType string

const (
Expand All @@ -83,6 +108,7 @@ type InfinitySettings struct {
AWSSettings AWSSettings
AWSAccessKey string
AWSSecretKey string
MicrosoftSettings MicrosoftSettings
URL string
BasicAuthEnabled bool
UserName string
Expand Down Expand Up @@ -170,26 +196,28 @@ type RefData struct {
}

type InfinitySettingsJson struct {
IsMock bool `json:"is_mock,omitempty"`
AuthenticationMethod string `json:"auth_method,omitempty"`
APIKeyKey string `json:"apiKeyKey,omitempty"`
APIKeyType string `json:"apiKeyType,omitempty"`
OAuth2Settings OAuth2Settings `json:"oauth2,omitempty"`
AWSSettings AWSSettings `json:"aws,omitempty"`
ForwardOauthIdentity bool `json:"oauthPassThru,omitempty"`
InsecureSkipVerify bool `json:"tlsSkipVerify,omitempty"`
ServerName string `json:"serverName,omitempty"`
TLSClientAuth bool `json:"tlsAuth,omitempty"`
TLSAuthWithCACert bool `json:"tlsAuthWithCACert,omitempty"`
TimeoutInSeconds int64 `json:"timeoutInSeconds,omitempty"`
ProxyType ProxyType `json:"proxy_type,omitempty"`
ProxyUrl string `json:"proxy_url,omitempty"`
AllowedHosts []string `json:"allowedHosts,omitempty"`
ReferenceData []RefData `json:"refData,omitempty"`
CustomHealthCheckEnabled bool `json:"customHealthCheckEnabled,omitempty"`
CustomHealthCheckUrl string `json:"customHealthCheckUrl,omitempty"`
AzureBlobAccountUrl string `json:"azureBlobAccountUrl,omitempty"`
AzureBlobAccountName string `json:"azureBlobAccountName,omitempty"`
IsMock bool `json:"is_mock,omitempty"`
AuthenticationMethod string `json:"auth_method,omitempty"`
APIKeyKey string `json:"apiKeyKey,omitempty"`
APIKeyType string `json:"apiKeyType,omitempty"`
OAuth2Settings OAuth2Settings `json:"oauth2,omitempty"`
AWSSettings AWSSettings `json:"aws,omitempty"`
MicrosoftSettings MicrosoftSettings `json:"microsoft,omitempty"`
ForwardOauthIdentity bool `json:"oauthPassThru,omitempty"`
InsecureSkipVerify bool `json:"tlsSkipVerify,omitempty"`
ServerName string `json:"serverName,omitempty"`
TLSClientAuth bool `json:"tlsAuth,omitempty"`
TLSAuthWithCACert bool `json:"tlsAuthWithCACert,omitempty"`
TimeoutInSeconds int64 `json:"timeoutInSeconds,omitempty"`
ProxyType ProxyType `json:"proxy_type,omitempty"`
ProxyUrl string `json:"proxy_url,omitempty"`
AllowedHosts []string `json:"allowedHosts,omitempty"`

ReferenceData []RefData `json:"refData,omitempty"`
CustomHealthCheckEnabled bool `json:"customHealthCheckEnabled,omitempty"`
CustomHealthCheckUrl string `json:"customHealthCheckUrl,omitempty"`
AzureBlobAccountUrl string `json:"azureBlobAccountUrl,omitempty"`
AzureBlobAccountName string `json:"azureBlobAccountName,omitempty"`
}

func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings) (settings InfinitySettings, err error) {
Expand Down Expand Up @@ -238,6 +266,16 @@ func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings
if len(infJson.AllowedHosts) > 0 {
settings.AllowedHosts = infJson.AllowedHosts
}

settings.MicrosoftSettings = infJson.MicrosoftSettings
if settings.AuthenticationMethod == "microsoft" {
if settings.MicrosoftSettings.AuthType == "" {
settings.MicrosoftSettings.AuthType = "clientsecret"
}
if settings.MicrosoftSettings.Cloud == "" {
settings.MicrosoftSettings.Cloud = MicrosoftCloudPublic
}
}
}
settings.ReferenceData = infJson.ReferenceData
settings.CustomHealthCheckEnabled = infJson.CustomHealthCheckEnabled
Expand Down
10 changes: 10 additions & 0 deletions pkg/models/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) {
"region" : "region1",
"service" : "service1"
},
"microsoft" : {
"cloud" : "AzureUSGovernment",
"auth_type" : "clientsecret",
"tenant_id" : "tenant1"
},
"oauth2" : {
"client_id":"myClientID",
"email":"myEmail",
Expand Down Expand Up @@ -212,6 +217,11 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) {
Service: "service1",
Region: "region1",
},
MicrosoftSettings: models.MicrosoftSettings{
Cloud: models.MicrosoftCloudUSGovernment,
AuthType: models.MicrosoftAuthTypeClientSecret,
TenantID: "tenant1",
},
OAuth2Settings: models.OAuth2Settings{
ClientID: "myClientID",
OAuth2Type: "client_credentials",
Expand Down
4 changes: 4 additions & 0 deletions src/editors/config/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Icon, InlineFormLabel, LegacyForms, RadioButtonGroup, Select, useTheme2
import React, { useState } from 'react';
import { AllowedHostsEditor } from './AllowedHosts';
import { OAuthInputsEditor } from './OAuthInput';
import { MicrosoftInputsEditor } from './MicrosoftInput';
import { OthersAuthentication } from './OtherAuthProviders';
import { AWSRegions } from './../../constants';
import type { APIKeyType, AuthType, InfinityOptions, InfinitySecureOptions } from './../../types';
Expand All @@ -17,6 +18,7 @@ const authTypes: Array<SelectableValue<AuthType | 'others'> & { logo?: string }>
{ value: 'oauthPassThru', label: 'Forward OAuth' },
{ value: 'oauth2', label: 'OAuth2', logo: '/public/plugins/yesoreyeram-infinity-datasource/img/oauth-2-sm.png' },
{ value: 'aws', label: 'AWS', logo: '/public/plugins/yesoreyeram-infinity-datasource/img/aws.jpg' },
{ value: 'microsoft', label: 'Microsoft Entra ID' },
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
{ value: 'azureBlob', label: 'Azure Blob' },
{ value: 'others', label: 'Other Auth Providers' },
];
Expand Down Expand Up @@ -76,6 +78,7 @@ export const AuthEditor = (props: DataSourcePluginOptionsEditorProps<InfinityOpt
case 'apiKey':
case 'bearerToken':
case 'aws':
case 'microsoft':
case 'azureBlob':
case 'oauth2':
case 'none':
Expand Down Expand Up @@ -276,6 +279,7 @@ export const AuthEditor = (props: DataSourcePluginOptionsEditorProps<InfinityOpt
</>
)}
{authType === 'oauth2' && <OAuthInputsEditor {...props} />}
{authType === 'microsoft' && <MicrosoftInputsEditor {...props} />}
{authType === 'azureBlob' && (
<>
<div className="gf-form">
Expand Down
Loading