Skip to content

Commit f82df04

Browse files
committed
✨ Feat: passtrough entra token from grafana
1 parent e886b5a commit f82df04

File tree

7 files changed

+97
-66
lines changed

7 files changed

+97
-66
lines changed

.config/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
ARG grafana_version=latest
2-
ARG grafana_image=grafana-enterprise
2+
ARG grafana_image=grafana
33

44
FROM grafana/${grafana_image}:${grafana_version}
55

@@ -18,6 +18,7 @@ ENV GF_AUTH_ANONYMOUS_ENABLED "true"
1818
ENV GF_AUTH_BASIC_ENABLED "false"
1919
# Set development mode so plugins can be loaded without the need to sign
2020
ENV GF_DEFAULT_APP_MODE "development"
21+
ENV GF_AZURE_FORWARD_SETTINGS_TO_PLUGINS "mullerpeter-databricks-datasource"
2122

2223

2324
LABEL maintainer="Grafana Labs <hello@grafana.com>"

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ toolchain go1.24.2
66

77
require (
88
github.com/databricks/databricks-sql-go v1.7.0
9-
github.com/grafana/grafana-azure-sdk-go v1.13.1
109
github.com/grafana/grafana-plugin-sdk-go v0.277.0
1110
golang.org/x/oauth2 v0.29.0
1211
)

go.sum

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1
9494
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
9595
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
9696
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
97-
github.com/grafana/grafana-azure-sdk-go v1.13.1/go.mod h1:SAlwLdEuox4vw8ZaeQwnepYXnhznnQQdstJbcw8LH68=
9897
github.com/grafana/grafana-plugin-sdk-go v0.277.0 h1:VDU2F4Y5NeRS//ejctdZtsAshrGaEdbtW33FsK0EQss=
9998
github.com/grafana/grafana-plugin-sdk-go v0.277.0/go.mod h1:mAUWg68w5+1f5TLDqagIr8sWr1RT9h7ufJl5NMcWJAU=
10099
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
@@ -189,8 +188,7 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX
189188
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
190189
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
191190
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
192-
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
193-
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
191+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
194192
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
195193
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
196194
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -343,7 +341,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
343341
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
344342
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
345343
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
346-
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
347344
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
348345
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
349346
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "mullerpeter-databricks-datasource",
33
"private": true,
4-
"version": "1.3.5",
4+
"version": "1.3.6-rc.0",
55
"description": "Databricks SQL Connector",
66
"scripts": {
77
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",

pkg/integrations/azure_ad.go

Lines changed: 0 additions & 29 deletions
This file was deleted.

pkg/plugin/plugin.go

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
dbsql "github.com/databricks/databricks-sql-go"
1010
"github.com/databricks/databricks-sql-go/auth"
1111
"github.com/databricks/databricks-sql-go/auth/oauth/m2m"
12-
"github.com/grafana/grafana-azure-sdk-go/azsettings"
1312
"github.com/grafana/grafana-plugin-sdk-go/backend"
1413
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
1514
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
@@ -70,7 +69,6 @@ type ConnectionSettings struct {
7069
MaxRetryDuration time.Duration
7170
Timeout time.Duration
7271
MaxRows int
73-
idToken string
7472
}
7573

7674
// NewSampleDatasource creates a new datasource instance.
@@ -93,7 +91,7 @@ func NewSampleDatasource(ctx context.Context, settings backend.DataSourceInstanc
9391
port = portInt
9492
}
9593

96-
if datasourceSettings.AuthenticationMethod == "m2m" || datasourceSettings.AuthenticationMethod == "oauth2_client_credentials" || datasourceSettings.AuthenticationMethod == "azure_ad_forward" {
94+
if datasourceSettings.AuthenticationMethod == "m2m" || datasourceSettings.AuthenticationMethod == "oauth2_client_credentials" {
9795
var authenticator auth.Authenticator
9896

9997
if datasourceSettings.AuthenticationMethod == "oauth2_client_credentials" {
@@ -114,15 +112,6 @@ func NewSampleDatasource(ctx context.Context, settings backend.DataSourceInstanc
114112
datasourceSettings.Hostname,
115113
[]string{},
116114
)
117-
} else if datasourceSettings.AuthenticationMethod == "azure_ad_forward" {
118-
azureSettings, err := azsettings.ReadSettings(ctx)
119-
if err != nil {
120-
log.DefaultLogger.Info("Failed to get Azure Setting", "err", err)
121-
return nil, err
122-
}
123-
authenticator = integrations.NewAzureADCredentials(
124-
azureSettings,
125-
)
126115
} else {
127116
log.DefaultLogger.Info("Authentication Method Parse Error", "err", nil)
128117
return nil, fmt.Errorf("authentication Method Parse Error")
@@ -142,12 +131,20 @@ func NewSampleDatasource(ctx context.Context, settings backend.DataSourceInstanc
142131
return nil, err
143132
} else {
144133
log.DefaultLogger.Info("Init Databricks SQL DB")
134+
databricksDB := sql.OpenDB(connector)
135+
136+
if err := databricksDB.Ping(); err != nil {
137+
log.DefaultLogger.Info("Ping Error (Could not ping Databricks)", "err", err)
138+
return nil, err
139+
}
145140

141+
SetDatasourceSettings(databricksDB, connectionSettings)
146142
log.DefaultLogger.Info("Store Databricks SQL DB Connection")
147143
return &Datasource{
148144
connector: connector,
149-
databricksDB: nil,
145+
databricksDB: databricksDB,
150146
connectionSettings: connectionSettings,
147+
datasourceSettings: *datasourceSettings,
151148
}, nil
152149
}
153150
} else if datasourceSettings.AuthenticationMethod == "dsn" || datasourceSettings.AuthenticationMethod == "" {
@@ -179,8 +176,17 @@ func NewSampleDatasource(ctx context.Context, settings backend.DataSourceInstanc
179176
connector: connector,
180177
databricksDB: databricksDB,
181178
connectionSettings: connectionSettings,
179+
datasourceSettings: *datasourceSettings,
182180
}, nil
183181

182+
} else if datasourceSettings.AuthenticationMethod == "azure_entra_pass_thru" {
183+
184+
return &Datasource{
185+
connector: nil,
186+
databricksDB: nil,
187+
connectionSettings: connectionSettings,
188+
datasourceSettings: *datasourceSettings,
189+
}, nil
184190
}
185191

186192
return nil, fmt.Errorf("Invalid Connection Method")
@@ -302,6 +308,8 @@ type Datasource struct {
302308
connector driver.Connector
303309
databricksDB *sql.DB
304310
connectionSettings ConnectionSettings
311+
datasourceSettings DatasourceSettings
312+
token string
305313
}
306314

307315
func (d *Datasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
@@ -322,6 +330,15 @@ func (d *Datasource) Dispose() {
322330
func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
323331
log.DefaultLogger.Info("QueryData called", "request", req)
324332

333+
if d.datasourceSettings.AuthenticationMethod == "azure_entra_pass_thru" {
334+
token := req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName)
335+
err := d.CheckAzureEntraPassThru(token)
336+
if err != nil {
337+
log.DefaultLogger.Error("Azure Entra Connection Failed", "err", err)
338+
return nil, err
339+
}
340+
}
341+
325342
// create response struct
326343
response := backend.NewQueryDataResponse()
327344

@@ -446,28 +463,74 @@ func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, quer
446463
return response
447464
}
448465

466+
func (d *Datasource) CheckAzureEntraPassThru(token string) error {
467+
if token == "" {
468+
log.DefaultLogger.Info("Token is empty")
469+
return fmt.Errorf("no Azure Entra Token provided")
470+
}
471+
if d.databricksDB != nil && token == d.token {
472+
return nil
473+
}
474+
if token != d.token {
475+
log.DefaultLogger.Info("Token is different")
476+
d.token = strings.TrimPrefix(token, "Bearer ")
477+
}
478+
port := 443
479+
if d.datasourceSettings.Port != "" {
480+
portInt, err := strconv.Atoi(d.datasourceSettings.Port)
481+
if err != nil {
482+
log.DefaultLogger.Info("Port Parse Error", "err", err)
483+
return err
484+
}
485+
port = portInt
486+
}
487+
488+
connector, err := dbsql.NewConnector(
489+
dbsql.WithAccessToken(d.token),
490+
dbsql.WithServerHostname(d.datasourceSettings.Hostname),
491+
dbsql.WithHTTPPath(d.datasourceSettings.Path),
492+
dbsql.WithPort(port),
493+
dbsql.WithTimeout(d.connectionSettings.Timeout),
494+
dbsql.WithMaxRows(d.connectionSettings.MaxRows),
495+
dbsql.WithRetries(d.connectionSettings.Retries, d.connectionSettings.RetryBackoff, d.connectionSettings.MaxRetryDuration),
496+
)
497+
if err != nil {
498+
log.DefaultLogger.Info("Connector Error", "err", err)
499+
return err
500+
} else {
501+
log.DefaultLogger.Info("Init Databricks SQL DB")
502+
databricksDB := sql.OpenDB(connector)
503+
504+
if err := databricksDB.Ping(); err != nil {
505+
log.DefaultLogger.Info("Ping Error (Could not ping Databricks)", "err", err)
506+
return err
507+
}
508+
509+
SetDatasourceSettings(databricksDB, d.connectionSettings)
510+
log.DefaultLogger.Info("Store Databricks SQL DB Connection")
511+
d.connector = connector
512+
d.databricksDB = databricksDB
513+
}
514+
return nil
515+
}
516+
449517
// CheckHealth handles health checks sent from Grafana to the plugin.
450518
// The main use case for these health checks is the test button on the
451519
// datasource configuration page which allows users to verify that
452520
// a datasource is working as expected.
453521
func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
454-
log.DefaultLogger.Info("CheckHealth called", "request", req)
522+
if d.datasourceSettings.AuthenticationMethod == "azure_entra_pass_thru" {
455523

456-
token := strings.Fields(req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName))
457-
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName)
458-
log.DefaultLogger.Info("Token", "token", token)
459-
log.DefaultLogger.Info("ID Token", "idToken", idToken)
460-
461-
ctx = context.WithValue(ctx, backend.OAuthIdentityTokenHeaderName, token)
462-
ctx = context.WithValue(ctx, backend.OAuthIdentityIDTokenHeaderName, idToken)
463-
464-
if d.databricksDB == nil {
465-
err := d.RefreshDBConnection()
524+
token := req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName)
525+
err := d.CheckAzureEntraPassThru(token)
466526
if err != nil {
467-
log.DefaultLogger.Info("RefreshDBConnection Error", "err", err)
468-
return nil, err
527+
return &backend.CheckHealthResult{
528+
Status: backend.HealthStatusError,
529+
Message: fmt.Sprintf("Azure Entra Connection Failed: %s", err),
530+
}, nil
469531
}
470532
}
533+
log.DefaultLogger.Info("CheckHealth called", "request", req)
471534

472535
rows, err := d.QueryContext(ctx, "SELECT 1")
473536

src/components/ConfigEditor/ConfigEditor.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
3535
...jsonData,
3636
[key]: value
3737
}
38-
if (key == 'authenticationMethod') {
38+
if (key == 'authenticationMethod' && value == 'azure_entra_pass_thru') {
3939
jsonData = {
4040
...jsonData,
4141
oauthPassThru: true,
@@ -127,8 +127,8 @@ export class ConfigEditor extends PureComponent<Props, State> {
127127
label: 'OAuth2 Client Credentials',
128128
},
129129
{
130-
value: 'azure_ad_forward',
131-
label: 'Forward Azure AD Auth',
130+
value: 'azure_entra_pass_thru',
131+
label: 'Pass Thru Azure Entra Auth',
132132
},
133133
]}
134134
value={jsonData.authenticationMethod || 'dsn'}
@@ -178,7 +178,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
178178
/>
179179
</InlineField>
180180
</>
181-
) : (
181+
) : jsonData.authenticationMethod != 'azure_entra_pass_thru' && (
182182
<InlineField label="Access Token" labelWidth={30} tooltip="Databricks Personal Access Token">
183183
<SecretInput
184184
isConfigured={(secureJsonFields && secureJsonFields.token) as boolean}

0 commit comments

Comments
 (0)