Fix migration of rbac resource rules
This commit is contained in:
parent
dada6e40da
commit
46e8f4d283
@ -224,7 +224,10 @@ func preloadResourceIndex(ctx context.Context, s store.Storer) (*resourceIndex,
|
||||
rx := &resourceIndex{}
|
||||
|
||||
rx.modules = make(map[uint64]*composeTypes.Module)
|
||||
modules, _, err := store.SearchComposeModules(ctx, s, composeTypes.ModuleFilter{Paging: filter.Paging{Limit: 0}})
|
||||
modules, _, err := store.SearchComposeModules(ctx, s, composeTypes.ModuleFilter{
|
||||
Paging: filter.Paging{Limit: 0},
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
modIDs := make([]uint64, 0, len(modules))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -236,7 +239,10 @@ func preloadResourceIndex(ctx context.Context, s store.Storer) (*resourceIndex,
|
||||
|
||||
if len(modIDs) > 0 {
|
||||
rx.fields = make(map[uint64]*composeTypes.ModuleField)
|
||||
fields, _, err := store.SearchComposeModuleFields(ctx, s, composeTypes.ModuleFieldFilter{ModuleID: modIDs})
|
||||
fields, _, err := store.SearchComposeModuleFields(ctx, s, composeTypes.ModuleFieldFilter{
|
||||
ModuleID: modIDs,
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -246,7 +252,10 @@ func preloadResourceIndex(ctx context.Context, s store.Storer) (*resourceIndex,
|
||||
}
|
||||
|
||||
rx.charts = make(map[uint64]*composeTypes.Chart)
|
||||
chart, _, err := store.SearchComposeCharts(ctx, s, composeTypes.ChartFilter{Paging: filter.Paging{Limit: 0}})
|
||||
chart, _, err := store.SearchComposeCharts(ctx, s, composeTypes.ChartFilter{
|
||||
Paging: filter.Paging{Limit: 0},
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -255,7 +264,10 @@ func preloadResourceIndex(ctx context.Context, s store.Storer) (*resourceIndex,
|
||||
}
|
||||
|
||||
rx.pages = make(map[uint64]*composeTypes.Page)
|
||||
page, _, err := store.SearchComposePages(ctx, s, composeTypes.PageFilter{Paging: filter.Paging{Limit: 0}})
|
||||
page, _, err := store.SearchComposePages(ctx, s, composeTypes.PageFilter{
|
||||
Paging: filter.Paging{Limit: 0},
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
340
pkg/provision/migrations_202203_rbac_resource_fix.go
Normal file
340
pkg/provision/migrations_202203_rbac_resource_fix.go
Normal file
@ -0,0 +1,340 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
composeTypes "github.com/cortezaproject/corteza-server/compose/types"
|
||||
federationTypes "github.com/cortezaproject/corteza-server/federation/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
resIndex struct {
|
||||
fields map[uint64]*composeTypes.ModuleField
|
||||
modules map[uint64]*composeTypes.Module
|
||||
records map[uint64]*composeTypes.Record
|
||||
charts map[uint64]*composeTypes.Chart
|
||||
pages map[uint64]*composeTypes.Page
|
||||
exposedModules map[uint64]*federationTypes.ExposedModule
|
||||
sharedModules map[uint64]*federationTypes.SharedModule
|
||||
}
|
||||
)
|
||||
|
||||
// MigrateOperations
|
||||
func migratePost202203RbacRules(ctx context.Context, log *zap.Logger, s store.Storer) error {
|
||||
return store.Tx(ctx, s, func(ctx context.Context, s store.Storer) error {
|
||||
rr, _, err := store.SearchRbacRules(ctx, s, rbac.RuleFilter{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("migrating RBAC resource rules to proper format", zap.Int("rules", len(rr)))
|
||||
|
||||
rx, err := preloadRbacResourceIndex(ctx, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var uniq = make(map[string]bool)
|
||||
var uniqID = func(r *rbac.Rule) string {
|
||||
return fmt.Sprintf("%s|%s|%d", r.Resource, r.Operation, r.RoleID)
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
var (
|
||||
cr = *r
|
||||
action = migratePost202203RbacRule(r, rx)
|
||||
)
|
||||
|
||||
if action != 0 {
|
||||
err = store.DeleteRbacRule(ctx, s, &cr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not delete RBAC rule %s: %v", r, err)
|
||||
}
|
||||
|
||||
if action == -1 {
|
||||
log.Debug("removed obsolete RBAC rule", zap.Stringer("rule", r))
|
||||
}
|
||||
}
|
||||
|
||||
if action == 1 {
|
||||
if uniq[uniqID(r)] {
|
||||
log.Warn("skipping duplicate RBAC rule", zap.Stringer("rule", r))
|
||||
continue
|
||||
}
|
||||
|
||||
err = store.CreateRbacRule(ctx, s, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create RBAC rule %s: %v", r, err)
|
||||
}
|
||||
|
||||
uniq[uniqID(r)] = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// 0 - no action
|
||||
// -1 - remove
|
||||
// 1 - update
|
||||
func migratePost202203RbacRule(r *rbac.Rule, rx *resIndex) (op int) {
|
||||
const (
|
||||
wildCard = "*"
|
||||
)
|
||||
|
||||
if r.Resource == "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
// split the IDs
|
||||
parts := strings.SplitN(r.Resource, "/", 4)
|
||||
parts = parts[1:]
|
||||
|
||||
isWildCard := func(s string) bool { return strings.Contains(s, wildCard) }
|
||||
parseUint := func(s string) uint64 {
|
||||
ID, _ := strconv.ParseUint(s, 10, 64)
|
||||
return ID
|
||||
}
|
||||
validID := func(i uint64) bool { return i > 0 }
|
||||
|
||||
invalid := false
|
||||
for i, p := range parts {
|
||||
if i == 0 || len(p) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !invalid {
|
||||
invalid = isWildCard(parts[i-1]) && !isWildCard(p)
|
||||
}
|
||||
}
|
||||
|
||||
if invalid {
|
||||
op = 1
|
||||
|
||||
partsLen := len(parts)
|
||||
ID := uint64(0)
|
||||
p1 := uint64(0)
|
||||
p2 := uint64(0)
|
||||
|
||||
if partsLen == 1 {
|
||||
ID = parseUint(parts[0])
|
||||
} else if partsLen == 2 {
|
||||
p1 = parseUint(parts[0])
|
||||
ID = parseUint(parts[1])
|
||||
} else if partsLen == 3 {
|
||||
p1 = parseUint(parts[0])
|
||||
p2 = parseUint(parts[1])
|
||||
ID = parseUint(parts[2])
|
||||
}
|
||||
|
||||
rType := rbac.ResourceType(r.Resource)
|
||||
switch rType {
|
||||
case composeTypes.ModuleFieldResourceType:
|
||||
if ID > 0 {
|
||||
if f, ok := rx.fields[ID]; ok {
|
||||
p2 = f.ModuleID
|
||||
if m, ok := rx.modules[p2]; ok {
|
||||
p1 = m.NamespaceID
|
||||
if !validID(p1) {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
r.Resource = composeTypes.ModuleFieldRbacResource(p1, p2, ID)
|
||||
|
||||
case composeTypes.ModuleResourceType:
|
||||
if ID > 0 {
|
||||
if r, ok := rx.modules[ID]; ok {
|
||||
p1 = r.NamespaceID
|
||||
if !validID(p1) {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
r.Resource = composeTypes.ModuleRbacResource(p1, ID)
|
||||
|
||||
case composeTypes.RecordResourceType:
|
||||
if ID > 0 {
|
||||
if r, ok := rx.records[ID]; ok {
|
||||
p2 = r.ModuleID
|
||||
if m, ok := rx.modules[p2]; ok {
|
||||
p1 = m.NamespaceID
|
||||
if !validID(p1) {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
r.Resource = composeTypes.RecordRbacResource(p1, p2, ID)
|
||||
|
||||
case composeTypes.ChartResourceType:
|
||||
if ID > 0 {
|
||||
if r, ok := rx.charts[ID]; ok {
|
||||
p1 = r.NamespaceID
|
||||
if !validID(p1) {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
r.Resource = composeTypes.ChartRbacResource(p1, ID)
|
||||
|
||||
case composeTypes.PageResourceType:
|
||||
if ID > 0 {
|
||||
if r, ok := rx.pages[ID]; ok {
|
||||
p1 = r.NamespaceID
|
||||
if !validID(p1) {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
r.Resource = composeTypes.PageRbacResource(p1, ID)
|
||||
|
||||
case federationTypes.ExposedModuleResourceType:
|
||||
if ID > 0 {
|
||||
if r, ok := rx.exposedModules[ID]; ok {
|
||||
p1 = r.NodeID
|
||||
if !validID(p1) {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
r.Resource = federationTypes.ExposedModuleRbacResource(p1, ID)
|
||||
|
||||
case federationTypes.SharedModuleResourceType:
|
||||
if ID > 0 {
|
||||
if r, ok := rx.sharedModules[ID]; ok {
|
||||
p1 = r.NodeID
|
||||
if !validID(p1) {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
r.Resource = federationTypes.SharedModuleRbacResource(p1, ID)
|
||||
|
||||
default:
|
||||
r.Resource = rType + "/" + func() string {
|
||||
if ID == 0 {
|
||||
return "*"
|
||||
}
|
||||
return strconv.FormatUint(ID, 10)
|
||||
}()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// helper to preload resources that may be used when properly constructing rules
|
||||
func preloadRbacResourceIndex(ctx context.Context, s store.Storer) (*resIndex, error) {
|
||||
rx := &resIndex{}
|
||||
|
||||
rx.modules = make(map[uint64]*composeTypes.Module)
|
||||
modules, _, err := store.SearchComposeModules(ctx, s, composeTypes.ModuleFilter{
|
||||
Paging: filter.Paging{Limit: 0},
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
modIDs := make([]uint64, 0, len(modules))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range modules {
|
||||
rx.modules[r.ID] = r
|
||||
modIDs = append(modIDs, r.ID)
|
||||
|
||||
rx.records = make(map[uint64]*composeTypes.Record)
|
||||
records, _, err := store.SearchComposeRecords(ctx, s, r, composeTypes.RecordFilter{
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rec := range records {
|
||||
rx.records[rec.ID] = rec
|
||||
}
|
||||
}
|
||||
|
||||
if len(modIDs) > 0 {
|
||||
rx.fields = make(map[uint64]*composeTypes.ModuleField)
|
||||
fields, _, err := store.SearchComposeModuleFields(ctx, s, composeTypes.ModuleFieldFilter{
|
||||
ModuleID: modIDs,
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range fields {
|
||||
rx.fields[r.ID] = r
|
||||
}
|
||||
}
|
||||
|
||||
rx.charts = make(map[uint64]*composeTypes.Chart)
|
||||
chart, _, err := store.SearchComposeCharts(ctx, s, composeTypes.ChartFilter{
|
||||
Paging: filter.Paging{Limit: 0},
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range chart {
|
||||
rx.charts[r.ID] = r
|
||||
}
|
||||
|
||||
rx.pages = make(map[uint64]*composeTypes.Page)
|
||||
page, _, err := store.SearchComposePages(ctx, s, composeTypes.PageFilter{
|
||||
Paging: filter.Paging{Limit: 0},
|
||||
Deleted: filter.StateInclusive,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range page {
|
||||
rx.pages[r.ID] = r
|
||||
}
|
||||
|
||||
rx.exposedModules = make(map[uint64]*federationTypes.ExposedModule)
|
||||
exposedModule, _, err := store.SearchFederationExposedModules(ctx, s, federationTypes.ExposedModuleFilter{Paging: filter.Paging{Limit: 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range exposedModule {
|
||||
rx.exposedModules[r.ID] = r
|
||||
}
|
||||
|
||||
rx.sharedModules = make(map[uint64]*federationTypes.SharedModule)
|
||||
sharedModule, _, err := store.SearchFederationSharedModules(ctx, s, federationTypes.SharedModuleFilter{Paging: filter.Paging{Limit: 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range sharedModule {
|
||||
rx.sharedModules[r.ID] = r
|
||||
}
|
||||
|
||||
return rx, nil
|
||||
}
|
||||
39
pkg/provision/migrations_202203_rbac_resource_fix_test.go
Normal file
39
pkg/provision/migrations_202203_rbac_resource_fix_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
composeTypes "github.com/cortezaproject/corteza-server/compose/types"
|
||||
federationTypes "github.com/cortezaproject/corteza-server/federation/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
)
|
||||
|
||||
func Test_migratePost202203RbacRules(t *testing.T) {
|
||||
rx := &resIndex{
|
||||
fields: make(map[uint64]*composeTypes.ModuleField),
|
||||
modules: make(map[uint64]*composeTypes.Module),
|
||||
charts: make(map[uint64]*composeTypes.Chart),
|
||||
pages: make(map[uint64]*composeTypes.Page),
|
||||
exposedModules: make(map[uint64]*federationTypes.ExposedModule),
|
||||
sharedModules: make(map[uint64]*federationTypes.SharedModule),
|
||||
}
|
||||
|
||||
tcc := []struct {
|
||||
wantOp int
|
||||
rule *rbac.Rule
|
||||
wantRule *rbac.Rule
|
||||
}{
|
||||
{-1, rbac.AllowRule(0, "corteza::compose:module/*/123", "op"), rbac.AllowRule(0, "corteza::compose:module/*/123", "op")},
|
||||
{-1, rbac.AllowRule(0, "corteza::compose:module-field/*/*/123", "op"), rbac.AllowRule(0, "corteza::compose:module-field/*/*/123", "op")},
|
||||
{-1, rbac.AllowRule(0, "corteza::compose:record/*/*/234", "op"), rbac.AllowRule(0, "corteza::compose:record/*/*/234", "op")},
|
||||
}
|
||||
for _, tc := range tcc {
|
||||
t.Run(tc.rule.String(), func(t *testing.T) {
|
||||
require.Equal(t, tc.wantOp, migratePost202203RbacRule(tc.rule, rx))
|
||||
if tc.wantRule != nil {
|
||||
require.Equal(t, tc.wantRule.String(), tc.rule.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -33,6 +33,9 @@ func Run(ctx context.Context, log *zap.Logger, s store.Storer, provisionOpt opti
|
||||
func() error { return cleanupPre202109Settings(ctx, log.Named("pre-202109-settings"), s) },
|
||||
func() error { return migrateResourceTranslations(ctx, log.Named("resource-translations"), s) },
|
||||
func() error { return migrateReportIdentifiers(ctx, log.Named("report-identifiers"), s) },
|
||||
func() error {
|
||||
return migratePost202203RbacRules(ctx, log.Named("post-202203-rbac-resource-rules-fix"), s)
|
||||
},
|
||||
func() error {
|
||||
return migratePost202203ResourceTranslations(ctx, log.Named("post-202203-resource-translations"), s)
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user