mirror of https://github.com/grafana/grafana.git
369 lines
12 KiB
Go
369 lines
12 KiB
Go
package testutils
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/grafana/authlib/authn"
|
|
"github.com/grafana/authlib/types"
|
|
"github.com/madflojo/testcerts"
|
|
"github.com/stretchr/testify/require"
|
|
"go.opentelemetry.io/otel/trace/noop"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/utils/ptr"
|
|
|
|
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
|
decryptcontracts "github.com/grafana/grafana/apps/secret/pkg/decrypt"
|
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
"github.com/grafana/grafana/pkg/infra/usagestats"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/decrypt"
|
|
cipher "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service"
|
|
osskmsproviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/garbagecollectionworker"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/mutator"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/service"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/validator"
|
|
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/storage/secret/database"
|
|
encryptionstorage "github.com/grafana/grafana/pkg/storage/secret/encryption"
|
|
|
|
"github.com/grafana/grafana/pkg/storage/secret/metadata"
|
|
"github.com/grafana/grafana/pkg/storage/secret/migrator"
|
|
)
|
|
|
|
type SetupConfig struct {
|
|
KeeperService contracts.KeeperService
|
|
}
|
|
|
|
func defaultSetupCfg() SetupConfig {
|
|
return SetupConfig{}
|
|
}
|
|
|
|
func WithKeeperService(keeperService contracts.KeeperService) func(*SetupConfig) {
|
|
return func(setupCfg *SetupConfig) {
|
|
setupCfg.KeeperService = keeperService
|
|
}
|
|
}
|
|
|
|
func WithMutateCfg(f func(*SetupConfig)) func(*SetupConfig) {
|
|
return func(cfg *SetupConfig) {
|
|
f(cfg)
|
|
}
|
|
}
|
|
|
|
func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
|
setupCfg := defaultSetupCfg()
|
|
for _, opt := range opts {
|
|
opt(&setupCfg)
|
|
}
|
|
|
|
tracer := noop.NewTracerProvider().Tracer("test")
|
|
|
|
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
|
|
|
|
database := database.ProvideDatabase(testDB, tracer)
|
|
|
|
keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(database, tracer, nil)
|
|
require.NoError(t, err)
|
|
|
|
clock := NewFakeClock()
|
|
|
|
secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(clock, database, tracer, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Initialize access client + access control
|
|
accessControl := acimpl.ProvideAccessControl(nil)
|
|
accessClient := accesscontrol.NewLegacyAccessClient(accessControl, accesscontrol.ResourceAuthorizerOptions{
|
|
Resource: "securevalues",
|
|
Attr: "uid",
|
|
})
|
|
|
|
defaultKey := "SdlklWklckeLS"
|
|
cfg := setting.NewCfg()
|
|
cfg.SecretsManagement = setting.SecretsManagerSettings{
|
|
CurrentEncryptionProvider: "secret_key.v1",
|
|
ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": defaultKey}},
|
|
GCWorkerEnabled: false,
|
|
GCWorkerMaxBatchSize: 2,
|
|
GCWorkerMaxConcurrentCleanups: 2,
|
|
}
|
|
store, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, nil)
|
|
require.NoError(t, err)
|
|
|
|
globalDataKeyStore, err := encryptionstorage.ProvideGlobalDataKeyStorage(database, tracer, nil)
|
|
require.NoError(t, err)
|
|
|
|
usageStats := &usagestats.UsageStatsMock{T: t}
|
|
|
|
enc, err := cipher.ProvideAESGCMCipherService(tracer, usageStats)
|
|
require.NoError(t, err)
|
|
|
|
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfg, enc)
|
|
require.NoError(t, err)
|
|
|
|
encryptionManager, err := manager.ProvideEncryptionManager(
|
|
tracer,
|
|
store,
|
|
usageStats,
|
|
enc,
|
|
ossProviders,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Initialize encrypted value storage with a fake db
|
|
encryptedValueStorage, err := encryptionstorage.ProvideEncryptedValueStorage(database, tracer)
|
|
require.NoError(t, err)
|
|
|
|
// Initialize global encrypted value storage with a fake db
|
|
globalEncryptedValueStorage, err := encryptionstorage.ProvideGlobalEncryptedValueStorage(database, tracer)
|
|
require.NoError(t, err)
|
|
|
|
sqlKeeper := sqlkeeper.NewSQLKeeper(tracer, encryptionManager, encryptedValueStorage, nil)
|
|
|
|
var keeperService contracts.KeeperService = newKeeperServiceWrapper(sqlKeeper)
|
|
|
|
if setupCfg.KeeperService != nil {
|
|
keeperService = setupCfg.KeeperService
|
|
}
|
|
|
|
secureValueValidator := validator.ProvideSecureValueValidator()
|
|
secureValueMutator := mutator.ProvideSecureValueMutator()
|
|
|
|
secureValueService := service.ProvideSecureValueService(tracer, accessClient, database, secureValueMetadataStorage, secureValueValidator, secureValueMutator, keeperMetadataStorage, keeperService, nil)
|
|
|
|
decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer, nil)
|
|
|
|
decryptStorage, err := metadata.ProvideDecryptStorage(tracer, keeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, nil)
|
|
require.NoError(t, err)
|
|
|
|
testCfg := setting.NewCfg()
|
|
|
|
decryptService, err := decrypt.ProvideDecryptService(testCfg, tracer, decryptStorage)
|
|
require.NoError(t, err)
|
|
|
|
consolidationService := service.ProvideConsolidationService(tracer, globalDataKeyStore, encryptedValueStorage, globalEncryptedValueStorage, encryptionManager)
|
|
|
|
garbageCollectionWorker := garbagecollectionworker.ProvideWorker(
|
|
cfg,
|
|
secureValueMetadataStorage,
|
|
keeperMetadataStorage,
|
|
keeperService)
|
|
|
|
return Sut{
|
|
SecureValueService: secureValueService,
|
|
SecureValueMetadataStorage: secureValueMetadataStorage,
|
|
DecryptStorage: decryptStorage,
|
|
DecryptService: decryptService,
|
|
EncryptedValueStorage: encryptedValueStorage,
|
|
GlobalEncryptedValueStorage: globalEncryptedValueStorage,
|
|
SQLKeeper: sqlKeeper,
|
|
Database: database,
|
|
AccessClient: accessClient,
|
|
ConsolidationService: consolidationService,
|
|
EncryptionManager: encryptionManager,
|
|
GlobalDataKeyStore: globalDataKeyStore,
|
|
GarbageCollectionWorker: garbageCollectionWorker,
|
|
Clock: clock,
|
|
KeeperService: keeperService,
|
|
KeeperMetadataStorage: keeperMetadataStorage,
|
|
}
|
|
}
|
|
|
|
type Sut struct {
|
|
SecureValueService contracts.SecureValueService
|
|
SecureValueMetadataStorage contracts.SecureValueMetadataStorage
|
|
DecryptStorage contracts.DecryptStorage
|
|
DecryptService decryptcontracts.DecryptService
|
|
EncryptedValueStorage contracts.EncryptedValueStorage
|
|
GlobalEncryptedValueStorage contracts.GlobalEncryptedValueStorage
|
|
SQLKeeper *sqlkeeper.SQLKeeper
|
|
Database *database.Database
|
|
AccessClient types.AccessClient
|
|
ConsolidationService contracts.ConsolidationService
|
|
EncryptionManager contracts.EncryptionManager
|
|
GlobalDataKeyStore contracts.GlobalDataKeyStorage
|
|
GarbageCollectionWorker *garbagecollectionworker.Worker
|
|
// The fake clock passed to implementations to make testing easier
|
|
Clock *FakeClock
|
|
KeeperService contracts.KeeperService
|
|
KeeperMetadataStorage contracts.KeeperMetadataStorage
|
|
}
|
|
|
|
type CreateSvConfig struct {
|
|
Sv *secretv1beta1.SecureValue
|
|
}
|
|
|
|
func CreateSvWithSv(sv *secretv1beta1.SecureValue) func(*CreateSvConfig) {
|
|
return func(cfg *CreateSvConfig) {
|
|
cfg.Sv = sv
|
|
}
|
|
}
|
|
|
|
func (s *Sut) CreateSv(ctx context.Context, opts ...func(*CreateSvConfig)) (*secretv1beta1.SecureValue, error) {
|
|
cfg := CreateSvConfig{
|
|
Sv: &secretv1beta1.SecureValue{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "sv1",
|
|
Namespace: "ns1",
|
|
},
|
|
Spec: secretv1beta1.SecureValueSpec{
|
|
Description: "desc1",
|
|
Value: ptr.To(secretv1beta1.NewExposedSecureValue("v1")),
|
|
Decrypters: []string{"decrypter1"},
|
|
},
|
|
Status: secretv1beta1.SecureValueStatus{},
|
|
},
|
|
}
|
|
for _, opt := range opts {
|
|
opt(&cfg)
|
|
}
|
|
|
|
createdSv, err := s.SecureValueService.Create(ctx, cfg.Sv, "actor-uid")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return createdSv, nil
|
|
}
|
|
|
|
func (s *Sut) UpdateSv(ctx context.Context, sv *secretv1beta1.SecureValue) (*secretv1beta1.SecureValue, error) {
|
|
newSv, _, err := s.SecureValueService.Update(ctx, sv, "actor-uid")
|
|
return newSv, err
|
|
}
|
|
|
|
func (s *Sut) DeleteSv(ctx context.Context, namespace, name string) (*secretv1beta1.SecureValue, error) {
|
|
sv, err := s.SecureValueService.Delete(ctx, xkube.Namespace(namespace), name)
|
|
return sv, err
|
|
}
|
|
|
|
type keeperServiceWrapper struct {
|
|
keeper contracts.Keeper
|
|
}
|
|
|
|
func newKeeperServiceWrapper(keeper contracts.Keeper) *keeperServiceWrapper {
|
|
return &keeperServiceWrapper{keeper: keeper}
|
|
}
|
|
|
|
func (wrapper *keeperServiceWrapper) KeeperForConfig(cfg secretv1beta1.KeeperConfig) (contracts.Keeper, error) {
|
|
return wrapper.keeper, nil
|
|
}
|
|
|
|
func CreateUserAuthContext(ctx context.Context, namespace string, permissions map[string][]string) context.Context {
|
|
orgID := int64(1)
|
|
requester := &identity.StaticRequester{
|
|
Namespace: namespace,
|
|
Type: types.TypeUser,
|
|
UserID: 1,
|
|
OrgID: orgID,
|
|
Permissions: map[int64]map[string][]string{
|
|
orgID: permissions,
|
|
},
|
|
}
|
|
|
|
return types.WithAuthInfo(ctx, requester)
|
|
}
|
|
|
|
func CreateServiceAuthContext(ctx context.Context, serviceIdentity string, namespace string, permissions []string) context.Context {
|
|
requester := &identity.StaticRequester{
|
|
Namespace: namespace,
|
|
AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{
|
|
Rest: authn.AccessTokenClaims{
|
|
Permissions: permissions,
|
|
ServiceIdentity: serviceIdentity,
|
|
},
|
|
},
|
|
}
|
|
|
|
return types.WithAuthInfo(ctx, requester)
|
|
}
|
|
|
|
// CreateOBOAuthContext emulates a context where the request is made on-behalf-of (OBO) a user, with an access token.
|
|
func CreateOBOAuthContext(
|
|
ctx context.Context,
|
|
serviceIdentity string,
|
|
namespace string,
|
|
userPermissions map[string][]string,
|
|
delegatedPermissions []string,
|
|
) context.Context {
|
|
requester := &identity.StaticRequester{
|
|
Namespace: namespace,
|
|
Type: types.TypeUser,
|
|
OrgID: 1,
|
|
UserID: 1,
|
|
Permissions: map[int64]map[string][]string{
|
|
1: userPermissions,
|
|
},
|
|
AccessTokenClaims: &authn.Claims[authn.AccessTokenClaims]{
|
|
Rest: authn.AccessTokenClaims{
|
|
ServiceIdentity: serviceIdentity,
|
|
DelegatedPermissions: delegatedPermissions,
|
|
Actor: &authn.ActorClaims{
|
|
Subject: "user:1",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
return types.WithAuthInfo(ctx, requester)
|
|
}
|
|
|
|
type TestCertPaths struct {
|
|
ClientCert string
|
|
ClientKey string
|
|
ServerCert string
|
|
ServerKey string
|
|
CA string
|
|
}
|
|
|
|
func CreateX509TestDir(t *testing.T) TestCertPaths {
|
|
t.Helper()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
ca := testcerts.NewCA()
|
|
caCertFile, _, err := ca.ToTempFile(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
serverKp, err := ca.NewKeyPair("localhost")
|
|
require.NoError(t, err)
|
|
|
|
serverCertFile, serverKeyFile, err := serverKp.ToTempFile(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
clientKp, err := ca.NewKeyPair()
|
|
require.NoError(t, err)
|
|
clientCertFile, clientKeyFile, err := clientKp.ToTempFile(tmpDir)
|
|
require.NoError(t, err)
|
|
|
|
return TestCertPaths{
|
|
ClientCert: clientCertFile.Name(),
|
|
ClientKey: clientKeyFile.Name(),
|
|
ServerCert: serverCertFile.Name(),
|
|
ServerKey: serverKeyFile.Name(),
|
|
CA: caCertFile.Name(),
|
|
}
|
|
}
|
|
|
|
type FakeClock struct {
|
|
Current time.Time
|
|
}
|
|
|
|
func NewFakeClock() *FakeClock {
|
|
return &FakeClock{Current: time.Now()}
|
|
}
|
|
|
|
func (c *FakeClock) Now() time.Time {
|
|
return c.Current
|
|
}
|
|
|
|
func (c *FakeClock) AdvanceBy(duration time.Duration) {
|
|
c.Current = c.Current.Add(duration)
|
|
}
|