Skip to content

Commit

Permalink
AuthN: Introduce DefaultOrgID function for managed service accounts (
Browse files Browse the repository at this point in the history
…grafana#93432)

* Managed Service Accounts: Use AutoAssignOrgID

* Fix the IsExternalServiceAccount function

* Reassign service account role

* Account for AutoAssignOrg

* Update pkg/services/serviceaccounts/models.go

* Simplify IsExternalServiceAccount function

* Add tests

* Easier to understand test

* Revert small change
  • Loading branch information
gamab authored Sep 20, 2024
1 parent bb69afe commit 8d84517
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 122 deletions.
14 changes: 9 additions & 5 deletions pkg/services/accesscontrol/database/externalservices.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,18 +240,22 @@ func (*AccessControlStore) saveUserAssignment(ctx context.Context, sess *db.Sess
return errGetAssigns
}

// Revoke assignment if it's assigned to another user or service account
if len(assignments) > 0 && assignments[0].UserID != assignment.UserID {
if _, errDel := sess.Where("role_id = ?", assignment.RoleID).Delete(&accesscontrol.UserRole{}); errDel != nil {
return errDel
}
assignments = nil
}

// If no assignment exists, insert a new one.
if len(assignments) == 0 {
if _, errInsert := sess.Insert(&assignment); errInsert != nil {
return errInsert
}
return nil
}

// Ensure the role was assigned only to this service account
if len(assignments) > 1 || assignments[0].UserID != assignment.UserID {
return errors.New("external service role assigned to another user or service account")
}

// Ensure the assignment is in the correct organization
_, errUpdate := sess.Where("role_id = ? AND user_id = ?", assignment.RoleID, assignment.UserID).MustCols("org_id").Update(&assignment)
return errUpdate
Expand Down
4 changes: 2 additions & 2 deletions pkg/services/accesscontrol/database/externalservices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ func TestAccessControlStore_SaveExternalServiceRole(t *testing.T) {
{
cmd: accesscontrol.SaveExternalServiceRoleCommand{
ExternalServiceID: "app1",
AssignmentOrgID: 1,
AssignmentOrgID: 2,
ServiceAccountID: 2,
},
wantErr: true,
wantErr: false,
},
},
},
Expand Down
13 changes: 9 additions & 4 deletions pkg/services/extsvcauth/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ import (
"context"

"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/setting"
)

const (
ServiceAccounts AuthProvider = "ServiceAccounts"

// TmpOrgID is the orgID we use while global service accounts are not supported.
TmpOrgIDStr string = "1"
TmpOrgID int64 = 1
)

func DefaultOrgID(cfg *setting.Cfg) int64 {
orgID := int64(1)
if cfg.AutoAssignOrg && cfg.AutoAssignOrgId > 0 {
orgID = int64(cfg.AutoAssignOrgId)
}
return orgID
}

type AuthProvider string

//go:generate mockery --name ExternalServiceRegistry --structname ExternalServiceRegistryMock --output tests --outpkg tests --filename extsvcregmock.go
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/serviceaccounts/database/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context,
whereConditions = append(
whereConditions,
"login "+s.sqlStore.GetDialect().LikeStr()+" ?")
whereParams = append(whereParams, serviceaccounts.ExtSvcLoginPrefix+"%")
whereParams = append(whereParams, serviceaccounts.ExtSvcLoginPrefix(query.OrgID)+"%")
default:
s.log.Warn("Invalid filter user for service account filtering", "service account search filtering", query.Filter)
}
Expand Down
7 changes: 3 additions & 4 deletions pkg/services/serviceaccounts/extsvcaccounts/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"time"

"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/extsvcauth"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/prometheus/client_golang/prometheus"
)
Expand All @@ -16,7 +15,7 @@ type metrics struct {
deletedCount prometheus.Counter
}

func newMetrics(reg prometheus.Registerer, saSvc serviceaccounts.Service, logger log.Logger) *metrics {
func newMetrics(reg prometheus.Registerer, defaultOrgID int64, saSvc serviceaccounts.Service, logger log.Logger) *metrics {
var m metrics

m.storedCount = prometheus.NewGaugeFunc(
Expand All @@ -29,10 +28,10 @@ func newMetrics(reg prometheus.Registerer, saSvc serviceaccounts.Service, logger
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
res, err := saSvc.SearchOrgServiceAccounts(ctx, &serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: extsvcauth.TmpOrgID,
OrgID: defaultOrgID,
Filter: serviceaccounts.FilterOnlyExternal,
CountOnly: true,
SignedInUser: extsvcuser,
SignedInUser: extsvcuser(defaultOrgID),
})
if err != nil {
logger.Error("Could not compute extsvc_total metric", "error", err)
Expand Down
11 changes: 6 additions & 5 deletions pkg/services/serviceaccounts/extsvcaccounts/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/extsvcauth"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/user"
)
Expand All @@ -28,14 +27,16 @@ var (
ErrCredentialsGenFailed = errutil.Internal("extsvcaccounts.ErrCredentialsGenFailed")
ErrCredentialsNotFound = errutil.NotFound("extsvcaccounts.ErrCredentialsNotFound")
ErrInvalidName = errutil.BadRequest("extsvcaccounts.ErrInvalidName", errutil.WithPublicMessage("only external service account names can be prefixed with 'extsvc-'"))
)

extsvcuser = &user.SignedInUser{
OrgID: extsvcauth.TmpOrgID,
func extsvcuser(orgID int64) *user.SignedInUser {
return &user.SignedInUser{
OrgID: orgID,
Permissions: map[int64]map[string][]string{
extsvcauth.TmpOrgID: {serviceaccounts.ActionRead: {"serviceaccounts:id:*"}},
orgID: {serviceaccounts.ActionRead: {"serviceaccounts:id:*"}},
},
}
)
}

// Credentials represents the credentials associated to an external service
type Credentials struct {
Expand Down
45 changes: 24 additions & 21 deletions pkg/services/serviceaccounts/extsvcaccounts/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,35 @@ import (
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
sa "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/manager"
"github.com/grafana/grafana/pkg/setting"
)

type ExtSvcAccountsService struct {
acSvc ac.Service
features featuremgmt.FeatureToggles
logger log.Logger
metrics *metrics
saSvc sa.Service
skvStore kvstore.SecretsKVStore
tracer tracing.Tracer
acSvc ac.Service
defaultOrgID int64
features featuremgmt.FeatureToggles
logger log.Logger
metrics *metrics
saSvc sa.Service
skvStore kvstore.SecretsKVStore
tracer tracing.Tracer
}

func ProvideExtSvcAccountsService(acSvc ac.Service, bus bus.Bus, db db.DB, features featuremgmt.FeatureToggles, reg prometheus.Registerer, saSvc *manager.ServiceAccountsService, secretsSvc secrets.Service, tracer tracing.Tracer) *ExtSvcAccountsService {
func ProvideExtSvcAccountsService(acSvc ac.Service, cfg *setting.Cfg, bus bus.Bus, db db.DB, features featuremgmt.FeatureToggles, reg prometheus.Registerer, saSvc *manager.ServiceAccountsService, secretsSvc secrets.Service, tracer tracing.Tracer) *ExtSvcAccountsService {
logger := log.New("serviceauth.extsvcaccounts")
esa := &ExtSvcAccountsService{
acSvc: acSvc,
logger: logger,
saSvc: saSvc,
features: features,
skvStore: kvstore.NewSQLSecretsKVStore(db, secretsSvc, logger), // Using SQL store to avoid a cyclic dependency
tracer: tracer,
acSvc: acSvc,
defaultOrgID: extsvcauth.DefaultOrgID(cfg),
logger: logger,
saSvc: saSvc,
features: features,
skvStore: kvstore.NewSQLSecretsKVStore(db, secretsSvc, logger), // Using SQL store to avoid a cyclic dependency
tracer: tracer,
}

if features.IsEnabledGlobally(featuremgmt.FlagExternalServiceAccounts) {
// Register the metrics
esa.metrics = newMetrics(reg, saSvc, logger)
esa.metrics = newMetrics(reg, esa.defaultOrgID, saSvc, logger)

// Register a listener to enable/disable service accounts
bus.AddEventListener(esa.handlePluginStateChanged)
Expand Down Expand Up @@ -78,7 +81,7 @@ func (esa *ExtSvcAccountsService) HasExternalService(ctx context.Context, name s

saName := sa.ExtSvcPrefix + slugify.Slugify(name)

saID, errRetrieve := esa.saSvc.RetrieveServiceAccountIdByName(ctx, extsvcauth.TmpOrgID, saName)
saID, errRetrieve := esa.saSvc.RetrieveServiceAccountIdByName(ctx, esa.defaultOrgID, saName)
if errRetrieve != nil && !errors.Is(errRetrieve, sa.ErrServiceAccountNotFound) {
return false, errRetrieve
}
Expand Down Expand Up @@ -114,9 +117,9 @@ func (esa *ExtSvcAccountsService) GetExternalServiceNames(ctx context.Context) (

ctxLogger.Debug("Get external service names from store")
sas, err := esa.saSvc.SearchOrgServiceAccounts(ctx, &sa.SearchOrgServiceAccountsQuery{
OrgID: extsvcauth.TmpOrgID,
OrgID: esa.defaultOrgID,
Filter: sa.FilterOnlyExternal,
SignedInUser: extsvcuser,
SignedInUser: extsvcuser(esa.defaultOrgID),
})
if err != nil {
ctxLogger.Error("Could not fetch external service accounts from store", "error", err.Error())
Expand Down Expand Up @@ -155,7 +158,7 @@ func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd *
saID, err := esa.ManageExtSvcAccount(ctx, &sa.ManageExtSvcAccountCmd{
ExtSvcSlug: slug,
Enabled: cmd.Self.Enabled,
OrgID: extsvcauth.TmpOrgID,
OrgID: esa.defaultOrgID,
Permissions: cmd.Self.Permissions,
})
if err != nil {
Expand All @@ -168,7 +171,7 @@ func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd *
return nil, nil
}

token, err := esa.getExtSvcAccountToken(ctx, extsvcauth.TmpOrgID, saID, slug)
token, err := esa.getExtSvcAccountToken(ctx, esa.defaultOrgID, saID, slug)
if err != nil {
ctxLogger.Error("Could not get the external svc token",
"service", slug,
Expand All @@ -189,7 +192,7 @@ func (esa *ExtSvcAccountsService) RemoveExternalService(ctx context.Context, nam
ctx, span := esa.tracer.Start(ctx, "ExtSvcAccountsService.RemoveExternalService")
defer span.End()

return esa.RemoveExtSvcAccount(ctx, extsvcauth.TmpOrgID, slugify.Slugify(name))
return esa.RemoveExtSvcAccount(ctx, esa.defaultOrgID, slugify.Slugify(name))
}

func (esa *ExtSvcAccountsService) RemoveExtSvcAccount(ctx context.Context, orgID int64, extSvcSlug string) error {
Expand Down
Loading

0 comments on commit 8d84517

Please sign in to comment.