mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AccessControl: upgrade apikeys by adding service accounts (#42425)
Co-authored-by: Eric Leijonmarck <eric.leijonmarck@gmail.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Change default options for cloned service account * Run in background * Add endpoint to upgrade api keys to service accounts
This commit is contained in:
@@ -19,17 +19,20 @@ type ServiceAccountsAPI struct {
|
||||
service serviceaccounts.Service
|
||||
accesscontrol accesscontrol.AccessControl
|
||||
RouterRegister routing.RouteRegister
|
||||
store serviceaccounts.Store
|
||||
}
|
||||
|
||||
func NewServiceAccountsAPI(
|
||||
service serviceaccounts.Service,
|
||||
accesscontrol accesscontrol.AccessControl,
|
||||
routerRegister routing.RouteRegister,
|
||||
store serviceaccounts.Store,
|
||||
) *ServiceAccountsAPI {
|
||||
return &ServiceAccountsAPI{
|
||||
service: service,
|
||||
accesscontrol: accesscontrol,
|
||||
RouterRegister: routerRegister,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +45,7 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints(
|
||||
auth := acmiddleware.Middleware(api.accesscontrol)
|
||||
api.RouterRegister.Group("/api/serviceaccounts", func(serviceAccountsRoute routing.RouteRegister) {
|
||||
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("/", auth(middleware.ReqOrgAdmin, accesscontrol.EvalPermission(serviceaccounts.ActionCreate, serviceaccounts.ScopeID)), routing.Wrap(api.CreateServiceAccount))
|
||||
})
|
||||
}
|
||||
@@ -71,3 +75,11 @@ func (api *ServiceAccountsAPI) DeleteServiceAccount(ctx *models.ReqContext) resp
|
||||
}
|
||||
return response.Success("service account deleted")
|
||||
}
|
||||
|
||||
func (api *ServiceAccountsAPI) UpgradeServiceAccounts(ctx *models.ReqContext) response.Response {
|
||||
if err := api.store.UpgradeServiceAccounts(ctx.Req.Context()); err == nil {
|
||||
return response.Success("service accounts upgraded")
|
||||
} else {
|
||||
return response.Error(500, "Internal server error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -93,11 +95,8 @@ func serviceAccountDeletionScenario(t *testing.T, httpMethod string, endpoint st
|
||||
}
|
||||
|
||||
func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock, routerRegister routing.RouteRegister, acmock *accesscontrolmock.Mock) *web.Mux {
|
||||
a := NewServiceAccountsAPI(
|
||||
svc,
|
||||
acmock,
|
||||
routerRegister,
|
||||
)
|
||||
store := sqlstore.InitTestDB(t)
|
||||
a := NewServiceAccountsAPI(svc, acmock, routerRegister, database.NewServiceAccountsStore(store))
|
||||
a.RegisterAPIEndpoints(&setting.Cfg{FeatureToggles: map[string]bool{"service-accounts": true}})
|
||||
|
||||
m := web.New()
|
||||
|
||||
@@ -2,16 +2,18 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServiceAccountsStoreImpl struct {
|
||||
sqlStore *sqlstore.SQLStore
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewServiceAccountsStore(store *sqlstore.SQLStore) *ServiceAccountsStoreImpl {
|
||||
@@ -30,7 +32,7 @@ func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, sa
|
||||
}
|
||||
newuser, err := s.sqlStore.CreateUser(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Failed to create user: %v", err)
|
||||
return nil, fmt.Errorf("failed to create user: %v", err)
|
||||
}
|
||||
return newuser, nil
|
||||
}
|
||||
@@ -58,3 +60,27 @@ func deleteServiceAccountInTransaction(sess *sqlstore.DBSession, orgID, serviceA
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceAccountsStoreImpl) UpgradeServiceAccounts(ctx context.Context) error {
|
||||
basicKeys := s.sqlStore.GetNonServiceAccountAPIKeys(ctx)
|
||||
if len(basicKeys) > 0 {
|
||||
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)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to create service account for API key", "err", err, "keyId", key.Id)
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -38,8 +38,9 @@ func ProvideServiceAccountsService(
|
||||
if err := ac.DeclareFixedRoles(role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serviceaccountsAPI := api.NewServiceAccountsAPI(s, ac, routeRegister)
|
||||
serviceaccountsAPI := api.NewServiceAccountsAPI(s, ac, routeRegister, s.store)
|
||||
serviceaccountsAPI.RegisterAPIEndpoints(cfg)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,4 +13,5 @@ type Service interface {
|
||||
type Store interface {
|
||||
CreateServiceAccount(ctx context.Context, saForm *CreateServiceaccountForm) (*models.User, error)
|
||||
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
|
||||
UpgradeServiceAccounts(ctx context.Context) error
|
||||
}
|
||||
|
||||
@@ -52,8 +52,9 @@ func SetupMockAccesscontrol(t *testing.T, userpermissionsfunc func(c context.Con
|
||||
var _ serviceaccounts.Store = new(ServiceAccountsStoreMock)
|
||||
|
||||
type Calls struct {
|
||||
CreateServiceAccount []interface{}
|
||||
DeleteServiceAccount []interface{}
|
||||
CreateServiceAccount []interface{}
|
||||
DeleteServiceAccount []interface{}
|
||||
UpgradeServiceAccounts []interface{}
|
||||
}
|
||||
|
||||
type ServiceAccountsStoreMock struct {
|
||||
@@ -71,3 +72,8 @@ func (s *ServiceAccountsStoreMock) DeleteServiceAccount(ctx context.Context, org
|
||||
s.Calls.DeleteServiceAccount = append(s.Calls.DeleteServiceAccount, []interface{}{ctx, orgID, serviceAccountID})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceAccountsStoreMock) UpgradeServiceAccounts(ctx context.Context) error {
|
||||
s.Calls.DeleteServiceAccount = append(s.Calls.UpgradeServiceAccounts, []interface{}{ctx})
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user