Skip to content

Commit fb81129

Browse files
Enable PDC for Grafana-infinity-datasource (#769)
Co-authored-by: Sriramajeyam Sugumaran <[email protected]>
1 parent 21a95e4 commit fb81129

File tree

11 files changed

+232
-13
lines changed

11 files changed

+232
-13
lines changed

CONTRIBUTING.md

+4
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ To create a new release, execute `yarn changeset version`. This will update the
3535

3636
- Ensure the loki docker plugin is installed `docker plugin install grafana/loki-docker-driver:2.9.1 --alias loki --grant-all-permissions`
3737
- Start the docker from debug file `docker compose -f docker-compose-debug.yaml up`
38+
39+
## Testing the PDC
40+
41+
To test the PDC functionality with Infinity, you can use the `docker compose -f docker-compose-debug.yaml up`. This debug docker compose file comes with **microsocks** proxy, PDC enabled and configured. [Provisioned datasources](./provisioning/datasources/default.yml) file also have some examples of datasource instances with secure socks proxy enabled and with different authentication mechanisms.(You can find the PDC enabled datasources with the prefix **PDC**.)

cspell.config.json

+4
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@
6868
"gtime",
6969
"httpadapter",
7070
"httpbin",
71+
"httpclient",
7172
"icholy",
7273
"ignorecase",
7374
"instancemgmt",
7475
"jsonata",
7576
"jsonframer",
7677
"jsonpath",
7778
"jsonplaceholder",
79+
"healthcheck",
7880
"kennethreitz",
7981
"Knetic",
8082
"kusto",
@@ -83,6 +85,7 @@
8385
"magefile",
8486
"mainbg",
8587
"maxif",
88+
"microsocks",
8689
"Milli",
8790
"minif",
8891
"mkdir",
@@ -108,6 +111,7 @@
108111
"popperjs",
109112
"prismjs",
110113
"promhttp",
114+
"proxying",
111115
"rudderstack",
112116
"scroller",
113117
"seriesgen",

docker-compose-debug.yaml

+11-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ x-logging: &default-logging
44
options:
55
loki-url: 'http://localhost:3100/api/prom/push'
66
services:
7+
microsocks:
8+
container_name: microsocks
9+
image: vimagick/microsocks
10+
ports:
11+
- 1080:1080
712
loki:
813
image: grafana/loki:main
914
command: -config.file=/etc/loki/loki-config.yaml
@@ -32,6 +37,7 @@ services:
3237
# image: grafana/grafana-enterprise:${GF_VERSION:-9.4.3}
3338
# image: grafana/grafana-enterprise:${GF_VERSION:-8.4.7}
3439
depends_on:
40+
- microsocks
3541
- loki
3642
- tempo
3743
ports:
@@ -43,17 +49,20 @@ services:
4349
environment:
4450
- TERM=linux
4551
- GF_DEFAULT_APP_MODE=development
52+
- GF_LOG_LEVEL=debug
4653
- GF_AUTH_ANONYMOUS_ENABLED=true
4754
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
4855
- GF_SECURITY_ADMIN_USER=infinity
4956
- GF_SECURITY_ADMIN_PASSWORD=infinity
5057
- GF_SECURITY_ANGULAR_SUPPORT_ENABLED=false
5158
- GF_SECURITY_CSRF_ALWAYS_CHECK=true
5259
- GF_ENTERPRISE_LICENSE_TEXT=$GF_ENTERPRISE_LICENSE_TEXT
53-
# - GF_FEATURE_TOGGLES_ENABLE=trimDefaults disableEnvelopeEncryption database_metrics live-service-web-workerqueryOverLive panelTitleSearch prometheusAzureOverrideAudience publicDashboards publicDashboardsEmailSharing lokiLive featureHighlights migrationLocking storage exploreMixedDatasource newTraceViewHeader correlations cloudWatchDynamicLabels datasourceQueryMultiStatus traceToMetrics newDBLibrary validateDashboardsOnSave autoMigrateOldPanels disableAngular prometheusWideSeries canvasPanelNesting scenes disableSecretsCompatibility logRequestsInstrumentedAsUnknown dataConnectionsConsole internationalization topnav grpcServer entityStore cloudWatchCrossAccountQuerying redshiftAsyncQueryDataSupport athenaAsyncQueryDataSupport newPanelChromeUI showDashboardValidationWarnings mysqlAnsiQuotes accessControlOnCall nestedFolders accessTokenExpirationCheck showTraceId datasourceOnboarding emptyDashboardPage authnService disablePrometheusExemplarSampling alertingBacktesting editPanelCSVDragAndDrop alertingNoNormalState logsSampleInExplore logsContextDatasourceUi lokiQuerySplitting lokiQuerySplittingConfig individualCookiePreferences onlyExternalOrgRoleSync traceqlSearch prometheusMetricEncyclopedia timeSeriesTable prometheusResourceBrowserCache influxdbBackendMigration clientTokenRotation prometheusDataplane lokiMetricDataplane dataplaneFrontendFallback disableSSEDataplane alertStateHistoryLokiSecondary alertStateHistoryLokiPrimary alertStateHistoryLokiOnly unifiedRequestLog renderAuthJWT pyroscopeFlameGraph externalServiceAuth useCachingService enableElasticsearchBackendQuerying authenticationConfigUI pluginsAPIManifestKey advancedDataSourcePicker opensearchDetectVersion enableDatagridEditing
54-
- GF_FEATURE_TOGGLES_ENABLE=publicDashboards topnav dataConnectionsConsole newPanelChromeUI emptyDashboardPage correlations nestedFolders advancedDataSourcePicker
60+
- GF_FEATURE_TOGGLES_ENABLE=publicDashboards topnav dataConnectionsConsole newPanelChromeUI emptyDashboardPage correlations nestedFolders advancedDataSourcePicker secureSocksDSProxyEnabled
5561
- GF_PLUGIN_YESOREYERAM_INFINITY_DATASOURCE_TRACING=true
5662
- GF_TRACING_OPENTELEMETRY_OTLP_ADDRESS=tempo:4317
5763
- GF_TRACING_OPENTELEMETRY_OTLP_PROPAGATION=w3c,jaeger
5864
- GF_INSTANCE_OTLP_ADDRESS=tempo:4317
5965
- GF_INSTANCE_OTLP_PROPAGATION=w3c,jaeger
66+
- GF_SECURE_SOCKS_DATASOURCE_PROXY_ENABLED=true
67+
- GF_SECURE_SOCKS_DATASOURCE_PROXY_PROXY_ADDRESS=microsocks:1080
68+
- GF_SECURE_SOCKS_DATASOURCE_PROXY_ALLOW_INSECURE=true

pkg/infinity/client.go

+31-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import (
1818
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
1919
"github.com/grafana/grafana-infinity-datasource/pkg/models"
2020
"github.com/grafana/grafana-plugin-sdk-go/backend"
21+
"github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
2122
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
23+
"github.com/icholy/digest"
24+
"golang.org/x/oauth2"
2225
)
2326

2427
type Client struct {
@@ -54,7 +57,7 @@ func GetTLSConfigFromSettings(settings models.InfinitySettings) (*tls.Config, er
5457
return tlsConfig, nil
5558
}
5659

57-
func getBaseHTTPClient(ctx context.Context, settings models.InfinitySettings) *http.Client {
60+
func getBaseHTTPClient(_ context.Context, settings models.InfinitySettings) *http.Client {
5861
tlsConfig, err := GetTLSConfigFromSettings(settings)
5962
if err != nil {
6063
return nil
@@ -74,6 +77,7 @@ func getBaseHTTPClient(ctx context.Context, settings models.InfinitySettings) *h
7477
default:
7578
transport.Proxy = http.ProxyFromEnvironment
7679
}
80+
7781
return &http.Client{
7882
Transport: transport,
7983
Timeout: time.Second * time.Duration(settings.TimeoutInSeconds),
@@ -101,10 +105,17 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C
101105
httpClient = ApplyOAuthClientCredentials(ctx, httpClient, settings)
102106
httpClient = ApplyOAuthJWT(ctx, httpClient, settings)
103107
httpClient = ApplyAWSAuth(ctx, httpClient, settings)
108+
109+
httpClient, err = ApplySecureSocksProxyConfiguration(httpClient, settings)
110+
if err != nil {
111+
return nil, err
112+
}
113+
104114
client = &Client{
105115
Settings: settings,
106116
HttpClient: httpClient,
107117
}
118+
108119
if settings.AuthenticationMethod == models.AuthenticationMethodAzureBlob {
109120
cred, err := azblob.NewSharedKeyCredential(settings.AzureBlobAccountName, settings.AzureBlobAccountKey)
110121
if err != nil {
@@ -138,6 +149,25 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C
138149
return client, err
139150
}
140151

152+
func ApplySecureSocksProxyConfiguration(httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
153+
t := httpClient.Transport
154+
if IsDigestAuthConfigured(settings) {
155+
// if we are using Digest, the Transport is 'digest.Transport' that wraps 'http.Transport'
156+
t = t.(*digest.Transport).Transport
157+
} else if IsOAuthCredentialsConfigured(settings) || IsOAuthJWTConfigured(settings) {
158+
// if we are using Oauth, the Transport is 'oauth2.Transport' that wraps 'http.Transport'
159+
t = t.(*oauth2.Transport).Base
160+
}
161+
162+
// secure socks proxy configuration - checks if enabled inside the function
163+
err := proxy.New(settings.ProxyOpts.ProxyOptions).ConfigureSecureSocksHTTPProxy(t.(*http.Transport))
164+
if err != nil {
165+
backend.Logger.Error("error configuring secure socks proxy", "err", err.Error())
166+
return nil, fmt.Errorf("error configuring secure socks proxy. %s", err)
167+
}
168+
return httpClient, nil
169+
}
170+
141171
func replaceSect(input string, settings models.InfinitySettings, includeSect bool) string {
142172
for key, value := range settings.SecureQueryFields {
143173
if includeSect {

pkg/infinity/oauth.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
func ApplyOAuthClientCredentials(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) *http.Client {
2020
_, span := tracing.DefaultTracer().Start(ctx, "ApplyOAuthClientCredentials")
2121
defer span.End()
22-
if settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthTypeClientCredentials {
22+
if IsOAuthCredentialsConfigured(settings) {
2323
oauthConfig := clientcredentials.Config{
2424
ClientID: settings.OAuth2Settings.ClientID,
2525
ClientSecret: settings.OAuth2Settings.ClientSecret,
@@ -43,10 +43,15 @@ func ApplyOAuthClientCredentials(ctx context.Context, httpClient *http.Client, s
4343
}
4444
return httpClient
4545
}
46+
47+
func IsOAuthCredentialsConfigured(settings models.InfinitySettings) bool {
48+
return settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthTypeClientCredentials
49+
}
50+
4651
func ApplyOAuthJWT(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) *http.Client {
4752
_, span := tracing.DefaultTracer().Start(ctx, "ApplyOAuthJWT")
4853
defer span.End()
49-
if settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthJWT {
54+
if IsOAuthJWTConfigured(settings) {
5055
jwtConfig := jwt.Config{
5156
Email: settings.OAuth2Settings.Email,
5257
TokenURL: settings.OAuth2Settings.TokenURL,
@@ -65,19 +70,27 @@ func ApplyOAuthJWT(ctx context.Context, httpClient *http.Client, settings models
6570
}
6671
return httpClient
6772
}
73+
74+
func IsOAuthJWTConfigured(settings models.InfinitySettings) bool {
75+
return settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthJWT
76+
}
6877
func ApplyDigestAuth(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) *http.Client {
6978
_, span := tracing.DefaultTracer().Start(ctx, "ApplyDigestAuth")
7079
defer span.End()
71-
if settings.AuthenticationMethod == models.AuthenticationMethodDigestAuth {
80+
if IsDigestAuthConfigured(settings) {
7281
a := digest.Transport{Username: settings.UserName, Password: settings.Password, Transport: httpClient.Transport}
7382
httpClient.Transport = &a
7483
}
7584
return httpClient
7685
}
86+
87+
func IsDigestAuthConfigured(settings models.InfinitySettings) bool {
88+
return settings.AuthenticationMethod == models.AuthenticationMethodDigestAuth
89+
}
7790
func ApplyAWSAuth(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) *http.Client {
7891
ctx, span := tracing.DefaultTracer().Start(ctx, "ApplyAWSAuth")
7992
defer span.End()
80-
if settings.AuthenticationMethod == models.AuthenticationMethodAWS {
93+
if IsAwsAuthConfigured(settings) {
8194
tempHttpClient := getBaseHTTPClient(ctx, settings)
8295
authType := settings.AWSSettings.AuthType
8396
if authType == "" {
@@ -106,3 +119,7 @@ func ApplyAWSAuth(ctx context.Context, httpClient *http.Client, settings models.
106119
}
107120
return httpClient
108121
}
122+
123+
func IsAwsAuthConfigured(settings models.InfinitySettings) bool {
124+
return settings.AuthenticationMethod == models.AuthenticationMethodAWS
125+
}

pkg/models/settings.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package models
22

33
import (
4+
"context"
45
"encoding/json"
56
"errors"
67
"fmt"
78
"net/textproto"
89
"strings"
910

1011
"github.com/grafana/grafana-plugin-sdk-go/backend"
12+
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
1113
"golang.org/x/oauth2"
1214
)
1315

@@ -107,6 +109,8 @@ type InfinitySettings struct {
107109
AzureBlobAccountUrl string
108110
AzureBlobAccountName string
109111
AzureBlobAccountKey string
112+
// ProxyOpts is used for Secure Socks Proxy configuration
113+
ProxyOpts httpclient.Options
110114
}
111115

112116
func (s *InfinitySettings) Validate() error {
@@ -179,7 +183,7 @@ type InfinitySettingsJson struct {
179183
AzureBlobAccountName string `json:"azureBlobAccountName,omitempty"`
180184
}
181185

182-
func LoadSettings(config backend.DataSourceInstanceSettings) (settings InfinitySettings, err error) {
186+
func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings) (settings InfinitySettings, err error) {
183187
settings.URL = config.URL
184188
if config.URL == "__IGNORE_URL__" {
185189
settings.URL = ""
@@ -278,6 +282,13 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings InfinityS
278282
if settings.AuthenticationMethod == AuthenticationMethodAzureBlob && settings.AzureBlobAccountUrl == "" {
279283
settings.AzureBlobAccountUrl = "https://%s.blob.core.windows.net/"
280284
}
285+
286+
// secure socks proxy config
287+
opts, err := config.HTTPClientOptions(ctx)
288+
if err != nil {
289+
return settings, err
290+
}
291+
settings.ProxyOpts = opts
281292
return
282293
}
283294

pkg/models/settings_test.go

+19-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package models_test
22

33
import (
4+
"context"
45
"errors"
56
"testing"
67

78
"github.com/grafana/grafana-infinity-datasource/pkg/models"
89
"github.com/grafana/grafana-plugin-sdk-go/backend"
10+
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
911
"github.com/stretchr/testify/assert"
1012
"github.com/stretchr/testify/require"
1113
)
@@ -112,11 +114,19 @@ func TestLoadSettings(t *testing.T) {
112114
}
113115
for _, tt := range tests {
114116
t.Run(tt.name, func(t *testing.T) {
115-
gotSettings, err := models.LoadSettings(tt.config)
117+
gotSettings, err := models.LoadSettings(context.Background(), tt.config)
116118
if (err != nil) != tt.wantErr {
117119
t.Errorf("LoadSettings() error = %v, wantErr %v", err, tt.wantErr)
118120
return
119121
}
122+
123+
assert.NotNil(t, gotSettings)
124+
125+
// settings.ProxyOpts are handled by the sdk, we just validate
126+
assert.NotNil(t, gotSettings.ProxyOpts)
127+
128+
// and then clean it to compare with the wanted settings
129+
gotSettings.ProxyOpts = httpclient.Options{}
120130
assert.Equal(t, tt.wantSettings, gotSettings)
121131
})
122132
}
@@ -176,9 +186,16 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) {
176186
"oauth2EndPointParamsValue2": "Resource2",
177187
},
178188
}
179-
gotSettings, err := models.LoadSettings(config)
189+
gotSettings, err := models.LoadSettings(context.Background(), config)
180190
assert.Nil(t, err)
181191
assert.NotNil(t, gotSettings)
192+
193+
// settings.ProxyOpts are handled by the sdk, we just validate
194+
assert.NotNil(t, gotSettings.ProxyOpts)
195+
196+
// and then clean it to compare with the wanted settings
197+
gotSettings.ProxyOpts = httpclient.Options{}
198+
182199
assert.Equal(t, models.InfinitySettings{
183200
AuthenticationMethod: "oauth2",
184201
ForwardOauthIdentity: true,

pkg/pluginhost/plugin.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ type instanceSettings struct {
3434
func (is *instanceSettings) Dispose() {}
3535

3636
func newDataSourceInstance(ctx context.Context, setting backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
37-
settings, err := models.LoadSettings(setting)
37+
settings, err := models.LoadSettings(ctx, setting)
3838
if err != nil {
3939
return nil, err
4040
}
41+
4142
client, err := infinity.NewClient(ctx, settings)
4243
if err != nil {
4344
return nil, err

0 commit comments

Comments
 (0)