Authz: Account for fixed roles when running oss and using authz service (#99244)

* Extract "PermissionStore" from general store interface

* Add static and union permission stores

* Add GetStaticRoles

* Use accesscontrol.Service for inproc to provide static permissions
pull/99268/head
Karl Persson 5 months ago committed by GitHub
parent 0baf3a8f95
commit 7329d2c34b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      pkg/services/accesscontrol/accesscontrol.go
  2. 16
      pkg/services/accesscontrol/acimpl/service.go
  3. 1
      pkg/services/accesscontrol/acimpl/service_test.go
  4. 3
      pkg/services/accesscontrol/mock/mock.go
  5. 27
      pkg/services/authz/client.go
  6. 43
      pkg/services/authz/rbac/service.go
  7. 41
      pkg/services/authz/rbac/service_test.go
  8. 10
      pkg/services/authz/rbac/store/models.go
  9. 159
      pkg/services/authz/rbac/store/permission_store.go
  10. 26
      pkg/services/authz/rbac/store/queries.go
  11. 40
      pkg/services/authz/rbac/store/store.go
  12. 9
      pkg/services/authz/server.go
  13. 9
      pkg/tests/apis/helper.go

@ -59,6 +59,8 @@ type Service interface {
DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error
// SyncUserRoles adds provided roles to user
SyncUserRoles(ctx context.Context, orgID int64, cmd SyncUserRolesCommand) error
// GetStaicRoles returns a map where key organization role and value is a static rbac role.
GetStaticRoles(ctx context.Context) map[string]*RoleDTO
}
//go:generate mockery --name Store --structname MockStore --outpkg actest --filename store_mock.go --output ./actest/

@ -464,7 +464,15 @@ func (s *Service) RegisterFixedRoles(ctx context.Context) error {
s.registrations.Range(func(registration accesscontrol.RoleRegistration) bool {
for br := range accesscontrol.BuiltInRolesWithParents(registration.Grants) {
if basicRole, ok := s.roles[br]; ok {
basicRole.Permissions = append(basicRole.Permissions, registration.Role.Permissions...)
for _, p := range registration.Role.Permissions {
perm := accesscontrol.Permission{
Action: p.Action,
Scope: p.Scope,
}
perm.Kind, perm.Attribute, perm.Identifier = accesscontrol.SplitScope(perm.Scope)
basicRole.Permissions = append(basicRole.Permissions, perm)
}
} else {
s.log.Error("Unknown builtin role", "builtInRole", br)
}
@ -770,10 +778,14 @@ func (s *Service) DeleteExternalServiceRole(ctx context.Context, externalService
return s.store.DeleteExternalServiceRole(ctx, slug)
}
func (*Service) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscontrol.SyncUserRolesCommand) error {
func (s *Service) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscontrol.SyncUserRolesCommand) error {
return nil
}
func (s *Service) GetStaticRoles(ctx context.Context) map[string]*accesscontrol.RoleDTO {
return s.roles
}
func (s *Service) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) {
_, span := tracer.Start(ctx, "accesscontrol.acimpl.GetRoleByName")
defer span.End()

@ -373,6 +373,7 @@ func TestService_RegisterFixedRoles(t *testing.T) {
builtinRole, ok := ac.roles[br]
assert.True(t, ok)
for _, expectedPermission := range registration.Role.Permissions {
expectedPermission.Kind, expectedPermission.Attribute, expectedPermission.Identifier = accesscontrol.SplitScope(expectedPermission.Scope)
assert.Contains(t, builtinRole.Permissions, expectedPermission)
}
}

@ -38,6 +38,9 @@ type Calls struct {
}
type Mock struct {
accesscontrol.Service
accesscontrol.AccessControl
// Unless an override is provided, permissions will be returned by GetUserPermissions
permissions []accesscontrol.Permission
// Unless an override is provided, builtInRoles will be returned by GetUserBuiltInRoles

@ -18,7 +18,9 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/authz/rbac"
"github.com/grafana/grafana/pkg/services/authz/rbac/store"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/grpcserver"
"github.com/grafana/grafana/pkg/setting"
@ -30,8 +32,12 @@ const authzServiceAudience = "authzService"
// ProvideAuthZClient provides an AuthZ client and creates the AuthZ service.
func ProvideAuthZClient(
cfg *setting.Cfg, features featuremgmt.FeatureToggles, grpcServer grpcserver.Provider,
tracer tracing.Tracer, db db.DB,
cfg *setting.Cfg,
features featuremgmt.FeatureToggles,
grpcServer grpcserver.Provider,
tracer tracing.Tracer,
db db.DB,
acService accesscontrol.Service,
) (authzlib.AccessClient, error) {
authCfg, err := ReadCfg(cfg)
if err != nil {
@ -43,16 +49,25 @@ func ProvideAuthZClient(
return nil, errors.New("authZGRPCServer feature toggle is required for cloud and grpc mode")
}
// Register the server
sql := legacysql.NewDatabaseProvider(db)
server := rbac.NewService(sql, legacy.NewLegacySQLStores(sql), log.New("authz-grpc-server"), tracer)
switch authCfg.mode {
case ModeGRPC:
return newGrpcLegacyClient(authCfg, tracer)
case ModeCloud:
return newCloudLegacyClient(authCfg, tracer)
default:
sql := legacysql.NewDatabaseProvider(db)
// Register the server
server := rbac.NewService(
sql,
legacy.NewLegacySQLStores(sql),
store.NewUnionPermissionStore(
store.NewStaticPermissionStore(acService),
store.NewSQLPermissionStore(sql, tracer),
),
log.New("authz-grpc-server"),
tracer,
)
return newInProcLegacyClient(server, tracer)
}
}

@ -40,9 +40,10 @@ type Service struct {
authzv1.UnimplementedAuthzServiceServer
authzextv1.UnimplementedAuthzExtentionServiceServer
store store.Store
identityStore legacy.LegacyIdentityStore
actionMapper *mappers.K8sRbacMapper
store store.Store
permissionStore store.PermissionStore
identityStore legacy.LegacyIdentityStore
actionMapper *mappers.K8sRbacMapper
logger log.Logger
tracer tracing.Tracer
@ -58,19 +59,27 @@ type Service struct {
sf *singleflight.Group
}
func NewService(sql legacysql.LegacyDatabaseProvider, identityStore legacy.LegacyIdentityStore, logger log.Logger, tracer tracing.Tracer) *Service {
func NewService(
sql legacysql.LegacyDatabaseProvider,
identityStore legacy.LegacyIdentityStore,
permissionStore store.PermissionStore,
logger log.Logger,
tracer tracing.Tracer,
) *Service {
return &Service{
store: store.NewStore(sql, tracer),
identityStore: identityStore,
actionMapper: mappers.NewK8sRbacMapper(),
logger: logger,
tracer: tracer,
idCache: localcache.New(longCacheTTL, longCleanupInterval),
permCache: localcache.New(shortCacheTTL, shortCleanupInterval),
teamCache: localcache.New(shortCacheTTL, shortCleanupInterval),
basicRoleCache: localcache.New(longCacheTTL, longCleanupInterval),
folderCache: localcache.New(shortCacheTTL, shortCleanupInterval),
sf: new(singleflight.Group),
store: store.NewStore(sql, tracer),
permissionStore: permissionStore,
identityStore: identityStore,
actionMapper: mappers.NewK8sRbacMapper(),
logger: logger,
tracer: tracer,
idCache: localcache.New(longCacheTTL, longCleanupInterval),
permCache: localcache.New(shortCacheTTL, shortCleanupInterval),
teamCache: localcache.New(shortCacheTTL, shortCleanupInterval),
basicRoleCache: localcache.New(longCacheTTL, longCleanupInterval),
folderCache: localcache.New(shortCacheTTL, shortCleanupInterval),
sf: new(singleflight.Group),
}
}
@ -282,7 +291,7 @@ func (s *Service) getUserPermissions(ctx context.Context, ns claims.NamespaceInf
IsServerAdmin: basicRoles.IsAdmin,
}
permissions, err := s.store.GetUserPermissions(ctx, ns, userPermQuery)
permissions, err := s.permissionStore.GetUserPermissions(ctx, ns, userPermQuery)
if err != nil {
return nil, err
}
@ -311,7 +320,7 @@ func (s *Service) getAnonymousPermissions(ctx context.Context, ns claims.Namespa
}
res, err, _ := s.sf.Do(anonPermKey+"_getAnonymousPermissions", func() (interface{}, error) {
permissions, err := s.store.GetUserPermissions(ctx, ns, store.PermissionsQuery{Action: action, ActionSets: actionSets, Role: "Viewer"})
permissions, err := s.permissionStore.GetUserPermissions(ctx, ns, store.PermissionsQuery{Action: action, ActionSets: actionSets, Role: "Viewer"})
if err != nil {
return nil, err
}

@ -286,10 +286,11 @@ func TestService_getUserBasicRole(t *testing.T) {
}
s := &Service{
basicRoleCache: cacheService,
store: store,
logger: log.New("test"),
tracer: tracing.NewNoopTracerService(),
basicRoleCache: cacheService,
store: store,
permissionStore: store,
logger: log.New("test"),
tracer: tracing.NewNoopTracerService(),
}
role, err := s.getUserBasicRole(ctx, ns, userIdentifiers)
@ -361,16 +362,17 @@ func TestService_getUserPermissions(t *testing.T) {
}
s := &Service{
store: store,
identityStore: &fakeIdentityStore{teams: []int64{1, 2}},
actionMapper: mappers.NewK8sRbacMapper(),
logger: log.New("test"),
tracer: tracing.NewNoopTracerService(),
idCache: localcache.New(longCacheTTL, longCleanupInterval),
permCache: cacheService,
sf: new(singleflight.Group),
basicRoleCache: localcache.New(longCacheTTL, longCleanupInterval),
teamCache: localcache.New(shortCacheTTL, shortCleanupInterval),
store: store,
permissionStore: store,
identityStore: &fakeIdentityStore{teams: []int64{1, 2}},
actionMapper: mappers.NewK8sRbacMapper(),
logger: log.New("test"),
tracer: tracing.NewNoopTracerService(),
idCache: localcache.New(longCacheTTL, longCleanupInterval),
permCache: cacheService,
sf: new(singleflight.Group),
basicRoleCache: localcache.New(longCacheTTL, longCleanupInterval),
teamCache: localcache.New(shortCacheTTL, shortCleanupInterval),
}
perms, err := s.getUserPermissions(ctx, ns, claims.TypeUser, userID.UID, action)
@ -437,11 +439,12 @@ func TestService_buildFolderTree(t *testing.T) {
store := &fakeStore{folders: tc.folders}
s := &Service{
store: store,
folderCache: cacheService,
logger: log.New("test"),
sf: new(singleflight.Group),
tracer: tracing.NewNoopTracerService(),
store: store,
permissionStore: store,
folderCache: cacheService,
logger: log.New("test"),
sf: new(singleflight.Group),
tracer: tracing.NewNoopTracerService(),
}
tree, err := s.buildFolderTree(ctx, ns)

@ -10,16 +10,6 @@ type BasicRole struct {
IsAdmin bool
}
type PermissionsQuery struct {
OrgID int64
UserID int64
Action string
ActionSets []string
TeamIDs []int64
Role string
IsServerAdmin bool
}
type BasicRoleQuery struct {
UserID int64
OrgID int64

@ -0,0 +1,159 @@
package store
import (
"context"
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
type PermissionStore interface {
GetUserPermissions(ctx context.Context, ns claims.NamespaceInfo, query PermissionsQuery) ([]accesscontrol.Permission, error)
}
type PermissionsQuery struct {
OrgID int64
UserID int64
Action string
ActionSets []string
TeamIDs []int64
Role string
IsServerAdmin bool
}
func NewSQLPermissionStore(sql legacysql.LegacyDatabaseProvider, tracer tracing.Tracer) *SQLPermissionsStore {
return &SQLPermissionsStore{sql, tracer}
}
var _ PermissionStore = (*SQLPermissionsStore)(nil)
type SQLPermissionsStore struct {
sql legacysql.LegacyDatabaseProvider
tracer tracing.Tracer
}
var sqlUserPerms = mustTemplate("permission_query.sql")
type getPermissionsQuery struct {
sqltemplate.SQLTemplate
Query *PermissionsQuery
PermissionTable string
UserRoleTable string
TeamRoleTable string
BuiltinRoleTable string
}
func (r getPermissionsQuery) Validate() error {
return nil
}
func newGetPermissions(sql *legacysql.LegacyDatabaseHelper, q *PermissionsQuery) getPermissionsQuery {
return getPermissionsQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
Query: q,
PermissionTable: sql.Table("permission"),
UserRoleTable: sql.Table("user_role"),
TeamRoleTable: sql.Table("team_role"),
BuiltinRoleTable: sql.Table("builtin_role"),
}
}
func (s *SQLPermissionsStore) GetUserPermissions(ctx context.Context, ns claims.NamespaceInfo, query PermissionsQuery) ([]accesscontrol.Permission, error) {
ctx, span := s.tracer.Start(ctx, "authz_direct_db.database.GetUserPermissions")
defer span.End()
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
query.OrgID = ns.OrgID
req := newGetPermissions(sql, &query)
q, err := sqltemplate.Execute(sqlUserPerms, req)
if err != nil {
return nil, err
}
res, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
if err != nil {
return nil, err
}
defer func() {
if res != nil {
_ = res.Close()
}
}()
var perms []accesscontrol.Permission
for res.Next() {
var perm accesscontrol.Permission
if err := res.Scan(&perm.Kind, &perm.Attribute, &perm.Identifier, &perm.Scope); err != nil {
return nil, err
}
perms = append(perms, perm)
}
return perms, nil
}
var _ PermissionStore = (*StaticPermissionStore)(nil)
func NewStaticPermissionStore(ac accesscontrol.Service) *StaticPermissionStore {
return &StaticPermissionStore{ac}
}
type StaticPermissionStore struct {
ac accesscontrol.Service
}
func (s *StaticPermissionStore) GetUserPermissions(ctx context.Context, ns claims.NamespaceInfo, query PermissionsQuery) ([]accesscontrol.Permission, error) {
roles := []string{query.Role}
if query.IsServerAdmin {
roles = append(roles, "Grafana Admin")
}
static := s.ac.GetStaticRoles(ctx)
var permissions []accesscontrol.Permission
for _, name := range roles {
r, ok := static[name]
if !ok {
continue
}
for _, p := range r.Permissions {
if p.Action == query.Action {
permissions = append(permissions, p)
}
}
}
return permissions, nil
}
var _ PermissionStore = (*UnionPermissionStore)(nil)
func NewUnionPermissionStore(stores ...PermissionStore) *UnionPermissionStore {
return &UnionPermissionStore{stores}
}
type UnionPermissionStore struct {
stores []PermissionStore
}
func (u *UnionPermissionStore) GetUserPermissions(ctx context.Context, ns claims.NamespaceInfo, query PermissionsQuery) ([]accesscontrol.Permission, error) {
var permissions []accesscontrol.Permission
for _, s := range u.stores {
result, err := s.GetUserPermissions(ctx, ns, query)
if err != nil {
return nil, err
}
permissions = append(permissions, result...)
}
return permissions, nil
}

@ -14,7 +14,6 @@ var (
sqlTemplatesFS embed.FS
sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `*.sql`))
sqlUserPerms = mustTemplate("permission_query.sql")
sqlQueryBasicRoles = mustTemplate("basic_role_query.sql")
sqlUserIdentifiers = mustTemplate("user_identifier_query.sql")
sqlFolders = mustTemplate("folder_query.sql")
@ -67,31 +66,6 @@ func newGetBasicRoles(sql *legacysql.LegacyDatabaseHelper, q *BasicRoleQuery) ge
}
}
type getPermissionsQuery struct {
sqltemplate.SQLTemplate
Query *PermissionsQuery
PermissionTable string
UserRoleTable string
TeamRoleTable string
BuiltinRoleTable string
}
func (r getPermissionsQuery) Validate() error {
return nil
}
func newGetPermissions(sql *legacysql.LegacyDatabaseHelper, q *PermissionsQuery) getPermissionsQuery {
return getPermissionsQuery{
SQLTemplate: sqltemplate.New(sql.DialectForDriver()),
Query: q,
PermissionTable: sql.Table("permission"),
UserRoleTable: sql.Table("user_role"),
TeamRoleTable: sql.Table("team_role"),
BuiltinRoleTable: sql.Table("builtin_role"),
}
}
type getFoldersQuery struct {
sqltemplate.SQLTemplate
Query *FolderQuery

@ -7,13 +7,11 @@ import (
"golang.org/x/net/context"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/storage/legacysql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
)
type Store interface {
GetUserPermissions(ctx context.Context, ns claims.NamespaceInfo, query PermissionsQuery) ([]accesscontrol.Permission, error)
GetUserIdentifiers(ctx context.Context, query UserIdentifierQuery) (*UserIdentifiers, error)
GetBasicRoles(ctx context.Context, ns claims.NamespaceInfo, query BasicRoleQuery) (*BasicRole, error)
GetFolders(ctx context.Context, ns claims.NamespaceInfo) ([]Folder, error)
@ -31,44 +29,6 @@ func NewStore(sql legacysql.LegacyDatabaseProvider, tracer tracing.Tracer) *Stor
}
}
func (s *StoreImpl) GetUserPermissions(ctx context.Context, ns claims.NamespaceInfo, query PermissionsQuery) ([]accesscontrol.Permission, error) {
ctx, span := s.tracer.Start(ctx, "authz_direct_db.database.GetUserPermissions")
defer span.End()
sql, err := s.sql(ctx)
if err != nil {
return nil, err
}
query.OrgID = ns.OrgID
req := newGetPermissions(sql, &query)
q, err := sqltemplate.Execute(sqlUserPerms, req)
if err != nil {
return nil, err
}
res, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...)
if err != nil {
return nil, err
}
defer func() {
if res != nil {
_ = res.Close()
}
}()
var perms []accesscontrol.Permission
for res.Next() {
var perm accesscontrol.Permission
if err := res.Scan(&perm.Kind, &perm.Attribute, &perm.Identifier, &perm.Scope); err != nil {
return nil, err
}
perms = append(perms, perm)
}
return perms, nil
}
func (s *StoreImpl) GetUserIdentifiers(ctx context.Context, query UserIdentifierQuery) (*UserIdentifiers, error) {
ctx, span := s.tracer.Start(ctx, "authz_direct_db.database.GetUserIdentifiers")
defer span.End()

@ -8,12 +8,19 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/rbac"
"github.com/grafana/grafana/pkg/services/authz/rbac/store"
"github.com/grafana/grafana/pkg/services/grpcserver"
"github.com/grafana/grafana/pkg/storage/legacysql"
)
func RegisterRBACAuthZService(handler grpcserver.Provider, db legacysql.LegacyDatabaseProvider, tracer tracing.Tracer) {
server := rbac.NewService(db, legacy.NewLegacySQLStores(db), log.New("authz-grpc-server"), tracer)
server := rbac.NewService(
db,
legacy.NewLegacySQLStores(db),
store.NewSQLPermissionStore(db, tracer),
log.New("authz-grpc-server"),
tracer,
)
srv := handler.GetServer()
authzv1.RegisterAuthzServiceServer(srv, server)

@ -442,14 +442,7 @@ func (c *K8sTestHelper) LoadYAMLOrJSON(body string) *unstructured.Unstructured {
func (c *K8sTestHelper) createTestUsers(orgName string) OrgUsers {
c.t.Helper()
users := OrgUsers{
Admin: c.CreateUser("admin", orgName, org.RoleAdmin, []resourcepermissions.SetResourcePermissionCommand{
{
Actions: []string{"dashboards:read", "dashboards:write", "dashboards:create", "dashboards:delete"},
Resource: "dashboards",
ResourceAttribute: "uid",
ResourceID: "*",
},
}),
Admin: c.CreateUser("admin", orgName, org.RoleAdmin, nil),
Editor: c.CreateUser("editor", orgName, org.RoleEditor, nil),
Viewer: c.CreateUser("viewer", orgName, org.RoleViewer, nil),
}

Loading…
Cancel
Save