AuthN: Add metrics to external service accounts management (#76789)

* AuthN: Add metrics to external service accounts management

* Add a new metric to count stored external service accounts

* Update variable names

Co-authored-by: linoman <2051016+linoman@users.noreply.github.com>

* Add test to SearchOrgServiceAccounts

* Add feature flags checks before registering and using the metrics

---------

Co-authored-by: linoman <2051016+linoman@users.noreply.github.com>
This commit is contained in:
Gabriel MABILLE
2023-10-24 15:54:14 +02:00
committed by GitHub
parent 07909464f1
commit 897e3a4dab
12 changed files with 352 additions and 30 deletions

View File

@@ -270,9 +270,6 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context,
}
err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
sess := dbSession.Table("org_user")
sess.Join("INNER", s.sqlStore.GetDialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.GetDialect().Quote("user")))
whereConditions := make([]string, 0)
whereParams := make([]any, 0)
@@ -312,10 +309,38 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context,
whereConditions,
"is_disabled = ?")
whereParams = append(whereParams, s.sqlStore.GetDialect().BooleanStr(true))
case serviceaccounts.FilterOnlyExternal:
whereConditions = append(
whereConditions,
"login "+s.sqlStore.GetDialect().LikeStr()+" ?")
whereParams = append(whereParams, serviceaccounts.ServiceAccountPrefix+serviceaccounts.ExtSvcPrefix+"%")
default:
s.log.Warn("Invalid filter user for service account filtering", "service account search filtering", query.Filter)
}
// Count the number of accounts
serviceaccount := serviceaccounts.ServiceAccountDTO{}
countSess := dbSession.Table("org_user")
countSess.Join("INNER", s.sqlStore.GetDialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.GetDialect().Quote("user")))
if len(whereConditions) > 0 {
countSess.Where(strings.Join(whereConditions, " AND "), whereParams...)
}
count, err := countSess.Count(&serviceaccount)
if err != nil {
return err
}
searchResult.TotalCount = count
// Stop here if we only wanted to count the number of accounts
if query.CountOnly {
return nil
}
// Fetch service accounts
sess := dbSession.Table("org_user")
sess.Join("INNER", s.sqlStore.GetDialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.GetDialect().Quote("user")))
if len(whereConditions) > 0 {
sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
}
@@ -338,21 +363,6 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context,
if err := sess.Find(&searchResult.ServiceAccounts); err != nil {
return err
}
// get total
serviceaccount := serviceaccounts.ServiceAccountDTO{}
countSess := dbSession.Table("org_user")
sess.Join("INNER", s.sqlStore.GetDialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.GetDialect().Quote("user")))
if len(whereConditions) > 0 {
countSess.Where(strings.Join(whereConditions, " AND "), whereParams...)
}
count, err := countSess.Count(&serviceaccount)
if err != nil {
return err
}
searchResult.TotalCount = count
return nil
})
if err != nil {

View File

@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/kvstore"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
@@ -368,3 +369,129 @@ func TestStore_MigrateAllApiKeys(t *testing.T) {
})
}
}
func TestServiceAccountsStoreImpl_SearchOrgServiceAccounts(t *testing.T) {
initUsers := []tests.TestUser{
{Name: "satest-1", Role: string(org.RoleViewer), Login: "sa-satest-1", IsServiceAccount: true},
{Name: "usertest-2", Role: string(org.RoleEditor), Login: "usertest-2", IsServiceAccount: false},
{Name: "satest-3", Role: string(org.RoleEditor), Login: "sa-satest-3", IsServiceAccount: true},
{Name: "satest-4", Role: string(org.RoleAdmin), Login: "sa-satest-4", IsServiceAccount: true},
{Name: "extsvc-test-5", Role: string(org.RoleNone), Login: "sa-extsvc-test-5", IsServiceAccount: true},
{Name: "extsvc-test-6", Role: string(org.RoleNone), Login: "sa-extsvc-test-6", IsServiceAccount: true},
{Name: "extsvc-test-7", Role: string(org.RoleNone), Login: "sa-extsvc-test-7", IsServiceAccount: true},
{Name: "extsvc-test-8", Role: string(org.RoleNone), Login: "sa-extsvc-test-8", IsServiceAccount: true},
}
db, store := setupTestDatabase(t)
orgID := tests.SetupUsersServiceAccounts(t, db, initUsers)
userWithPerm := &user.SignedInUser{
OrgID: orgID,
Permissions: map[int64]map[string][]string{orgID: {serviceaccounts.ActionRead: {serviceaccounts.ScopeAll}}},
}
tt := []struct {
desc string
query *serviceaccounts.SearchOrgServiceAccountsQuery
expectedTotal int64 // Value of the result.TotalCount
expectedCount int // Length of the result.ServiceAccounts slice
expectedErr error
}{
{
desc: "should list all service accounts",
query: &serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: orgID,
SignedInUser: userWithPerm,
Filter: serviceaccounts.FilterIncludeAll,
},
expectedTotal: 7,
expectedCount: 7,
},
{
desc: "should list no service accounts without permissions",
query: &serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: orgID,
SignedInUser: &user.SignedInUser{
OrgID: orgID,
Permissions: map[int64]map[string][]string{orgID: {}},
},
Filter: serviceaccounts.FilterIncludeAll,
},
expectedTotal: 0,
expectedCount: 0,
},
{
desc: "should list one service accounts with restricted permissions",
query: &serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: orgID,
SignedInUser: &user.SignedInUser{
OrgID: orgID,
Permissions: map[int64]map[string][]string{orgID: {serviceaccounts.ActionRead: {
ac.Scope("serviceaccounts", "id", "1"),
ac.Scope("serviceaccounts", "id", "7"),
}}},
},
Filter: serviceaccounts.FilterIncludeAll,
},
expectedTotal: 2,
expectedCount: 2,
},
{
desc: "should list only external service accounts",
query: &serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: orgID,
SignedInUser: userWithPerm,
Filter: serviceaccounts.FilterOnlyExternal,
},
expectedTotal: 4,
expectedCount: 4,
},
{
desc: "should return service accounts with sa-satest login",
query: &serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: orgID,
Query: "sa-satest",
SignedInUser: userWithPerm,
Filter: serviceaccounts.FilterIncludeAll,
},
expectedTotal: 3,
expectedCount: 3,
},
{
desc: "should only count service accounts",
query: &serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: orgID,
SignedInUser: userWithPerm,
Filter: serviceaccounts.FilterIncludeAll,
CountOnly: true,
},
expectedTotal: 7,
expectedCount: 0,
},
{
desc: "should paginate result",
query: &serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: orgID,
Page: 4,
Limit: 2,
SignedInUser: userWithPerm,
Filter: serviceaccounts.FilterIncludeAll,
},
expectedTotal: 7,
expectedCount: 1,
},
}
for _, tc := range tt {
t.Run(tc.desc, func(t *testing.T) {
ctx := context.Background()
got, err := store.SearchOrgServiceAccounts(ctx, tc.query)
if tc.expectedErr != nil {
require.ErrorIs(t, err, tc.expectedErr)
return
}
require.Equal(t, tc.expectedTotal, got.TotalCount)
require.Len(t, got.ServiceAccounts, tc.expectedCount)
})
}
}