diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5c5de6c7246..232d737f538 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -180,7 +180,6 @@ /pkg/tests/apis/query @grafana/grafana-datasources-core-services /pkg/tests/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend /pkg/tests/api/correlations/ @grafana/datapro -/pkg/tests/apis/secret/ @grafana/grafana-operator-experience-squad /pkg/tsdb/grafanads/ @grafana/grafana-backend-group /pkg/tsdb/opentsdb/ @grafana/partner-datasources /pkg/util/ @grafana/grafana-backend-group diff --git a/pkg/registry/apis/apis.go b/pkg/registry/apis/apis.go index e100c3c1af7..4ae591b24da 100644 --- a/pkg/registry/apis/apis.go +++ b/pkg/registry/apis/apis.go @@ -27,9 +27,9 @@ func ProvideRegistryServiceSink( _ *iam.IdentityAccessManagementAPIBuilder, _ *query.QueryAPIBuilder, _ *userstorage.UserStorageAPIBuilder, - _ *secret.SecretAPIBuilder, _ *provisioning.APIBuilder, _ *ofrep.APIBuilder, + _ *secret.DependencyRegisterer, ) *Service { return &Service{} } diff --git a/pkg/registry/apis/secret/accesscontrol.go b/pkg/registry/apis/secret/accesscontrol.go index e93fe22d1cc..1b4c050fb58 100644 --- a/pkg/registry/apis/secret/accesscontrol.go +++ b/pkg/registry/apis/secret/accesscontrol.go @@ -27,12 +27,12 @@ var ( ScopeAllKeepers = ScopeProviderSecretKeepers.GetResourceAllScope() ) -func RegisterAccessControlRoles(service accesscontrol.Service) error { +func registerAccessControlRoles(service accesscontrol.Service) error { // SecureValues secureValuesReader := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.securevalues:reader", - DisplayName: "Secrets Manager secure values reader", + DisplayName: "Secure Values Reader", Description: "Read and list secure values.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ @@ -48,7 +48,7 @@ func RegisterAccessControlRoles(service accesscontrol.Service) error { secureValuesWriter := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.securevalues:writer", - DisplayName: "Secrets Manager secure values writer", + DisplayName: "Secure Values Writer", Description: "Create, update and delete secure values.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ @@ -77,7 +77,7 @@ func RegisterAccessControlRoles(service accesscontrol.Service) error { keepersReader := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.keepers:reader", - DisplayName: "Secrets Manager keepers reader", + DisplayName: "Keepers Reader", Description: "Read and list keepers.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ @@ -93,7 +93,7 @@ func RegisterAccessControlRoles(service accesscontrol.Service) error { keepersWriter := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Name: "fixed:secret.keepers:writer", - DisplayName: "Secrets Manager keepers writer", + DisplayName: "Keepers Writer", Description: "Create, update and delete keepers.", Group: "Secrets Manager", Permissions: []accesscontrol.Permission{ diff --git a/pkg/registry/apis/secret/register.go b/pkg/registry/apis/secret/register.go index d0bb292e695..f43b164f6b4 100644 --- a/pkg/registry/apis/secret/register.go +++ b/pkg/registry/apis/secret/register.go @@ -4,268 +4,40 @@ import ( "context" "fmt" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/apiserver/pkg/registry/rest" - genericapiserver "k8s.io/apiserver/pkg/server" - "k8s.io/kube-openapi/pkg/common" - - claims "github.com/grafana/authlib/types" - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/reststorage" "github.com/grafana/grafana/pkg/services/accesscontrol" - authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" - "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" ) -var ( - _ builder.APIGroupBuilder = (*SecretAPIBuilder)(nil) - _ builder.APIGroupMutation = (*SecretAPIBuilder)(nil) - _ builder.APIGroupValidation = (*SecretAPIBuilder)(nil) -) +// DependencyRegisterer is set to satisfy wire gen and make sure the `RegisterDependencies` is called. +type DependencyRegisterer struct{} -type SecretAPIBuilder struct { - tracer tracing.Tracer - secureValueMetadataStorage contracts.SecureValueMetadataStorage - keeperMetadataStorage contracts.KeeperMetadataStorage - accessClient claims.AccessClient - decryptersAllowList map[string]struct{} -} - -func NewSecretAPIBuilder( - tracer tracing.Tracer, - secureValueMetadataStorage contracts.SecureValueMetadataStorage, - keeperMetadataStorage contracts.KeeperMetadataStorage, - accessClient claims.AccessClient, - decryptersAllowList map[string]struct{}, -) *SecretAPIBuilder { - return &SecretAPIBuilder{tracer, secureValueMetadataStorage, keeperMetadataStorage, accessClient, decryptersAllowList} -} - -func RegisterAPIService( +func RegisterDependencies( features featuremgmt.FeatureToggles, cfg *setting.Cfg, - apiregistration builder.APIRegistrar, - tracer tracing.Tracer, - secureValueMetadataStorage contracts.SecureValueMetadataStorage, - keeperMetadataStorage contracts.KeeperMetadataStorage, - accessClient claims.AccessClient, - accessControlService accesscontrol.Service, secretDBMigrator contracts.SecretDBMigrator, -) (*SecretAPIBuilder, error) { - // Skip registration unless opting into experimental apis and the secrets management app platform flag. - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || - !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { + accessControlService accesscontrol.Service, +) (*DependencyRegisterer, error) { + if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { return nil, nil } - // Some DBs that claim to be MySQL/Postgres-compatible might not support table locking. - lockDatabase := cfg.Raw.Section("database").Key("migration_locking").MustBool(true) - - if err := secretDBMigrator.RunMigrations(context.Background(), lockDatabase); err != nil { - return nil, fmt.Errorf("running secret database migrations: %w", err) + // Permissions for requests in multi-tenant mode will come from HG. + if err := registerAccessControlRoles(accessControlService); err != nil { + return nil, fmt.Errorf("registering access control roles: %w", err) } - if err := RegisterAccessControlRoles(accessControlService); err != nil { - return nil, fmt.Errorf("register secret access control roles: %w", err) - } + // We shouldn't need to create the DB in HG, as that will use the MT api server. + if cfg.StackID == "" { + // Some DBs that claim to be MySQL/Postgres-compatible might not support table locking. + lockDatabase := cfg.Raw.Section("database").Key("migration_locking").MustBool(true) - builder := NewSecretAPIBuilder( - tracer, - secureValueMetadataStorage, - keeperMetadataStorage, - accessClient, - nil, // OSS does not need an allow list. - ) - - apiregistration.RegisterAPI(builder) - - return builder, nil -} - -// GetGroupVersion returns the tuple of `group` and `version` for the API which uniquely identifies it. -func (b *SecretAPIBuilder) GetGroupVersion() schema.GroupVersion { - return secretv0alpha1.SchemeGroupVersion -} - -// InstallSchema is called by the `apiserver` which exposes the defined kinds. -func (b *SecretAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - err := secretv0alpha1.AddKnownTypes(scheme, secretv0alpha1.VERSION) - if err != nil { - return err - } - - // Link this version to the internal representation. - // This is used for server-side-apply (PATCH), and avoids the error: - // "no kind is registered for the type" - err = secretv0alpha1.AddKnownTypes(scheme, runtime.APIVersionInternal) - if err != nil { - return err - } - - // Internal Kubernetes metadata API. Presumably to display the available APIs? - // e.g. http://localhost:3000/apis/secret.grafana.app/v0alpha1 - metav1.AddToGroupVersion(scheme, secretv0alpha1.SchemeGroupVersion) - - // This sets the priority in case we have multiple versions. - // By default Kubernetes will only let you use `kubectl get ` with one version. - // In case there are multiple versions, we'd need to pass the full path with the `--raw` flag. - if err := scheme.SetVersionPriority(secretv0alpha1.SchemeGroupVersion); err != nil { - return fmt.Errorf("scheme set version priority: %w", err) - } - - return nil -} - -func (b *SecretAPIBuilder) AllowedV0Alpha1Resources() []string { - return nil -} - -// UpdateAPIGroupInfo is called when creating a generic API server for this group of kinds. -func (b *SecretAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error { - secureValueResource := secretv0alpha1.SecureValuesResourceInfo - keeperResource := secretv0alpha1.KeeperResourceInfo - - // rest.Storage is a generic interface for RESTful storage services. - // The constructors need to at least implement this interface, but will most likely implement - // other interfaces that equal to different operations like `get`, `list` and so on. - secureRestStorage := map[string]rest.Storage{ - // Default path for `securevalue`. - // The `reststorage.SecureValueRest` struct will implement interfaces for CRUDL operations on `securevalue`. - secureValueResource.StoragePath(): reststorage.NewSecureValueRest(b.secureValueMetadataStorage, secureValueResource), - - // The `reststorage.KeeperRest` struct will implement interfaces for CRUDL operations on `keeper`. - keeperResource.StoragePath(): reststorage.NewKeeperRest(b.keeperMetadataStorage, b.accessClient, keeperResource), - } - - apiGroupInfo.VersionedResourcesStorageMap[secretv0alpha1.VERSION] = secureRestStorage - return nil -} - -// GetOpenAPIDefinitions, is this only for documentation? -func (b *SecretAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return secretv0alpha1.GetOpenAPIDefinitions -} - -// GetAuthorizer decides whether the request is allowed, denied or no opinion based on credentials and request attributes. -// Usually most resource are stored in folders (e.g. alerts, dashboards), which allows users to manage permissions at folder level, -// rather than at resource level which also has the benefit of lowering the load on AuthZ side, since instead of storing access to -// a single dashboard, you'd store access to all dashboards in a specific folder. -// For Secrets, this is not the case, but if we want to make it so, we need to update this ResourceAuthorizer to check the containing folder. -// If we ever want to do that, get guidance from IAM first as well. -func (b *SecretAPIBuilder) GetAuthorizer() authorizer.Authorizer { - return authsvc.NewResourceAuthorizer(b.accessClient) -} - -// Validate is called in `Create`, `Update` and `Delete` REST funcs, if the body calls the argument `rest.ValidateObjectFunc`. -func (b *SecretAPIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { - obj := a.GetObject() - operation := a.GetOperation() - - if obj == nil || operation == admission.Connect { - return nil // This is normal for sub-resource - } - - groupKind := obj.GetObjectKind().GroupVersionKind().GroupKind() - - // Generic validations for all kinds. At this point the name+namespace must not be empty. - if a.GetName() == "" { - return apierrors.NewInvalid( - groupKind, - a.GetName(), - field.ErrorList{field.Required(field.NewPath("metadata", "name"), "a `name` is required")}, - ) - } - - if a.GetNamespace() == "" { - return apierrors.NewInvalid( - groupKind, - a.GetName(), - field.ErrorList{field.Required(field.NewPath("metadata", "namespace"), "a `namespace` is required")}, - ) - } - - switch typedObj := obj.(type) { - case *secretv0alpha1.SecureValue: - var oldObj *secretv0alpha1.SecureValue - - if a.GetOldObject() != nil { - var ok bool - - oldObj, ok = a.GetOldObject().(*secretv0alpha1.SecureValue) - if !ok { - return apierrors.NewBadRequest(fmt.Sprintf("old object is not a SecureValue, found %T", a.GetOldObject())) - } - } - - if errs := reststorage.ValidateSecureValue(typedObj, oldObj, operation, b.decryptersAllowList); len(errs) > 0 { - return apierrors.NewInvalid(groupKind, a.GetName(), errs) - } - - return nil - case *secretv0alpha1.Keeper: - if errs := reststorage.ValidateKeeper(typedObj, operation); len(errs) > 0 { - return apierrors.NewInvalid(groupKind, a.GetName(), errs) - } - - return nil - } - - return apierrors.NewBadRequest(fmt.Sprintf("unknown spec %T", obj)) -} - -func (b *SecretAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error { - obj := a.GetObject() - operation := a.GetOperation() - - if obj == nil || operation == admission.Connect { - return nil // This is normal for sub-resource - } - - // When creating a resource and the name is empty, we need to generate one. - if operation == admission.Create && a.GetName() == "" { - generatedName, err := util.GetRandomString(8) - if err != nil { - return fmt.Errorf("generate random string: %w", err) - } - - switch typedObj := obj.(type) { - case *secretv0alpha1.SecureValue: - optionalPrefix := typedObj.GenerateName - if optionalPrefix == "" { - optionalPrefix = "sv-" - } - - typedObj.Name = optionalPrefix + generatedName - - case *secretv0alpha1.Keeper: - optionalPrefix := typedObj.GenerateName - if optionalPrefix == "" { - optionalPrefix = "kp-" - } - - typedObj.Name = optionalPrefix + generatedName + // This is needed to wire up and run DB migrations for Secrets Manager, which is not run by the generic OSS DB migrator. + if err := secretDBMigrator.RunMigrations(context.Background(), lockDatabase); err != nil { + return nil, fmt.Errorf("running secret database migrations: %w", err) } } - // On any mutation to a `SecureValue`, override the `phase` as `Pending` and an empty `message`. - if operation == admission.Create || operation == admission.Update { - sv, ok := obj.(*secretv0alpha1.SecureValue) - if ok && sv != nil { - sv.Status.Phase = secretv0alpha1.SecureValuePhasePending - sv.Status.Message = "" - } - } - - return nil + return &DependencyRegisterer{}, nil } diff --git a/pkg/registry/apis/secret/reststorage/keeper_rest.go b/pkg/registry/apis/secret/reststorage/keeper_rest.go deleted file mode 100644 index c7c2eb25e8f..00000000000 --- a/pkg/registry/apis/secret/reststorage/keeper_rest.go +++ /dev/null @@ -1,386 +0,0 @@ -package reststorage - -import ( - "context" - "errors" - "fmt" - "strings" - - claims "github.com/grafana/authlib/types" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" - - "github.com/grafana/grafana/pkg/apimachinery/utils" - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" -) - -var ( - _ rest.Scoper = (*KeeperRest)(nil) - _ rest.SingularNameProvider = (*KeeperRest)(nil) - _ rest.Getter = (*KeeperRest)(nil) - _ rest.Lister = (*KeeperRest)(nil) - _ rest.Storage = (*KeeperRest)(nil) - _ rest.Creater = (*KeeperRest)(nil) - _ rest.Updater = (*KeeperRest)(nil) - _ rest.GracefulDeleter = (*KeeperRest)(nil) -) - -// KeeperRest is an implementation of CRUDL operations on a `keeper` backed by TODO. -type KeeperRest struct { - storage contracts.KeeperMetadataStorage - accessClient claims.AccessClient - resource utils.ResourceInfo - tableConverter rest.TableConvertor -} - -// NewKeeperRest is a returns a constructed `*KeeperRest`. -func NewKeeperRest(storage contracts.KeeperMetadataStorage, accessClient claims.AccessClient, resource utils.ResourceInfo) *KeeperRest { - return &KeeperRest{storage, accessClient, resource, resource.TableConverter()} -} - -// New returns an empty `*Keeper` that is used by the `Create` method. -func (s *KeeperRest) New() runtime.Object { - return s.resource.NewFunc() -} - -// Destroy is called when? [TODO] -func (s *KeeperRest) Destroy() {} - -// NamespaceScoped returns `true` because the storage is namespaced (== org). -func (s *KeeperRest) NamespaceScoped() bool { - return true -} - -// GetSingularName is used by `kubectl` discovery to have singular name representation of resources. -func (s *KeeperRest) GetSingularName() string { - return s.resource.GetSingularName() -} - -// NewList returns an empty `*KeeperList` that is used by the `List` method. -func (s *KeeperRest) NewList() runtime.Object { - return s.resource.NewListFunc() -} - -// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`. -func (s *KeeperRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) -} - -// List calls the inner `store` (persistence) and returns a list of `Keepers` within a `namespace` filtered by the `options`. -func (s *KeeperRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing auth info in context") - } - - hasPermissionFor, err := s.accessClient.Compile(ctx, user, claims.ListRequest{ - Group: secretv0alpha1.GROUP, - Resource: secretv0alpha1.KeeperResourceInfo.GetName(), - Namespace: namespace, - Verb: utils.VerbGet, - }) - if err != nil { - return nil, fmt.Errorf("failed to compile checker: %w", err) - } - - labelSelector := options.LabelSelector - if labelSelector == nil { - labelSelector = labels.Everything() - } - - keepersList, err := s.storage.List(ctx, xkube.Namespace(namespace)) - if err != nil { - return nil, fmt.Errorf("failed to list keepers: %w", err) - } - - allowedKeepers := make([]secretv0alpha1.Keeper, 0) - - for _, keeper := range keepersList { - // Check whether the user has permission to access this specific Keeper in the namespace. - if !hasPermissionFor(keeper.Name, "") { - continue - } - - if labelSelector.Matches(labels.Set(keeper.Labels)) { - allowedKeepers = append(allowedKeepers, keeper) - } - } - - return &secretv0alpha1.KeeperList{ - Items: allowedKeepers, - }, nil -} - -// Get calls the inner `store` (persistence) and returns a `Keeper` by `name`. -func (s *KeeperRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - // TODO: readopts - kp, err := s.storage.Read(ctx, xkube.Namespace(namespace), name, contracts.ReadOpts{}) - if err != nil { - if errors.Is(err, contracts.ErrKeeperNotFound) { - return nil, s.resource.NewNotFound(name) - } - return nil, fmt.Errorf("failed to read keeper: %w", err) - } - - return kp, nil -} - -// Create a new `Keeper`. Does some validation and allows empty `name` (generated). -func (s *KeeperRest) Create( - ctx context.Context, - obj runtime.Object, - createValidation rest.ValidateObjectFunc, - options *metav1.CreateOptions, -) (runtime.Object, error) { - kp, ok := obj.(*secretv0alpha1.Keeper) - if !ok { - return nil, fmt.Errorf("expected Keeper for create") - } - - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing auth info in context") - } - - if err := createValidation(ctx, obj); err != nil { - return nil, err - } - - createdKeeper, err := s.storage.Create(ctx, kp, user.GetUID()) - if err != nil { - var kErr xkube.ErrorLister - if errors.As(err, &kErr) { - return nil, apierrors.NewInvalid(kp.GroupVersionKind().GroupKind(), kp.Name, kErr.ErrorList()) - } - - return nil, fmt.Errorf("failed to create keeper: %w", err) - } - - return createdKeeper, nil -} - -// Update a `Keeper`'s `value`. The second return parameter indicates whether the resource was newly created. -func (s *KeeperRest) Update( - ctx context.Context, - name string, - objInfo rest.UpdatedObjectInfo, - createValidation rest.ValidateObjectFunc, - updateValidation rest.ValidateObjectUpdateFunc, - forceAllowCreate bool, - options *metav1.UpdateOptions, -) (runtime.Object, bool, error) { - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing auth info in context") - } - - oldObj, err := s.Get(ctx, name, &metav1.GetOptions{}) - if err != nil { - return nil, false, err - } - - // Makes sure the UID and ResourceVersion are OK. - // TODO: this also makes it so the labels and annotations are additive, unless we check and remove manually. - newObj, err := objInfo.UpdatedObject(ctx, oldObj) - if err != nil { - return nil, false, fmt.Errorf("k8s updated object: %w", err) - } - - // The current supported behavior for `Update` is to replace the entire `spec` with the new one. - // Each provider-specific setting of a keeper lives at the top-level, so it makes it possible to change a provider - // during an update. Otherwise both old and new providers would be merged in the `newObj` which is not allowed. - if err := updateValidation(ctx, newObj, oldObj); err != nil { - return nil, false, err - } - - newKeeper, ok := newObj.(*secretv0alpha1.Keeper) - if !ok { - return nil, false, fmt.Errorf("expected Keeper for update") - } - - // TODO: do we need to do this here again? Probably not, but double-check! - newKeeper.Annotations = xkube.CleanAnnotations(newKeeper.Annotations) - - // Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that? - updatedKeeper, err := s.storage.Update(ctx, newKeeper, user.GetUID()) - if err != nil { - var kErr xkube.ErrorLister - if errors.As(err, &kErr) { - return nil, false, apierrors.NewInvalid(newKeeper.GroupVersionKind().GroupKind(), newKeeper.Name, kErr.ErrorList()) - } - - return nil, false, fmt.Errorf("failed to update keeper: %w", err) - } - - return updatedKeeper, false, nil -} - -// Delete calls the inner `store` (persistence) in order to delete the `Keeper`. -// The second return parameter `bool` indicates whether the delete was intant or not. It always is for `Keepers`. -func (s *KeeperRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing namespace") - } - - err := s.storage.Delete(ctx, xkube.Namespace(namespace), name) - if err != nil { - if errors.Is(err, contracts.ErrKeeperNotFound) { - return nil, false, s.resource.NewNotFound(name) - } - return nil, false, fmt.Errorf("failed to delete keeper: %w", err) - } - - return nil, true, nil -} - -// ValidateKeeper does basic spec validation of a keeper. -func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation) field.ErrorList { - // Only validate Create and Update for now. - if operation != admission.Create && operation != admission.Update { - return nil - } - - errs := make(field.ErrorList, 0) - - if keeper.Spec.Description == "" { - errs = append(errs, field.Required(field.NewPath("spec", "description"), "a `description` is required")) - } - - // Only one keeper type can be configured. Return early and don't validate the specific keeper fields. - if err := validateKeepers(keeper); err != nil { - errs = append(errs, err) - - return errs - } - - if keeper.Spec.AWS != nil { - if err := validateCredentialValue(field.NewPath("spec", "aws", "accessKeyId"), keeper.Spec.AWS.AccessKeyID); err != nil { - errs = append(errs, err) - } - - if err := validateCredentialValue(field.NewPath("spec", "aws", "secretAccessKey"), keeper.Spec.AWS.SecretAccessKey); err != nil { - errs = append(errs, err) - } - } - - if keeper.Spec.Azure != nil { - if keeper.Spec.Azure.KeyVaultName == "" { - errs = append(errs, field.Required(field.NewPath("spec", "azure", "keyVaultName"), "a `keyVaultName` is required")) - } - - if keeper.Spec.Azure.TenantID == "" { - errs = append(errs, field.Required(field.NewPath("spec", "azure", "tenantId"), "a `tenantId` is required")) - } - - if keeper.Spec.Azure.ClientID == "" { - errs = append(errs, field.Required(field.NewPath("spec", "azure", "clientId"), "a `clientId` is required")) - } - - if err := validateCredentialValue(field.NewPath("spec", "azure", "clientSecret"), keeper.Spec.Azure.ClientSecret); err != nil { - errs = append(errs, err) - } - } - - if keeper.Spec.GCP != nil { - if keeper.Spec.GCP.ProjectID == "" { - errs = append(errs, field.Required(field.NewPath("spec", "gcp", "projectId"), "a `projectId` is required")) - } - - if keeper.Spec.GCP.CredentialsFile == "" { - errs = append(errs, field.Required(field.NewPath("spec", "gcp", "credentialsFile"), "a `credentialsFile` is required")) - } - } - - if keeper.Spec.HashiCorp != nil { - if keeper.Spec.HashiCorp.Address == "" { - errs = append(errs, field.Required(field.NewPath("spec", "hashicorp", "address"), "a `address` is required")) - } - - if err := validateCredentialValue(field.NewPath("spec", "hashicorp", "token"), keeper.Spec.HashiCorp.Token); err != nil { - errs = append(errs, err) - } - } - - return errs -} - -func validateKeepers(keeper *secretv0alpha1.Keeper) *field.Error { - availableKeepers := map[string]bool{ - "aws": keeper.Spec.AWS != nil, - "azure": keeper.Spec.Azure != nil, - "gcp": keeper.Spec.GCP != nil, - "hashicorp": keeper.Spec.HashiCorp != nil, - } - - configuredKeepers := make([]string, 0) - - for keeperKind, notNil := range availableKeepers { - if notNil { - configuredKeepers = append(configuredKeepers, keeperKind) - } - } - - if len(configuredKeepers) == 0 { - return field.Required(field.NewPath("spec"), "at least one `keeper` must be present") - } - - if len(configuredKeepers) > 1 { - return field.Invalid( - field.NewPath("spec"), - strings.Join(configuredKeepers, " & "), - "only one `keeper` can be present at a time but found more", - ) - } - - return nil -} - -func validateCredentialValue(path *field.Path, credentials secretv0alpha1.CredentialValue) *field.Error { - availableOptions := map[string]bool{ - "secureValueName": credentials.SecureValueName != "", - "valueFromEnv": credentials.ValueFromEnv != "", - "valueFromConfig": credentials.ValueFromConfig != "", - } - - configuredCredentials := make([]string, 0) - - for credentialKind, notEmpty := range availableOptions { - if notEmpty { - configuredCredentials = append(configuredCredentials, credentialKind) - } - } - - if len(configuredCredentials) == 0 { - return field.Required(path, "one of `secureValueName`, `valueFromEnv` or `valueFromConfig` must be present") - } - - if len(configuredCredentials) > 1 { - return field.Invalid( - path, - strings.Join(configuredCredentials, " & "), - "only one of `secureValueName`, `valueFromEnv` or `valueFromConfig` must be present at a time but found more", - ) - } - - return nil -} diff --git a/pkg/registry/apis/secret/reststorage/keeper_rest_test.go b/pkg/registry/apis/secret/reststorage/keeper_rest_test.go deleted file mode 100644 index 0191b79458d..00000000000 --- a/pkg/registry/apis/secret/reststorage/keeper_rest_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package reststorage - -import ( - "testing" - - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/stretchr/testify/require" - "k8s.io/apiserver/pkg/admission" -) - -func TestValidateKeeper(t *testing.T) { - t.Run("when creating a new keeper", func(t *testing.T) { - t.Run("the `description` must be present", func(t *testing.T) { - keeper := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - AWS: &secretv0alpha1.AWSKeeperConfig{ - AWSCredentials: secretv0alpha1.AWSCredentials{ - AccessKeyID: secretv0alpha1.CredentialValue{ValueFromEnv: "some-value"}, - SecretAccessKey: secretv0alpha1.CredentialValue{ValueFromEnv: "some-value"}, - KMSKeyID: "kms-key-id", - }, - }, - }, - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.description", errs[0].Field) - }) - }) - - t.Run("only one `keeper` must be present", func(t *testing.T) { - keeper := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "short description", - AWS: &secretv0alpha1.AWSKeeperConfig{}, - Azure: &secretv0alpha1.AzureKeeperConfig{}, - GCP: &secretv0alpha1.GCPKeeperConfig{}, - HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{}, - }, - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("at least one `keeper` must be present", func(t *testing.T) { - keeper := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - }, - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("aws keeper validation", func(t *testing.T) { - validKeeperAWS := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - AWS: &secretv0alpha1.AWSKeeperConfig{ - AWSCredentials: secretv0alpha1.AWSCredentials{ - AccessKeyID: secretv0alpha1.CredentialValue{ - ValueFromEnv: "some-value", - }, - SecretAccessKey: secretv0alpha1.CredentialValue{ - SecureValueName: "some-value", - }, - KMSKeyID: "optional", - }, - }, - }, - } - - t.Run("`accessKeyId` must be present", func(t *testing.T) { - t.Run("at least one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAWS.DeepCopy() - keeper.Spec.AWS.AccessKeyID = secretv0alpha1.CredentialValue{} - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.aws.accessKeyId", errs[0].Field) - }) - - t.Run("at most one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAWS.DeepCopy() - keeper.Spec.AWS.AccessKeyID = secretv0alpha1.CredentialValue{ - SecureValueName: "a", - ValueFromEnv: "b", - ValueFromConfig: "c", - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.aws.accessKeyId", errs[0].Field) - }) - }) - - t.Run("`secretAccessKey` must be present", func(t *testing.T) { - t.Run("at least one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAWS.DeepCopy() - keeper.Spec.AWS.SecretAccessKey = secretv0alpha1.CredentialValue{} - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field) - }) - - t.Run("at most one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAWS.DeepCopy() - keeper.Spec.AWS.SecretAccessKey = secretv0alpha1.CredentialValue{ - SecureValueName: "a", - ValueFromEnv: "b", - ValueFromConfig: "c", - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field) - }) - }) - }) - - t.Run("azure keeper validation", func(t *testing.T) { - validKeeperAzure := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - Azure: &secretv0alpha1.AzureKeeperConfig{ - AzureCredentials: secretv0alpha1.AzureCredentials{ - KeyVaultName: "kv-name", - TenantID: "tenant-id", - ClientID: "client-id", - ClientSecret: secretv0alpha1.CredentialValue{ - ValueFromConfig: "config.path.value", - }, - }, - }, - }, - } - - t.Run("`keyVaultName` must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.KeyVaultName = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.keyVaultName", errs[0].Field) - }) - - t.Run("`tenantId` must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.TenantID = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.tenantId", errs[0].Field) - }) - - t.Run("`clientId` must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.ClientID = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.clientId", errs[0].Field) - }) - - t.Run("`clientSecret` must be present", func(t *testing.T) { - t.Run("at least one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.ClientSecret = secretv0alpha1.CredentialValue{} - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.clientSecret", errs[0].Field) - }) - - t.Run("at most one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperAzure.DeepCopy() - keeper.Spec.Azure.ClientSecret = secretv0alpha1.CredentialValue{ - SecureValueName: "a", - ValueFromEnv: "b", - ValueFromConfig: "c", - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.azure.clientSecret", errs[0].Field) - }) - }) - }) - - t.Run("gcp keeper validation", func(t *testing.T) { - validKeeperGCP := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - GCP: &secretv0alpha1.GCPKeeperConfig{ - GCPCredentials: secretv0alpha1.GCPCredentials{ - ProjectID: "project-id", - CredentialsFile: "/path/to/credentials/file.json", - }, - }, - }, - } - - t.Run("`projectId` must be present", func(t *testing.T) { - keeper := validKeeperGCP.DeepCopy() - keeper.Spec.GCP.ProjectID = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.gcp.projectId", errs[0].Field) - }) - - t.Run("`credentialsFile` must be present", func(t *testing.T) { - keeper := validKeeperGCP.DeepCopy() - keeper.Spec.GCP.CredentialsFile = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.gcp.credentialsFile", errs[0].Field) - }) - }) - - t.Run("hashicorp keeper validation", func(t *testing.T) { - validKeeperHashiCorp := &secretv0alpha1.Keeper{ - Spec: secretv0alpha1.KeeperSpec{ - Description: "description", - HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{ - HashiCorpCredentials: secretv0alpha1.HashiCorpCredentials{ - Address: "http://address", - Token: secretv0alpha1.CredentialValue{ - ValueFromConfig: "config.path.value", - }, - }, - }, - }, - } - - t.Run("`address` must be present", func(t *testing.T) { - keeper := validKeeperHashiCorp.DeepCopy() - keeper.Spec.HashiCorp.Address = "" - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.hashicorp.address", errs[0].Field) - }) - - t.Run("`token` must be present", func(t *testing.T) { - t.Run("at least one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperHashiCorp.DeepCopy() - keeper.Spec.HashiCorp.Token = secretv0alpha1.CredentialValue{} - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.hashicorp.token", errs[0].Field) - }) - - t.Run("at most one of the credential value must be present", func(t *testing.T) { - keeper := validKeeperHashiCorp.DeepCopy() - keeper.Spec.HashiCorp.Token = secretv0alpha1.CredentialValue{ - SecureValueName: "a", - ValueFromEnv: "b", - ValueFromConfig: "c", - } - - errs := ValidateKeeper(keeper, admission.Create) - require.Len(t, errs, 1) - require.Equal(t, "spec.hashicorp.token", errs[0].Field) - }) - }) - }) -} diff --git a/pkg/registry/apis/secret/reststorage/secure_value_rest.go b/pkg/registry/apis/secret/reststorage/secure_value_rest.go deleted file mode 100644 index 7ea856509ea..00000000000 --- a/pkg/registry/apis/secret/reststorage/secure_value_rest.go +++ /dev/null @@ -1,379 +0,0 @@ -package reststorage - -import ( - "context" - "errors" - "fmt" - "strconv" - "strings" - - claims "github.com/grafana/authlib/types" - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/admission" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" - - "github.com/grafana/grafana/pkg/apimachinery/utils" - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" - "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" -) - -var ( - _ rest.Scoper = (*SecureValueRest)(nil) - _ rest.SingularNameProvider = (*SecureValueRest)(nil) - _ rest.Getter = (*SecureValueRest)(nil) - _ rest.Lister = (*SecureValueRest)(nil) - _ rest.Storage = (*SecureValueRest)(nil) - _ rest.Creater = (*SecureValueRest)(nil) - _ rest.Updater = (*SecureValueRest)(nil) - _ rest.GracefulDeleter = (*SecureValueRest)(nil) -) - -// SecureValueRest is an implementation of CRUDL operations on a `securevalue` backed by a persistence layer `store`. -type SecureValueRest struct { - storage contracts.SecureValueMetadataStorage - resource utils.ResourceInfo - tableConverter rest.TableConvertor -} - -// NewSecureValueRest is a returns a constructed `*SecureValueRest`. -func NewSecureValueRest(storage contracts.SecureValueMetadataStorage, resource utils.ResourceInfo) *SecureValueRest { - return &SecureValueRest{storage, resource, resource.TableConverter()} -} - -// New returns an empty `*SecureValue` that is used by the `Create` method. -func (s *SecureValueRest) New() runtime.Object { - return s.resource.NewFunc() -} - -// Destroy is called when? [TODO] -func (s *SecureValueRest) Destroy() {} - -// NamespaceScoped returns `true` because the storage is namespaced (== org). -func (s *SecureValueRest) NamespaceScoped() bool { - return true -} - -// GetSingularName is used by `kubectl` discovery to have singular name representation of resources. -func (s *SecureValueRest) GetSingularName() string { - return s.resource.GetSingularName() -} - -// NewList returns an empty `*SecureValueList` that is used by the `List` method. -func (s *SecureValueRest) NewList() runtime.Object { - return s.resource.NewListFunc() -} - -// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`. -func (s *SecureValueRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) -} - -// List calls the inner `store` (persistence) and returns a list of `securevalues` within a `namespace` filtered by the `options`. -func (s *SecureValueRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - secureValueList, err := s.storage.List(ctx, xkube.Namespace(namespace)) - if err != nil { - return nil, fmt.Errorf("failed to list secure values: %w", err) - } - - labelSelector := options.LabelSelector - - if labelSelector == nil { - labelSelector = labels.Everything() - } - - fieldSelector := options.FieldSelector - if fieldSelector == nil { - fieldSelector = fields.Everything() - } - - allowedSecureValues := make([]secretv0alpha1.SecureValue, 0, len(secureValueList)) - - for _, secureValue := range secureValueList { - // Filter by label - if labelSelector.Matches(labels.Set(secureValue.Labels)) { - // Filter by status.phase - if fieldSelector.Matches(fields.Set{"status.phase": string(secureValue.Status.Phase)}) { - allowedSecureValues = append(allowedSecureValues, secureValue) - } - } - } - - return &secretv0alpha1.SecureValueList{Items: allowedSecureValues}, nil -} - -// Get calls the inner `store` (persistence) and returns a `securevalue` by `name`. It will NOT return the decrypted `value`. -func (s *SecureValueRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing namespace") - } - - sv, err := s.storage.Read(ctx, xkube.Namespace(namespace), name, contracts.ReadOpts{}) - if err != nil { - if errors.Is(err, contracts.ErrSecureValueNotFound) { - return nil, s.resource.NewNotFound(name) - } - - return nil, fmt.Errorf("failed to read secure value: %w", err) - } - - return sv, nil -} - -// Create a new `securevalue`. Does some validation and allows empty `name` (generated). -func (s *SecureValueRest) Create( - ctx context.Context, - obj runtime.Object, - createValidation rest.ValidateObjectFunc, - _ *metav1.CreateOptions, -) (runtime.Object, error) { - sv, ok := obj.(*secretv0alpha1.SecureValue) - if !ok { - return nil, fmt.Errorf("expected SecureValue for create") - } - - if err := createValidation(ctx, obj); err != nil { - return nil, err - } - - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, fmt.Errorf("missing auth info in context") - } - - createdSecureValueMetadata, err := s.storage.Create(ctx, sv, user.GetUID()) - if err != nil { - return nil, fmt.Errorf("creating secure value %+w", err) - } - - return createdSecureValueMetadata, nil -} - -// Update a `securevalue`'s `value`. The second return parameter indicates whether the resource was newly created. -// Currently does not support "create on update" functionality. If the securevalue does not yet exist, it returns an error. -func (s *SecureValueRest) Update( - ctx context.Context, - name string, - objInfo rest.UpdatedObjectInfo, - _ rest.ValidateObjectFunc, - updateValidation rest.ValidateObjectUpdateFunc, - _forceAllowCreate bool, - _ *metav1.UpdateOptions, -) (runtime.Object, bool, error) { - oldObj, err := s.Get(ctx, name, &metav1.GetOptions{}) - if err != nil { - return nil, false, err - } - - // Makes sure the UID and ResourceVersion are OK. - // TODO: this also makes it so the labels and annotations are additive, unless we check and remove manually. - newObj, err := objInfo.UpdatedObject(ctx, oldObj) - if err != nil { - return nil, false, fmt.Errorf("k8s updated object: %w", err) - } - - if err := updateValidation(ctx, newObj, oldObj); err != nil { - return nil, false, err - } - - newSecureValue, ok := newObj.(*secretv0alpha1.SecureValue) - if !ok { - return nil, false, fmt.Errorf("expected SecureValue for update") - } - - // TODO: do we need to do this here again? Probably not, but double-check! - newSecureValue.Annotations = xkube.CleanAnnotations(newSecureValue.Annotations) - - user, ok := claims.AuthInfoFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing auth info in context") - } - - // Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that? - updatedSecureValueMetadata, err := s.storage.Update(ctx, newSecureValue, user.GetUID()) - if err != nil { - return updatedSecureValueMetadata, false, fmt.Errorf("updating secure value metadata: %+w", err) - } - - return updatedSecureValueMetadata, false, nil -} - -// The second return parameter `bool` indicates whether the delete was instant or not. It always is for `securevalues`. -func (s *SecureValueRest) Delete(ctx context.Context, name string, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions) (runtime.Object, bool, error) { - namespace, ok := request.NamespaceFrom(ctx) - if !ok { - return nil, false, fmt.Errorf("missing namespace") - } - - if err := s.storage.Delete(ctx, xkube.Namespace(namespace), name); err != nil { - if errors.Is(err, contracts.ErrSecureValueNotFound) { - return nil, false, s.resource.NewNotFound(name) - } - return nil, false, fmt.Errorf("deleting secure value: %+w", err) - } - - return nil, false, nil -} - -// ValidateSecureValue does basic spec validation of a securevalue. -func ValidateSecureValue(sv, oldSv *secretv0alpha1.SecureValue, operation admission.Operation, decryptersAllowList map[string]struct{}) field.ErrorList { - errs := make(field.ErrorList, 0) - - // Operation-specific field validation. - switch operation { - case admission.Create: - errs = validateSecureValueCreate(sv) - - // If we plan to support PATCH-style updates, we shouldn't be requiring fields to be set. - case admission.Update: - errs = validateSecureValueUpdate(sv, oldSv) - - case admission.Delete: - case admission.Connect: - } - - // General validations. - if len(sv.Spec.Value) > contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES { - errs = append( - errs, - field.TooLong(field.NewPath("spec", "value"), len(sv.Spec.Value), contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES), - ) - } - - if errs := validateDecrypters(sv.Spec.Decrypters, decryptersAllowList); len(errs) > 0 { - return errs - } - - return errs -} - -// validateSecureValueCreate does basic spec validation of a securevalue for the Create operation. -func validateSecureValueCreate(sv *secretv0alpha1.SecureValue) field.ErrorList { - errs := make(field.ErrorList, 0) - - if sv.Spec.Description == "" { - errs = append(errs, field.Required(field.NewPath("spec", "description"), "a `description` is required")) - } - - if sv.Spec.Value == "" && (sv.Spec.Ref == nil || (sv.Spec.Ref != nil && *sv.Spec.Ref == "")) { - errs = append(errs, field.Required(field.NewPath("spec"), "either a `value` or `ref` is required")) - } - - if sv.Spec.Value != "" && (sv.Spec.Ref != nil && *sv.Spec.Ref != "") { - errs = append(errs, field.Forbidden(field.NewPath("spec"), "only one of `value` or `ref` can be set")) - } - - return errs -} - -// validateSecureValueUpdate does basic spec validation of a securevalue for the Update operation. -func validateSecureValueUpdate(sv, oldSv *secretv0alpha1.SecureValue) field.ErrorList { - errs := make(field.ErrorList, 0) - - // For updates, an `old` object is required. - if oldSv == nil { - errs = append(errs, field.InternalError(field.NewPath("spec"), errors.New("old object is nil"))) - - return errs - } - - // Only validate if one of the fields is being changed/set. - if sv.Spec.Value != "" || (sv.Spec.Ref != nil && *sv.Spec.Ref != "") { - if (oldSv.Spec.Ref != nil && *oldSv.Spec.Ref != "") && sv.Spec.Value != "" { - errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `value` when `ref` was already previously set")) - } - - if (oldSv.Spec.Ref == nil || (oldSv.Spec.Ref != nil && *oldSv.Spec.Ref == "")) && (sv.Spec.Ref != nil && *sv.Spec.Ref != "") { - errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `ref` when `value` was already previously set")) - } - } - - // Keeper cannot be changed. - if sv.Spec.Keeper != oldSv.Spec.Keeper { - errs = append(errs, field.Forbidden(field.NewPath("spec"), "the `keeper` cannot be changed")) - } - - return errs -} - -// validateDecrypters validates that (if populated) the `decrypters` must be unique. -func validateDecrypters(decrypters []string, decryptersAllowList map[string]struct{}) field.ErrorList { - errs := make(field.ErrorList, 0) - - // Limit the number of decrypters to 64 to not have it unbounded. - // The number was chosen arbitrarily and should be enough. - if len(decrypters) > 64 { - errs = append( - errs, - field.TooMany(field.NewPath("spec", "decrypters"), len(decrypters), 64), - ) - - return errs - } - - decrypterNames := make(map[string]struct{}, 0) - - for i, decrypter := range decrypters { - decrypter = strings.TrimSpace(decrypter) - if decrypter == "" { - errs = append( - errs, - field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "decrypters cannot be empty if specified"), - ) - - continue - } - - // Allow List: decrypters must match exactly and be in the allowed list to be able to decrypt. - if len(decryptersAllowList) > 0 { - if _, exists := decryptersAllowList[decrypter]; !exists { - errs = append( - errs, - field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, fmt.Sprintf("allowed values: %v", decryptersAllowList)), - ) - - return errs - } - - continue - } - - // Use the same validation as labels for the decrypters. - if verrs := validation.IsValidLabelValue(decrypter); len(verrs) > 0 { - for _, verr := range verrs { - errs = append( - errs, - field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, verr), - ) - } - - continue - } - - if _, exists := decrypterNames[decrypter]; exists { - errs = append( - errs, - field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "decrypters must be unique"), - ) - - continue - } - - decrypterNames[decrypter] = struct{}{} - } - - return errs -} diff --git a/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go b/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go deleted file mode 100644 index 1656ca7a1fc..00000000000 --- a/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go +++ /dev/null @@ -1,308 +0,0 @@ -package reststorage - -import ( - "fmt" - "maps" - "slices" - "strings" - "testing" - - "github.com/stretchr/testify/require" - "k8s.io/apiserver/pkg/admission" - - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" -) - -func TestValidateSecureValue(t *testing.T) { - t.Run("when creating a new securevalue", func(t *testing.T) { - keeper := "keeper" - validSecureValue := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", - Value: "value", - Keeper: &keeper, - Decrypters: []string{"app1", "app2"}, - }, - } - - t.Run("the `description` must be present", func(t *testing.T) { - sv := validSecureValue.DeepCopy() - sv.Spec.Description = "" - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec.description", errs[0].Field) - }) - - t.Run("either a `value` or `ref` must be present but not both", func(t *testing.T) { - sv := validSecureValue.DeepCopy() - sv.Spec.Value = "" - sv.Spec.Ref = nil - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - - ref := "value" - sv.Spec.Value = "value" - sv.Spec.Ref = &ref - - errs = ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("`value` cannot exceed 24576 bytes", func(t *testing.T) { - sv := validSecureValue.DeepCopy() - sv.Spec.Value = secretv0alpha1.NewExposedSecureValue(strings.Repeat("a", contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES+1)) - sv.Spec.Ref = nil - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec.value", errs[0].Field) - }) - }) - - t.Run("when updating a securevalue", func(t *testing.T) { - t.Run("when trying to switch from a `value` (old) to a `ref` (new), it returns an error", func(t *testing.T) { - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Ref: nil, // empty `ref` means a `value` was present. - }, - } - - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Ref: &ref, - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("when trying to switch from a `ref` (old) to a `value` (new), it returns an error", func(t *testing.T) { - ref := "non-empty" - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Ref: &ref, - }, - } - - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Value: "value", - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("when both `value` and `ref` are set, it returns an error", func(t *testing.T) { - refNonEmpty := "non-empty" - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Ref: &refNonEmpty, - }, - } - - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Value: "value", - Ref: &ref, - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - - oldSv = &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Value: "non-empty", - }, - } - - errs = ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("when no changes are made, it returns no errors", func(t *testing.T) { - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "old-description", - }, - } - - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "new-description", - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Empty(t, errs) - }) - - t.Run("when the old object is `nil` it returns an error", func(t *testing.T) { - sv := &secretv0alpha1.SecureValue{} - - errs := ValidateSecureValue(sv, nil, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - - t.Run("when trying to change the `keeper`, it returns an error", func(t *testing.T) { - keeperA := "a-keeper" - keeperAnother := "another-keeper" - oldSv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Keeper: &keeperA, - }, - } - - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Keeper: &keeperAnother, - }, - } - - errs := ValidateSecureValue(sv, oldSv, admission.Update, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec", errs[0].Field) - }) - }) - - t.Run("`decrypters` must have unique items", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: []string{ - "app1", - "app1", - }, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec.decrypters.[1]", errs[0].Field) - }) - - t.Run("when set, the `decrypters` must be one of the allowed in the allow list", func(t *testing.T) { - allowList := map[string]struct{}{"app1": {}, "app2": {}} - decrypters := slices.Collect(maps.Keys(allowList)) - - t.Run("no matches, returns an error", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: []string{"app3"}, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, allowList) - require.Len(t, errs, 1) - }) - - t.Run("no decrypters, returns no error", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: []string{}, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, allowList) - require.Empty(t, errs) - }) - - t.Run("one match, returns no errors", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: []string{decrypters[0]}, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, allowList) - require.Empty(t, errs) - }) - - t.Run("all matches, returns no errors", func(t *testing.T) { - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: decrypters, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, allowList) - require.Empty(t, errs) - }) - }) - - t.Run("`decrypters` must be a valid label value", func(t *testing.T) { - decrypters := []string{ - "", // invalid - "is/this/valid", // invalid - "is this valid", // invalid - "is.this.valid", - "is-this-valid", - "is_this_valid", - "0isthisvalid9", - "isthisvalid9", - "0isthisvalid", - "isthisvalid", - } - - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: decrypters, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 3) - }) - - t.Run("`decrypters` cannot have more than 64 items", func(t *testing.T) { - decrypters := make([]string, 0, 64+1) - for i := 0; i < 64+1; i++ { - decrypters = append(decrypters, fmt.Sprintf("app%d", i)) - } - - ref := "ref" - sv := &secretv0alpha1.SecureValue{ - Spec: secretv0alpha1.SecureValueSpec{ - Description: "description", Ref: &ref, - - Decrypters: decrypters, - }, - } - - errs := ValidateSecureValue(sv, nil, admission.Create, nil) - require.Len(t, errs, 1) - require.Equal(t, "spec.decrypters", errs[0].Field) - }) -} diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index 7f8c1497111..ed7d0aa1c5e 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -46,6 +46,9 @@ var WireSet = wire.NewSet( wire.Bind(new(datasource.PluginContextWrapper), new(*plugincontext.Provider)), datasource.ProvideDefaultPluginConfigs, + // Secrets + secret.RegisterDependencies, + // Each must be added here *and* in the ServiceSink above dashboardinternal.RegisterAPIService, dashboardsnapshot.RegisterAPIService, @@ -58,7 +61,6 @@ var WireSet = wire.NewSet( provisioning.RegisterAPIService, service.RegisterAPIService, query.RegisterAPIService, - secret.RegisterAPIService, userstorage.RegisterAPIService, ofrep.RegisterAPIService, ) diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index efa495e511f..5657b0e4c14 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -732,20 +732,6 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser return nil, err } userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer) - databaseDatabase := database5.ProvideDatabase(sqlStore) - secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, featureToggles) - if err != nil { - return nil, err - } - keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(databaseDatabase, featureToggles) - if err != nil { - return nil, err - } - secretDBMigrator := migrator2.NewWithEngine(sqlStore) - secretAPIBuilder, err := secret.RegisterAPIService(featureToggles, cfg, apiserverService, tracingService, secureValueMetadataStorage, keeperMetadataStorage, accessClient, acimplService, secretDBMigrator) - if err != nil { - return nil, err - } factory := github.ProvideFactory() legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl) webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, secretsService, factory, renderingService, resourceClient, eventualRestConfigProvider) @@ -759,7 +745,12 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser return nil, err } ofrepAPIBuilder := ofrep.RegisterAPIService(apiserverService, cfg, staticFlagEvaluator) - apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, secretAPIBuilder, apiBuilder, ofrepAPIBuilder) + secretDBMigrator := migrator2.NewWithEngine(sqlStore) + dependencyRegisterer, err := secret.RegisterDependencies(featureToggles, cfg, secretDBMigrator, acimplService) + if err != nil { + return nil, err + } + apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, ofrepAPIBuilder, dependencyRegisterer) teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService) if err != nil { return nil, err @@ -1254,20 +1245,6 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { return nil, err } userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer) - databaseDatabase := database5.ProvideDatabase(sqlStore) - secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, featureToggles) - if err != nil { - return nil, err - } - keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(databaseDatabase, featureToggles) - if err != nil { - return nil, err - } - secretDBMigrator := migrator2.NewWithEngine(sqlStore) - secretAPIBuilder, err := secret.RegisterAPIService(featureToggles, cfg, apiserverService, tracingService, secureValueMetadataStorage, keeperMetadataStorage, accessClient, acimplService, secretDBMigrator) - if err != nil { - return nil, err - } factory := github.ProvideFactory() legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl) webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, secretsService, factory, renderingService, resourceClient, eventualRestConfigProvider) @@ -1281,7 +1258,12 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { return nil, err } ofrepAPIBuilder := ofrep.RegisterAPIService(apiserverService, cfg, staticFlagEvaluator) - apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, secretAPIBuilder, apiBuilder, ofrepAPIBuilder) + secretDBMigrator := migrator2.NewWithEngine(sqlStore) + dependencyRegisterer, err := secret.RegisterDependencies(featureToggles, cfg, secretDBMigrator, acimplService) + if err != nil { + return nil, err + } + apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, ofrepAPIBuilder, dependencyRegisterer) teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService) if err != nil { return nil, err diff --git a/pkg/tests/apis/secret/keeper_test.go b/pkg/tests/apis/secret/keeper_test.go deleted file mode 100644 index c81a8544ba7..00000000000 --- a/pkg/tests/apis/secret/keeper_test.go +++ /dev/null @@ -1,567 +0,0 @@ -package secret - -import ( - "context" - "errors" - "math/rand/v2" - "net/http" - "strconv" - "strings" - "testing" - - secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/tests/apis" - "github.com/grafana/grafana/pkg/tests/testinfra" - "github.com/stretchr/testify/require" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -var gvrKeepers = schema.GroupVersionResource{ - Group: secretv0alpha1.GROUP, - Version: secretv0alpha1.VERSION, - Resource: secretv0alpha1.KeeperResourceInfo.GetName(), -} - -func TestIntegrationKeeper(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for experimental APIs - EnableFeatureToggles: []string{ - // Required to start the example service - featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, - featuremgmt.FlagSecretsManagementAppPlatform, - }, - }) - - permissions := map[string]ResourcePermission{ResourceKeepers: {Actions: ActionsAllKeepers}} - - genericUserEditor := mustCreateUsers(t, helper, permissions).Editor - - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: genericUserEditor, - GVR: gvrKeepers, - }) - - t.Run("reading a keeper that does not exist returns a 404", func(t *testing.T) { - raw, err := client.Resource.Get(ctx, "some-keeper-that-does-not-exist", metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, raw) - - var statusErr *apierrors.StatusError - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, "keeper.secret.grafana.app \"some-keeper-that-does-not-exist\" not found", err.Error()) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - t.Run("deleting a keeper that does not exist returns an error", func(t *testing.T) { - err := client.Resource.Delete(ctx, "some-keeper-that-does-not-exist", metav1.DeleteOptions{}) - require.Error(t, err) - - var statusErr *apierrors.StatusError - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, "keeper.secret.grafana.app \"some-keeper-that-does-not-exist\" not found", err.Error()) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - t.Run("creating a keeper returns it", func(t *testing.T) { - raw := mustGenerateKeeper(t, helper, genericUserEditor, nil, "testdata/keeper-aws-generate.yaml") - - keeper := new(secretv0alpha1.Keeper) - err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, keeper) - require.NoError(t, err) - require.NotNil(t, keeper) - - require.NotEmpty(t, keeper.Spec.Description) - require.NotEmpty(t, keeper.Spec.AWS) - require.Empty(t, keeper.Spec.Azure) - - t.Run("and creating another keeper with the same name in the same namespace returns an error", func(t *testing.T) { - testKeeper := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") - testKeeper.SetName(raw.GetName()) - - raw, err := client.Resource.Create(ctx, testKeeper, metav1.CreateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - }) - - t.Run("and reading th keeper returns it same as if when it was created", func(t *testing.T) { - raw, err := client.Resource.Get(ctx, keeper.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - anotherKeeper := new(secretv0alpha1.Keeper) - err = runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, anotherKeeper) - require.NoError(t, err) - require.NotNil(t, anotherKeeper) - - require.EqualValues(t, keeper, anotherKeeper) - }) - - t.Run("and listing keepers returns the created keeper", func(t *testing.T) { - rawList, err := client.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, rawList) - require.GreaterOrEqual(t, len(rawList.Items), 1) - require.Equal(t, keeper.Name, rawList.Items[0].GetName()) - }) - - t.Run("and updating the keeper replaces the spec fields and returns them", func(t *testing.T) { - newRaw := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") - newRaw.SetName(raw.GetName()) - newRaw.Object["spec"].(map[string]any)["description"] = "New description" - newRaw.Object["metadata"].(map[string]any)["annotations"] = map[string]any{"newAnnotation": "newValue"} - - updatedRaw, err := client.Resource.Update(ctx, newRaw, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, updatedRaw) - - updatedKeeper := new(secretv0alpha1.Keeper) - err = runtime.DefaultUnstructuredConverter.FromUnstructured(updatedRaw.Object, updatedKeeper) - require.NoError(t, err) - require.NotNil(t, updatedKeeper) - - require.NotEqualValues(t, updatedKeeper.Spec, keeper.Spec) - }) - - t.Run("and updating the keeper to reference securevalues that does not exist returns an error", func(t *testing.T) { - newRaw := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - newRaw.SetName(raw.GetName()) - newRaw.Object["spec"].(map[string]any)["aws"] = map[string]any{ - "accessKeyId": map[string]any{ - "secureValueName": "securevalue-does-not-exist-1", - }, - "secretAccessKey": map[string]any{ - "secureValueName": "securevalue-does-not-exist-2", - }, - } - - updatedRaw, err := client.Resource.Update(ctx, newRaw, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, updatedRaw) - require.Contains(t, err.Error(), "securevalue-does-not-exist-1") - require.Contains(t, err.Error(), "securevalue-does-not-exist-2") - }) - }) - - t.Run("creating an invalid keeper fails validation and returns an error", func(t *testing.T) { - testData := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.Object["spec"].(map[string]any)["description"] = "" - - raw, err := client.Resource.Create(ctx, testData, metav1.CreateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - - var statusErr *apierrors.StatusError - require.True(t, errors.As(err, &statusErr)) - }) - - t.Run("creating a keeper with a provider then changing the provider does not return an error", func(t *testing.T) { - rawAWS := mustGenerateKeeper(t, helper, genericUserEditor, nil, "testdata/keeper-aws-generate.yaml") - - testDataKeeperGCP := rawAWS.DeepCopy() - testDataKeeperGCP.Object["spec"].(map[string]any)["aws"] = nil - testDataKeeperGCP.Object["spec"].(map[string]any)["gcp"] = map[string]any{ - "projectId": "project-id", - "credentialsFile": "/path/to/file.json", - } - - rawGCP, err := client.Resource.Update(ctx, testDataKeeperGCP, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawGCP) - - require.NotEqualValues(t, rawAWS.Object["spec"], rawGCP.Object["spec"]) - }) - - t.Run("creating a keeper that references securevalues that does not exist returns an error", func(t *testing.T) { - testDataKeeper := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testDataKeeper.Object["spec"].(map[string]any)["aws"] = map[string]any{ - "accessKeyId": map[string]any{ - "secureValueName": "securevalue-does-not-exist-1", - }, - "secretAccessKey": map[string]any{ - "secureValueName": "securevalue-does-not-exist-2", - }, - } - - raw, err := client.Resource.Create(ctx, testDataKeeper, metav1.CreateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.Contains(t, err.Error(), "securevalue-does-not-exist-1") - require.Contains(t, err.Error(), "securevalue-does-not-exist-2") - }) - - t.Run("deleting a keeper that exists does not return an error", func(t *testing.T) { - generatePrefix := "generated-" - - testData := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetGenerateName(generatePrefix) - - raw, err := client.Resource.Create(ctx, testData, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - name := raw.GetName() - require.True(t, strings.HasPrefix(name, generatePrefix)) - - err = client.Resource.Delete(ctx, name, metav1.DeleteOptions{}) - require.NoError(t, err) - - t.Run("and then trying to read it returns a 404 error", func(t *testing.T) { - raw, err := client.Resource.Get(ctx, name, metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, raw) - - var statusErr *apierrors.StatusError - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - t.Run("and listing keepers returns an empty list", func(t *testing.T) { - rawList, err := client.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, rawList) - require.Empty(t, rawList.Items) - }) - }) - - t.Run("creating keepers in multiple namespaces", func(t *testing.T) { - permissions := map[string]ResourcePermission{ - ResourceKeepers: {Actions: ActionsAllKeepers}, - } - - editorOrgA := mustCreateUsers(t, helper, permissions).Editor - editorOrgB := mustCreateUsers(t, helper, permissions).Editor - - keeperOrgA := mustGenerateKeeper(t, helper, editorOrgA, nil, "testdata/keeper-aws-generate.yaml") - keeperOrgB := mustGenerateKeeper(t, helper, editorOrgB, nil, "testdata/keeper-aws-generate.yaml") - - clientOrgA := helper.GetResourceClient(apis.ResourceClientArgs{User: editorOrgA, GVR: gvrKeepers}) - clientOrgB := helper.GetResourceClient(apis.ResourceClientArgs{User: editorOrgB, GVR: gvrKeepers}) - - // Create - t.Run("creating a keeper with the same name as one from another namespace does not return an error", func(t *testing.T) { - // OrgA creating a keeper with the same name from OrgB. - testData := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetName(keeperOrgB.GetName()) - - raw, err := clientOrgA.Resource.Create(ctx, testData, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - // OrgA creating a keeper with the same name from OrgB. - testData = helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetName(keeperOrgA.GetName()) - - raw, err = clientOrgB.Resource.Create(ctx, testData, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - require.NoError(t, clientOrgA.Resource.Delete(ctx, keeperOrgB.GetName(), metav1.DeleteOptions{})) - require.NoError(t, clientOrgB.Resource.Delete(ctx, keeperOrgA.GetName(), metav1.DeleteOptions{})) - }) - - // Read - t.Run("fetching a keeper from another namespace returns not found", func(t *testing.T) { - var statusErr *apierrors.StatusError - - // OrgA trying to fetch keeper from OrgB. - raw, err := clientOrgA.Resource.Get(ctx, keeperOrgB.GetName(), metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - - // OrgB trying to fetch keeper from OrgA. - raw, err = clientOrgB.Resource.Get(ctx, keeperOrgA.GetName(), metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - // Update - t.Run("updating a keeper from another namespace returns not found", func(t *testing.T) { - var statusErr *apierrors.StatusError - - // OrgA trying to update securevalue from OrgB. - testData := helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetName(keeperOrgB.GetName()) - testData.Object["spec"].(map[string]any)["description"] = "New description" - - raw, err := clientOrgA.Resource.Update(ctx, testData, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - - // OrgB trying to update keeper from OrgA. - testData = helper.LoadYAMLOrJSONFile("testdata/keeper-aws-generate.yaml") - testData.SetName(keeperOrgA.GetName()) - testData.Object["spec"].(map[string]any)["description"] = "New description" - - raw, err = clientOrgB.Resource.Update(ctx, testData, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, raw) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - }) - - // Delete - t.Run("deleting a keeper from another namespace returns an error and does not delete it", func(t *testing.T) { - var statusErr *apierrors.StatusError - - // OrgA trying to delete keeper from OrgB. - err := clientOrgA.Resource.Delete(ctx, keeperOrgB.GetName(), metav1.DeleteOptions{}) - require.Error(t, err) - require.True(t, errors.As(err, &statusErr)) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - - // Check that it still exists from the perspective of OrgB. - raw, err := clientOrgB.Resource.Get(ctx, keeperOrgB.GetName(), metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - // OrgB trying to delete keeper from OrgA. - err = clientOrgB.Resource.Delete(ctx, keeperOrgA.GetName(), metav1.DeleteOptions{}) - require.Error(t, err) - require.Equal(t, http.StatusNotFound, int(statusErr.Status().Code)) - - // Check that it still exists from the perspective of OrgA. - raw, err = clientOrgA.Resource.Get(ctx, keeperOrgA.GetName(), metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - }) - - // List - t.Run("listing keeper from a namespace does not return the ones from another namespace", func(t *testing.T) { - // OrgA listing keeper. - listOrgA, err := clientOrgA.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, listOrgA) - require.Len(t, listOrgA.Items, 1) - require.Equal(t, *keeperOrgA, listOrgA.Items[0]) - - // OrgB listing keeper. - listOrgB, err := clientOrgB.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, listOrgB) - require.Len(t, listOrgB.Items, 1) - require.Equal(t, *keeperOrgB, listOrgB.Items[0]) - }) - }) - - t.Run("keeper actions without having required permissions", func(t *testing.T) { - // Create users on a random org without specifying secrets-related permissions. - editorP := mustCreateUsers(t, helper, nil).Editor - - clientP := helper.GetResourceClient(apis.ResourceClientArgs{ - User: editorP, - GVR: gvrKeepers, - }) - - // GET - rawGet, err := clientP.Resource.Get(ctx, "some-keeper", metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, rawGet) - - var statusGetErr *apierrors.StatusError - require.True(t, errors.As(err, &statusGetErr)) - require.EqualValues(t, http.StatusForbidden, statusGetErr.Status().Code) - - // LIST - rawList, err := clientP.Resource.List(ctx, metav1.ListOptions{}) - require.Error(t, err) - require.Nil(t, rawList) - - var statusListErr *apierrors.StatusError - require.True(t, errors.As(err, &statusListErr)) - require.EqualValues(t, http.StatusForbidden, statusListErr.Status().Code) - - // CREATE - testKeeper := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") // to pass validation before authz. - rawCreate, err := clientP.Resource.Create(ctx, testKeeper, metav1.CreateOptions{}) - require.Error(t, err) - require.Nil(t, rawCreate) - - var statusCreateErr *apierrors.StatusError - require.True(t, errors.As(err, &statusCreateErr)) - require.EqualValues(t, http.StatusForbidden, statusCreateErr.Status().Code) - - // UPDATE - testKeeper.SetName("test") // to pass validation before authz. - rawUpdate, err := clientP.Resource.Update(ctx, testKeeper, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, rawUpdate) - - var statusUpdateErr *apierrors.StatusError - require.True(t, errors.As(err, &statusUpdateErr)) - require.EqualValues(t, http.StatusForbidden, statusUpdateErr.Status().Code) - - // DELETE - err = clientP.Resource.Delete(ctx, "some-keeper", metav1.DeleteOptions{}) - require.Error(t, err) - - var statusDeleteErr *apierrors.StatusError - require.True(t, errors.As(err, &statusDeleteErr)) - require.EqualValues(t, http.StatusForbidden, statusDeleteErr.Status().Code) - }) - - t.Run("keeper actions with permissions but with limited scope", func(t *testing.T) { - suffix := strconv.FormatInt(rand.Int64(), 10) - - // Fix the Keeper names. - keeperName := "kp-" + suffix - testKeeper := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") - testKeeper.SetName(keeperName) - - keeperNameAnother := "kp-another-" + suffix - testKeeperAnother := helper.LoadYAMLOrJSONFile("testdata/keeper-gcp-generate.yaml") - testKeeperAnother.SetName(keeperNameAnother) - - // Fix the org ID because we will create another user with scope "all" permissions on the same org, to compare. - orgID := rand.Int64() + 2 - - // Permissions which allow any action, but scoped actions (get, update, delete) only on `keeperName` and NO OTHER Keeper. - scopedLimitedPermissions := map[string]ResourcePermission{ - ResourceKeepers: { - Actions: ActionsAllKeepers, - Name: keeperName, - }, - } - - // Create users (+ client) with permission to manage ONLY the Keeper `keeperName`. - editorLimited := mustCreateUsersWithOrg(t, helper, orgID, scopedLimitedPermissions).Editor - - clientScopedLimited := helper.GetResourceClient(apis.ResourceClientArgs{ - User: editorLimited, - GVR: gvrKeepers, - }) - - // Create users (+ client) with permission to manage ANY Keepers. - scopedAllPermissions := map[string]ResourcePermission{ - ResourceKeepers: { - Actions: ActionsAllKeepers, - Name: "*", // this or not sending a `Name` have the same effect. - }, - } - - editorAll := mustCreateUsersWithOrg(t, helper, orgID, scopedAllPermissions).Editor - - clientScopedAll := helper.GetResourceClient(apis.ResourceClientArgs{ - User: editorAll, - GVR: gvrKeepers, - }) - - // For create, we don't have actual granular permissions, so we can use any client that has unscoped create permissions. - // This is because when we don't know yet what the name of the resource will be, the request comes with an empty value. - // And thus the authorizer can't do granular checks. - t.Run("CREATE", func(t *testing.T) { - rawCreateLimited, err := clientScopedAll.Resource.Create(ctx, testKeeper, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawCreateLimited) - - rawCreateLimited, err = clientScopedAll.Resource.Create(ctx, testKeeperAnother, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawCreateLimited) - }) - - t.Run("READ", func(t *testing.T) { - // Retrieve `keeperName` from the limited client. - rawGetLimited, err := clientScopedLimited.Resource.Get(ctx, keeperName, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, rawGetLimited) - require.Equal(t, rawGetLimited.GetUID(), rawGetLimited.GetUID()) - - // Retrieve `keeperName` from the scope-all client. - rawGetAll, err := clientScopedAll.Resource.Get(ctx, keeperName, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, rawGetAll) - require.Equal(t, rawGetAll.GetUID(), rawGetLimited.GetUID()) - - // Even though we can create it, we cannot retrieve `keeperNameAnother` from the limited client. - rawGetLimited, err = clientScopedLimited.Resource.Get(ctx, keeperNameAnother, metav1.GetOptions{}) - require.Error(t, err) - require.Nil(t, rawGetLimited) - - var statusGetErr *apierrors.StatusError - require.True(t, errors.As(err, &statusGetErr)) - require.EqualValues(t, http.StatusForbidden, statusGetErr.Status().Code) - - // Retrieve `keeperNameAnother` from the scope-all client. - rawGetAll, err = clientScopedAll.Resource.Get(ctx, keeperNameAnother, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, rawGetAll) - }) - - t.Run("LIST", func(t *testing.T) { - // List Keepers from the limited client should return only 1. - rawList, err := clientScopedLimited.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, rawList) - require.Len(t, rawList.Items, 1) - require.Equal(t, keeperName, rawList.Items[0].GetName()) - - // List Keepers from the scope-all client should return all of them. - rawList, err = clientScopedAll.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, rawList) - require.Len(t, rawList.Items, 2) - }) - - t.Run("UPDATE", func(t *testing.T) { - // Update `keeperName` from the limited client. - testKeeperUpdate := testKeeper.DeepCopy() - testKeeperUpdate.Object["spec"].(map[string]any)["description"] = "keeper-description-1234" - - rawUpdate, err := clientScopedLimited.Resource.Update(ctx, testKeeperUpdate, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawUpdate) - - // Try to update `keeperNameAnother` from the limited client. - testKeeperAnotherUpdate := testKeeperAnother.DeepCopy() - testKeeperAnotherUpdate.Object["spec"].(map[string]any)["description"] = "keeper-description-5678" - - rawUpdate, err = clientScopedLimited.Resource.Update(ctx, testKeeperAnotherUpdate, metav1.UpdateOptions{}) - require.Error(t, err) - require.Nil(t, rawUpdate) - - var statusUpdateErr *apierrors.StatusError - require.True(t, errors.As(err, &statusUpdateErr)) - require.EqualValues(t, http.StatusForbidden, statusUpdateErr.Status().Code) - - // Update `keeperNameAnother` from the scope-all client. - rawUpdate, err = clientScopedAll.Resource.Update(ctx, testKeeperAnotherUpdate, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, rawUpdate) - }) - - // Keep this last for cleaning up the resources. - t.Run("DELETE", func(t *testing.T) { - // Try to delete `keeperNameAnother` from the limited client. - err := clientScopedLimited.Resource.Delete(ctx, keeperNameAnother, metav1.DeleteOptions{}) - require.Error(t, err) - - var statusDeleteErr *apierrors.StatusError - require.True(t, errors.As(err, &statusDeleteErr)) - require.EqualValues(t, http.StatusForbidden, statusDeleteErr.Status().Code) - - // Delete `keeperNameAnother` from the scope-all client. - err = clientScopedAll.Resource.Delete(ctx, keeperNameAnother, metav1.DeleteOptions{}) - require.NoError(t, err) - - // Delete `keeperName` from the limited client. - err = clientScopedLimited.Resource.Delete(ctx, keeperName, metav1.DeleteOptions{}) - require.NoError(t, err) - }) - }) -} diff --git a/pkg/tests/apis/secret/main_test.go b/pkg/tests/apis/secret/main_test.go deleted file mode 100644 index 20169fdbaa2..00000000000 --- a/pkg/tests/apis/secret/main_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package secret - -import ( - "cmp" - "context" - "encoding/json" - "math/rand/v2" - "strconv" - "testing" - - "github.com/grafana/grafana/pkg/registry/apis/secret" - "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" - "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/org" - "github.com/grafana/grafana/pkg/services/team" - "github.com/grafana/grafana/pkg/tests/apis" - "github.com/grafana/grafana/pkg/tests/testinfra" - "github.com/grafana/grafana/pkg/tests/testsuite" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -var ( - ResourceSecureValues = "secret.securevalues" - ResourceKeepers = "secret.keepers" - - ActionsAllKeepers = []string{ - secret.ActionSecretKeepersCreate, - secret.ActionSecretKeepersWrite, - secret.ActionSecretKeepersRead, - secret.ActionSecretKeepersDelete, - } - ActionsAllSecureValues = []string{ - secret.ActionSecretSecureValuesCreate, - secret.ActionSecretSecureValuesWrite, - secret.ActionSecretSecureValuesRead, - secret.ActionSecretSecureValuesDelete, - } -) - -type ResourcePermission struct { - Actions []string - Name string // empty or "*" for all -} - -func TestMain(m *testing.M) { - testsuite.Run(m) -} - -func TestIntegrationDiscoveryClient(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for experimental APIs - EnableFeatureToggles: []string{ - // Required to start the example service - featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, - featuremgmt.FlagSecretsManagementAppPlatform, - }, - }) - - t.Run("check discovery client", func(t *testing.T) { - disco := helper.NewDiscoveryClient() - - resources, err := disco.ServerResourcesForGroupVersion("secret.grafana.app/v0alpha1") - require.NoError(t, err) - - v1Disco, err := json.MarshalIndent(resources, "", " ") - require.NoError(t, err) - - var apiResourceList map[string]any - require.NoError(t, json.Unmarshal(v1Disco, &apiResourceList)) - - groupVersion, ok := apiResourceList["groupVersion"].(string) - require.True(t, ok) - require.Equal(t, "secret.grafana.app/v0alpha1", groupVersion) - - apiResources, ok := apiResourceList["resources"].([]any) - require.True(t, ok) - require.Len(t, apiResources, 2) // securevalue + keeper + (subresources...) - }) -} - -func mustCreateUsersWithOrg(t *testing.T, helper *apis.K8sTestHelper, orgID int64, permissionMap map[string]ResourcePermission) apis.OrgUsers { - t.Helper() - - permissions := make([]resourcepermissions.SetResourcePermissionCommand, 0, len(permissionMap)) - for resource, permission := range permissionMap { - permissions = append(permissions, resourcepermissions.SetResourcePermissionCommand{ - Actions: permission.Actions, - Resource: resource, - ResourceAttribute: "uid", - ResourceID: cmp.Or(permission.Name, "*"), - }) - } - - orgName := "org-" + strconv.FormatInt(orgID, 10) - - userSuffix := strconv.FormatInt(rand.Int64(), 10) - - // Add here admin or viewer if necessary. - editor := helper.CreateUser("editor-"+userSuffix, orgName, org.RoleEditor, permissions) - - staff := helper.CreateTeam("staff-"+userSuffix, "staff-"+userSuffix+"@"+orgName, editor.Identity.GetOrgID()) - - // Also call this method for each new user. - helper.AddOrUpdateTeamMember(editor, staff.ID, team.PermissionTypeMember) - - return apis.OrgUsers{ - Editor: editor, - Staff: staff, - } -} - -func mustCreateUsers(t *testing.T, helper *apis.K8sTestHelper, permissionMap map[string]ResourcePermission) apis.OrgUsers { - orgID := rand.Int64() + 2 // if it is 0, becomes 2 and not 1. - return mustCreateUsersWithOrg(t, helper, orgID, permissionMap) -} - -func mustGenerateKeeper(t *testing.T, helper *apis.K8sTestHelper, user apis.User, specType map[string]any, testFile string) *unstructured.Unstructured { - t.Helper() - - require.NotEmpty(t, testFile, "testFile must not be empty") - - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - keeperClient := helper.GetResourceClient(apis.ResourceClientArgs{ - User: user, - GVR: gvrKeepers, - }) - - testKeeper := helper.LoadYAMLOrJSONFile(testFile) - if specType != nil { - testKeeper.Object["spec"] = specType - } - - raw, err := keeperClient.Resource.Create(ctx, testKeeper, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, raw) - - t.Cleanup(func() { - require.NoError(t, keeperClient.Resource.Delete(ctx, raw.GetName(), metav1.DeleteOptions{})) - }) - - return raw -} diff --git a/pkg/tests/apis/secret/testdata/keeper-aws-generate.yaml b/pkg/tests/apis/secret/testdata/keeper-aws-generate.yaml deleted file mode 100644 index c210ab66838..00000000000 --- a/pkg/tests/apis/secret/testdata/keeper-aws-generate.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: secret.grafana.app/v0alpha1 -kind: Keeper -metadata: - annotations: - xx: XXX - labels: - aa: AAA -spec: - description: AWS XYZ value - aws: - accessKeyId: - valueFromEnv: ACCESS_KEY_ID_XYZ - secretAccessKey: - valueFromEnv: SECRET_ACCESS_KEY_XYZ - kmsKeyId: kmsKeyId-xyz diff --git a/pkg/tests/apis/secret/testdata/keeper-gcp-generate.yaml b/pkg/tests/apis/secret/testdata/keeper-gcp-generate.yaml deleted file mode 100644 index 485a4cb2ea5..00000000000 --- a/pkg/tests/apis/secret/testdata/keeper-gcp-generate.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: secret.grafana.app/v0alpha1 -kind: Keeper -metadata: - annotations: - xx: XXX - yy: YYY - labels: - aa: AAA - bb: BBB -spec: - description: GCP XYZ value - gcp: - projectId: project-id - credentialsFile: /path/to/file.json diff --git a/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml b/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml deleted file mode 100644 index dec2cd8611b..00000000000 --- a/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: secret.grafana.app/v0alpha1 -kind: SecureValue -metadata: - annotations: - xx: XXX - yy: YYY - labels: - aa: AAA - bb: BBB -spec: - description: This is a secret - value: this is super duper secure - decrypters: - - k6 - - synthetic-monitoring diff --git a/pkg/tests/apis/secret/testdata/secure-value-generate.yaml b/pkg/tests/apis/secret/testdata/secure-value-generate.yaml deleted file mode 100644 index 158052350c9..00000000000 --- a/pkg/tests/apis/secret/testdata/secure-value-generate.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: secret.grafana.app/v0alpha1 -kind: SecureValue -metadata: - annotations: - xx: XXX - yy: YYY - labels: - aa: AAA - bb: BBB -spec: - description: XYZ value - keeper: my-keeper-1 - value: super duper secure - decrypters: - - k6 - - synthetic-monitoring