From 9ccc7ec76ec057ecd28d9b01e573c9aa0e74c45d Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Thu, 20 Jan 2022 16:51:18 +0100 Subject: [PATCH] Serviceaccounts: convert selection of apikeys to serviceaccounts (#44154) * convert selected of apikeys to serviceaccounts * update to use one by one * expose endpoint of service account --- pkg/services/serviceaccounts/api/api.go | 13 +++++ .../serviceaccounts/database/database.go | 56 +++++++++++++++---- .../serviceaccounts/serviceaccounts.go | 1 + pkg/services/serviceaccounts/tests/common.go | 6 ++ 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index 60a1a91f6f1..edc76f417fd 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -52,6 +52,7 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints( serviceAccountsRoute.Get("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.RetrieveServiceAccount)) serviceAccountsRoute.Delete("/:serviceAccountId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount)) serviceAccountsRoute.Get("/upgrade", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.UpgradeServiceAccounts)) + serviceAccountsRoute.Post("/convert/:keyId", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.ConvertToServiceAccount)) serviceAccountsRoute.Post("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.CreateServiceAccount)) serviceAccountsRoute.Get("/:serviceAccountId/tokens", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.ListTokens)) }) @@ -94,6 +95,18 @@ func (api *ServiceAccountsAPI) UpgradeServiceAccounts(ctx *models.ReqContext) re } } +func (api *ServiceAccountsAPI) ConvertToServiceAccount(ctx *models.ReqContext) response.Response { + keyId, err := strconv.ParseInt(web.Params(ctx.Req)[":keyId"], 10, 64) + if err != nil { + return response.Error(http.StatusBadRequest, "keyId is invalid", err) + } + if err := api.store.ConvertToServiceAccounts(ctx.Req.Context(), []int64{keyId}); err == nil { + return response.Success("service accounts converted") + } else { + return response.Error(500, "Internal server error", err) + } +} + func (api *ServiceAccountsAPI) ListTokens(ctx *models.ReqContext) response.Response { saID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64) if err != nil { diff --git a/pkg/services/serviceaccounts/database/database.go b/pkg/services/serviceaccounts/database/database.go index 95810a5262b..3cdd1cc1f09 100644 --- a/pkg/services/serviceaccounts/database/database.go +++ b/pkg/services/serviceaccounts/database/database.go @@ -70,24 +70,51 @@ func (s *ServiceAccountsStoreImpl) UpgradeServiceAccounts(ctx context.Context) e s.log.Info("Launching background thread to upgrade API keys to service accounts", "numberKeys", len(basicKeys)) go func() { for _, key := range basicKeys { - sa, err := s.sqlStore.CreateServiceAccountForApikey(ctx, key.OrgId, key.Name, key.Role) + err := s.CreateServiceAccountFromApikey(ctx, key) if err != nil { - s.log.Error("Failed to create service account for API key", "err", err, "keyId", key.Id) - continue + s.log.Error("migating to service accounts failed with error", err) } - - err = s.sqlStore.UpdateApikeyServiceAccount(ctx, key.Id, sa.Id) - if err != nil { - s.log.Error("Failed to attach new service account to API key", "err", err, "keyId", key.Id, "newServiceAccountId", sa.Id) - continue - } - s.log.Debug("Updated basic api key", "keyId", key.Id, "newServiceAccountId", sa.Id) } }() } return nil } +func (s *ServiceAccountsStoreImpl) ConvertToServiceAccounts(ctx context.Context, keys []int64) error { + basicKeys := s.sqlStore.GetNonServiceAccountAPIKeys(ctx) + if len(basicKeys) == 0 { + return nil + } + if len(basicKeys) != len(keys) { + return fmt.Errorf("one of the keys already has a serviceaccount") + } + for _, key := range basicKeys { + if !contains(keys, key.Id) { + s.log.Error("convert service accounts stopped for keyId %d as it is not part of the query to convert or already has a service account", key.Id) + continue + } + err := s.CreateServiceAccountFromApikey(ctx, key) + if err != nil { + s.log.Error("converting to service accounts failed with error", err) + } + } + return nil +} + +func (s *ServiceAccountsStoreImpl) CreateServiceAccountFromApikey(ctx context.Context, key *models.ApiKey) error { + sa, err := s.sqlStore.CreateServiceAccountForApikey(ctx, key.OrgId, key.Name, key.Role) + if err != nil { + return fmt.Errorf("failed to create service account for API key with error : %w", err) + } + + err = s.sqlStore.UpdateApikeyServiceAccount(ctx, key.Id, sa.Id) + if err != nil { + return fmt.Errorf("failed to attach new service account to API key for keyId: %d and newServiceAccountId: %d with error: %w", key.Id, sa.Id, err) + } + s.log.Debug("Updated basic api key", "keyId", key.Id, "newServiceAccountId", sa.Id) + return nil +} + //nolint:gosimple func (s *ServiceAccountsStoreImpl) ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*models.ApiKey, error) { result := make([]*models.ApiKey, 0) @@ -126,3 +153,12 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, o return query.Result[0], err } + +func contains(s []int64, e int64) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/pkg/services/serviceaccounts/serviceaccounts.go b/pkg/services/serviceaccounts/serviceaccounts.go index 07f8959e82d..a5718ff9043 100644 --- a/pkg/services/serviceaccounts/serviceaccounts.go +++ b/pkg/services/serviceaccounts/serviceaccounts.go @@ -18,5 +18,6 @@ type Store interface { RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*models.OrgUserDTO, error) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error UpgradeServiceAccounts(ctx context.Context) error + ConvertToServiceAccounts(ctx context.Context, keys []int64) error ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*models.ApiKey, error) } diff --git a/pkg/services/serviceaccounts/tests/common.go b/pkg/services/serviceaccounts/tests/common.go index c338e075e18..4451c265ba6 100644 --- a/pkg/services/serviceaccounts/tests/common.go +++ b/pkg/services/serviceaccounts/tests/common.go @@ -61,6 +61,7 @@ type Calls struct { RetrieveServiceAccount []interface{} DeleteServiceAccount []interface{} UpgradeServiceAccounts []interface{} + ConvertServiceAccounts []interface{} ListTokens []interface{} } @@ -85,6 +86,11 @@ func (s *ServiceAccountsStoreMock) UpgradeServiceAccounts(ctx context.Context) e return nil } +func (s *ServiceAccountsStoreMock) ConvertToServiceAccounts(ctx context.Context, keys []int64) error { + s.Calls.ConvertServiceAccounts = append(s.Calls.ConvertServiceAccounts, []interface{}{ctx}) + return nil +} + func (s *ServiceAccountsStoreMock) ListTokens(ctx context.Context, orgID int64, serviceAccount int64) ([]*models.ApiKey, error) { s.Calls.ListTokens = append(s.Calls.ListTokens, []interface{}{ctx, orgID, serviceAccount}) return nil, nil