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:
Jeremy Price
2021-12-16 14:28:16 +01:00
committed by GitHub
parent 57def82f26
commit 13fdc5231d
10 changed files with 121 additions and 14 deletions

View File

@@ -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)
}
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}