grafana/pkg/registry/apis/secret/testutils/testutils.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)
}