Secrets: add better error handling for secret plugin failures when updating datasources (#50542)

* Add protobuf config and generated code, and client wrapper

* wire up loading of secretsmanager plugin, using renderer plugin as a model

* update kvstore provider to check if we should use the grpc plugin. return false always in OSS

* add OSS remote plugin check

* refactor wire gen file

* log which secrets manager is being used

* Fix argument types for remote checker

* Turns out if err != nil, then the result is always nil. Return empty values if there is an error.

* remove duplicate import

* ensure atomicity by adding secret management as a step to sql operations and rolling back if necessary

* Update pkg/services/secrets/kvstore/kvstore.go

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>

* Update pkg/services/secrets/kvstore/kvstore.go

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>

* refactor RemotePluginCheck interface to just return the Plugin client directly

* rename struct to something less silly

* add special error handling for remote secrets management

* switch to errors.as instead of type inference

* remove unnecessary rollback call

* just declare error once

* refactor .proto file according to prior PR suggestions

* re-generate protobuf files and fix compilation errors

* only wrap (ergo display in the front end) errors that are user friendly from the plugin

* rename error type to suggest user friendly only

* rename plugin functions to be more descriptive

* change delete message name

* Revert "change delete message name"

This reverts commit 8ca978301e.

* Revert "rename plugin functions to be more descriptive"

This reverts commit 4355c9b9ff.

* fix pointer to pointer problem

* change plugin user error to just hold a string

* fix sequencing problem with datasource updates

* clean up some return statements

* need to wrap multiple transactions with the InTransaction() func in order to keep the lock

* make linter happy

* revert input var name

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
Michael Mandrus
2022-06-16 12:26:57 -04:00
committed by GitHub
parent df90ddffb0
commit c043a8818a
5 changed files with 150 additions and 86 deletions

View File

@@ -142,91 +142,96 @@ func (s *Service) GetDataSourcesByType(ctx context.Context, query *models.GetDat
}
func (s *Service) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCommand) error {
var err error
// this is here for backwards compatibility
cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope())
if err != nil {
return err
}
if err := s.SQLStore.AddDataSource(ctx, cmd); err != nil {
return err
}
secret, err := json.Marshal(cmd.SecureJsonData)
if err != nil {
return err
}
err = s.SecretsStore.Set(ctx, cmd.OrgId, cmd.Name, secretType, string(secret))
if err != nil {
return err
}
if !s.ac.IsDisabled() {
// This belongs in Data source permissions, and we probably want
// to do this with a hook in the store and rollback on fail.
// We can't use events, because there's no way to communicate
// failure, and we want "not being able to set default perms"
// to fail the creation.
permissions := []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: "Viewer", Permission: "Query"},
{BuiltinRole: "Editor", Permission: "Query"},
}
if cmd.UserId != 0 {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{UserID: cmd.UserId, Permission: "Edit"})
}
if _, err := s.permissionsService.SetPermissions(ctx, cmd.OrgId, cmd.Result.Uid, permissions...); err != nil {
return err
}
}
return nil
}
func (s *Service) DeleteDataSource(ctx context.Context, cmd *models.DeleteDataSourceCommand) error {
err := s.SQLStore.DeleteDataSource(ctx, cmd)
if err != nil {
return err
}
return s.SecretsStore.Del(ctx, cmd.OrgID, cmd.Name, secretType)
}
func (s *Service) UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
var err error
query := &models.GetDataSourceQuery{
Id: cmd.Id,
OrgId: cmd.OrgId,
}
err = s.SQLStore.GetDataSource(ctx, query)
if err != nil {
return err
}
err = s.fillWithSecureJSONData(ctx, cmd, query.Result)
if err != nil {
return err
}
err = s.SQLStore.UpdateDataSource(ctx, cmd)
if err != nil {
return err
}
if query.Result.Name != cmd.Name {
err = s.SecretsStore.Rename(ctx, cmd.OrgId, query.Result.Name, secretType, cmd.Name)
return s.SQLStore.InTransaction(ctx, func(ctx context.Context) error {
var err error
// this is here for backwards compatibility
cmd.EncryptedSecureJsonData, err = s.SecretsService.EncryptJsonData(ctx, cmd.SecureJsonData, secrets.WithoutScope())
if err != nil {
return err
}
}
secret, err := json.Marshal(cmd.SecureJsonData)
if err != nil {
return err
}
secret, err := json.Marshal(cmd.SecureJsonData)
if err != nil {
return err
}
return s.SecretsStore.Set(ctx, cmd.OrgId, cmd.Name, secretType, string(secret))
cmd.UpdateSecretFn = func() error {
return s.SecretsStore.Set(ctx, cmd.OrgId, cmd.Name, secretType, string(secret))
}
if err := s.SQLStore.AddDataSource(ctx, cmd); err != nil {
return err
}
if !s.ac.IsDisabled() {
// This belongs in Data source permissions, and we probably want
// to do this with a hook in the store and rollback on fail.
// We can't use events, because there's no way to communicate
// failure, and we want "not being able to set default perms"
// to fail the creation.
permissions := []accesscontrol.SetResourcePermissionCommand{
{BuiltinRole: "Viewer", Permission: "Query"},
{BuiltinRole: "Editor", Permission: "Query"},
}
if cmd.UserId != 0 {
permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{UserID: cmd.UserId, Permission: "Edit"})
}
if _, err := s.permissionsService.SetPermissions(ctx, cmd.OrgId, cmd.Result.Uid, permissions...); err != nil {
return err
}
}
return nil
})
}
func (s *Service) DeleteDataSource(ctx context.Context, cmd *models.DeleteDataSourceCommand) error {
return s.SQLStore.InTransaction(ctx, func(ctx context.Context) error {
cmd.UpdateSecretFn = func() error {
return s.SecretsStore.Del(ctx, cmd.OrgID, cmd.Name, secretType)
}
return s.SQLStore.DeleteDataSource(ctx, cmd)
})
}
func (s *Service) UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
return s.SQLStore.InTransaction(ctx, func(ctx context.Context) error {
var err error
query := &models.GetDataSourceQuery{
Id: cmd.Id,
OrgId: cmd.OrgId,
}
err = s.SQLStore.GetDataSource(ctx, query)
if err != nil {
return err
}
err = s.fillWithSecureJSONData(ctx, cmd, query.Result)
if err != nil {
return err
}
secret, err := json.Marshal(cmd.SecureJsonData)
if err != nil {
return err
}
cmd.UpdateSecretFn = func() error {
var secretsErr error
if query.Result.Name != cmd.Name {
secretsErr = s.SecretsStore.Rename(ctx, cmd.OrgId, query.Result.Name, secretType, cmd.Name)
}
if secretsErr != nil {
return secretsErr
}
return s.SecretsStore.Set(ctx, cmd.OrgId, cmd.Name, secretType, string(secret))
}
return s.SQLStore.UpdateDataSource(ctx, cmd)
})
}
func (s *Service) GetDefaultDataSource(ctx context.Context, query *models.GetDefaultDataSourceQuery) error {