mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			1584 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			1584 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Go
		
	
	
	
| package service
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"net/http/httptest"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/google/uuid"
 | |
| 	"github.com/grafana/grafana-plugin-sdk-go/backend"
 | |
| 	sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/mock"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 	"gopkg.in/ini.v1"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/components/simplejson"
 | |
| 	"github.com/grafana/grafana/pkg/infra/db"
 | |
| 	"github.com/grafana/grafana/pkg/infra/httpclient"
 | |
| 	"github.com/grafana/grafana/pkg/infra/log"
 | |
| 	"github.com/grafana/grafana/pkg/plugins"
 | |
| 	pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
 | |
| 	"github.com/grafana/grafana/pkg/services/accesscontrol"
 | |
| 	"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
 | |
| 	acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
 | |
| 	"github.com/grafana/grafana/pkg/services/datasources"
 | |
| 	"github.com/grafana/grafana/pkg/services/featuremgmt"
 | |
| 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig"
 | |
| 	"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
 | |
| 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
 | |
| 	"github.com/grafana/grafana/pkg/services/quota/quotatest"
 | |
| 	"github.com/grafana/grafana/pkg/services/secrets"
 | |
| 	"github.com/grafana/grafana/pkg/services/secrets/fakes"
 | |
| 	secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
 | |
| 	secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
 | |
| 	"github.com/grafana/grafana/pkg/setting"
 | |
| 	"github.com/grafana/grafana/pkg/tests/testsuite"
 | |
| 	// testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource"
 | |
| )
 | |
| 
 | |
| func TestMain(m *testing.M) {
 | |
| 	testsuite.Run(m)
 | |
| }
 | |
| 
 | |
| type dataSourceMockRetriever struct {
 | |
| 	res []*datasources.DataSource
 | |
| }
 | |
| 
 | |
| func (d *dataSourceMockRetriever) GetDataSource(ctx context.Context, query *datasources.GetDataSourceQuery) (*datasources.DataSource, error) {
 | |
| 	for _, dataSource := range d.res {
 | |
| 		idMatch := query.ID != 0 && query.ID == dataSource.ID
 | |
| 		uidMatch := query.UID != "" && query.UID == dataSource.UID
 | |
| 		nameMatch := query.Name != "" && query.Name == dataSource.Name
 | |
| 		if idMatch || nameMatch || uidMatch {
 | |
| 			return dataSource, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return nil, datasources.ErrDataSourceNotFound
 | |
| }
 | |
| 
 | |
| func TestService_AddDataSource(t *testing.T) {
 | |
| 	t.Run("should not fail if the plugin is not installed", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 		dsService.pluginStore = &pluginstore.FakePluginStore{
 | |
| 			PluginList: []pluginstore.Plugin{}, // empty list
 | |
| 		}
 | |
| 
 | |
| 		cmd := &datasources.AddDataSourceCommand{
 | |
| 			OrgID: 1,
 | |
| 			Type:  datasources.DS_TESTDATA,
 | |
| 			Name:  "test",
 | |
| 		}
 | |
| 
 | |
| 		ds, err := dsService.AddDataSource(context.Background(), cmd)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Equal(t, "test", ds.Name)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should fail if the datasource name is too long", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 
 | |
| 		cmd := &datasources.AddDataSourceCommand{
 | |
| 			OrgID: 1,
 | |
| 			Name:  string(make([]byte, 256)),
 | |
| 		}
 | |
| 
 | |
| 		_, err := dsService.AddDataSource(context.Background(), cmd)
 | |
| 		require.EqualError(t, err, "[datasource.nameInvalid] max length is 190")
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should fail if the datasource url is invalid", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 
 | |
| 		cmd := &datasources.AddDataSourceCommand{
 | |
| 			OrgID: 1,
 | |
| 			URL:   string(make([]byte, 256)),
 | |
| 		}
 | |
| 
 | |
| 		_, err := dsService.AddDataSource(context.Background(), cmd)
 | |
| 		require.EqualError(t, err, "[datasource.urlInvalid] max length is 255")
 | |
| 	})
 | |
| 
 | |
| 	t.Run("if a plugin has an API version defined (EXPERIMENTAL)", func(t *testing.T) {
 | |
| 		t.Run("should success to run admission hooks", func(t *testing.T) {
 | |
| 			dsService := initDSService(t)
 | |
| 			validateExecuted := false
 | |
| 			dsService.pluginStore = &pluginstore.FakePluginStore{
 | |
| 				PluginList: []pluginstore.Plugin{{
 | |
| 					JSONData: plugins.JSONData{
 | |
| 						ID:   "test",
 | |
| 						Type: plugins.TypeDataSource,
 | |
| 						Name: "test",
 | |
| 					},
 | |
| 				}},
 | |
| 			}
 | |
| 			dsService.pluginClient = &pluginfakes.FakePluginClient{
 | |
| 				ValidateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
 | |
| 					validateExecuted = true
 | |
| 					return &backend.ValidationResponse{
 | |
| 						Allowed: true,
 | |
| 					}, nil
 | |
| 				},
 | |
| 				MutateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
 | |
| 					return &backend.MutationResponse{
 | |
| 						Allowed:     true,
 | |
| 						ObjectBytes: req.ObjectBytes,
 | |
| 					}, nil
 | |
| 				},
 | |
| 				ConvertObjectFunc: func(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
 | |
| 					return nil, fmt.Errorf("not implemented")
 | |
| 				},
 | |
| 			}
 | |
| 			cmd := &datasources.AddDataSourceCommand{
 | |
| 				OrgID:      1,
 | |
| 				Type:       "test", // required to validate apiserver
 | |
| 				Name:       "test",
 | |
| 				APIVersion: "v0alpha1",
 | |
| 			}
 | |
| 			_, err := dsService.AddDataSource(context.Background(), cmd)
 | |
| 			require.NoError(t, err)
 | |
| 			require.True(t, validateExecuted)
 | |
| 		})
 | |
| 
 | |
| 		t.Run("should fail at validation", func(t *testing.T) {
 | |
| 			dsService := initDSService(t)
 | |
| 			dsService.pluginStore = &pluginstore.FakePluginStore{
 | |
| 				PluginList: []pluginstore.Plugin{{
 | |
| 					JSONData: plugins.JSONData{
 | |
| 						ID:   "test",
 | |
| 						Type: plugins.TypeDataSource,
 | |
| 						Name: "test",
 | |
| 					},
 | |
| 				}},
 | |
| 			}
 | |
| 			dsService.pluginClient = &pluginfakes.FakePluginClient{
 | |
| 				ValidateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
 | |
| 					settings, err := backend.DataSourceInstanceSettingsFromProto(req.ObjectBytes, "")
 | |
| 					if err != nil {
 | |
| 						return nil, err
 | |
| 					}
 | |
| 					if settings.APIVersion != "v0alpha1" {
 | |
| 						return &backend.ValidationResponse{
 | |
| 							Allowed: false,
 | |
| 							Result: &backend.StatusResult{
 | |
| 								Status:  "Failure",
 | |
| 								Message: fmt.Sprintf("expected apiVersion: v0alpha1, found: %s", settings.APIVersion),
 | |
| 								Reason:  "badRequest",
 | |
| 								Code:    http.StatusBadRequest,
 | |
| 							},
 | |
| 						}, nil
 | |
| 					}
 | |
| 					return &backend.ValidationResponse{
 | |
| 						Allowed: true,
 | |
| 					}, nil
 | |
| 				},
 | |
| 				MutateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
 | |
| 					return nil, fmt.Errorf("not implemented")
 | |
| 				},
 | |
| 				ConvertObjectFunc: func(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
 | |
| 					return nil, fmt.Errorf("not implemented")
 | |
| 				},
 | |
| 			}
 | |
| 			cmd := &datasources.AddDataSourceCommand{
 | |
| 				OrgID:      1,
 | |
| 				Type:       "test", // required to validate apiserver
 | |
| 				Name:       "test",
 | |
| 				APIVersion: "v123", // invalid apiVersion
 | |
| 			}
 | |
| 			_, err := dsService.AddDataSource(context.Background(), cmd)
 | |
| 			assert.ErrorContains(t, err, "expected apiVersion: v0alpha1, found: v123")
 | |
| 		})
 | |
| 
 | |
| 		t.Run("should mutate a request", func(t *testing.T) {
 | |
| 			dsService := initDSService(t)
 | |
| 			dsService.pluginStore = &pluginstore.FakePluginStore{
 | |
| 				PluginList: []pluginstore.Plugin{{
 | |
| 					JSONData: plugins.JSONData{
 | |
| 						ID:   "test",
 | |
| 						Type: plugins.TypeDataSource,
 | |
| 						Name: "test",
 | |
| 					},
 | |
| 				}},
 | |
| 			}
 | |
| 			dsService.pluginClient = &pluginfakes.FakePluginClient{
 | |
| 				ValidateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
 | |
| 					return &backend.ValidationResponse{
 | |
| 						Allowed: true,
 | |
| 					}, nil
 | |
| 				},
 | |
| 				MutateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
 | |
| 					settings, err := backend.DataSourceInstanceSettingsFromProto(req.ObjectBytes, "")
 | |
| 					if err != nil {
 | |
| 						return nil, err
 | |
| 					}
 | |
| 					settings.URL = "url-mutated"
 | |
| 					pb, err := backend.DataSourceInstanceSettingsToProtoBytes(settings)
 | |
| 					return &backend.MutationResponse{
 | |
| 						Allowed:     true,
 | |
| 						ObjectBytes: pb,
 | |
| 					}, err
 | |
| 				},
 | |
| 				ConvertObjectFunc: func(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
 | |
| 					return nil, fmt.Errorf("not implemented")
 | |
| 				},
 | |
| 			}
 | |
| 			cmd := &datasources.AddDataSourceCommand{
 | |
| 				OrgID:      1,
 | |
| 				Type:       "test", // required to validate apiserver
 | |
| 				Name:       "test",
 | |
| 				APIVersion: "v0alpha1",
 | |
| 			}
 | |
| 			ds, err := dsService.AddDataSource(context.Background(), cmd)
 | |
| 			require.NoError(t, err)
 | |
| 			require.Equal(t, "url-mutated", ds.URL)
 | |
| 		})
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestService_getAvailableName(t *testing.T) {
 | |
| 	type testCase struct {
 | |
| 		desc       string
 | |
| 		dsType     string
 | |
| 		existingDs []*datasources.DataSource
 | |
| 		expected   string
 | |
| 	}
 | |
| 
 | |
| 	testCases := []testCase{
 | |
| 		{
 | |
| 			desc:     "should return type as the name if no DS are passed in",
 | |
| 			dsType:   "prometheus",
 | |
| 			expected: "prometheus",
 | |
| 		},
 | |
| 		{
 | |
| 			desc:   "should return type as the name if no DS with that name exists",
 | |
| 			dsType: "prometheus",
 | |
| 			existingDs: []*datasources.DataSource{
 | |
| 				{Name: "graphite"},
 | |
| 				{Name: "loki"},
 | |
| 			},
 | |
| 			expected: "prometheus",
 | |
| 		},
 | |
| 		{
 | |
| 			desc:   "should return type-1 as the name if one data source with that name exists",
 | |
| 			dsType: "prometheus",
 | |
| 			existingDs: []*datasources.DataSource{
 | |
| 				{Name: "graphite"},
 | |
| 				{Name: "prometheus"},
 | |
| 			},
 | |
| 			expected: "prometheus-1",
 | |
| 		},
 | |
| 		{
 | |
| 			desc:   "should correctly increment the number suffix of the name",
 | |
| 			dsType: "prometheus",
 | |
| 			existingDs: []*datasources.DataSource{
 | |
| 				{Name: "prometheus"},
 | |
| 				{Name: "prometheus-1"},
 | |
| 				{Name: "prometheus-3"},
 | |
| 			},
 | |
| 			expected: "prometheus-2",
 | |
| 		},
 | |
| 		{
 | |
| 			desc:   "should correctly increment the number suffix for multidigit numbers",
 | |
| 			dsType: "prometheus",
 | |
| 			existingDs: []*datasources.DataSource{
 | |
| 				{Name: "prometheus"},
 | |
| 				{Name: "prometheus-1"},
 | |
| 				{Name: "prometheus-2"},
 | |
| 				{Name: "prometheus-3"},
 | |
| 				{Name: "prometheus-4"},
 | |
| 				{Name: "prometheus-5"},
 | |
| 				{Name: "prometheus-6"},
 | |
| 				{Name: "prometheus-7"},
 | |
| 				{Name: "prometheus-8"},
 | |
| 				{Name: "prometheus-9"},
 | |
| 				{Name: "prometheus-10"},
 | |
| 			},
 | |
| 			expected: "prometheus-11",
 | |
| 		},
 | |
| 		{
 | |
| 			desc:   "name comparison should be case insensitive",
 | |
| 			dsType: "prometheus",
 | |
| 			existingDs: []*datasources.DataSource{
 | |
| 				{Name: "Prometheus"},
 | |
| 				{Name: "PROMETHEUS"},
 | |
| 			},
 | |
| 			expected: "prometheus-1",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.desc, func(t *testing.T) {
 | |
| 			name := getAvailableName(tc.dsType, tc.existingDs)
 | |
| 			assert.Equal(t, tc.expected, name)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestService_UpdateDataSource(t *testing.T) {
 | |
| 	t.Run("should return not found error if datasource not found", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 
 | |
| 		cmd := &datasources.UpdateDataSourceCommand{
 | |
| 			UID:   uuid.New().String(),
 | |
| 			ID:    1,
 | |
| 			OrgID: 1,
 | |
| 		}
 | |
| 
 | |
| 		_, err := dsService.UpdateDataSource(context.Background(), cmd)
 | |
| 		require.ErrorIs(t, err, datasources.ErrDataSourceNotFound)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should return validation error if command validation failed", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 
 | |
| 		// First add the datasource
 | |
| 		ds, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
 | |
| 			OrgID:  1,
 | |
| 			Name:   "test",
 | |
| 			Type:   "test",
 | |
| 			UserID: 0,
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		cmd := &datasources.UpdateDataSourceCommand{
 | |
| 			ID:    ds.ID,
 | |
| 			UID:   ds.UID,
 | |
| 			OrgID: 1,
 | |
| 			Name:  string(make([]byte, 256)),
 | |
| 		}
 | |
| 
 | |
| 		_, err = dsService.UpdateDataSource(context.Background(), cmd)
 | |
| 		require.EqualError(t, err, "[datasource.nameInvalid] max length is 190")
 | |
| 
 | |
| 		cmd = &datasources.UpdateDataSourceCommand{
 | |
| 			ID:    ds.ID,
 | |
| 			UID:   ds.UID,
 | |
| 			OrgID: 1,
 | |
| 			URL:   string(make([]byte, 256)),
 | |
| 		}
 | |
| 
 | |
| 		_, err = dsService.UpdateDataSource(context.Background(), cmd)
 | |
| 		require.EqualError(t, err, "[datasource.urlInvalid] max length is 255")
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should return no error if updated datasource", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 
 | |
| 		ds, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
 | |
| 			OrgID: 1,
 | |
| 			Name:  "test-datasource",
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		cmd := &datasources.UpdateDataSourceCommand{
 | |
| 			ID:    ds.ID,
 | |
| 			OrgID: ds.OrgID,
 | |
| 			Name:  "test-datasource-updated",
 | |
| 		}
 | |
| 
 | |
| 		_, err = dsService.UpdateDataSource(context.Background(), cmd)
 | |
| 		require.NoError(t, err)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should return error if datasource with same name exist", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 
 | |
| 		dsToUpdate, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
 | |
| 			OrgID: 1,
 | |
| 			Name:  "test-datasource",
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		existingDs, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
 | |
| 			OrgID: 1,
 | |
| 			Name:  "name already taken",
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		cmd := &datasources.UpdateDataSourceCommand{
 | |
| 			ID:    dsToUpdate.ID,
 | |
| 			OrgID: dsToUpdate.OrgID,
 | |
| 			Name:  existingDs.Name,
 | |
| 		}
 | |
| 
 | |
| 		_, err = dsService.UpdateDataSource(context.Background(), cmd)
 | |
| 		require.ErrorIs(t, err, datasources.ErrDataSourceNameExists)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should merge cmd.SecureJsonData with db data", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 
 | |
| 		expectedDbKey := "db-secure-key"
 | |
| 		expectedDbValue := "db-secure-value"
 | |
| 		ds, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
 | |
| 			OrgID: 1,
 | |
| 			Name:  "test-datasource",
 | |
| 			SecureJsonData: map[string]string{
 | |
| 				expectedDbKey: expectedDbValue,
 | |
| 			},
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		expectedOgKey := "cmd-secure-key"
 | |
| 		expectedOgValue := "cmd-secure-value"
 | |
| 
 | |
| 		cmd := &datasources.UpdateDataSourceCommand{
 | |
| 			ID:    ds.ID,
 | |
| 			OrgID: ds.OrgID,
 | |
| 			Name:  "test-datasource-updated",
 | |
| 			SecureJsonData: map[string]string{
 | |
| 				expectedOgKey: expectedOgValue,
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		ds, err = dsService.UpdateDataSource(context.Background(), cmd)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		secret, err := dsService.DecryptedValues(context.Background(), ds)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		assert.Equal(t, secret[expectedDbKey], expectedDbValue)
 | |
| 		assert.Equal(t, secret[expectedOgKey], expectedOgValue)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should preserve cmd.SecureJsonData when cmd.IgnoreOldSecureJsonData=true", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 
 | |
| 		notExpectedDbKey := "db-secure-key"
 | |
| 		dbValue := "db-secure-value"
 | |
| 		ds, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
 | |
| 			OrgID: 1,
 | |
| 			Name:  "test-datasource",
 | |
| 			SecureJsonData: map[string]string{
 | |
| 				notExpectedDbKey: dbValue,
 | |
| 			},
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		expectedOgKey := "cmd-secure-key"
 | |
| 		expectedOgValue := "cmd-secure-value"
 | |
| 
 | |
| 		cmd := &datasources.UpdateDataSourceCommand{
 | |
| 			ID:    ds.ID,
 | |
| 			OrgID: ds.OrgID,
 | |
| 			Name:  "test-datasource-updated",
 | |
| 			SecureJsonData: map[string]string{
 | |
| 				expectedOgKey: expectedOgValue,
 | |
| 			},
 | |
| 			IgnoreOldSecureJsonData: true,
 | |
| 		}
 | |
| 
 | |
| 		ds, err = dsService.UpdateDataSource(context.Background(), cmd)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		secret, err := dsService.DecryptedValues(context.Background(), ds)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		assert.Equal(t, secret[expectedOgKey], expectedOgValue)
 | |
| 		_, ok := secret[notExpectedDbKey]
 | |
| 		assert.False(t, ok)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should run validation and mutation hooks", func(t *testing.T) {
 | |
| 		dsService := initDSService(t)
 | |
| 		dsService.pluginStore = &pluginstore.FakePluginStore{
 | |
| 			PluginList: []pluginstore.Plugin{{
 | |
| 				JSONData: plugins.JSONData{
 | |
| 					ID:   "test",
 | |
| 					Type: plugins.TypeDataSource,
 | |
| 					Name: "test",
 | |
| 				},
 | |
| 			}},
 | |
| 		}
 | |
| 		validateExecuted := false
 | |
| 		mutateExecuted := false
 | |
| 		dsService.pluginClient = &pluginfakes.FakePluginClient{
 | |
| 			ValidateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
 | |
| 				validateExecuted = true
 | |
| 				return &backend.ValidationResponse{
 | |
| 					Allowed: true,
 | |
| 				}, nil
 | |
| 			},
 | |
| 			MutateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
 | |
| 				mutateExecuted = true
 | |
| 				return &backend.MutationResponse{
 | |
| 					Allowed:     true,
 | |
| 					ObjectBytes: req.ObjectBytes,
 | |
| 				}, nil
 | |
| 			},
 | |
| 			ConvertObjectFunc: func(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
 | |
| 				return nil, fmt.Errorf("not implemented")
 | |
| 			},
 | |
| 		}
 | |
| 		ds, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
 | |
| 			OrgID:      1,
 | |
| 			Name:       "test-datasource",
 | |
| 			APIVersion: "v0alpha1",
 | |
| 			Type:       "test",
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		cmd := &datasources.UpdateDataSourceCommand{
 | |
| 			ID:         ds.ID,
 | |
| 			OrgID:      ds.OrgID,
 | |
| 			Name:       "test-datasource-updated",
 | |
| 			APIVersion: "v0alpha1",
 | |
| 			Type:       "test",
 | |
| 		}
 | |
| 
 | |
| 		dsUpdated, err := dsService.UpdateDataSource(context.Background(), cmd)
 | |
| 		require.NoError(t, err)
 | |
| 		require.True(t, validateExecuted)
 | |
| 		require.True(t, mutateExecuted)
 | |
| 		require.Equal(t, "test-datasource-updated", dsUpdated.Name)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestService_DeleteDataSource(t *testing.T) {
 | |
| 	t.Run("should not return an error if data source doesn't exist", func(t *testing.T) {
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		permissionSvc := acmock.NewMockedPermissionsService()
 | |
| 		permissionSvc.On("DeleteResourcePermissions", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe()
 | |
| 
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, featuremgmt.WithFeatures(), acmock.New(), permissionSvc, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		cmd := &datasources.DeleteDataSourceCommand{
 | |
| 			UID:   uuid.New().String(),
 | |
| 			ID:    1,
 | |
| 			OrgID: 1,
 | |
| 		}
 | |
| 
 | |
| 		err = dsService.DeleteDataSource(context.Background(), cmd)
 | |
| 		require.NoError(t, err)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should successfully delete a data source that exists", func(t *testing.T) {
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 
 | |
| 		permissionSvc := acmock.NewMockedPermissionsService()
 | |
| 		permissionSvc.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil).Once()
 | |
| 		permissionSvc.On("DeleteResourcePermissions", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
 | |
| 
 | |
| 		f := ini.Empty()
 | |
| 		f.Section("rbac").Key("resources_with_managed_permissions_on_creation").SetValue("datasource")
 | |
| 		cfg, err := setting.NewCfgFromINIFile(f)
 | |
| 		require.NoError(t, err)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), permissionSvc, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		// First add the datasource
 | |
| 		ds, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
 | |
| 			OrgID:  1,
 | |
| 			Name:   "test",
 | |
| 			Type:   "test",
 | |
| 			UserID: 0,
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		cmd := &datasources.DeleteDataSourceCommand{
 | |
| 			ID:    ds.ID,
 | |
| 			UID:   ds.UID,
 | |
| 			OrgID: 1,
 | |
| 		}
 | |
| 
 | |
| 		err = dsService.DeleteDataSource(context.Background(), cmd)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		// Data source doesn't exist anymore
 | |
| 		ds, err = dsService.GetDataSource(context.Background(), &datasources.GetDataSourceQuery{
 | |
| 			OrgID: 1,
 | |
| 			UID:   ds.UID,
 | |
| 		})
 | |
| 		require.Nil(t, ds)
 | |
| 		require.ErrorIs(t, err, datasources.ErrDataSourceNotFound)
 | |
| 
 | |
| 		permissionSvc.AssertExpectations(t)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestService_NameScopeResolver(t *testing.T) {
 | |
| 	retriever := &dataSourceMockRetriever{[]*datasources.DataSource{
 | |
| 		{Name: "test-datasource", UID: "1"},
 | |
| 		{Name: "*", UID: "2"},
 | |
| 		{Name: ":/*", UID: "3"},
 | |
| 		{Name: ":", UID: "4"},
 | |
| 	}}
 | |
| 
 | |
| 	type testCaseResolver struct {
 | |
| 		desc    string
 | |
| 		given   string
 | |
| 		want    string
 | |
| 		wantErr error
 | |
| 	}
 | |
| 
 | |
| 	testCases := []testCaseResolver{
 | |
| 		{
 | |
| 			desc:    "correct",
 | |
| 			given:   "datasources:name:test-datasource",
 | |
| 			want:    "datasources:uid:1",
 | |
| 			wantErr: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "asterisk in name",
 | |
| 			given:   "datasources:name:*",
 | |
| 			want:    "datasources:uid:2",
 | |
| 			wantErr: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "complex name",
 | |
| 			given:   "datasources:name::/*",
 | |
| 			want:    "datasources:uid:3",
 | |
| 			wantErr: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "colon in name",
 | |
| 			given:   "datasources:name::",
 | |
| 			want:    "datasources:uid:4",
 | |
| 			wantErr: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "unknown datasource",
 | |
| 			given:   "datasources:name:unknown-datasource",
 | |
| 			want:    "",
 | |
| 			wantErr: datasources.ErrDataSourceNotFound,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "malformed scope",
 | |
| 			given:   "datasources:unknown-datasource",
 | |
| 			want:    "",
 | |
| 			wantErr: accesscontrol.ErrInvalidScope,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "empty name scope",
 | |
| 			given:   "datasources:name:",
 | |
| 			want:    "",
 | |
| 			wantErr: accesscontrol.ErrInvalidScope,
 | |
| 		},
 | |
| 	}
 | |
| 	prefix, resolver := NewNameScopeResolver(retriever)
 | |
| 	require.Equal(t, "datasources:name:", prefix)
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.desc, func(t *testing.T) {
 | |
| 			resolved, err := resolver.Resolve(context.Background(), 1, tc.given)
 | |
| 			if tc.wantErr != nil {
 | |
| 				require.Error(t, err)
 | |
| 				require.Equal(t, tc.wantErr, err)
 | |
| 			} else {
 | |
| 				require.NoError(t, err)
 | |
| 				require.Len(t, resolved, 1)
 | |
| 				require.Equal(t, tc.want, resolved[0])
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestService_IDScopeResolver(t *testing.T) {
 | |
| 	retriever := &dataSourceMockRetriever{[]*datasources.DataSource{
 | |
| 		{ID: 1, UID: "NnftN9Lnz"},
 | |
| 	}}
 | |
| 
 | |
| 	type testCaseResolver struct {
 | |
| 		desc    string
 | |
| 		given   string
 | |
| 		want    string
 | |
| 		wantErr error
 | |
| 	}
 | |
| 
 | |
| 	testCases := []testCaseResolver{
 | |
| 		{
 | |
| 			desc:    "correct",
 | |
| 			given:   "datasources:id:1",
 | |
| 			want:    "datasources:uid:NnftN9Lnz",
 | |
| 			wantErr: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "unknown datasource",
 | |
| 			given:   "datasources:id:unknown",
 | |
| 			want:    "",
 | |
| 			wantErr: accesscontrol.ErrInvalidScope,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "malformed scope",
 | |
| 			given:   "datasources:unknown",
 | |
| 			want:    "",
 | |
| 			wantErr: accesscontrol.ErrInvalidScope,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:    "empty uid scope",
 | |
| 			given:   "datasources:id:",
 | |
| 			want:    "",
 | |
| 			wantErr: accesscontrol.ErrInvalidScope,
 | |
| 		},
 | |
| 	}
 | |
| 	prefix, resolver := NewIDScopeResolver(retriever)
 | |
| 	require.Equal(t, "datasources:id:", prefix)
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.desc, func(t *testing.T) {
 | |
| 			resolved, err := resolver.Resolve(context.Background(), 1, tc.given)
 | |
| 			if tc.wantErr != nil {
 | |
| 				require.Error(t, err)
 | |
| 				require.Equal(t, tc.wantErr, err)
 | |
| 			} else {
 | |
| 				require.NoError(t, err)
 | |
| 				require.Len(t, resolved, 1)
 | |
| 				require.Equal(t, tc.want, resolved[0])
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestService_awsServiceNamespace(t *testing.T) {
 | |
| 	type testCaseResolver struct {
 | |
| 		desc      string
 | |
| 		givenDs   string
 | |
| 		givenJson string
 | |
| 		want      string
 | |
| 		panic     bool
 | |
| 	}
 | |
| 
 | |
| 	testCases := []testCaseResolver{
 | |
| 		{
 | |
| 			desc:      "elasticsearch",
 | |
| 			givenDs:   datasources.DS_ES,
 | |
| 			givenJson: `{ "sigV4Auth": true, "serverless": true }`,
 | |
| 			want:      "es",
 | |
| 		}, {
 | |
| 			desc:      "opendistro",
 | |
| 			givenDs:   datasources.DS_ES_OPEN_DISTRO,
 | |
| 			givenJson: `{ "sigV4Auth": true, "serverless": true }`,
 | |
| 			want:      "es",
 | |
| 		}, {
 | |
| 			desc:      "opensearch not serverless",
 | |
| 			givenDs:   datasources.DS_ES_OPENSEARCH,
 | |
| 			givenJson: `{ "sigV4Auth": true }`,
 | |
| 			want:      "es",
 | |
| 		}, {
 | |
| 			desc:      "opensearch not serverless",
 | |
| 			givenDs:   datasources.DS_ES_OPENSEARCH,
 | |
| 			givenJson: `{ "sigV4Auth": true, "serverless": false }`,
 | |
| 			want:      "es",
 | |
| 		}, {
 | |
| 			desc:      "opensearch serverless",
 | |
| 			givenDs:   datasources.DS_ES_OPENSEARCH,
 | |
| 			givenJson: `{ "sigV4Auth": true, "serverless": true }`,
 | |
| 			want:      "aoss",
 | |
| 		}, {
 | |
| 			desc:      "prometheus",
 | |
| 			givenDs:   datasources.DS_PROMETHEUS,
 | |
| 			givenJson: `{ "sigV4Auth": true, "serverless": true }`,
 | |
| 			want:      "aps",
 | |
| 		}, {
 | |
| 			desc:      "alertmanager",
 | |
| 			givenDs:   datasources.DS_ALERTMANAGER,
 | |
| 			givenJson: `{ "sigV4Auth": true, "serverless": true }`,
 | |
| 			want:      "aps",
 | |
| 		}, {
 | |
| 			desc:      "panic",
 | |
| 			givenDs:   "panic",
 | |
| 			givenJson: `{ "sigV4Auth": true, "serverless": true }`,
 | |
| 			want:      "aps",
 | |
| 			panic:     true,
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.desc, func(t *testing.T) {
 | |
| 			json, _ := simplejson.NewJson([]byte(tc.givenJson))
 | |
| 			if tc.panic {
 | |
| 				require.Panics(t, func() { awsServiceNamespace(tc.givenDs, json) })
 | |
| 			} else {
 | |
| 				resolved := awsServiceNamespace(tc.givenDs, json)
 | |
| 				require.Equal(t, tc.want, resolved)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| //nolint:goconst
 | |
| func TestService_GetHttpTransport(t *testing.T) {
 | |
| 	cfg := &setting.Cfg{}
 | |
| 
 | |
| 	t.Run("Should use cached proxy", func(t *testing.T) {
 | |
| 		var configuredTransport *http.Transport
 | |
| 		provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
 | |
| 			ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
 | |
| 				configuredTransport = transport
 | |
| 			},
 | |
| 		})
 | |
| 
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:   1,
 | |
| 			URL:  "http://k8s:8001",
 | |
| 			Type: "Kubernetes",
 | |
| 		}
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		rt1, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt1)
 | |
| 		tr1 := configuredTransport
 | |
| 
 | |
| 		rt2, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt2)
 | |
| 		tr2 := configuredTransport
 | |
| 
 | |
| 		require.Same(t, tr1, tr2)
 | |
| 
 | |
| 		require.False(t, tr1.TLSClientConfig.InsecureSkipVerify)
 | |
| 		require.Empty(t, tr1.TLSClientConfig.Certificates)
 | |
| 		require.Nil(t, tr1.TLSClientConfig.RootCAs)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Should not use cached proxy when datasource updated", func(t *testing.T) {
 | |
| 		var configuredTransport *http.Transport
 | |
| 		provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
 | |
| 			ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
 | |
| 				configuredTransport = transport
 | |
| 			},
 | |
| 		})
 | |
| 
 | |
| 		cfg.SecretKey = "password"
 | |
| 
 | |
| 		sjson := simplejson.New()
 | |
| 		sjson.Set("tlsAuthWithCACert", true)
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:             1,
 | |
| 			URL:            "http://k8s:8001",
 | |
| 			Type:           "Kubernetes",
 | |
| 			SecureJsonData: map[string][]byte{"tlsCACert": []byte(caCert)},
 | |
| 			Updated:        time.Now().Add(-2 * time.Minute),
 | |
| 		}
 | |
| 
 | |
| 		rt1, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NotNil(t, rt1)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		tr1 := configuredTransport
 | |
| 
 | |
| 		require.False(t, tr1.TLSClientConfig.InsecureSkipVerify)
 | |
| 		require.Empty(t, tr1.TLSClientConfig.Certificates)
 | |
| 		require.Nil(t, tr1.TLSClientConfig.RootCAs)
 | |
| 
 | |
| 		ds.JsonData = nil
 | |
| 		ds.SecureJsonData = map[string][]byte{}
 | |
| 		ds.Updated = time.Now()
 | |
| 
 | |
| 		rt2, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt2)
 | |
| 		tr2 := configuredTransport
 | |
| 
 | |
| 		require.NotSame(t, tr1, tr2)
 | |
| 		require.Nil(t, tr2.TLSClientConfig.RootCAs)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Should set TLS client authentication enabled if configured in JsonData", func(t *testing.T) {
 | |
| 		var configuredTransport *http.Transport
 | |
| 		provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
 | |
| 			ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
 | |
| 				configuredTransport = transport
 | |
| 			},
 | |
| 		})
 | |
| 
 | |
| 		cfg.SecretKey = "password"
 | |
| 
 | |
| 		sjson := simplejson.New()
 | |
| 		sjson.Set("tlsAuth", true)
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:       1,
 | |
| 			OrgID:    1,
 | |
| 			Name:     "kubernetes",
 | |
| 			URL:      "http://k8s:8001",
 | |
| 			Type:     "Kubernetes",
 | |
| 			JsonData: sjson,
 | |
| 		}
 | |
| 
 | |
| 		secureJsonData, err := json.Marshal(map[string]string{
 | |
| 			"tlsClientCert": clientCert,
 | |
| 			"tlsClientKey":  clientKey,
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		err = secretsStore.Set(context.Background(), ds.OrgID, ds.Name, secretskvs.DataSourceSecretType, string(secureJsonData))
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		rt, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt)
 | |
| 		tr := configuredTransport
 | |
| 
 | |
| 		require.False(t, tr.TLSClientConfig.InsecureSkipVerify)
 | |
| 		require.Len(t, tr.TLSClientConfig.Certificates, 1)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Should set user-supplied TLS CA if configured in JsonData", func(t *testing.T) {
 | |
| 		var configuredTransport *http.Transport
 | |
| 		provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
 | |
| 			ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
 | |
| 				configuredTransport = transport
 | |
| 			},
 | |
| 		})
 | |
| 
 | |
| 		cfg.SecretKey = "password"
 | |
| 
 | |
| 		sjson := simplejson.New()
 | |
| 		sjson.Set("tlsAuthWithCACert", true)
 | |
| 		sjson.Set("serverName", "server-name")
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:       1,
 | |
| 			OrgID:    1,
 | |
| 			Name:     "kubernetes",
 | |
| 			URL:      "http://k8s:8001",
 | |
| 			Type:     "Kubernetes",
 | |
| 			JsonData: sjson,
 | |
| 		}
 | |
| 
 | |
| 		secureJsonData, err := json.Marshal(map[string]string{
 | |
| 			"tlsCACert": caCert,
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		err = secretsStore.Set(context.Background(), ds.OrgID, ds.Name, secretskvs.DataSourceSecretType, string(secureJsonData))
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		rt, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt)
 | |
| 		tr := configuredTransport
 | |
| 
 | |
| 		opts, err := dsService.httpClientOptions(context.Background(), &ds)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Equal(t, ds.JsonData.MustMap()["grafanaData"], opts.CustomOptions["grafanaData"])
 | |
| 
 | |
| 		// make sure we can still marshal the JsonData after httpClientOptions (avoid cycles)
 | |
| 		_, err = ds.JsonData.MarshalJSON()
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.False(t, tr.TLSClientConfig.InsecureSkipVerify)
 | |
| 		// Ignoring deprecation, the system will not include the root CA
 | |
| 		// used in this scenario.
 | |
| 		//nolint:staticcheck
 | |
| 		require.Len(t, tr.TLSClientConfig.RootCAs.Subjects(), 1)
 | |
| 		require.Equal(t, "server-name", tr.TLSClientConfig.ServerName)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Should set skip TLS verification if configured in JsonData", func(t *testing.T) {
 | |
| 		var configuredTransport *http.Transport
 | |
| 		provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
 | |
| 			ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
 | |
| 				configuredTransport = transport
 | |
| 			},
 | |
| 		})
 | |
| 
 | |
| 		sjson := simplejson.New()
 | |
| 		sjson.Set("tlsSkipVerify", true)
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:       1,
 | |
| 			URL:      "http://k8s:8001",
 | |
| 			Type:     "Kubernetes",
 | |
| 			JsonData: sjson,
 | |
| 		}
 | |
| 
 | |
| 		rt1, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt1)
 | |
| 		tr1 := configuredTransport
 | |
| 
 | |
| 		rt2, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt2)
 | |
| 		tr2 := configuredTransport
 | |
| 
 | |
| 		require.Same(t, tr1, tr2)
 | |
| 		require.True(t, tr1.TLSClientConfig.InsecureSkipVerify)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Should set custom headers if configured in JsonData", func(t *testing.T) {
 | |
| 		provider := httpclient.NewProvider()
 | |
| 
 | |
| 		sjson := simplejson.NewFromAny(map[string]any{
 | |
| 			"httpHeaderName1": "Authorization",
 | |
| 		})
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:       1,
 | |
| 			OrgID:    1,
 | |
| 			Name:     "kubernetes",
 | |
| 			URL:      "http://k8s:8001",
 | |
| 			Type:     "Kubernetes",
 | |
| 			JsonData: sjson,
 | |
| 		}
 | |
| 
 | |
| 		secureJsonData, err := json.Marshal(map[string]string{
 | |
| 			"httpHeaderValue1": "Bearer xf5yhfkpsnmgo",
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		err = secretsStore.Set(context.Background(), ds.OrgID, ds.Name, secretskvs.DataSourceSecretType, string(secureJsonData))
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		headers := dsService.getCustomHeaders(sjson, map[string]string{"httpHeaderValue1": "Bearer xf5yhfkpsnmgo"})
 | |
| 		require.Equal(t, "Bearer xf5yhfkpsnmgo", headers.Get("Authorization"))
 | |
| 
 | |
| 		// 1. Start HTTP test server which checks the request headers
 | |
| 		backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 			if r.Header.Get("Authorization") == "Bearer xf5yhfkpsnmgo" {
 | |
| 				w.WriteHeader(200)
 | |
| 				_, err := w.Write([]byte("Ok"))
 | |
| 				require.NoError(t, err)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			w.WriteHeader(403)
 | |
| 			_, err := w.Write([]byte("Invalid bearer token provided"))
 | |
| 			require.NoError(t, err)
 | |
| 		}))
 | |
| 		defer backend.Close()
 | |
| 
 | |
| 		// 2. Get HTTP transport from datasource which uses the test server as backend
 | |
| 		ds.URL = backend.URL
 | |
| 		rt, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt)
 | |
| 
 | |
| 		// 3. Send test request which should have the Authorization header set
 | |
| 		req := httptest.NewRequest("GET", backend.URL+"/test-headers", nil)
 | |
| 		res, err := rt.RoundTrip(req)
 | |
| 		require.NoError(t, err)
 | |
| 		t.Cleanup(func() {
 | |
| 			err := res.Body.Close()
 | |
| 			require.NoError(t, err)
 | |
| 		})
 | |
| 		body, err := io.ReadAll(res.Body)
 | |
| 		require.NoError(t, err)
 | |
| 		bodyStr := string(body)
 | |
| 		require.Equal(t, "Ok", bodyStr)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Should set request Host if it is configured in custom headers within JsonData", func(t *testing.T) {
 | |
| 		provider := httpclient.NewProvider()
 | |
| 
 | |
| 		sjson := simplejson.NewFromAny(map[string]any{
 | |
| 			"httpHeaderName1": "Host",
 | |
| 		})
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:       1,
 | |
| 			OrgID:    1,
 | |
| 			Name:     "kubernetes",
 | |
| 			URL:      "http://k8s:8001",
 | |
| 			Type:     "Kubernetes",
 | |
| 			JsonData: sjson,
 | |
| 		}
 | |
| 
 | |
| 		secureJsonData, err := json.Marshal(map[string]string{
 | |
| 			"httpHeaderValue1": "example.com",
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		err = secretsStore.Set(context.Background(), ds.OrgID, ds.Name, secretskvs.DataSourceSecretType, string(secureJsonData))
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		headers := dsService.getCustomHeaders(sjson, map[string]string{"httpHeaderValue1": "example.com"})
 | |
| 		require.Equal(t, "example.com", headers.Get("Host"))
 | |
| 
 | |
| 		// 1. Start HTTP test server which checks the request headers
 | |
| 		backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 			if r.Host == "example.com" {
 | |
| 				w.WriteHeader(200)
 | |
| 				_, err := w.Write([]byte("Ok"))
 | |
| 				require.NoError(t, err)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			w.WriteHeader(503)
 | |
| 			_, err := w.Write([]byte("Server name mismatch"))
 | |
| 			require.NoError(t, err)
 | |
| 		}))
 | |
| 		defer backend.Close()
 | |
| 
 | |
| 		// 2. Get HTTP transport from datasource which uses the test server as backend
 | |
| 		ds.URL = backend.URL
 | |
| 		rt, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, rt)
 | |
| 
 | |
| 		// 3. Send test request which should have the Authorization header set
 | |
| 		req := httptest.NewRequest("GET", backend.URL+"/test-host", nil)
 | |
| 		res, err := rt.RoundTrip(req)
 | |
| 		require.NoError(t, err)
 | |
| 		t.Cleanup(func() {
 | |
| 			err := res.Body.Close()
 | |
| 			require.NoError(t, err)
 | |
| 		})
 | |
| 		body, err := io.ReadAll(res.Body)
 | |
| 		require.NoError(t, err)
 | |
| 		bodyStr := string(body)
 | |
| 		require.Equal(t, "Ok", bodyStr)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Should populate SigV4 options if configured in JsonData", func(t *testing.T) {
 | |
| 		var configuredOpts sdkhttpclient.Options
 | |
| 		provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
 | |
| 			ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
 | |
| 				configuredOpts = opts
 | |
| 			},
 | |
| 		})
 | |
| 
 | |
| 		origSigV4Enabled := cfg.SigV4AuthEnabled
 | |
| 		cfg.SigV4AuthEnabled = true
 | |
| 		t.Cleanup(func() {
 | |
| 			cfg.SigV4AuthEnabled = origSigV4Enabled
 | |
| 		})
 | |
| 
 | |
| 		sjson, err := simplejson.NewJson([]byte(`{ "sigV4Auth": true }`))
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		ds := datasources.DataSource{
 | |
| 			Type:     datasources.DS_ES,
 | |
| 			JsonData: sjson,
 | |
| 		}
 | |
| 
 | |
| 		_, err = dsService.GetHTTPTransport(context.Background(), &ds, provider)
 | |
| 		require.NoError(t, err)
 | |
| 		require.NotNil(t, configuredOpts)
 | |
| 		require.NotNil(t, configuredOpts.SigV4)
 | |
| 		require.Equal(t, "es", configuredOpts.SigV4.Service)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestService_getProxySettings(t *testing.T) {
 | |
| 	sqlStore := db.InitTestDB(t)
 | |
| 	secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 	secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 	quotaService := quotatest.New(false, nil)
 | |
| 	dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	t.Run("Should default to disabled", func(t *testing.T) {
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:    1,
 | |
| 			OrgID: 1,
 | |
| 			UID:   "uid",
 | |
| 			Name:  "graphite",
 | |
| 			URL:   "http://test:8001",
 | |
| 			Type:  "Graphite",
 | |
| 		}
 | |
| 
 | |
| 		opts, err := dsService.httpClientOptions(context.Background(), &ds)
 | |
| 		require.NoError(t, err)
 | |
| 		require.Nil(t, opts.ProxyOptions)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Username should default to datasource UID", func(t *testing.T) {
 | |
| 		sjson := simplejson.New()
 | |
| 		sjson.Set("enableSecureSocksProxy", true)
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:       1,
 | |
| 			OrgID:    1,
 | |
| 			UID:      "uid",
 | |
| 			Name:     "graphite",
 | |
| 			URL:      "http://test:8001",
 | |
| 			Type:     "Graphite",
 | |
| 			JsonData: sjson,
 | |
| 		}
 | |
| 
 | |
| 		opts, err := dsService.httpClientOptions(context.Background(), &ds)
 | |
| 		require.NoError(t, err)
 | |
| 		require.True(t, opts.ProxyOptions.Enabled)
 | |
| 		require.Equal(t, opts.ProxyOptions.Auth.Username, ds.UID)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("Can override options", func(t *testing.T) {
 | |
| 		sjson := simplejson.New()
 | |
| 		pass := "testpass"
 | |
| 		user := "testuser"
 | |
| 		sjson.Set("enableSecureSocksProxy", true)
 | |
| 		sjson.Set("secureSocksProxyUsername", user)
 | |
| 		sjson.Set("timeout", 10)
 | |
| 		sjson.Set("keepAlive", 5)
 | |
| 		ds := datasources.DataSource{
 | |
| 			ID:       1,
 | |
| 			OrgID:    1,
 | |
| 			UID:      "uid",
 | |
| 			Name:     "graphite",
 | |
| 			URL:      "http://test:8001",
 | |
| 			Type:     "Graphite",
 | |
| 			JsonData: sjson,
 | |
| 		}
 | |
| 
 | |
| 		secureJsonData, err := json.Marshal(map[string]string{
 | |
| 			"secureSocksProxyPassword": pass,
 | |
| 		})
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		err = secretsStore.Set(context.Background(), ds.OrgID, ds.Name, secretskvs.DataSourceSecretType, string(secureJsonData))
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		opts, err := dsService.httpClientOptions(context.Background(), &ds)
 | |
| 		require.NoError(t, err)
 | |
| 		require.True(t, opts.ProxyOptions.Enabled)
 | |
| 		require.Equal(t, opts.ProxyOptions.Auth.Username, user)
 | |
| 		require.Equal(t, opts.ProxyOptions.Auth.Password, pass)
 | |
| 		require.Equal(t, opts.ProxyOptions.Timeouts.Timeout, 10*time.Second)
 | |
| 		require.Equal(t, opts.ProxyOptions.Timeouts.KeepAlive, 5*time.Second)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestService_getTimeout(t *testing.T) {
 | |
| 	cfg := &setting.Cfg{}
 | |
| 	originalTimeout := sdkhttpclient.DefaultTimeoutOptions.Timeout
 | |
| 	sdkhttpclient.DefaultTimeoutOptions.Timeout = time.Minute
 | |
| 	t.Cleanup(func() {
 | |
| 		sdkhttpclient.DefaultTimeoutOptions.Timeout = originalTimeout
 | |
| 	})
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		jsonData        *simplejson.Json
 | |
| 		expectedTimeout time.Duration
 | |
| 	}{
 | |
| 		{jsonData: simplejson.New(), expectedTimeout: time.Minute},
 | |
| 		{jsonData: simplejson.NewFromAny(map[string]any{"timeout": nil}), expectedTimeout: time.Minute},
 | |
| 		{jsonData: simplejson.NewFromAny(map[string]any{"timeout": 0}), expectedTimeout: time.Minute},
 | |
| 		{jsonData: simplejson.NewFromAny(map[string]any{"timeout": 1}), expectedTimeout: time.Second},
 | |
| 		{jsonData: simplejson.NewFromAny(map[string]any{"timeout": "2"}), expectedTimeout: 2 * time.Second},
 | |
| 	}
 | |
| 
 | |
| 	sqlStore := db.InitTestDB(t)
 | |
| 	secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 	secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 	quotaService := quotatest.New(false, nil)
 | |
| 	dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		ds := &datasources.DataSource{
 | |
| 			JsonData: tc.jsonData,
 | |
| 		}
 | |
| 		assert.Equal(t, tc.expectedTimeout, dsService.getTimeout(ds))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestService_GetDecryptedValues(t *testing.T) {
 | |
| 	t.Run("should migrate and retrieve values from secure json data", func(t *testing.T) {
 | |
| 		ds := &datasources.DataSource{
 | |
| 			ID:   1,
 | |
| 			URL:  "https://api.example.com",
 | |
| 			Type: "prometheus",
 | |
| 		}
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		jsonData := map[string]string{
 | |
| 			"password": "securePassword",
 | |
| 		}
 | |
| 		secureJsonData, err := dsService.SecretsService.EncryptJsonData(context.Background(), jsonData, secrets.WithoutScope())
 | |
| 
 | |
| 		require.NoError(t, err)
 | |
| 		ds.SecureJsonData = secureJsonData
 | |
| 
 | |
| 		values, err := dsService.DecryptedValues(context.Background(), ds)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Equal(t, jsonData, values)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("should retrieve values from secret store", func(t *testing.T) {
 | |
| 		ds := &datasources.DataSource{
 | |
| 			ID:   1,
 | |
| 			URL:  "https://api.example.com",
 | |
| 			Type: "prometheus",
 | |
| 		}
 | |
| 
 | |
| 		sqlStore := db.InitTestDB(t)
 | |
| 		secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 		secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 		quotaService := quotatest.New(false, nil)
 | |
| 		dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		jsonData := map[string]string{
 | |
| 			"password": "securePassword",
 | |
| 		}
 | |
| 		jsonString, err := json.Marshal(jsonData)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		err = secretsStore.Set(context.Background(), ds.OrgID, ds.Name, secretskvs.DataSourceSecretType, string(jsonString))
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		values, err := dsService.DecryptedValues(context.Background(), ds)
 | |
| 		require.NoError(t, err)
 | |
| 
 | |
| 		require.Equal(t, jsonData, values)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestDataSource_CustomHeaders(t *testing.T) {
 | |
| 	sqlStore := db.InitTestDB(t)
 | |
| 	secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 	secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 	quotaService := quotatest.New(false, nil)
 | |
| 	dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{}, nil)
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	dsService.cfg = setting.NewCfg()
 | |
| 
 | |
| 	testValue := "HeaderValue1"
 | |
| 
 | |
| 	encryptedValue, err := secretsService.Encrypt(context.Background(), []byte(testValue), secrets.WithoutScope())
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name             string
 | |
| 		jsonData         *simplejson.Json
 | |
| 		secureJsonData   map[string][]byte
 | |
| 		expectedHeaders  http.Header
 | |
| 		expectedErrorMsg string
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "valid custom headers",
 | |
| 			jsonData: simplejson.NewFromAny(map[string]any{
 | |
| 				"httpHeaderName1": "X-Test-Header1",
 | |
| 			}),
 | |
| 			secureJsonData: map[string][]byte{
 | |
| 				"httpHeaderValue1": encryptedValue,
 | |
| 			},
 | |
| 			expectedHeaders: http.Header{
 | |
| 				"X-Test-Header1": []string{testValue},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "missing header value",
 | |
| 			jsonData: simplejson.NewFromAny(map[string]any{
 | |
| 				"httpHeaderName1": "X-Test-Header1",
 | |
| 			}),
 | |
| 			secureJsonData:  map[string][]byte{},
 | |
| 			expectedHeaders: http.Header{},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "non customer header value",
 | |
| 			jsonData: simplejson.NewFromAny(map[string]any{
 | |
| 				"someotherheader": "X-Test-Header1",
 | |
| 			}),
 | |
| 			secureJsonData:  map[string][]byte{},
 | |
| 			expectedHeaders: http.Header{},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "add multiple header value",
 | |
| 			jsonData: simplejson.NewFromAny(map[string]any{
 | |
| 				"httpHeaderName1": "X-Test-Header1",
 | |
| 				"httpHeaderName2": "X-Test-Header1",
 | |
| 			}),
 | |
| 			secureJsonData: map[string][]byte{
 | |
| 				"httpHeaderValue1": encryptedValue,
 | |
| 				"httpHeaderValue2": encryptedValue,
 | |
| 			},
 | |
| 			expectedHeaders: http.Header{
 | |
| 				"X-Test-Header1": []string{testValue, testValue},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			ds := &datasources.DataSource{
 | |
| 				JsonData:       tc.jsonData,
 | |
| 				SecureJsonData: tc.secureJsonData,
 | |
| 			}
 | |
| 
 | |
| 			headers, err := dsService.CustomHeaders(context.Background(), ds)
 | |
| 
 | |
| 			if tc.expectedErrorMsg != "" {
 | |
| 				require.Error(t, err)
 | |
| 				assert.Contains(t, err.Error(), tc.expectedErrorMsg)
 | |
| 			} else {
 | |
| 				require.NoError(t, err)
 | |
| 				assert.Equal(t, tc.expectedHeaders, headers)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func initDSService(t *testing.T) *Service {
 | |
| 	cfg := &setting.Cfg{}
 | |
| 	sqlStore := db.InitTestDB(t)
 | |
| 	secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
 | |
| 	secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
 | |
| 	quotaService := quotatest.New(false, nil)
 | |
| 	mockPermission := acmock.NewMockedPermissionsService()
 | |
| 	mockPermission.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
 | |
| 	dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{
 | |
| 		PluginList: []pluginstore.Plugin{{
 | |
| 			JSONData: plugins.JSONData{
 | |
| 				ID:   "test",
 | |
| 				Type: plugins.TypeDataSource,
 | |
| 				Name: "test",
 | |
| 			},
 | |
| 		}},
 | |
| 	}, &pluginfakes.FakePluginClient{
 | |
| 		ValidateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
 | |
| 			return &backend.ValidationResponse{
 | |
| 				Allowed: true,
 | |
| 			}, nil
 | |
| 		},
 | |
| 		MutateAdmissionFunc: func(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
 | |
| 			return &backend.MutationResponse{
 | |
| 				Allowed:     true,
 | |
| 				ObjectBytes: req.ObjectBytes,
 | |
| 			}, nil
 | |
| 		},
 | |
| 		ConvertObjectFunc: func(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
 | |
| 			return nil, fmt.Errorf("not implemented")
 | |
| 		},
 | |
| 	}, plugincontext.ProvideBaseService(cfg, pluginconfig.NewFakePluginRequestConfigProvider()))
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	return dsService
 | |
| }
 | |
| 
 | |
| const caCert string = `-----BEGIN CERTIFICATE-----
 | |
| MIIDATCCAemgAwIBAgIJAMQ5hC3CPDTeMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
 | |
| BAMMDGNhLWs4cy1zdGhsbTAeFw0xNjEwMjcwODQyMjdaFw00NDAzMTQwODQyMjda
 | |
| MBcxFTATBgNVBAMMDGNhLWs4cy1zdGhsbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
 | |
| ADCCAQoCggEBAMLe2AmJ6IleeUt69vgNchOjjmxIIxz5sp1vFu94m1vUip7CqnOg
 | |
| QkpUsHeBPrGYv8UGloARCL1xEWS+9FVZeXWQoDmbC0SxXhFwRIESNCET7Q8KMi/4
 | |
| 4YPvnMLGZi3Fjwxa8BdUBCN1cx4WEooMVTWXm7RFMtZgDfuOAn3TNXla732sfT/d
 | |
| 1HNFrh48b0wA+HhmA3nXoBnBEblA665hCeo7lIAdRr0zJxJpnFnWXkyTClsAUTMN
 | |
| iL905LdBiiIRenojipfKXvMz88XSaWTI7JjZYU3BvhyXndkT6f12cef3I96NY3WJ
 | |
| 0uIK4k04WrbzdYXMU3rN6NqlvbHqnI+E7aMCAwEAAaNQME4wHQYDVR0OBBYEFHHx
 | |
| 2+vSPw9bECHj3O51KNo5VdWOMB8GA1UdIwQYMBaAFHHx2+vSPw9bECHj3O51KNo5
 | |
| VdWOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH2eV5NcV3LBJHs9
 | |
| I+adbiTPg2vyumrGWwy73T0X8Dtchgt8wU7Q9b9Ucg2fOTmSSyS0iMqEu1Yb2ORB
 | |
| CknM9mixHC9PwEBbkGCom3VVkqdLwSP6gdILZgyLoH4i8sTUz+S1yGPepi+Vzhs7
 | |
| adOXtryjcGnwft6HdfKPNklMOHFnjw6uqpho54oj/z55jUpicY/8glDHdrr1bh3k
 | |
| MHuiWLGewHXPvxfG6UoUx1te65IhifVcJGFZDQwfEmhBflfCmtAJlZEsgTLlBBCh
 | |
| FHoXIyGOdq1chmRVocdGBCF8fUoGIbuF14r53rpvcbEKtKnnP8+96luKAZLq0a4n
 | |
| 3lb92xM=
 | |
| -----END CERTIFICATE-----`
 | |
| 
 | |
| const clientCert string = `
 | |
| -----BEGIN CERTIFICATE-----
 | |
| MIICsjCCAZoCCQCcd8sOfstQLzANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj
 | |
| YS1rOHMtc3RobG0wHhcNMTYxMTAyMDkyNTE1WhcNMTcxMTAyMDkyNTE1WjAfMR0w
 | |
| GwYDVQQDDBRhZG0tZGFuaWVsLWs4cy1zdGhsbTCCASIwDQYJKoZIhvcNAQEBBQAD
 | |
| ggEPADCCAQoCggEBAOMliaWyNEUJKM37vWCl5bGub3lMicyRAqGQyY/qxD9yKKM2
 | |
| FbucVcmWmg5vvTqQVl5rlQ+c7GI8OD6ptmFl8a26coEki7bFr8bkpSyBSEc5p27b
 | |
| Z0ORFSqBHWHQbr9PkxPLYW6T3gZYUtRYv3OQgGxLXlvUh85n/mQfuR3N1FgmShHo
 | |
| GtAFi/ht6leXa0Ms+jNSDLCmXpJm1GIEqgyKX7K3+g3vzo9coYqXq4XTa8Efs2v8
 | |
| SCwqWfBC3rHfgs/5DLB8WT4Kul8QzxkytzcaBQfRfzhSV6bkgm7oTzt2/1eRRsf4
 | |
| YnXzLE9YkCC9sAn+Owzqf+TYC1KRluWDfqqBTJUCAwEAATANBgkqhkiG9w0BAQsF
 | |
| AAOCAQEAdMsZg6edWGC+xngizn0uamrUg1ViaDqUsz0vpzY5NWLA4MsBc4EtxWRP
 | |
| ueQvjUimZ3U3+AX0YWNLIrH1FCVos2jdij/xkTUmHcwzr8rQy+B17cFi+a8jtpgw
 | |
| AU6WWoaAIEhhbWQfth/Diz3mivl1ARB+YqiWca2mjRPLTPcKJEURDVddQ423el0Q
 | |
| 4JNxS5icu7T2zYTYHAo/cT9zVdLZl0xuLxYm3asK1IONJ/evxyVZima3il6MPvhe
 | |
| 58Hwz+m+HdqHxi24b/1J/VKYbISG4huOQCdLzeNXgvwFlGPUmHSnnKo1/KbQDAR5
 | |
| llG/Sw5+FquFuChaA6l5KWy7F3bQyA==
 | |
| -----END CERTIFICATE-----`
 | |
| 
 | |
| // #nosec G101
 | |
| const clientKey string = `-----BEGIN RSA PRIVATE KEY-----
 | |
| MIIEpQIBAAKCAQEA4yWJpbI0RQkozfu9YKXlsa5veUyJzJECoZDJj+rEP3IoozYV
 | |
| u5xVyZaaDm+9OpBWXmuVD5zsYjw4Pqm2YWXxrbpygSSLtsWvxuSlLIFIRzmnbttn
 | |
| Q5EVKoEdYdBuv0+TE8thbpPeBlhS1Fi/c5CAbEteW9SHzmf+ZB+5Hc3UWCZKEega
 | |
| 0AWL+G3qV5drQyz6M1IMsKZekmbUYgSqDIpfsrf6De/Oj1yhiperhdNrwR+za/xI
 | |
| LCpZ8ELesd+Cz/kMsHxZPgq6XxDPGTK3NxoFB9F/OFJXpuSCbuhPO3b/V5FGx/hi
 | |
| dfMsT1iQIL2wCf47DOp/5NgLUpGW5YN+qoFMlQIDAQABAoIBAQCzy4u312XeW1Cs
 | |
| Mx6EuOwmh59/ESFmBkZh4rxZKYgrfE5EWlQ7i5SwG4BX+wR6rbNfy6JSmHDXlTkk
 | |
| CKvvToVNcW6fYHEivDnVojhIERFIJ4+rhQmpBtcNLOQ3/4cZ8X/GxE6b+3lb5l+x
 | |
| 64mnjPLKRaIr5/+TVuebEy0xNTJmjnJ7yiB2HRz7uXEQaVSk/P7KAkkyl/9J3/LM
 | |
| 8N9AX1w6qDaNQZ4/P0++1H4SQenosM/b/GqGTomarEk/GE0NcB9rzmR9VCXa7FRh
 | |
| WV5jyt9vUrwIEiK/6nUnOkGO8Ei3kB7Y+e+2m6WdaNoU5RAfqXmXa0Q/a0lLRruf
 | |
| vTMo2WrBAoGBAPRaK4cx76Q+3SJ/wfznaPsMM06OSR8A3ctKdV+ip/lyKtb1W8Pz
 | |
| k8MYQDH7GwPtSu5QD8doL00pPjugZL/ba7X9nAsI+pinyEErfnB9y7ORNEjIYYzs
 | |
| DiqDKup7ANgw1gZvznWvb9Ge0WUSXvWS0pFkgootQAf+RmnnbWGH6l6RAoGBAO35
 | |
| aGUrLro5u9RD24uSXNU3NmojINIQFK5dHAT3yl0BBYstL43AEsye9lX95uMPTvOQ
 | |
| Cqcn42Hjp/bSe3n0ObyOZeXVrWcDFAfE0wwB1BkvL1lpgnFO9+VQORlH4w3Ppnpo
 | |
| jcPkR2TFeDaAYtvckhxe/Bk3OnuFmnsQ3VzM75fFAoGBAI6PvS2XeNU+yA3EtA01
 | |
| hg5SQ+zlHswz2TMuMeSmJZJnhY78f5mHlwIQOAPxGQXlf/4iP9J7en1uPpzTK3S0
 | |
| M9duK4hUqMA/w5oiIhbHjf0qDnMYVbG+V1V+SZ+cPBXmCDihKreGr5qBKnHpkfV8
 | |
| v9WL6o1rcRw4wiQvnaV1gsvBAoGBALtzVTczr6gDKCAIn5wuWy+cQSGTsBunjRLX
 | |
| xuVm5iEiV+KMYkPvAx/pKzMLP96lRVR3ptyKgAKwl7LFk3u50+zh4gQLr35QH2wL
 | |
| Lw7rNc3srAhrItPsFzqrWX6/cGuFoKYVS239l/sZzRppQPXcpb7xVvTp2whHcir0
 | |
| Wtnpl+TdAoGAGqKqo2KU3JoY3IuTDUk1dsNAm8jd9EWDh+s1x4aG4N79mwcss5GD
 | |
| FF8MbFPneK7xQd8L6HisKUDAUi2NOyynM81LAftPkvN6ZuUVeFDfCL4vCA0HUXLD
 | |
| +VrOhtUZkNNJlLMiVRJuQKUOGlg8PpObqYbstQAf/0/yFJMRHG82Tcg=
 | |
| -----END RSA PRIVATE KEY-----`
 |