Files
grafana/pkg/services/serviceaccounts/database/database.go
J Guerreiro 2cf421dfe3 ServiceAccounts: Add access control metadata to service accounts (#45096)
* add role to DTO

* add access control metadata
2022-02-08 19:19:22 +00:00

196 lines
6.0 KiB
Go

package database
//nolint:goimports
import (
"context"
"fmt"
"time"
"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"
"xorm.io/xorm"
)
type ServiceAccountsStoreImpl struct {
sqlStore *sqlstore.SQLStore
log log.Logger
}
func NewServiceAccountsStore(store *sqlstore.SQLStore) *ServiceAccountsStoreImpl {
return &ServiceAccountsStoreImpl{
sqlStore: store,
}
}
func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, sa *serviceaccounts.CreateServiceaccountForm) (saDTO *serviceaccounts.ServiceAccountDTO, err error) {
// create a new service account - "user" with empty permissions
generatedLogin := "Service-Account-" + uuid.New().String()
cmd := models.CreateUserCommand{
Login: generatedLogin,
Name: sa.Name,
OrgId: sa.OrgID,
IsServiceAccount: true,
}
newuser, err := s.sqlStore.CreateUser(ctx, cmd)
if err != nil {
return nil, fmt.Errorf("failed to create user: %v", err)
}
return &serviceaccounts.ServiceAccountDTO{
Id: newuser.Id,
Name: newuser.Name,
Login: newuser.Login,
OrgId: newuser.OrgId,
Tokens: 0,
}, nil
}
func (s *ServiceAccountsStoreImpl) DeleteServiceAccount(ctx context.Context, orgID, serviceaccountID int64) error {
return s.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
return deleteServiceAccountInTransaction(sess, orgID, serviceaccountID)
})
}
func deleteServiceAccountInTransaction(sess *sqlstore.DBSession, orgID, serviceAccountID int64) error {
user := models.User{}
has, err := sess.Where(`org_id = ? and id = ? and is_service_account = true`, orgID, serviceAccountID).Get(&user)
if err != nil {
return err
}
if !has {
return serviceaccounts.ErrServiceAccountNotFound
}
for _, sql := range sqlstore.ServiceAccountDeletions() {
_, err := sess.Exec(sql, user.Id)
if err != nil {
return err
}
}
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 {
err := s.CreateServiceAccountFromApikey(ctx, key)
if err != nil {
s.log.Error("migating to service accounts failed with error", err)
}
}
}()
}
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, serviceAccountID int64) ([]*models.ApiKey, error) {
result := make([]*models.ApiKey, 0)
err := s.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
var sess *xorm.Session
sess = dbSession.Limit(100, 0).
Join("inner", "user", "user.id = api_key.service_account_id").
Where("user.org_id=? AND user.id=? AND ( expires IS NULL or expires >= ?)", orgID, serviceAccountID, time.Now().Unix()).
Asc("name")
return sess.Find(&result)
})
return result, err
}
func (s *ServiceAccountsStoreImpl) ListServiceAccounts(ctx context.Context, orgID, serviceAccountID int64) ([]*serviceaccounts.ServiceAccountDTO, error) {
query := models.GetOrgUsersQuery{OrgId: orgID, IsServiceAccount: true}
if serviceAccountID > 0 {
query.UserID = serviceAccountID
}
if err := s.sqlStore.GetOrgUsers(ctx, &query); err != nil {
return nil, err
}
saDTOs := make([]*serviceaccounts.ServiceAccountDTO, len(query.Result))
for i, user := range query.Result {
saDTOs[i] = &serviceaccounts.ServiceAccountDTO{
Id: user.UserId,
OrgId: user.OrgId,
Name: user.Name,
Login: user.Login,
Role: user.Role,
}
tokens, err := s.ListTokens(ctx, user.OrgId, user.UserId)
if err != nil {
return nil, err
}
saDTOs[i].Tokens = int64(len(tokens))
}
return saDTOs, nil
}
// RetrieveServiceAccountByID returns a service account by its ID
func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
query := models.GetOrgUsersQuery{UserID: serviceAccountID, OrgId: orgID, IsServiceAccount: true}
err := s.sqlStore.GetOrgUsers(ctx, &query)
if err != nil {
return nil, err
}
if len(query.Result) != 1 {
return nil, serviceaccounts.ErrServiceAccountNotFound
}
saProfile := &serviceaccounts.ServiceAccountProfileDTO{
Id: query.Result[0].UserId,
Name: query.Result[0].Name,
Login: query.Result[0].Login,
}
return saProfile, err
}
func contains(s []int64, e int64) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}