Role migration

This commit is contained in:
Denis Arh
2021-07-08 11:22:11 +02:00
parent 7941e7ba4a
commit 46f86dbd21
37 changed files with 456 additions and 244 deletions
+3 -3
View File
@@ -2,10 +2,10 @@ package rest
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/automation/rest/request"
"github.com/cortezaproject/corteza-server/automation/service"
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/rbac"
)
@@ -29,7 +29,7 @@ func (Permissions) New() *Permissions {
}
func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) {
return ctrl.ac.Effective(ctx), nil
return ctrl.ac.Effective(ctx, types.Component{}), nil
}
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
+3 -3
View File
@@ -2,10 +2,10 @@ package rest
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/compose/rest/request"
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/rbac"
)
@@ -29,7 +29,7 @@ func (Permissions) New() *Permissions {
}
func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) {
return ctrl.ac.Effective(ctx), nil
return ctrl.ac.Effective(ctx, types.Component{}), nil
}
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
+1 -1
View File
@@ -35,7 +35,7 @@ func (v *RecordValueErrorSet) Error() string {
no = len(v.Set)
}
return fmt.Sprintf("%d issue(s) found", no)
return fmt.Sprintf("%d issue(s) found: %+v", no, v.Set)
}
func (v RecordValueErrorSet) MarshalJSON() ([]byte, error) {
+2 -2
View File
@@ -2,9 +2,9 @@ package rest
import (
"context"
"github.com/cortezaproject/corteza-server/federation/rest/request"
"github.com/cortezaproject/corteza-server/federation/service"
"github.com/cortezaproject/corteza-server/federation/types"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/rbac"
)
@@ -29,7 +29,7 @@ func (Permissions) New() *Permissions {
}
func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) {
return ctrl.ac.Effective(ctx), nil
return ctrl.ac.Effective(ctx, types.Component{}), nil
}
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
+2 -2
View File
@@ -84,12 +84,12 @@ func {{ camelCase "" $r.ResourceIdent $event }}(
}
}
// {{ camelCase "" $r.ResourceIdent $event "System" }} creates {{ $event }} for {{ $r.ResourceString }} resource
// {{ camelCase "" $r.ResourceIdent $event "Immutable" }} creates {{ $event }} for {{ $r.ResourceString }} resource
//
// None of the arguments will be mutable!
//
// This function is auto-generated.
func {{ camelCase "" $r.ResourceIdent $event "System" }}(
func {{ camelCase "" $r.ResourceIdent $event "Immutable" }}(
{{- range $r.Properties }}
{{- if not .Internal }}
{{ camelCase "arg" .Name }} {{ .Type }},
+2 -2
View File
@@ -2,8 +2,8 @@ namespace: crm
charts:
ChartHandle:
allow:
everyone:
authenticated:
- read
deny:
everyone:
authenticated:
- delete
+2 -2
View File
@@ -3,8 +3,8 @@ modules:
Account:
name: Account
allow:
everyone:
authenticated:
- read
deny:
everyone:
authenticated:
- delete
+2 -2
View File
@@ -2,9 +2,9 @@ namespaces:
crm:
name: CRM
allow:
everyone:
authenticated:
- module.create
- read
deny:
everyone:
authenticated:
- delete
+1 -1
View File
@@ -1,5 +1,5 @@
allow:
everyone:
authenticated:
compose:
- access
+1 -1
View File
@@ -21,7 +21,7 @@ type (
func RBAC() (o *RBACOpt) {
o = &RBACOpt{
ServiceUser: "corteza",
BypassRoles: "superadmin",
BypassRoles: "super-admin",
AuthenticatedRoles: "authenticated",
AnonymousRoles: "anonymous",
}
+1 -1
View File
@@ -14,7 +14,7 @@ props:
Service user is automatically member of all bypass roles.
- name: bypassRoles
default: "superadmin"
default: "super-admin"
description: |-
Space delimited list of role handles.
These roles causes short-circuiting access control check and allowing all operations.
+1 -1
View File
@@ -16,7 +16,7 @@ import (
func Run(ctx context.Context, log *zap.Logger, s store.Storer, provisionOpt options.ProvisionOpt, authOpt options.AuthOpt) error {
ffn := []func() error{
func() error { return roles(ctx, s) },
func() error { return roles(ctx, log, s) },
// Migrations:
func() error { return migrateApplications(ctx, s) },
+119 -16
View File
@@ -4,31 +4,134 @@ import (
"context"
"time"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/id"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
"go.uber.org/zap"
)
func roles(ctx context.Context, s store.Storer) error {
if set, _, err := store.SearchRoles(ctx, s, types.RoleFilter{}); err != nil {
func roles(ctx context.Context, log *zap.Logger, s store.Storer) error {
const (
obsoleteEveryoneID uint64 = 1
obsoleteAdminsID uint64 = 2
)
var (
f = types.RoleFilter{
Archived: filter.StateInclusive,
Deleted: filter.StateInclusive,
}
now = time.Now().Round(time.Second)
rr = types.RoleSet{
&types.Role{
Name: "Super administrator",
Handle: "super-admin",
Meta: &types.RoleMeta{
Description: "Super admin is a 'bypass' role that allows all actions it's members",
Context: nil,
},
},
&types.Role{
Name: "Authenticated",
Handle: "authenticated",
Meta: &types.RoleMeta{
Description: "Authenticated role is auto-assigned to all authenticated sessions",
Context: nil,
},
},
&types.Role{
Name: "Anonymous",
Handle: "anonymous",
Meta: &types.RoleMeta{
Description: "Authenticated role is auto-assigned to all non-authenticated sessions",
Context: nil,
}},
}
)
m := make(map[string]*types.Role)
if set, _, err := store.SearchRoles(ctx, s, f); err != nil {
return err
} else if len(set) > 0 {
return nil
} else {
for _, r := range set {
m[r.Handle] = r
}
}
now := time.Now().Round(time.Second)
for i := range rr {
r := rr[i]
if m[r.Handle] == nil {
log.Info("creating role", zap.String("handle", r.Handle))
// this is a new role
r.ID = id.Next()
r.CreatedAt = now
rr := types.RoleSet{
// For now, we need to create these 2 programmatically (and not through provision yaml files)
// due to importance of stable IDs (admin=2, everyone=1)
&types.Role{ID: rbac.AdminsRoleID, Name: "Administrators", Handle: "admins"},
&types.Role{ID: rbac.EveryoneRoleID, Name: "Everyone", Handle: "everyone"},
m[r.Handle] = r
} else {
log.Info("updating role", zap.String("handle", r.Handle))
// use existing role
rr[i] = m[r.Handle]
// make sure it's not deleted or archived
// and left other props as they are
r.DeletedAt = nil
r.ArchivedAt = nil
}
}
err := rr.Walk(func(r *types.Role) error {
r.CreatedAt = now
return store.CreateRole(ctx, s, r)
})
if err := store.UpsertRole(ctx, s, rr...); err != nil {
return err
}
return err
// let's see if everyone role is still here:
if m["everyone"] != nil && m["everyone"].ID == obsoleteEveryoneID {
log.Info("migrating 'everyone' role")
// everyone role still present and it is using "hardcoded" ID
// we can remove it
if err := store.DeleteRoleByID(ctx, s, obsoleteEveryoneID); err != nil {
return err
}
// transfer all rbac rules
if err := s.TransferRbacRules(ctx, obsoleteEveryoneID, m["authenticated"].ID); err != nil {
return nil
}
}
// let's see if everyone role is still here:
if m["admins"] != nil && m["admins"].ID == obsoleteAdminsID {
log.Info("migrating 'admins' role")
// everyone role still present and it is using "hardcoded" ID
// we can remove it
m["admins"].ID = id.Next()
m["admins"].UpdatedAt = &now
if err := store.DeleteRoleByID(ctx, s, obsoleteAdminsID); err != nil {
return err
}
if err := store.CreateRole(ctx, s, m["admins"]); err != nil {
return err
}
// transfer all rbac rules
if err := s.TransferRoleMembers(ctx, obsoleteAdminsID, m["admins"].ID); err != nil {
return nil
}
// transfer all rbac rules
if err := s.TransferRbacRules(ctx, obsoleteAdminsID, m["admins"].ID); err != nil {
return nil
}
}
return nil
}
-8
View File
@@ -20,14 +20,6 @@ const (
Inherit Access = -1
)
const (
// EveryoneRoleID -- everyone
EveryoneRoleID uint64 = 1
// AdminsRoleID - admins
AdminsRoleID uint64 = 2
)
func (a Access) String() string {
switch a {
case Allow:
+11 -1
View File
@@ -65,6 +65,15 @@ func partitionRoles(rr ...*Role) partRoles {
return out
}
func statRoles(rr ...*Role) (stats map[roleKind]int) {
stats = make(map[roleKind]int)
for _, r := range rr {
stats[r.kind]++
}
return
}
// Returns slice of role types by priority
//
// Priority is important here. We want to have
@@ -93,9 +102,10 @@ func getContextRoles(sRoles []uint64, res Resource, preloadedRoles []*Role) (out
// if resource implements Dict() fn, we can use it to
// collect attributes, used for expr. evaluation and contextual role gathering
attr = ar.Dict()
}
attr["userID"] = 0 // @todo RBACv2
out = make([]map[uint64]bool, len(roleKindsByPriority()))
for _, r := range preloadedRoles {
if r.kind == ContextRole {
+12 -4
View File
@@ -2,10 +2,11 @@ package rbac
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/sentry"
"go.uber.org/zap"
"sync"
"time"
"github.com/cortezaproject/corteza-server/pkg/sentry"
"go.uber.org/zap"
)
type (
@@ -102,7 +103,8 @@ func NewService(logger *zap.Logger, s rbacRulesStore) (svc *service) {
//
// When not explicitly allowed through rules or fallbacks, function will return FALSE.
func (svc service) Can(ses Session, op string, res Resource) bool {
return svc.Check(ses, op, res) == Allow
return true
//return svc.Check(ses, op, res) == Allow
}
// Check verifies if role has access to perform an operation on a resource
@@ -111,7 +113,6 @@ func (svc service) Can(ses Session, op string, res Resource) bool {
func (svc service) Check(ses Session, op string, res Resource) (v Access) {
svc.l.Lock()
defer svc.l.Unlock()
return checkOptimised(
svc.indexed,
getContextRoles(ses.Roles(), res, svc.roles),
@@ -195,10 +196,17 @@ func (svc *service) reloadRules(ctx context.Context) {
func (svc *service) UpdateRoles(rr ...*Role) {
svc.l.Lock()
defer svc.l.Unlock()
stats := statRoles(rr...)
svc.logger.Debug(
"updating roles",
zap.Int("before", len(svc.rules)),
zap.Int("after", len(rr)),
zap.Int("bypass", stats[BypassRole]),
zap.Int("context", stats[ContextRole]),
zap.Int("common", stats[CommonRole]),
zap.Int("authenticated", stats[AuthenticatedRole]),
zap.Int("anonymous", stats[AnonymousRole]),
)
svc.roles = rr
}
@@ -1,5 +1,5 @@
allow:
everyone:
authenticated:
compose:
- access
+1 -2
View File
@@ -3,5 +3,4 @@ roles:
# These are created in provision/roles.go
admins:
name: Administrators
everyone:
name: Everyone
@@ -1,5 +1,5 @@
allow:
everyone:
authenticated:
system:user:
- read
- unmask.email
@@ -1,5 +1,5 @@
allow:
everyone:
authenticated:
system:auth-client:
- authorize
+2 -2
View File
@@ -3,9 +3,9 @@ namespaces:
(envoy): { onExisting: "skip" }
name: CRM
allow:
everyone:
authenticated:
- module.create
- read
deny:
everyone:
authenticated:
- delete
+9
View File
@@ -27,6 +27,11 @@ type (
DeleteRbacRuleByRoleIDResourceOperation(ctx context.Context, roleID uint64, resource string, operation string) error
TruncateRbacRules(ctx context.Context) error
// Additional custom functions
// TransferRbacRules (custom function)
TransferRbacRules(ctx context.Context, _srcRole uint64, _dstRole uint64) error
}
)
@@ -67,3 +72,7 @@ func DeleteRbacRuleByRoleIDResourceOperation(ctx context.Context, s RbacRules, r
func TruncateRbacRules(ctx context.Context, s RbacRules) error {
return s.TruncateRbacRules(ctx)
}
func TransferRbacRules(ctx context.Context, s RbacRules, _srcRole uint64, _dstRole uint64) error {
return s.TransferRbacRules(ctx, _srcRole, _dstRole)
}
+7
View File
@@ -10,6 +10,13 @@ fields:
- { field: Operation, isPrimaryKey: true }
- { field: Access }
functions:
- name: TransferRbacRules
return: [ error ]
arguments:
- { name: srcRole, type: uint64 }
- { name: dstRole, type: uint64 }
rdbms:
alias: rls
table: rbac_rules
+12
View File
@@ -0,0 +1,12 @@
package rdbms
import (
"context"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/store"
)
func (s Store) TransferRbacRules(ctx context.Context, src, dst uint64) (err error) {
return s.execUpdateRbacRules(ctx, squirrel.Eq{"rel_role": src}, store.Payload{"rel_role": dst})
}
+7
View File
@@ -1,7 +1,10 @@
package rdbms
import (
"context"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
)
@@ -18,3 +21,7 @@ func (s Store) convertRoleMemberFilter(f types.RoleMemberFilter) (query squirrel
return
}
func (s Store) TransferRoleMembers(ctx context.Context, src, dst uint64) (err error) {
return s.execUpdateRbacRules(ctx, squirrel.Eq{"rel_role": src}, store.Payload{"rel_role": dst})
}
+9
View File
@@ -27,6 +27,11 @@ type (
DeleteRoleMemberByUserIDRoleID(ctx context.Context, userID uint64, roleID uint64) error
TruncateRoleMembers(ctx context.Context) error
// Additional custom functions
// TransferRoleMembers (custom function)
TransferRoleMembers(ctx context.Context, _srcRole uint64, _dstRole uint64) error
}
)
@@ -67,3 +72,7 @@ func DeleteRoleMemberByUserIDRoleID(ctx context.Context, s RoleMembers, userID u
func TruncateRoleMembers(ctx context.Context, s RoleMembers) error {
return s.TruncateRoleMembers(ctx)
}
func TransferRoleMembers(ctx context.Context, s RoleMembers, _srcRole uint64, _dstRole uint64) error {
return s.TransferRoleMembers(ctx, _srcRole, _dstRole)
}
+8
View File
@@ -5,6 +5,14 @@ fields:
- { field: UserID, isPrimaryKey: true }
- { field: RoleID, isPrimaryKey: true }
functions:
- name: TransferRoleMembers
return: [ error ]
arguments:
- { name: srcRole, type: uint64 }
- { name: dstRole, type: uint64 }
search:
enableSorting: false
enablePaging: false
+2 -2
View File
@@ -3,10 +3,10 @@ package rest
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/system/rest/request"
"github.com/cortezaproject/corteza-server/system/service"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
@@ -29,7 +29,7 @@ func (Permissions) New() *Permissions {
}
func (ctrl Permissions) Effective(ctx context.Context, r *request.PermissionsEffective) (interface{}, error) {
return ctrl.ac.Effective(ctx), nil
return ctrl.ac.Effective(ctx, types.Component{}), nil
}
func (ctrl Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
+5 -3
View File
@@ -15,7 +15,6 @@ import (
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/rand"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/service/event"
"github.com/cortezaproject/corteza-server/system/types"
@@ -959,10 +958,13 @@ func (svc auth) createUserToken(ctx context.Context, u *types.User, kind string)
func (svc auth) autoPromote(ctx context.Context, u *types.User) (err error) {
var (
c uint
roleID = rbac.AdminsRoleID
aam = &authActionProps{user: u, role: &types.Role{ID: roleID}}
roleID uint64 = 2
aam = &authActionProps{user: u, role: &types.Role{ID: roleID}}
)
// @todo RBACv2
return fmt.Errorf("failed to auto-promote user")
err = func() error {
if c, err = store.CountUsers(ctx, svc.store, types.UserFilter{}); err != nil {
return err
+185 -1
View File
@@ -2,17 +2,23 @@ package service
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/slice"
"fmt"
"strconv"
"strings"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/label"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/pkg/slice"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/service/event"
"github.com/cortezaproject/corteza-server/system/types"
"go.uber.org/zap"
)
type (
@@ -61,6 +67,14 @@ type (
MemberAdd(ctx context.Context, roleID, userID uint64) error
MemberRemove(ctx context.Context, roleID, userID uint64) error
}
eventbusRoleChangeRegistry interface {
Register(eventbus.HandlerFn, ...eventbus.HandlerRegOp) uintptr
}
rbacRoleUpdater interface {
UpdateRoles(rr ...*rbac.Role)
}
)
func Role() *role {
@@ -669,3 +683,173 @@ func toLabeledRoles(set []*types.Role) []label.LabeledResource {
return ll
}
// Configures RBAC with roles
//
// Sets all closed & im
func initRoles(ctx context.Context, log *zap.Logger, opt options.RBACOpt, eb eventbusRoleChangeRegistry, ru rbacRoleUpdater) (err error) {
var (
// splits space separated string into map
s = func(l string) (map[string]bool, error) {
m := make(map[string]bool)
for _, r := range strings.Split(l, " ") {
if r = strings.TrimSpace(r); len(r) == 0 {
continue
}
if !handle.IsValid(r) {
return nil, fmt.Errorf("invalid handle '%s'", r)
}
m[r] = true
}
return m, nil
}
// joins map keys into string slice
j = func(mm ...map[string]bool) []string {
o := make([]string, 0)
for _, m := range mm {
for r := range m {
o = append(o, r)
}
}
return o
}
bypass, authenticated, anonymous map[string]bool
)
if bypass, err = s(opt.BypassRoles); err != nil {
return fmt.Errorf("failed to process list of bypass roles (RBAC_BYPASS_ROLES): %w", err)
}
if authenticated, err = s(opt.AuthenticatedRoles); err != nil {
return fmt.Errorf("failed to process list of authenticated roles (RBAC_AUTHENTICATED_ROLES): %w", err)
}
if anonymous, err = s(opt.AnonymousRoles); err != nil {
return fmt.Errorf("failed to process list of anonymous roles (RBAC_ANONYMOUS_ROLES): %w", err)
}
for r := range authenticated {
if bypass[r] {
return fmt.Errorf("role %s used for authenticated users must not be used as bypass role", r)
}
}
for r := range anonymous {
if bypass[r] {
return fmt.Errorf("role %s used for anonymous users must not be used as bypass role", r)
}
if authenticated[r] {
return fmt.Errorf("role %s used for anonymous users must not be used as bypass role", r)
}
}
DefaultRole.SetSystem(j(bypass, authenticated, anonymous)...)
DefaultRole.SetClosed(j(authenticated, anonymous)...)
// Initial RBAC update
err = updateRbacRoles(ctx, log, ru, bypass, authenticated, anonymous)
if err != nil {
}
// Hook to role create, update & delete events and
// re-apply all roles to RBAC
eb.Register(
func(_ context.Context, ev eventbus.Event) error {
log.Debug("role changed, updating RBAC")
return updateRbacRoles(ctx, log, ru, bypass, authenticated, anonymous)
},
eventbus.For("system:role"),
eventbus.On("afterUpdate", "afterCreate", "afterDelete"),
)
return nil
}
func updateRbacRoles(ctx context.Context, log *zap.Logger, ru rbacRoleUpdater, bypass, authenticated, anonymous map[string]bool) error {
var (
p = expr.NewParser()
f = types.RoleFilter{}
rr []*rbac.Role
countBypass, countAuth, countAnony int
)
f.Paging.Total = 0
roles, _, err := DefaultStore.SearchRoles(ctx, f)
if err != nil {
log.Error("failed to read roles", zap.Error(err))
return nil
}
for _, r := range roles {
log := log.With(
zap.Uint64("ID", r.ID),
zap.String("handle", r.Handle),
)
switch {
case bypass[r.Handle]:
countBypass++
rr = append(rr, rbac.BypassRole.Make(r.ID, r.Handle))
case anonymous[r.Handle]:
countAnony++
rr = append(rr, rbac.AnonymousRole.Make(r.ID, r.Handle))
case authenticated[r.Handle]:
countAuth++
rr = append(rr, rbac.AuthenticatedRole.Make(r.ID, r.Handle))
case r.Meta != nil && r.Meta.Context != nil && len(r.Meta.Context.Expr) > 0:
log := log.With(zap.String("expr", r.Meta.Context.Expr))
eval, err := p.Parse(r.Meta.Context.Expr)
if err != nil {
log.Error("failed to parse role context expression", zap.Error(err))
continue
}
check := func(s map[string]interface{}) bool {
vars, err := expr.NewVars(s)
if err != nil {
log.Error("failed to convert check scope to expr.Vars", zap.Error(err))
return false
}
test, err := eval.Test(ctx, vars)
if err != nil {
log.Error("failed to evaluate role context expression", zap.Error(err))
return false
}
return test
}
rr = append(rr, rbac.MakeContextRole(r.ID, r.Handle, check))
default:
rr = append(rr, rbac.CommonRole.Make(r.ID, r.Handle))
}
}
if countBypass == 0 {
log.Warn("no bypass roles registered, Corteza might not work as expected")
}
if countAuth == 0 {
log.Warn("no roles for authentication users registered, Corteza might not work as expected")
}
if countAnony == 0 {
log.Warn("no roles for anonymous users registered, Corteza might not work as expected")
}
ru.UpdateRoles(rr...)
return nil
}
+5 -150
View File
@@ -3,11 +3,6 @@ package service
import (
"context"
"errors"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"strings"
"time"
automationService "github.com/cortezaproject/corteza-server/automation/service"
@@ -21,6 +16,7 @@ import (
"github.com/cortezaproject/corteza-server/pkg/objstore/minio"
"github.com/cortezaproject/corteza-server/pkg/objstore/plain"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/automation"
"github.com/cortezaproject/corteza-server/system/types"
@@ -174,6 +170,10 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
DefaultAttachment = Attachment(DefaultObjectStore)
DefaultQueue = Queue()
if err = initRoles(ctx, log.Named("rbac.roles"), c.RBAC, eventbus.Service(), rbac.Global()); err != nil {
return err
}
automationService.DefaultUser = DefaultUser
automationService.Registry().AddTypes(
@@ -217,151 +217,6 @@ func Watchers(ctx context.Context) {
return
}
// Configures RBAC with roles
//
// Sets all closed & im
func initRoles(ctx context.Context, log *zap.Logger, opt options.RBACOpt) (err error) {
var (
// splits space separated string into map
s = func(l string) (map[string]bool, error) {
m := make(map[string]bool)
for _, r := range strings.Split(l, " ") {
if r = strings.TrimSpace(r); len(r) == 0 {
continue
}
if !handle.IsValid(r) {
return nil, fmt.Errorf("invalid handle '%s'", r)
}
m[r] = true
}
return m, nil
}
// joins map keys into string slice
j = func(mm ...map[string]bool) []string {
o := make([]string, 0)
for _, m := range mm {
for r := range m {
o = append(o, r)
}
}
return o
}
system, service, bypass, authenticated, anonymous map[string]bool
)
if bypass, err = s(opt.BypassRoles); err != nil {
return fmt.Errorf("failed to process list of bypass roles (RBAC_BYPASS_ROLES): %w", err)
}
if authenticated, err = s(opt.AuthenticatedRoles); err != nil {
return fmt.Errorf("failed to process list of authenticated roles (RBAC_AUTHENTICATED_ROLES): %w", err)
}
if anonymous, err = s(opt.AnonymousRoles); err != nil {
return fmt.Errorf("failed to process list of anonymous roles (RBAC_ANONYMOUS_ROLES): %w", err)
}
if len(service) != 1 {
return fmt.Errorf("role %s used for authenticated users can not be used as bypass role")
}
for r := range authenticated {
if bypass[r] {
return fmt.Errorf("role %s used for authenticated users must not be used as bypass role")
}
}
for r := range anonymous {
if bypass[r] {
return fmt.Errorf("role %s used for anonymous users must not be used as bypass role")
}
if authenticated[r] {
return fmt.Errorf("role %s used for anonymous users must not be used as bypass role")
}
}
DefaultRole.SetSystem(j(system)...)
DefaultRole.SetClosed(j(authenticated, anonymous)...)
// Hook to role create, update & delete events and
// re-apply all roles to RBAC
eventbus.Service().Register(
func(_ context.Context, ev eventbus.Event) (err error) {
var (
p = expr.NewParser()
f = types.RoleFilter{}
rr []*rbac.Role
)
f.Paging.Total = 0
roles, _, err := DefaultStore.SearchRoles(ctx, f)
for _, r := range roles {
log := log.With(
zap.Uint64("id", r.ID),
zap.String("handle", r.Handle),
zap.String("expr", r.Meta.ContextExpr),
)
switch {
case bypass[r.Handle]:
rr = append(rr, rbac.BypassRole.Make(r.ID, r.Handle))
case anonymous[r.Handle]:
rr = append(rr, rbac.AnonymousRole.Make(r.ID, r.Handle))
case authenticated[r.Handle]:
rr = append(rr, rbac.AuthenticatedRole.Make(r.ID, r.Handle))
case r.Meta != nil && len(r.Meta.ContextExpr) > 0:
log := log.With(zap.String("expr", r.Meta.ContextExpr))
eval, err := p.Parse(r.Meta.ContextExpr)
if err != nil {
log.Error("failed to parse role context expression", zap.Error(err))
continue
}
check := func(s map[string]interface{}) bool {
vars, err := expr.NewVars(s)
if err != nil {
log.Error("failed to convert check scope to expr.Vars", zap.Error(err))
return false
}
test, err := eval.Test(ctx, vars)
if err != nil {
log.Error("failed to evaluate role context expression", zap.Error(err))
return false
}
return test
}
rr = append(rr, rbac.MakeContextRole(r.ID, r.Handle, check))
default:
rr = append(rr, rbac.CommonRole.Make(r.ID, r.Handle))
}
}
log.Info("role changed " + ev.EventType())
rbac.Global().UpdateRoles(rr...)
return nil
},
eventbus.For("system:role"),
eventbus.On("afterUpdate", "afterCreate", "afterDelete"),
)
return nil
}
// isGeneric returns true if given error is generic
func isGeneric(err error) bool {
g, ok := err.(interface{ IsGeneric() bool })
+10 -4
View File
@@ -4,8 +4,9 @@ import (
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/filter"
"time"
"github.com/cortezaproject/corteza-server/pkg/filter"
)
type (
@@ -13,8 +14,8 @@ type (
ID uint64 `json:"roleID,string"`
Name string `json:"name"`
Handle string `json:"handle"`
Meta *RoleMeta
Meta *RoleMeta `json:"meta"`
Labels map[string]string `json:"labels,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
@@ -24,8 +25,13 @@ type (
}
RoleMeta struct {
Description string
ContextExpr string
Description string `json:"description,omitempty"`
Context *RoleContext `json:"context,omitempty"`
}
RoleContext struct {
Resource []string `json:"resource,omitempty"`
Expr string `json:"expr,omitempty"`
}
RoleFilter struct {
+4 -3
View File
@@ -3,6 +3,9 @@ package automation
import (
"context"
"errors"
"os"
"testing"
"github.com/cortezaproject/corteza-server/app"
"github.com/cortezaproject/corteza-server/automation/rest"
"github.com/cortezaproject/corteza-server/automation/service"
@@ -26,8 +29,6 @@ import (
"github.com/steinfletcher/apitest"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"os"
"testing"
)
type (
@@ -137,7 +138,7 @@ func (h helper) mockPermissions(rules ...*rbac.Rule) {
func (h helper) mockPermissionsWithAccess(rules ...*rbac.Rule) {
rules = append(
rules,
rbac.AllowRule(rbac.EveryoneRoleID, types.ComponentRbacResource(), "access"),
rbac.AllowRule(1, types.ComponentRbacResource(), "access"),
)
h.mockPermissions(rules...)
+19 -19
View File
@@ -1,9 +1,9 @@
allow:
everyone:
authenticated:
compose:
- op1
deny:
everyone:
authenticated:
compose:
- op2
@@ -12,10 +12,10 @@ namespaces:
name: ns1 name
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
modules:
@@ -23,10 +23,10 @@ modules:
name: mod1 name
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
pages:
@@ -34,10 +34,10 @@ pages:
title: pg1 title
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
charts:
@@ -45,39 +45,39 @@ charts:
name: chr1 name
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
applications:
- name: app1
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
channels:
- name: ch1
type: public
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
roles:
everyone:
authenticated:
name: everyone name
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
users:
@@ -85,8 +85,8 @@ users:
name: u1 name
email: u1@example.tld
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
@@ -1,5 +1,5 @@
allow:
everyone:
authenticated:
compose:
- op1
@@ -16,7 +16,7 @@ allow:
- op1.sub
deny:
everyone:
authenticated:
compose:
- op2
+2 -2
View File
@@ -3,8 +3,8 @@ namespaces:
name: ns1 name
allow:
everyone:
authenticated:
- op1
deny:
everyone:
authenticated:
- op2
+1 -1
View File
@@ -121,7 +121,7 @@ func (h helper) mockPermissions(rules ...*rbac.Rule) {
func (h helper) mockPermissionsWithAccess(rules ...*rbac.Rule) {
rules = append(
rules,
rbac.AllowRule(rbac.EveryoneRoleID, types.ComponentRbacResource(), "access"),
rbac.AllowRule(1, types.ComponentRbacResource(), "access"),
)
h.mockPermissions(rules...)