IAM: Protect external service accounts frontend list page (#77834)

* Add `isExternal` property to frontend model

* Remove enabled and token buttons for external SA

* Replace trash icon for lock icon for external SA

* Block the role picker for external SA

* Filter SA list using the external filter

* Add only external filter at backend

---------

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
linoman
2023-11-09 17:45:46 +01:00
committed by GitHub
co-authored by Gabriel MABILLE
parent d4322f6e5a
commit 5bc4f56c79
11 changed files with 80 additions and 42 deletions
+8
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/auth/identity"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/setting"
@@ -27,6 +28,7 @@ type ServiceAccountsAPI struct {
RouterRegister routing.RouteRegister
log log.Logger
permissionService accesscontrol.ServiceAccountPermissionsService
isExternalSAEnabled bool
}
func NewServiceAccountsAPI(
@@ -36,6 +38,7 @@ func NewServiceAccountsAPI(
accesscontrolService accesscontrol.Service,
routerRegister routing.RouteRegister,
permissionService accesscontrol.ServiceAccountPermissionsService,
features *featuremgmt.FeatureManager,
) *ServiceAccountsAPI {
return &ServiceAccountsAPI{
cfg: cfg,
@@ -45,6 +48,7 @@ func NewServiceAccountsAPI(
RouterRegister: routerRegister,
log: log.New("serviceaccounts.api"),
permissionService: permissionService,
isExternalSAEnabled: features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabled(featuremgmt.FlagExternalServiceAuth),
}
}
@@ -265,10 +269,14 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *contextmode
// its okay that it fails, it is only filtering that might be weird, but to safe quard against any weird incoming query param
onlyWithExpiredTokens := c.QueryBool("expiredTokens")
onlyDisabled := c.QueryBool("disabled")
onlyExternal := c.QueryBool("external")
filter := serviceaccounts.FilterIncludeAll
if onlyWithExpiredTokens {
filter = serviceaccounts.FilterOnlyExpiredTokens
}
if api.isExternalSAEnabled && onlyExternal {
filter = serviceaccounts.FilterOnlyExternal
}
if onlyDisabled {
filter = serviceaccounts.FilterOnlyDisabled
}
+2 -2
View File
@@ -85,7 +85,7 @@ type ServiceAccountDTO struct {
// example: false
IsDisabled bool `json:"isDisabled" xorm:"is_disabled"`
// example: false
IsManaged bool `json:"isManaged,omitempty" xorm:"-"`
IsExternal bool `json:"isExternal,omitempty" xorm:"-"`
// example: Viewer
Role string `json:"role" xorm:"role"`
// example: 0
@@ -157,7 +157,7 @@ type ServiceAccountProfileDTO struct {
// example: []
Teams []string `json:"teams" xorm:"-"`
// example: false
IsManaged bool `json:"isManaged,omitempty" xorm:"-"`
IsExternal bool `json:"isExternal,omitempty" xorm:"-"`
Tokens int64 `json:"tokens,omitempty"`
AccessControl map[string]bool `json:"accessControl,omitempty" xorm:"-"`
@@ -41,7 +41,7 @@ func ProvideServiceAccountsProxy(
isProxyEnabled: features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabled(featuremgmt.FlagExternalServiceAuth),
}
serviceaccountsAPI := api.NewServiceAccountsAPI(cfg, s, ac, accesscontrolService, routeRegister, permissionService)
serviceaccountsAPI := api.NewServiceAccountsAPI(cfg, s, ac, accesscontrolService, routeRegister, permissionService, features)
serviceaccountsAPI.RegisterAPIEndpoints()
return s, nil
@@ -138,7 +138,7 @@ func (s *ServiceAccountsProxy) RetrieveServiceAccount(ctx context.Context, orgID
}
if s.isProxyEnabled {
sa.IsManaged = isExternalServiceAccount(sa.Login)
sa.IsExternal = isExternalServiceAccount(sa.Login)
}
return sa, nil
@@ -175,7 +175,7 @@ func (s *ServiceAccountsProxy) SearchOrgServiceAccounts(ctx context.Context, que
if s.isProxyEnabled {
for i := range sa.ServiceAccounts {
sa.ServiceAccounts[i].IsManaged = isExternalServiceAccount(sa.ServiceAccounts[i].Login)
sa.ServiceAccounts[i].IsExternal = isExternalServiceAccount(sa.ServiceAccounts[i].Login)
}
}
return sa, nil
@@ -146,7 +146,7 @@ func TestProvideServiceAccount_crudServiceAccount(t *testing.T) {
serviceMock.ExpectedServiceAccountProfile = tc.expectedServiceAccount
sa, err := svc.RetrieveServiceAccount(context.Background(), testOrgId, testServiceAccountId)
assert.NoError(t, err, tc.description)
assert.Equal(t, tc.expectedIsExternal, sa.IsManaged, tc.description)
assert.Equal(t, tc.expectedIsExternal, sa.IsExternal, tc.description)
})
}
})
@@ -164,8 +164,8 @@ func TestProvideServiceAccount_crudServiceAccount(t *testing.T) {
res, err := svc.SearchOrgServiceAccounts(context.Background(), &serviceaccounts.SearchOrgServiceAccountsQuery{OrgID: 1})
require.Len(t, res.ServiceAccounts, 2)
require.NoError(t, err)
require.False(t, res.ServiceAccounts[0].IsManaged)
require.True(t, res.ServiceAccounts[1].IsManaged)
require.False(t, res.ServiceAccounts[0].IsExternal)
require.True(t, res.ServiceAccounts[1].IsExternal)
})
t.Run("should update service account", func(t *testing.T) {