Skip to content

Commit

Permalink
Enable PDC for Grafana-infinity-datasource (#769)
Browse files Browse the repository at this point in the history
Co-authored-by: Sriramajeyam Sugumaran <[email protected]>
  • Loading branch information
leandro-deveikis and yesoreyeram authored Feb 22, 2024
1 parent 21a95e4 commit fb81129
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ To create a new release, execute `yarn changeset version`. This will update the

- Ensure the loki docker plugin is installed `docker plugin install grafana/loki-docker-driver:2.9.1 --alias loki --grant-all-permissions`
- Start the docker from debug file `docker compose -f docker-compose-debug.yaml up`

## Testing the PDC

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**.)
4 changes: 4 additions & 0 deletions cspell.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,15 @@
"gtime",
"httpadapter",
"httpbin",
"httpclient",
"icholy",
"ignorecase",
"instancemgmt",
"jsonata",
"jsonframer",
"jsonpath",
"jsonplaceholder",
"healthcheck",
"kennethreitz",
"Knetic",
"kusto",
Expand All @@ -83,6 +85,7 @@
"magefile",
"mainbg",
"maxif",
"microsocks",
"Milli",
"minif",
"mkdir",
Expand All @@ -108,6 +111,7 @@
"popperjs",
"prismjs",
"promhttp",
"proxying",
"rudderstack",
"scroller",
"seriesgen",
Expand Down
13 changes: 11 additions & 2 deletions docker-compose-debug.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ x-logging: &default-logging
options:
loki-url: 'http://localhost:3100/api/prom/push'
services:
microsocks:
container_name: microsocks
image: vimagick/microsocks
ports:
- 1080:1080
loki:
image: grafana/loki:main
command: -config.file=/etc/loki/loki-config.yaml
Expand Down Expand Up @@ -32,6 +37,7 @@ services:
# image: grafana/grafana-enterprise:${GF_VERSION:-9.4.3}
# image: grafana/grafana-enterprise:${GF_VERSION:-8.4.7}
depends_on:
- microsocks
- loki
- tempo
ports:
Expand All @@ -43,17 +49,20 @@ services:
environment:
- TERM=linux
- GF_DEFAULT_APP_MODE=development
- GF_LOG_LEVEL=debug
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_SECURITY_ADMIN_USER=infinity
- GF_SECURITY_ADMIN_PASSWORD=infinity
- GF_SECURITY_ANGULAR_SUPPORT_ENABLED=false
- GF_SECURITY_CSRF_ALWAYS_CHECK=true
- GF_ENTERPRISE_LICENSE_TEXT=$GF_ENTERPRISE_LICENSE_TEXT
# - 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
- GF_FEATURE_TOGGLES_ENABLE=publicDashboards topnav dataConnectionsConsole newPanelChromeUI emptyDashboardPage correlations nestedFolders advancedDataSourcePicker
- GF_FEATURE_TOGGLES_ENABLE=publicDashboards topnav dataConnectionsConsole newPanelChromeUI emptyDashboardPage correlations nestedFolders advancedDataSourcePicker secureSocksDSProxyEnabled
- GF_PLUGIN_YESOREYERAM_INFINITY_DATASOURCE_TRACING=true
- GF_TRACING_OPENTELEMETRY_OTLP_ADDRESS=tempo:4317
- GF_TRACING_OPENTELEMETRY_OTLP_PROPAGATION=w3c,jaeger
- GF_INSTANCE_OTLP_ADDRESS=tempo:4317
- GF_INSTANCE_OTLP_PROPAGATION=w3c,jaeger
- GF_SECURE_SOCKS_DATASOURCE_PROXY_ENABLED=true
- GF_SECURE_SOCKS_DATASOURCE_PROXY_PROXY_ADDRESS=microsocks:1080
- GF_SECURE_SOCKS_DATASOURCE_PROXY_ALLOW_INSECURE=true
32 changes: 31 additions & 1 deletion pkg/infinity/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/grafana/grafana-infinity-datasource/pkg/models"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
"github.com/icholy/digest"
"golang.org/x/oauth2"
)

type Client struct {
Expand Down Expand Up @@ -54,7 +57,7 @@ func GetTLSConfigFromSettings(settings models.InfinitySettings) (*tls.Config, er
return tlsConfig, nil
}

func getBaseHTTPClient(ctx context.Context, settings models.InfinitySettings) *http.Client {
func getBaseHTTPClient(_ context.Context, settings models.InfinitySettings) *http.Client {
tlsConfig, err := GetTLSConfigFromSettings(settings)
if err != nil {
return nil
Expand All @@ -74,6 +77,7 @@ func getBaseHTTPClient(ctx context.Context, settings models.InfinitySettings) *h
default:
transport.Proxy = http.ProxyFromEnvironment
}

return &http.Client{
Transport: transport,
Timeout: time.Second * time.Duration(settings.TimeoutInSeconds),
Expand Down Expand Up @@ -101,10 +105,17 @@ 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 = ApplySecureSocksProxyConfiguration(httpClient, settings)
if err != nil {
return nil, err
}

client = &Client{
Settings: settings,
HttpClient: httpClient,
}

if settings.AuthenticationMethod == models.AuthenticationMethodAzureBlob {
cred, err := azblob.NewSharedKeyCredential(settings.AzureBlobAccountName, settings.AzureBlobAccountKey)
if err != nil {
Expand Down Expand Up @@ -138,6 +149,25 @@ func NewClient(ctx context.Context, settings models.InfinitySettings) (client *C
return client, err
}

func ApplySecureSocksProxyConfiguration(httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
t := httpClient.Transport
if IsDigestAuthConfigured(settings) {
// if we are using Digest, the Transport is 'digest.Transport' that wraps 'http.Transport'
t = t.(*digest.Transport).Transport
} else if IsOAuthCredentialsConfigured(settings) || IsOAuthJWTConfigured(settings) {
// if we are using Oauth, the Transport is 'oauth2.Transport' that wraps 'http.Transport'
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)
}
return httpClient, nil
}

func replaceSect(input string, settings models.InfinitySettings, includeSect bool) string {
for key, value := range settings.SecureQueryFields {
if includeSect {
Expand Down
25 changes: 21 additions & 4 deletions pkg/infinity/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
func ApplyOAuthClientCredentials(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) *http.Client {
_, span := tracing.DefaultTracer().Start(ctx, "ApplyOAuthClientCredentials")
defer span.End()
if settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthTypeClientCredentials {
if IsOAuthCredentialsConfigured(settings) {
oauthConfig := clientcredentials.Config{
ClientID: settings.OAuth2Settings.ClientID,
ClientSecret: settings.OAuth2Settings.ClientSecret,
Expand All @@ -43,10 +43,15 @@ func ApplyOAuthClientCredentials(ctx context.Context, httpClient *http.Client, s
}
return httpClient
}

func IsOAuthCredentialsConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthTypeClientCredentials
}

func ApplyOAuthJWT(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) *http.Client {
_, span := tracing.DefaultTracer().Start(ctx, "ApplyOAuthJWT")
defer span.End()
if settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthJWT {
if IsOAuthJWTConfigured(settings) {
jwtConfig := jwt.Config{
Email: settings.OAuth2Settings.Email,
TokenURL: settings.OAuth2Settings.TokenURL,
Expand All @@ -65,19 +70,27 @@ func ApplyOAuthJWT(ctx context.Context, httpClient *http.Client, settings models
}
return httpClient
}

func IsOAuthJWTConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthJWT
}
func ApplyDigestAuth(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) *http.Client {
_, span := tracing.DefaultTracer().Start(ctx, "ApplyDigestAuth")
defer span.End()
if settings.AuthenticationMethod == models.AuthenticationMethodDigestAuth {
if IsDigestAuthConfigured(settings) {
a := digest.Transport{Username: settings.UserName, Password: settings.Password, Transport: httpClient.Transport}
httpClient.Transport = &a
}
return httpClient
}

func IsDigestAuthConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodDigestAuth
}
func ApplyAWSAuth(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) *http.Client {
ctx, span := tracing.DefaultTracer().Start(ctx, "ApplyAWSAuth")
defer span.End()
if settings.AuthenticationMethod == models.AuthenticationMethodAWS {
if IsAwsAuthConfigured(settings) {
tempHttpClient := getBaseHTTPClient(ctx, settings)
authType := settings.AWSSettings.AuthType
if authType == "" {
Expand Down Expand Up @@ -106,3 +119,7 @@ func ApplyAWSAuth(ctx context.Context, httpClient *http.Client, settings models.
}
return httpClient
}

func IsAwsAuthConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodAWS
}
13 changes: 12 additions & 1 deletion pkg/models/settings.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package models

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/textproto"
"strings"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"golang.org/x/oauth2"
)

Expand Down Expand Up @@ -107,6 +109,8 @@ type InfinitySettings struct {
AzureBlobAccountUrl string
AzureBlobAccountName string
AzureBlobAccountKey string
// ProxyOpts is used for Secure Socks Proxy configuration
ProxyOpts httpclient.Options
}

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

func LoadSettings(config backend.DataSourceInstanceSettings) (settings InfinitySettings, err error) {
func LoadSettings(ctx context.Context, config backend.DataSourceInstanceSettings) (settings InfinitySettings, err error) {
settings.URL = config.URL
if config.URL == "__IGNORE_URL__" {
settings.URL = ""
Expand Down Expand Up @@ -278,6 +282,13 @@ func LoadSettings(config backend.DataSourceInstanceSettings) (settings InfinityS
if settings.AuthenticationMethod == AuthenticationMethodAzureBlob && settings.AzureBlobAccountUrl == "" {
settings.AzureBlobAccountUrl = "https://%s.blob.core.windows.net/"
}

// secure socks proxy config
opts, err := config.HTTPClientOptions(ctx)
if err != nil {
return settings, err
}
settings.ProxyOpts = opts
return
}

Expand Down
21 changes: 19 additions & 2 deletions pkg/models/settings_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package models_test

import (
"context"
"errors"
"testing"

"github.com/grafana/grafana-infinity-datasource/pkg/models"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -112,11 +114,19 @@ func TestLoadSettings(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotSettings, err := models.LoadSettings(tt.config)
gotSettings, err := models.LoadSettings(context.Background(), tt.config)
if (err != nil) != tt.wantErr {
t.Errorf("LoadSettings() error = %v, wantErr %v", err, tt.wantErr)
return
}

assert.NotNil(t, gotSettings)

// settings.ProxyOpts are handled by the sdk, we just validate
assert.NotNil(t, gotSettings.ProxyOpts)

// and then clean it to compare with the wanted settings
gotSettings.ProxyOpts = httpclient.Options{}
assert.Equal(t, tt.wantSettings, gotSettings)
})
}
Expand Down Expand Up @@ -176,9 +186,16 @@ func TestAllSettingsAgainstFrontEnd(t *testing.T) {
"oauth2EndPointParamsValue2": "Resource2",
},
}
gotSettings, err := models.LoadSettings(config)
gotSettings, err := models.LoadSettings(context.Background(), config)
assert.Nil(t, err)
assert.NotNil(t, gotSettings)

// settings.ProxyOpts are handled by the sdk, we just validate
assert.NotNil(t, gotSettings.ProxyOpts)

// and then clean it to compare with the wanted settings
gotSettings.ProxyOpts = httpclient.Options{}

assert.Equal(t, models.InfinitySettings{
AuthenticationMethod: "oauth2",
ForwardOauthIdentity: true,
Expand Down
3 changes: 2 additions & 1 deletion pkg/pluginhost/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ type instanceSettings struct {
func (is *instanceSettings) Dispose() {}

func newDataSourceInstance(ctx context.Context, setting backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
settings, err := models.LoadSettings(setting)
settings, err := models.LoadSettings(ctx, setting)
if err != nil {
return nil, err
}

client, err := infinity.NewClient(ctx, settings)
if err != nil {
return nil, err
Expand Down
Loading

0 comments on commit fb81129

Please sign in to comment.