diff --git a/pkg/envoy/resource/api_gateway.go b/pkg/envoy/resource/api_gateway.go index 4fe5a6a5a..07407ee57 100644 --- a/pkg/envoy/resource/api_gateway.go +++ b/pkg/envoy/resource/api_gateway.go @@ -2,6 +2,7 @@ package resource import ( "fmt" + "strconv" "github.com/cortezaproject/corteza-server/system/types" ) @@ -37,6 +38,14 @@ func NewAPIGateway(res *types.ApigwRoute) *APIGateway { return r } +func (r *APIGateway) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = nil + resource = fmt.Sprintf(types.ApigwRouteRbacResourceTpl(), types.ApigwRouteResourceType, firstOkString(strconv.FormatUint(r.Res.ID, 10))) + + return +} + func (r *APIGateway) AddGatewayFilter(res *types.ApigwFilter) *APIGatewayFilter { f := &APIGatewayFilter{ base: &base{}, diff --git a/pkg/envoy/resource/application.go b/pkg/envoy/resource/application.go index b47914930..8913e41dd 100644 --- a/pkg/envoy/resource/application.go +++ b/pkg/envoy/resource/application.go @@ -2,6 +2,7 @@ package resource import ( "fmt" + "strconv" "github.com/cortezaproject/corteza-server/system/types" ) @@ -37,6 +38,14 @@ func (r *Application) SysID() uint64 { return r.Res.ID } +func (r *Application) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = nil + resource = fmt.Sprintf(types.ApplicationRbacResourceTpl(), types.ApplicationResourceType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Name)) + + return +} + // FindApplication looks for the app in the resource set func FindApplication(rr InterfaceSet, ii Identifiers) (ap *types.Application) { var apRes *Application diff --git a/pkg/envoy/resource/automation_workflow.go b/pkg/envoy/resource/automation_workflow.go index ec0f3fa24..6a8243f31 100644 --- a/pkg/envoy/resource/automation_workflow.go +++ b/pkg/envoy/resource/automation_workflow.go @@ -73,6 +73,14 @@ func (r *AutomationWorkflow) AddAutomationTrigger(res *types.Trigger) *Automatio return t } +func (r *AutomationWorkflow) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = nil + resource = fmt.Sprintf(types.WorkflowRbacResourceTpl(), types.WorkflowResourceType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) + + return +} + func (r *AutomationWorkflow) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { ref = r.Ref() path = nil diff --git a/pkg/envoy/resource/compose_chart.go b/pkg/envoy/resource/compose_chart.go index 7642793d7..20b957d28 100644 --- a/pkg/envoy/resource/compose_chart.go +++ b/pkg/envoy/resource/compose_chart.go @@ -43,8 +43,12 @@ func (r *ComposeChart) SysID() uint64 { return r.Res.ID } -func (r *ComposeChart) RBACPath() []*Ref { - return []*Ref{r.RefNs} +func (r *ComposeChart) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = []*Ref{r.RefNs} + resource = fmt.Sprintf(types.ChartRbacResourceTpl(), types.ChartResourceType, r.RefNs.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) + + return } // func (r *ComposeChart) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { diff --git a/pkg/envoy/resource/compose_module.go b/pkg/envoy/resource/compose_module.go index d1917a076..39abd0550 100644 --- a/pkg/envoy/resource/compose_module.go +++ b/pkg/envoy/resource/compose_module.go @@ -80,9 +80,12 @@ func (r *ComposeModule) SysID() uint64 { return r.Res.ID } -// @todo name -func (r *ComposeModule) RBACPath() []*Ref { - return []*Ref{r.RefNs} +func (r *ComposeModule) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = []*Ref{r.RefNs} + resource = fmt.Sprintf(types.ModuleRbacResourceTpl(), types.ModuleResourceType, r.RefNs.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) + + return } func (r *ComposeModule) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { @@ -133,46 +136,33 @@ func (r *ComposeModule) AddField(f *ComposeModuleField) { r.ResFields = append(r.ResFields, f) } -// func FindComposeModuleField(mod *ComposeModule, ii Identifiers) (f *ComposeModuleField) { -// for _, f := range mod.ResFields { -// if f.Identifiers().HasAny(ii) { -// return f -// } -// } +// FindComposeModule looks for the module in the resource set +func FindComposeModuleField(rr InterfaceSet, mod, ii Identifiers) (field *types.ModuleField) { + var fieldRes *ComposeModuleField -// return nil -// } - -// FindComposeModuleField looks for the module field in the given module -func FindComposeModuleField(mod *types.Module, ii Identifiers) (f *types.ModuleField) { - ids := make(map[uint64]bool) - handles := make(map[string]bool) - for i := range ii { - auxID, err := cast.ToUint64E(i) - if err == nil { - ids[auxID] = true - continue + rr.Walk(func(r Interface) error { + mr, ok := r.(*ComposeModule) + if !ok { + return nil } - handles[i] = true - } - - var ok bool - ff, _ := mod.Fields.Filter(func(mf *types.ModuleField) (bool, error) { - if _, ok = ids[mf.ID]; ok { - return true, nil - } - if _, ok = handles[mf.Name]; ok { - return true, nil + if !mr.Identifiers().HasAny(mod) { + return nil } - return false, nil + for _, rf := range mr.ResFields { + if rf.Identifiers().HasAny(ii) { + fieldRes = rf + } + } + return nil }) - if len(ff) == 0 { - return nil + // Found it + if fieldRes != nil { + return fieldRes.Res } - return ff[0] + return nil } func (r *ComposeModuleField) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { @@ -217,6 +207,10 @@ func NewComposeModuleField(res *types.ModuleField, nsRef, modRef string) *Compos // Initial timestamps r.SetTimestamps(MakeTimestampsCUDA(&res.CreatedAt, res.UpdatedAt, res.DeletedAt, nil)) + res.ID = 0 + res.NamespaceID = 0 + res.ModuleID = 0 + return r } diff --git a/pkg/envoy/resource/compose_namespace.go b/pkg/envoy/resource/compose_namespace.go index 37b5d3e42..b1e79ccb7 100644 --- a/pkg/envoy/resource/compose_namespace.go +++ b/pkg/envoy/resource/compose_namespace.go @@ -27,6 +27,14 @@ func NewComposeNamespace(ns *types.Namespace) *ComposeNamespace { return r } +func (r *ComposeNamespace) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = nil + resource = fmt.Sprintf(types.NamespaceRbacResourceTpl(), types.NamespaceResourceType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Slug)) + + return +} + func (r *ComposeNamespace) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { ref = r.Ref() path = nil diff --git a/pkg/envoy/resource/compose_page.go b/pkg/envoy/resource/compose_page.go index 102dcce0a..021c3c0f5 100644 --- a/pkg/envoy/resource/compose_page.go +++ b/pkg/envoy/resource/compose_page.go @@ -150,8 +150,12 @@ func (r *ComposePage) SysID() uint64 { return r.Res.ID } -func (r *ComposePage) RBACPath() []*Ref { - return []*Ref{r.RefNs} +func (r *ComposePage) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = []*Ref{r.RefNs} + resource = fmt.Sprintf(types.PageRbacResourceTpl(), types.PageResourceType, r.RefNs.Identifiers.First(), firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) + + return } func (r *ComposePage) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { diff --git a/pkg/envoy/resource/compose_record.go b/pkg/envoy/resource/compose_record.go index 357510302..005bf0136 100644 --- a/pkg/envoy/resource/compose_record.go +++ b/pkg/envoy/resource/compose_record.go @@ -3,6 +3,7 @@ package resource import ( "fmt" + "github.com/cortezaproject/corteza-server/compose/types" composeTypes "github.com/cortezaproject/corteza-server/compose/types" systemTypes "github.com/cortezaproject/corteza-server/system/types" ) @@ -71,8 +72,13 @@ func (r *ComposeRecord) SetUserFlakes(uu UserstampIndex) { r.AddRef(systemTypes.UserResourceType, "*") } -func (r *ComposeRecord) RBACPath() []*Ref { - return []*Ref{r.RefNs, r.RefMod} +func (r *ComposeRecord) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = []*Ref{r.RefNs, r.RefMod} + // @todo specific records + resource = fmt.Sprintf(types.RecordRbacResourceTpl(), types.RecordResourceType, r.RefNs.Identifiers.First(), r.RefMod.Identifiers.First(), "*") + + return } func FindComposeRecordResource(rr InterfaceSet, ii Identifiers) (rec *ComposeRecord) { diff --git a/pkg/envoy/resource/rbac_rule.go b/pkg/envoy/resource/rbac_rule.go index 7e429ea50..7259da5ba 100644 --- a/pkg/envoy/resource/rbac_rule.go +++ b/pkg/envoy/resource/rbac_rule.go @@ -13,21 +13,23 @@ type ( *base Res *rbac.Rule + RefResource string + RefRes *Ref RefRole *Ref - RefResource *Ref RefPath []*Ref } ) -func NewRbacRule(res *rbac.Rule, refRole string, resRef *Ref, refPath ...*Ref) *RbacRule { +func NewRbacRule(res *rbac.Rule, refRole string, refRes *Ref, refResource string, refPath ...*Ref) *RbacRule { r := &RbacRule{base: &base{}} r.SetResourceType(RbacResourceType) r.Res = res r.RefRole = r.AddRef(types.RoleResourceType, refRole) - if resRef != nil { - r.RefResource = r.AddRef(resRef.ResourceType, resRef.Identifiers.StringSlice()...) + r.RefResource = refResource + if refRes != nil { + r.RefRes = r.AddRef(refRes.ResourceType, refRes.Identifiers.StringSlice()...) } // any additional constraints @@ -35,14 +37,41 @@ func NewRbacRule(res *rbac.Rule, refRole string, resRef *Ref, refPath ...*Ref) * r.RefPath = append(r.RefPath, r.AddRef(rp.ResourceType, rp.Identifiers.StringSlice()...)) } - // ComposeRecords are internally grouped and identified with the module identifier. - if res.Resource == composeTypes.RecordResourceType && resRef != nil && len(refPath) == 2 { - r.AddRef(res.Resource, refPath[1].Identifiers.StringSlice()...) + // Handle cases for nested resources + if refRes != nil { + switch refRes.ResourceType { + case composeTypes.RecordResourceType: + r.handleComposeRecord(res, refPath) + case composeTypes.ModuleFieldResourceType: + r.handleComposeModuleField(res, refPath) + } } return r } +func (r *RbacRule) handleComposeRecord(res *rbac.Rule, refPath []*Ref) { + + // records are grouped under module + + if len(refPath) < 2 { + return + } + + r.AddRef(composeTypes.RecordResourceType, refPath[1].Identifiers.StringSlice()...) +} + +func (r *RbacRule) handleComposeModuleField(res *rbac.Rule, refPath []*Ref) { + + // module fields are grouped under module + + if len(refPath) < 2 { + return + } + + r.AddRef(composeTypes.ModuleFieldResourceTranslationType, refPath[1].Identifiers.StringSlice()...) +} + func RbacResourceErrNotFound(ii Identifiers) error { return fmt.Errorf("rbac resource not found %v", ii.StringSlice()) } diff --git a/pkg/envoy/resource/report.go b/pkg/envoy/resource/report.go index aa453fc20..7d2eea55f 100644 --- a/pkg/envoy/resource/report.go +++ b/pkg/envoy/resource/report.go @@ -54,6 +54,14 @@ func (r *Report) AddReportSource(res *types.ReportDataSource) *ReportSource { return s } +func (r *Report) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = nil + resource = fmt.Sprintf(types.ReportRbacResourceTpl(), types.ReportResourceType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) + + return +} + func (r *Report) ResourceTranslationParts() (resource string, ref *Ref, path []*Ref) { ref = r.Ref() path = nil diff --git a/pkg/envoy/resource/resource_translation.go b/pkg/envoy/resource/resource_translation.go index eb222918c..6b62b1622 100644 --- a/pkg/envoy/resource/resource_translation.go +++ b/pkg/envoy/resource/resource_translation.go @@ -12,7 +12,6 @@ type ( *base Res types.ResourceTranslationSet - // RelResource string RefResource string RefRes *Ref RefPath []*Ref diff --git a/pkg/envoy/resource/role.go b/pkg/envoy/resource/role.go index 06d3b024b..0497ee48d 100644 --- a/pkg/envoy/resource/role.go +++ b/pkg/envoy/resource/role.go @@ -2,6 +2,7 @@ package resource import ( "fmt" + "strconv" "github.com/cortezaproject/corteza-server/system/types" ) @@ -31,6 +32,14 @@ func (r *Role) SysID() uint64 { return r.Res.ID } +func (r *Role) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = nil + resource = fmt.Sprintf(types.RoleRbacResourceTpl(), types.RoleResourceType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) + + return +} + // FindRole looks for the role in the resources func FindRole(rr InterfaceSet, ii Identifiers) (rl *types.Role) { var rlRes *Role diff --git a/pkg/envoy/resource/template.go b/pkg/envoy/resource/template.go index f480aa032..351b36774 100644 --- a/pkg/envoy/resource/template.go +++ b/pkg/envoy/resource/template.go @@ -2,6 +2,7 @@ package resource import ( "fmt" + "strconv" "github.com/cortezaproject/corteza-server/system/types" ) @@ -31,6 +32,14 @@ func (r *Template) SysID() uint64 { return r.Res.ID } +func (r *Template) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = nil + resource = fmt.Sprintf(types.TemplateRbacResourceTpl(), types.TemplateResourceType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle)) + + return +} + // FindTemplate looks for the template in the resources func FindTemplate(rr InterfaceSet, ii Identifiers) (u *types.Template) { var tRes *Template diff --git a/pkg/envoy/resource/types.go b/pkg/envoy/resource/types.go index 9a9e30625..193fc2673 100644 --- a/pkg/envoy/resource/types.go +++ b/pkg/envoy/resource/types.go @@ -29,7 +29,7 @@ type ( RBACInterface interface { Interface - RBACPath() []*Ref + RBACParts() (string, *Ref, []*Ref) } LocaleInterface interface { diff --git a/pkg/envoy/resource/user.go b/pkg/envoy/resource/user.go index 70ce18d37..c0fc1dac8 100644 --- a/pkg/envoy/resource/user.go +++ b/pkg/envoy/resource/user.go @@ -2,6 +2,7 @@ package resource import ( "fmt" + "strconv" "github.com/cortezaproject/corteza-server/system/types" ) @@ -31,6 +32,14 @@ func (r *User) SysID() uint64 { return r.Res.ID } +func (r *User) RBACParts() (resource string, ref *Ref, path []*Ref) { + ref = r.Ref() + path = nil + resource = fmt.Sprintf(types.UserRbacResourceTpl(), types.UserResourceType, firstOkString(strconv.FormatUint(r.Res.ID, 10), r.Res.Handle, r.Res.Username)) + + return +} + // FindUser looks for the user in the resources func FindUser(rr InterfaceSet, ii Identifiers) (u *types.User) { var uRes *User diff --git a/pkg/envoy/resource/util.go b/pkg/envoy/resource/util.go index 24b6d4895..6bf842efc 100644 --- a/pkg/envoy/resource/util.go +++ b/pkg/envoy/resource/util.go @@ -6,7 +6,7 @@ import ( func firstOkString(ss ...string) string { for _, s := range ss { - if s != "" { + if s != "" && s != "0" { return s } } diff --git a/pkg/envoy/store/compose_module_marshal.go b/pkg/envoy/store/compose_module_marshal.go index 52907c04c..67f9e453e 100644 --- a/pkg/envoy/store/compose_module_marshal.go +++ b/pkg/envoy/store/compose_module_marshal.go @@ -167,7 +167,7 @@ func (n *composeModule) Encode(ctx context.Context, pl *payload) (err error) { f.DeletedAt = nil f.CreatedAt = *now() - if f.Kind == "Record" { + if f.Options != nil && f.Kind == "Record" { refMod := f.Options.String("module") if refMod == "" { refMod = f.Options.String("moduleID") @@ -186,7 +186,7 @@ func (n *composeModule) Encode(ctx context.Context, pl *payload) (err error) { delete(f.Options, "module") } - if f.Kind == "User" { + if f.Options != nil && f.Kind == "User" { roles := resource.ComposeModuleFieldExtractUserFieldRoles(f.Options["roles"]) if len(roles) == 0 { roles = resource.ComposeModuleFieldExtractUserFieldRoles(f.Options["role"]) diff --git a/pkg/envoy/store/rbac_rule.go b/pkg/envoy/store/rbac_rule.go index 466dd8e90..b178a9293 100644 --- a/pkg/envoy/store/rbac_rule.go +++ b/pkg/envoy/store/rbac_rule.go @@ -12,13 +12,16 @@ type ( rbacRule struct { cfg *EncoderConfig - res *resource.RbacRule rule *rbac.Rule - refRes *resource.Ref + // point to the rbac rule + refRbacResource string + refRbacRes *resource.Ref + refPathRes []*resource.Ref - relRole *types.Role + refRole *resource.Ref + role *types.Role } ) diff --git a/pkg/envoy/store/rbac_rule_marshal.go b/pkg/envoy/store/rbac_rule_marshal.go index 6a5378f40..7196ac016 100644 --- a/pkg/envoy/store/rbac_rule_marshal.go +++ b/pkg/envoy/store/rbac_rule_marshal.go @@ -3,6 +3,7 @@ package store import ( "context" "fmt" + "strings" automationTypes "github.com/cortezaproject/corteza-server/automation/types" composeTypes "github.com/cortezaproject/corteza-server/compose/types" @@ -16,54 +17,62 @@ import ( var ( gRbacRules map[string]bool - - rbacRuleIndex = func(r *rbac.Rule) string { - return fmt.Sprintf("%d_%s_%s", r.RoleID, r.Resource, r.Operation) - } ) func newRbacRuleFromResource(res *resource.RbacRule, cfg *EncoderConfig) resourceState { return &rbacRule{ cfg: mergeConfig(cfg, res.Config()), - res: res, rule: res.Res, + + refRbacResource: res.RefResource, + refRbacRes: res.RefRes, + + refPathRes: res.RefPath, + + refRole: res.RefRole, } } func (n *rbacRule) Prepare(ctx context.Context, pl *payload) (err error) { // Init global state if gRbacRules == nil { - gRbacRules = make(map[string]bool) - rr, _, err := store.SearchRbacRules(ctx, pl.s, rbac.RuleFilter{}) - if err != store.ErrNotFound && err != nil { - return err - } - for _, r := range rr { - gRbacRules[rbacRuleIndex(r)] = true + err = n.initGlobalIndex(ctx, pl.s) + if err != nil { + return } } // Related role - n.relRole, err = findRole(ctx, pl.s, pl.state.ParentResources, n.res.RefRole.Identifiers) + n.role, err = findRole(ctx, pl.s, pl.state.ParentResources, n.refRole.Identifiers) if err != nil { return err } - if n.relRole == nil { - return resource.RoleErrUnresolved(n.res.RefRole.Identifiers) + if n.role == nil { + return resource.RoleErrUnresolved(n.refRole.Identifiers) + } + + refRes := n.refRbacRes + if refRes == nil { + return } // For now we will only allow resource specific RBAC rules if that resource is // also present. // Here we check if we can find it in case we're handling a resource specific rule. - refRes := n.res.RefResource - if refRes != nil && refRes.ResourceType == composeTypes.RecordResourceType { - for _, r := range pl.state.ParentResources { - if r.ResourceType() == composeTypes.RecordResourceType && r.Identifiers().HasAny(n.res.RefPath[1].Identifiers) { - return - } + found := false + switch refRes.ResourceType { + case composeTypes.RecordResourceType: + if found = n.handleComposeRecord(pl.state.ParentResources); !found { + return resource.RbacResourceErrNotFound(refRes.Identifiers) } - } else if refRes != nil && len(refRes.Identifiers) > 0 { + + case composeTypes.ModuleFieldResourceType: + if found = n.handleComposeModuleField(pl.state.ParentResources); !found { + return resource.RbacResourceErrNotFound(refRes.Identifiers) + } + + default: for _, r := range pl.state.ParentResources { if refRes.ResourceType == r.ResourceType() && r.Identifiers().HasAny(refRes.Identifiers) { return @@ -77,236 +86,25 @@ func (n *rbacRule) Prepare(ctx context.Context, pl *payload) (err error) { } func (n *rbacRule) Encode(ctx context.Context, pl *payload) (err error) { - res := n.res.Res - - // Related role - res.RoleID = n.relRole.ID - if res.RoleID == 0 { - rl := resource.FindRole(pl.state.ParentResources, n.res.RefRole.Identifiers) - res.RoleID = rl.ID - } - if res.RoleID == 0 { - return resource.RoleErrUnresolved(n.res.RefRole.Identifiers) + // Assure correct resource + n.rule.Resource, err = n.makeRBACResource(pl) + if err != nil { + return err } - p0ID := uint64(0) - p1ID := uint64(0) - p2ID := uint64(0) - - switch n.rule.Resource { - case composeTypes.ComponentResourceType: - res.Resource = composeTypes.ComponentRbacResource() - goto store - case systemTypes.ComponentResourceType: - res.Resource = systemTypes.ComponentRbacResource() - goto store - case automationTypes.ComponentResourceType: - res.Resource = automationTypes.ComponentRbacResource() - goto store - case federationTypes.ComponentResourceType: - res.Resource = federationTypes.ComponentRbacResource() - goto store + // Assure correct role + n.rule.RoleID = n.role.ID + if n.rule.RoleID == 0 { + rl := resource.FindRole(pl.state.ParentResources, n.refRole.Identifiers) + n.rule.RoleID = rl.ID + } + if n.rule.RoleID == 0 { + return resource.RoleErrUnresolved(n.refRole.Identifiers) } - switch n.rule.Resource { - case automationTypes.WorkflowResourceType: - if n.res.RefResource != nil { - p1 := resource.FindAutomationWorkflow(pl.state.ParentResources, n.res.RefResource.Identifiers) - if p1 == nil { - return resource.AutomationWorkflowErrUnresolved(n.res.RefResource.Identifiers) - } - p1ID = p1.ID - } - - res.Resource = automationTypes.WorkflowRbacResource(p1ID) - - case composeTypes.NamespaceResourceType: - if n.res.RefResource != nil { - p1 := resource.FindComposeNamespace(pl.state.ParentResources, n.res.RefResource.Identifiers) - if p1 == nil { - return resource.ComposeNamespaceErrUnresolved(n.res.RefResource.Identifiers) - } - p1ID = p1.ID - } - - res.Resource = composeTypes.NamespaceRbacResource(p1ID) - case composeTypes.ModuleResourceType: - if len(n.res.RefPath) > 0 { - p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.res.RefPath[0].Identifiers) - if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(n.res.RefPath[0].Identifiers) - } - p0ID = p0.ID - } - - if n.res.RefResource != nil { - p1 := resource.FindComposeModule(pl.state.ParentResources, n.res.RefResource.Identifiers) - if p1 == nil { - return resource.ComposeModuleErrUnresolved(n.res.RefResource.Identifiers) - } - p1ID = p1.ID - } - - res.Resource = composeTypes.ModuleRbacResource(p0ID, p1ID) - case composeTypes.ChartResourceType: - if len(n.res.RefPath) > 0 { - p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.res.RefPath[0].Identifiers) - if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(n.res.RefPath[0].Identifiers) - } - p0ID = p0.ID - } - - if n.res.RefResource != nil { - p1 := resource.FindComposeChart(pl.state.ParentResources, n.res.RefResource.Identifiers) - if p1 == nil { - return resource.ComposeChartErrUnresolved(n.res.RefResource.Identifiers) - } - p1ID = p1.ID - } - - res.Resource = composeTypes.ChartRbacResource(p0ID, p1ID) - case composeTypes.PageResourceType: - if len(n.res.RefPath) > 0 { - p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.res.RefPath[0].Identifiers) - if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(n.res.RefPath[0].Identifiers) - } - p0ID = p0.ID - } - - if n.res.RefResource != nil { - p1 := resource.FindComposePage(pl.state.ParentResources, n.res.RefResource.Identifiers) - if p1 == nil { - return resource.ComposePageErrUnresolved(n.res.RefResource.Identifiers) - } - p1ID = p1.ID - } - - res.Resource = composeTypes.PageRbacResource(p0ID, p1ID) - case composeTypes.RecordResourceType: - if len(n.res.RefPath) > 0 { - p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.res.RefPath[0].Identifiers) - if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(n.res.RefPath[0].Identifiers) - } - p0ID = p0.ID - } - if len(n.res.RefPath) > 1 { - p1 := resource.FindComposeModule(pl.state.ParentResources, n.res.RefPath[1].Identifiers) - if p1 == nil { - return resource.ComposeModuleErrUnresolved(n.res.RefPath[1].Identifiers) - } - p1ID = p1.ID - } - if n.res.RefResource != nil { - ref := n.res.RefPath[1] - - p2 := resource.FindComposeRecordResource(pl.state.ParentResources, ref.Identifiers) - if p2 == nil { - return resource.ComposeRecordErrUnresolved(n.res.RefResource.Identifiers) - } - - for i := range n.res.RefResource.Identifiers { - if p2ID = p2.IDMap[i]; p2ID > 0 { - break - } - } - if p2ID == 0 { - return resource.ComposeRecordErrUnresolved(n.res.RefResource.Identifiers) - } - } - - res.Resource = composeTypes.RecordRbacResource(p0ID, p1ID, p2ID) - - case composeTypes.ModuleFieldResourceType: - if len(n.res.RefPath) > 0 { - p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.res.RefPath[0].Identifiers) - if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(n.res.RefPath[0].Identifiers) - } - p0ID = p0.ID - } - if len(n.res.RefPath) > 1 { - p1 := resource.FindComposeModule(pl.state.ParentResources, n.res.RefPath[1].Identifiers) - if p1 == nil { - return resource.ComposeModuleErrUnresolved(n.res.RefPath[1].Identifiers) - } - p1ID = p1.ID - } - - // @todo specific ModuleField RBAC - - res.Resource = composeTypes.ModuleFieldRbacResource(p0ID, p1ID, p2ID) - - case systemTypes.UserResourceType: - if n.res.RefResource != nil { - p1 := resource.FindUser(pl.state.ParentResources, n.res.RefResource.Identifiers) - if p1 == nil { - return resource.UserErrUnresolved(n.res.RefResource.Identifiers) - } - p1ID = p1.ID - } - - res.Resource = systemTypes.UserRbacResource(p1ID) - case systemTypes.RoleResourceType: - if n.res.RefResource != nil { - p1 := resource.FindRole(pl.state.ParentResources, n.res.RefResource.Identifiers) - if p1 == nil { - return resource.RoleErrUnresolved(n.res.RefResource.Identifiers) - } - p1ID = p1.ID - } - - res.Resource = systemTypes.RoleRbacResource(p1ID) - case systemTypes.ApplicationResourceType: - if n.res.RefResource != nil { - p1 := resource.FindApplication(pl.state.ParentResources, n.res.RefResource.Identifiers) - if p1 == nil { - return resource.ApplicationErrUnresolved(n.res.RefResource.Identifiers) - } - p1ID = p1.ID - } - - res.Resource = systemTypes.ApplicationRbacResource(p1ID) - case systemTypes.ApigwRouteResourceType: - res.Resource = systemTypes.ApigwRouteRbacResource(p1ID) - case systemTypes.ApigwFilterResourceType: - res.Resource = systemTypes.ApigwFilterRbacResource(p1ID) - case systemTypes.AuthClientResourceType: - // @todo add support for importing rbac rules for specific client - res.Resource = systemTypes.AuthClientRbacResource(p1ID) - case systemTypes.TemplateResourceType: - // @todo add support for importing rbac rules for specific template - res.Resource = systemTypes.TemplateRbacResource(p1ID) - case systemTypes.ReportResourceType: - res.Resource = systemTypes.ReportRbacResource(p1ID) - case messagebus.QueueResourceType: - // @todo add support for importing rbac rules for specific queue - res.Resource = messagebus.QueueRbacResource(p1ID) - - case federationTypes.NodeResourceType: - // @todo add support for importing rbac rules for specific queue - res.Resource = federationTypes.NodeRbacResource(p1ID) - - case federationTypes.SharedModuleResourceType: - // @todo add support for importing rbac rules for specific queue - res.Resource = federationTypes.SharedModuleRbacResource(p0ID, p1ID) - - case federationTypes.ExposedModuleResourceType: - // @todo add support for importing rbac rules for specific queue - res.Resource = federationTypes.ExposedModuleRbacResource(p0ID, p1ID) - - default: - // @todo if we wish to support rbac for external stuff, this needs to pass through. - // this also requires some tweaks in the path ID thing. - return fmt.Errorf("unsupported resource type '%s' for RBAC store encode", n.rule.Resource) - } - -store: - - if _, exists := gRbacRules[rbacRuleIndex(res)]; !exists { - return store.CreateRbacRule(ctx, pl.s, res) + // Upsert + if _, exists := gRbacRules[n.rbacRuleIndex((n.rule))]; !exists { + return store.CreateRbacRule(ctx, pl.s, n.rule) } // On existing rbac rule, replace/merge right basically overwrites the existing rule; @@ -314,8 +112,267 @@ store: switch n.cfg.OnExisting { case resource.Replace, resource.MergeRight: - return store.UpdateRbacRule(ctx, pl.s, res) + return store.UpdateRbacRule(ctx, pl.s, n.rule) } return nil } + +func (n *rbacRule) rbacRuleIndex(r *rbac.Rule) string { + return fmt.Sprintf("%d_%s_%s", r.RoleID, r.Resource, r.Operation) +} + +func (n *rbacRule) initGlobalIndex(ctx context.Context, s store.Storer) error { + gRbacRules = make(map[string]bool) + + rr, _, err := store.SearchRbacRules(ctx, s, rbac.RuleFilter{}) + if err != store.ErrNotFound && err != nil { + return err + } + + for _, r := range rr { + gRbacRules[n.rbacRuleIndex(r)] = true + } + return nil +} + +func (n *rbacRule) makeRBACResource(pl *payload) (string, error) { + p0ID := uint64(0) + p1ID := uint64(0) + p2ID := uint64(0) + + rt := strings.Split(n.refRbacResource, "/")[0] + + // Component level stuff + switch rt { + case composeTypes.ComponentResourceType: + return composeTypes.ComponentRbacResource(), nil + case systemTypes.ComponentResourceType: + return systemTypes.ComponentRbacResource(), nil + case automationTypes.ComponentResourceType: + return automationTypes.ComponentRbacResource(), nil + case federationTypes.ComponentResourceType: + return federationTypes.ComponentRbacResource(), nil + } + + // Component resource stuff + switch rt { + case automationTypes.WorkflowResourceType: + if n.refRbacRes != nil { + p1 := resource.FindAutomationWorkflow(pl.state.ParentResources, n.refRbacRes.Identifiers) + if p1 == nil { + return "", resource.AutomationWorkflowErrUnresolved(n.refRbacRes.Identifiers) + } + p1ID = p1.ID + } + return automationTypes.WorkflowRbacResource(p1ID), nil + + case composeTypes.NamespaceResourceType: + if n.refRbacRes != nil { + p1 := resource.FindComposeNamespace(pl.state.ParentResources, n.refRbacRes.Identifiers) + if p1 == nil { + return "", resource.ComposeNamespaceErrUnresolved(n.refRbacRes.Identifiers) + } + p1ID = p1.ID + } + return composeTypes.NamespaceRbacResource(p1ID), nil + + case composeTypes.ModuleResourceType: + if len(n.refPathRes) > 0 { + p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers) + if p0 == nil { + return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers) + } + p0ID = p0.ID + } + + if n.refRbacRes != nil { + p1 := resource.FindComposeModule(pl.state.ParentResources, n.refRbacRes.Identifiers) + if p1 == nil { + return "", resource.ComposeModuleErrUnresolved(n.refRbacRes.Identifiers) + } + p1ID = p1.ID + } + return composeTypes.ModuleRbacResource(p0ID, p1ID), nil + + case composeTypes.ChartResourceType: + if len(n.refPathRes) > 0 { + p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers) + if p0 == nil { + return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers) + } + p0ID = p0.ID + } + + if n.refRbacRes != nil { + p1 := resource.FindComposeChart(pl.state.ParentResources, n.refRbacRes.Identifiers) + if p1 == nil { + return "", resource.ComposeChartErrUnresolved(n.refRbacRes.Identifiers) + } + p1ID = p1.ID + } + return composeTypes.ChartRbacResource(p0ID, p1ID), nil + + case composeTypes.PageResourceType: + if len(n.refPathRes) > 0 { + p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers) + if p0 == nil { + return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers) + } + p0ID = p0.ID + } + + if n.refRbacRes != nil { + p1 := resource.FindComposePage(pl.state.ParentResources, n.refRbacRes.Identifiers) + if p1 == nil { + return "", resource.ComposePageErrUnresolved(n.refRbacRes.Identifiers) + } + p1ID = p1.ID + } + return composeTypes.PageRbacResource(p0ID, p1ID), nil + + case composeTypes.RecordResourceType: + if len(n.refPathRes) > 0 { + p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers) + if p0 == nil { + return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers) + } + p0ID = p0.ID + } + if len(n.refPathRes) > 1 { + p1 := resource.FindComposeModule(pl.state.ParentResources, n.refPathRes[1].Identifiers) + if p1 == nil { + return "", resource.ComposeModuleErrUnresolved(n.refPathRes[1].Identifiers) + } + p1ID = p1.ID + } + if n.refRbacRes != nil { + ref := n.refPathRes[1] + + p2 := resource.FindComposeRecordResource(pl.state.ParentResources, ref.Identifiers) + if p2 == nil { + return "", resource.ComposeRecordErrUnresolved(n.refRbacRes.Identifiers) + } + + for i := range n.refRbacRes.Identifiers { + if p2ID = p2.IDMap[i]; p2ID > 0 { + break + } + } + if p2ID == 0 { + return "", resource.ComposeRecordErrUnresolved(n.refRbacRes.Identifiers) + } + } + return composeTypes.RecordRbacResource(p0ID, p1ID, p2ID), nil + + case composeTypes.ModuleFieldResourceType: + if len(n.refPathRes) > 0 { + p0 := resource.FindComposeNamespace(pl.state.ParentResources, n.refPathRes[0].Identifiers) + if p0 == nil { + return "", resource.ComposeNamespaceErrUnresolved(n.refPathRes[0].Identifiers) + } + p0ID = p0.ID + } + if len(n.refPathRes) > 1 { + p1 := resource.FindComposeModule(pl.state.ParentResources, n.refPathRes[1].Identifiers) + if p1 == nil { + return "", resource.ComposeModuleErrUnresolved(n.refPathRes[1].Identifiers) + } + p1ID = p1.ID + } + + if n.refRbacRes != nil { + modRef := n.refPathRes[1] + p2 := resource.FindComposeModuleField(pl.state.ParentResources, modRef.Identifiers, n.refRbacRes.Identifiers) + if p2 == nil { + return "", resource.ComposeModuleFieldErrUnresolved(n.refRbacRes.Identifiers) + } + + p2ID = p2.ID + } + + return composeTypes.ModuleFieldRbacResource(p0ID, p1ID, p2ID), nil + + case systemTypes.UserResourceType: + if n.refRbacRes != nil { + p1 := resource.FindUser(pl.state.ParentResources, n.refRbacRes.Identifiers) + if p1 == nil { + return "", resource.UserErrUnresolved(n.refRbacRes.Identifiers) + } + p1ID = p1.ID + } + return systemTypes.UserRbacResource(p1ID), nil + + case systemTypes.RoleResourceType: + if n.refRbacRes != nil { + p1 := resource.FindRole(pl.state.ParentResources, n.refRbacRes.Identifiers) + if p1 == nil { + return "", resource.RoleErrUnresolved(n.refRbacRes.Identifiers) + } + p1ID = p1.ID + } + return systemTypes.RoleRbacResource(p1ID), nil + + case systemTypes.ApplicationResourceType: + if n.refRbacRes != nil { + p1 := resource.FindApplication(pl.state.ParentResources, n.refRbacRes.Identifiers) + if p1 == nil { + return "", resource.ApplicationErrUnresolved(n.refRbacRes.Identifiers) + } + p1ID = p1.ID + } + return systemTypes.ApplicationRbacResource(p1ID), nil + + case systemTypes.ApigwRouteResourceType: + return systemTypes.ApigwRouteRbacResource(p1ID), nil + case systemTypes.ApigwFilterResourceType: + return systemTypes.ApigwFilterRbacResource(p1ID), nil + case systemTypes.AuthClientResourceType: + // @todo add support for importing rbac rules for specific client + return systemTypes.AuthClientRbacResource(p1ID), nil + case systemTypes.TemplateResourceType: + // @todo add support for importing rbac rules for specific template + return systemTypes.TemplateRbacResource(p1ID), nil + case systemTypes.ReportResourceType: + return systemTypes.ReportRbacResource(p1ID), nil + case messagebus.QueueResourceType: + // @todo add support for importing rbac rules for specific queue + return messagebus.QueueRbacResource(p1ID), nil + + case federationTypes.NodeResourceType: + // @todo add support for importing rbac rules for specific queue + return federationTypes.NodeRbacResource(p1ID), nil + + case federationTypes.SharedModuleResourceType: + // @todo add support for importing rbac rules for specific queue + return federationTypes.SharedModuleRbacResource(p0ID, p1ID), nil + + case federationTypes.ExposedModuleResourceType: + // @todo add support for importing rbac rules for specific queue + return federationTypes.ExposedModuleRbacResource(p0ID, p1ID), nil + } + + // @todo if we wish to support rbac for external stuff, this needs to pass through. + // this also requires some tweaks in the path ID thing. + return "", fmt.Errorf("unsupported resource type '%s' for RBAC store encode", n.rule.Resource) +} + +func (n *rbacRule) handleComposeRecord(pp []resource.Interface) bool { + for _, p := range pp { + if p.ResourceType() == composeTypes.RecordResourceType && p.Identifiers().HasAny(n.refPathRes[1].Identifiers) { + return true + } + } + + return false +} + +func (n *rbacRule) handleComposeModuleField(pp []resource.Interface) bool { + for _, p := range pp { + if p.ResourceType() == composeTypes.ModuleResourceType && p.Identifiers().HasAny(n.refPathRes[1].Identifiers) { + return true + } + } + + return false +} diff --git a/pkg/envoy/store/rbac_rule_unmarshal.go b/pkg/envoy/store/rbac_rule_unmarshal.go index cb2be53dd..6f593cff9 100644 --- a/pkg/envoy/store/rbac_rule_unmarshal.go +++ b/pkg/envoy/store/rbac_rule_unmarshal.go @@ -7,21 +7,28 @@ import ( "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/cortezaproject/corteza-server/pkg/envoy/resource" "github.com/cortezaproject/corteza-server/pkg/rbac" + "github.com/cortezaproject/corteza-server/system/types" ) -func newRbacRule(rl *rbac.Rule) *rbacRule { +func newRbacRule(rl *rbac.Rule) (*rbacRule, error) { + res := rl.Resource + _, ref, pp, err := resource.ParseRule(res) + return &rbacRule{ rule: rl, - } + + refRbacResource: res, + refRbacRes: ref, + + refPathRes: pp, + + refRole: resource.MakeRef(types.RoleResourceType, resource.MakeIdentifiers(strconv.FormatUint(rl.RoleID, 10))), + }, err } func (rl *rbacRule) MarshalEnvoy() ([]resource.Interface, error) { - refRole := strconv.FormatUint(rl.rule.RoleID, 10) - - rl.rule.Resource = rbac.ResourceType(rl.rule.Resource) - return envoy.CollectNodes( - resource.NewRbacRule(rl.rule, refRole, rl.refRes, rl.refPathRes...), + resource.NewRbacRule(rl.rule, rl.refRole.Identifiers.First(), rl.refRbacRes, rl.refRbacResource, rl.refPathRes...), ) } diff --git a/pkg/envoy/store/resource_translation_marshal.go b/pkg/envoy/store/resource_translation_marshal.go index 663fc44c4..200dae77c 100644 --- a/pkg/envoy/store/resource_translation_marshal.go +++ b/pkg/envoy/store/resource_translation_marshal.go @@ -201,7 +201,7 @@ func (n *resourceTranslation) makeResourceTranslation(pl *payload) (string, erro p1ID = p1.ID // field - f := resource.FindComposeModuleField(p1, n.refLocaleRes.Identifiers) + f := resource.FindComposeModuleField(pl.state.ParentResources, n.refPathRes[1].Identifiers, n.refLocaleRes.Identifiers) if f == nil { return "", resource.ComposeModuleFieldErrUnresolved(n.refLocaleRes.Identifiers) } diff --git a/pkg/envoy/store/system.go b/pkg/envoy/store/system.go index 49d447a34..1048a1ade 100644 --- a/pkg/envoy/store/system.go +++ b/pkg/envoy/store/system.go @@ -379,7 +379,7 @@ func (d *systemDecoder) decodeRbac(ctx context.Context, s store.Storer, ff []*rb var nn rbac.RuleSet var err error - c := func(r *resource.Ref, f *rbacFilter) (bool, error) { + c := func(r *resource.Ref, f *rbacFilter, path ...*resource.Ref) (bool, error) { if r == nil { return true, nil } @@ -390,6 +390,13 @@ func (d *systemDecoder) decodeRbac(ctx context.Context, s store.Storer, ff []*rb return false, err } + if r.ResourceType == composeTypes.ModuleFieldResourceType { + id, err = cast.ToUint64E(path[1].Identifiers.First()) + if err != nil { + return false, err + } + } + return f.resourceID[id], nil } @@ -405,12 +412,8 @@ func (d *systemDecoder) decodeRbac(ctx context.Context, s store.Storer, ff []*rb } for _, n := range nn { - r := newRbacRule(n) - - // parse the resource wo we can define relations/check if we can unmarshal it - rt, ref, pp, err := resource.ParseRule(n.Resource) - r.refRes = ref - r.refPathRes = pp + r, err := newRbacRule(n) + rt := strings.Split(r.refRbacResource, "/")[0] if err != nil { return &auxRsp{ err: err, @@ -419,13 +422,13 @@ func (d *systemDecoder) decodeRbac(ctx context.Context, s store.Storer, ff []*rb // somesort of a generic rule; no specifc resource // @todo check for pp inclusion!! - if ref == nil && len(pp) == 0 { + if r.refRbacRes == nil && len(r.refPathRes) == 0 { mm = append(mm, r) } else { // Check the resource ref and the path refs for validity. // ComposeRecords are fetched in chunks so this check is not valid here. if rt != composeTypes.RecordResourceType { - if ok, err := c(ref, f); err != nil { + if ok, err := c(r.refRbacRes, f, r.refPathRes...); err != nil { return &auxRsp{ err: err, } @@ -434,7 +437,7 @@ func (d *systemDecoder) decodeRbac(ctx context.Context, s store.Storer, ff []*rb } } - for _, p := range pp { + for _, p := range r.refPathRes { if ok, err := c(p, f); err != nil { return &auxRsp{ err: err, diff --git a/pkg/envoy/yaml/compose_module_marshal.go b/pkg/envoy/yaml/compose_module_marshal.go index 3d0c2d7dc..a856b9333 100644 --- a/pkg/envoy/yaml/compose_module_marshal.go +++ b/pkg/envoy/yaml/compose_module_marshal.go @@ -155,6 +155,9 @@ func (c *composeModule) MarshalYAML() (interface{}, error) { func (c *composeModuleField) MarshalYAML() (interface{}, error) { auxOpt := c.res.Options + if auxOpt == nil { + auxOpt = make(types.ModuleFieldOptions) + } switch c.res.Kind { case "Record": ref := c.relMod.Handle diff --git a/pkg/envoy/yaml/rbac_rules.go b/pkg/envoy/yaml/rbac_rules.go index 357c9c7ca..bd777adc8 100644 --- a/pkg/envoy/yaml/rbac_rules.go +++ b/pkg/envoy/yaml/rbac_rules.go @@ -1,6 +1,7 @@ package yaml import ( + "strconv" "strings" "github.com/cortezaproject/corteza-server/pkg/envoy/resource" @@ -12,18 +13,16 @@ type ( rbacRule struct { res *rbac.Rule - // To help us construct the resource - resource string + // The resource in question + refRbacResource string + refRbacRes *resource.Ref - refResource string - refRes *resource.Ref + // The path items + refPathRes []*resource.Ref - // PathRes and PathResource slices hold parent resources we should nest the rule by - refPathRes []*resource.Ref - relPathResource []resource.Interface - - refRole string - relRole *types.Role + // The role + refRole *resource.Ref + role *types.Role } rbacRuleSet []*rbacRule ) @@ -32,15 +31,19 @@ func rbacRuleFromResource(r *resource.RbacRule, cfg *EncoderConfig) *rbacRule { rr := r.Res.Resource rr = strings.Trim(rr, ":") - if !strings.Contains(rr, ";") { + if !strings.Contains(rr, ":") { rr = "corteza::" + rr } return &rbacRule{ - res: r.Res, - refRes: r.RefResource, - refResource: rr, - refRole: r.RefRole.Identifiers.First(), + res: r.Res, + + refRbacResource: rr, + refRbacRes: r.RefRes, + + refPathRes: r.RefPath, + + refRole: r.RefRole, } } @@ -62,11 +65,13 @@ func (rr rbacRuleSet) groupByRole() []rbacRuleSet { rolx := make(map[string]rbacRuleSet) for _, r := range rr { - if _, has := rolx[r.refRole]; !has { - rolx[r.refRole] = make(rbacRuleSet, 0, 100) + roleKey := r.getRoleKey() + + if _, has := rolx[roleKey]; !has { + rolx[roleKey] = make(rbacRuleSet, 0, 100) } - rolx[r.refRole] = append(rolx[r.refRole], r) + rolx[roleKey] = append(rolx[roleKey], r) } rtr := make([]rbacRuleSet, 0, len(rolx)) @@ -97,13 +102,12 @@ func (rr rbacRuleSet) groupByResource() []rbacRuleSet { return rtr } -// Just a helper to make the above code shorter -func (r *rbacRule) bindRefs(res *resource.Ref, path []*resource.Ref, err error) error { - if err != nil { - return err +func (r *rbacRule) getRoleKey() string { + if r.role == nil { + return "" } - - r.refRes = res - r.refPathRes = path - return nil + if r.role.Handle != "" { + return r.role.Handle + } + return strconv.FormatUint(r.res.RoleID, 10) } diff --git a/pkg/envoy/yaml/rbac_rules_marshal.go b/pkg/envoy/yaml/rbac_rules_marshal.go index f213032e8..6698f9560 100644 --- a/pkg/envoy/yaml/rbac_rules_marshal.go +++ b/pkg/envoy/yaml/rbac_rules_marshal.go @@ -3,6 +3,7 @@ package yaml import ( "context" "fmt" + "strings" automationTypes "github.com/cortezaproject/corteza-server/automation/types" composeTypes "github.com/cortezaproject/corteza-server/compose/types" @@ -19,26 +20,32 @@ func (n *rbacRule) Prepare(ctx context.Context, state *envoy.ResourceState) (err } // Get the related role - n.relRole = resource.FindRole(state.ParentResources, rl.RefRole.Identifiers) - if n.relRole == nil { + n.role = resource.FindRole(state.ParentResources, rl.RefRole.Identifiers) + if n.role == nil { return resource.RoleErrUnresolved(rl.RefRole.Identifiers) } - if n.relRole == nil { - return resource.RoleErrUnresolved(rl.RefRole.Identifiers) + + refRes := n.refRbacRes + if refRes == nil { + return } // For now we will only allow resource specific RBAC rules if that resource is // also present. // Here we check if we can find it in case we're handling a resource specific rule. - res := state.Res.(*resource.RbacRule) - refRes := res.RefResource - if refRes != nil && refRes.ResourceType == composeTypes.RecordResourceType { - for _, r := range state.ParentResources { - if r.ResourceType() == composeTypes.RecordResourceType && r.Identifiers().HasAny(res.RefPath[1].Identifiers) { - return - } + found := false + switch refRes.ResourceType { + case composeTypes.RecordResourceType: + if found = n.handleComposeRecord(state.ParentResources); !found { + return resource.RbacResourceErrNotFound(refRes.Identifiers) } - } else if refRes != nil && len(refRes.Identifiers) > 0 { + + case composeTypes.ModuleFieldResourceType: + if found = n.handleComposeModuleField(state.ParentResources); !found { + return resource.RbacResourceErrNotFound(refRes.Identifiers) + } + + default: for _, r := range state.ParentResources { if refRes.ResourceType == r.ResourceType() && r.Identifiers().HasAny(refRes.Identifiers) { return @@ -58,186 +65,225 @@ func (r *rbacRule) Encode(ctx context.Context, doc *Document, state *envoy.Resou // under the related namespace. // For now all rules will be nested under a root node for simplicity sake. + refResource, err := r.makeRBACResource(state) + if err != nil { + return err + } + + r.refRbacResource = refResource + r.res.Resource = refResource + + doc.addRbacRule(r) + + return nil +} + +func (r *rbacRule) makeRBACResource(state *envoy.ResourceState) (string, error) { res := state.Res.(*resource.RbacRule) p0ID := "*" p1ID := "*" p2ID := "*" - switch r.res.Resource { + rt := strings.Split(r.refRbacResource, "/")[0] + + switch rt { case composeTypes.ComponentResourceType, systemTypes.ComponentResourceType, automationTypes.ComponentResourceType, federationTypes.ComponentResourceType: - r.res.Resource += "/" - goto next + + return rt, nil } - // @todo the following stuff I'm not too sure about - switch r.res.Resource { + switch rt { case composeTypes.NamespaceResourceType: - if res.RefResource != nil { - p1 := resource.FindComposeNamespace(state.ParentResources, res.RefResource.Identifiers) + if res.RefRes != nil { + p1 := resource.FindComposeNamespace(state.ParentResources, res.RefRes.Identifiers) if p1 == nil { - return resource.ComposeNamespaceErrUnresolved(res.RefResource.Identifiers) + return "", resource.ComposeNamespaceErrUnresolved(res.RefRes.Identifiers) } p1ID = p1.Slug } - r.res.Resource = fmt.Sprintf(composeTypes.NamespaceRbacResourceTpl(), composeTypes.NamespaceResourceType, p1ID) + return fmt.Sprintf(composeTypes.NamespaceRbacResourceTpl(), composeTypes.NamespaceResourceType, p1ID), nil case composeTypes.ModuleResourceType: if len(res.RefPath) > 0 { p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers) if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) + return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) } p0ID = p0.Slug } - if res.RefResource != nil { - p1 := resource.FindComposeModule(state.ParentResources, res.RefResource.Identifiers) + if res.RefRes != nil { + p1 := resource.FindComposeModule(state.ParentResources, res.RefRes.Identifiers) if p1 == nil { - return resource.ComposeModuleErrUnresolved(res.RefResource.Identifiers) + return "", resource.ComposeModuleErrUnresolved(res.RefRes.Identifiers) } p1ID = p1.Handle } - r.res.Resource = fmt.Sprintf(composeTypes.ModuleRbacResourceTpl(), composeTypes.ModuleResourceType, p0ID, p1ID) + return fmt.Sprintf(composeTypes.ModuleRbacResourceTpl(), composeTypes.ModuleResourceType, p0ID, p1ID), nil case composeTypes.ChartResourceType: if len(res.RefPath) > 0 { p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers) if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) + return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) } p0ID = p0.Slug } - if res.RefResource != nil { - p1 := resource.FindComposeChart(state.ParentResources, res.RefResource.Identifiers) + if res.RefRes != nil { + p1 := resource.FindComposeChart(state.ParentResources, res.RefRes.Identifiers) if p1 == nil { - return resource.ComposeChartErrUnresolved(res.RefResource.Identifiers) + return "", resource.ComposeChartErrUnresolved(res.RefRes.Identifiers) } p1ID = p1.Handle } - r.res.Resource = fmt.Sprintf(composeTypes.ChartRbacResourceTpl(), composeTypes.ChartResourceType, p0ID, p1ID) + return fmt.Sprintf(composeTypes.ChartRbacResourceTpl(), composeTypes.ChartResourceType, p0ID, p1ID), nil case composeTypes.PageResourceType: if len(res.RefPath) > 0 { p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers) if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) + return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) } p0ID = p0.Slug } - if res.RefResource != nil { - p1 := resource.FindComposePage(state.ParentResources, res.RefResource.Identifiers) + if res.RefRes != nil { + p1 := resource.FindComposePage(state.ParentResources, res.RefRes.Identifiers) if p1 == nil { - return resource.ComposePageErrUnresolved(res.RefResource.Identifiers) + return "", resource.ComposePageErrUnresolved(res.RefRes.Identifiers) } p1ID = p1.Handle } - r.res.Resource = fmt.Sprintf(composeTypes.PageRbacResourceTpl(), composeTypes.PageResourceType, p0ID, p1ID) + return fmt.Sprintf(composeTypes.PageRbacResourceTpl(), composeTypes.PageResourceType, p0ID, p1ID), nil case composeTypes.RecordResourceType: if len(res.RefPath) > 0 { p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers) if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) + return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) } p0ID = p0.Slug } if len(res.RefPath) > 1 { p1 := resource.FindComposeModule(state.ParentResources, res.RefPath[1].Identifiers) if p1 == nil { - return resource.ComposeModuleErrUnresolved(res.RefPath[1].Identifiers) + return "", resource.ComposeModuleErrUnresolved(res.RefPath[1].Identifiers) } p1ID = p1.Handle } - if res.RefResource != nil { + if res.RefRes != nil { ref := res.RefPath[1] p2 := resource.FindComposeRecordResource(state.ParentResources, ref.Identifiers) if p2 == nil { - return resource.ComposeRecordErrUnresolved(res.RefResource.Identifiers) + return "", resource.ComposeRecordErrUnresolved(res.RefRes.Identifiers) } - for i := range res.RefResource.Identifiers { + for i := range res.RefRes.Identifiers { if _, ok := p2.IDMap[i]; ok { - p2ID = res.RefResource.Identifiers.First() + p2ID = res.RefRes.Identifiers.First() break } } if p2ID == "" { - return resource.ComposeRecordErrUnresolved(res.RefResource.Identifiers) + return "", resource.ComposeRecordErrUnresolved(res.RefRes.Identifiers) } } - r.res.Resource = fmt.Sprintf(composeTypes.RecordRbacResourceTpl(), composeTypes.RecordResourceType, p0ID, p1ID, p2ID) + return fmt.Sprintf(composeTypes.RecordRbacResourceTpl(), composeTypes.RecordResourceType, p0ID, p1ID, p2ID), nil case composeTypes.ModuleFieldResourceType: if len(res.RefPath) > 0 { p0 := resource.FindComposeNamespace(state.ParentResources, res.RefPath[0].Identifiers) if p0 == nil { - return resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) + return "", resource.ComposeNamespaceErrUnresolved(res.RefPath[0].Identifiers) } p0ID = p0.Slug } if len(res.RefPath) > 1 { p1 := resource.FindComposeModule(state.ParentResources, res.RefPath[1].Identifiers) if p1 == nil { - return resource.ComposeModuleErrUnresolved(res.RefPath[1].Identifiers) + return "", resource.ComposeModuleErrUnresolved(res.RefPath[1].Identifiers) } p1ID = p1.Handle } + if r.refRbacRes != nil { + modRef := r.refPathRes[1] + p2 := resource.FindComposeModuleField(state.ParentResources, modRef.Identifiers, r.refRbacRes.Identifiers) + if p2 == nil { + return "", resource.ComposeModuleFieldErrUnresolved(r.refRbacRes.Identifiers) + } + + p2ID = p2.Name + } + // @todo specific module field RBAC - r.res.Resource = fmt.Sprintf(composeTypes.ModuleFieldRbacResourceTpl(), composeTypes.ModuleFieldResourceType, p0ID, p1ID, p2ID) + return fmt.Sprintf(composeTypes.ModuleFieldRbacResourceTpl(), composeTypes.ModuleFieldResourceType, p0ID, p1ID, p2ID), nil case systemTypes.UserResourceType: - if res.RefResource != nil { - p1 := resource.FindUser(state.ParentResources, res.RefResource.Identifiers) + if res.RefRes != nil { + p1 := resource.FindUser(state.ParentResources, res.RefRes.Identifiers) if p1 == nil { - return resource.UserErrUnresolved(res.RefResource.Identifiers) + return "", resource.UserErrUnresolved(res.RefRes.Identifiers) } p1ID = p1.Handle } - r.res.Resource = fmt.Sprintf(systemTypes.UserRbacResourceTpl(), systemTypes.UserResourceType, p1ID) + return fmt.Sprintf(systemTypes.UserRbacResourceTpl(), systemTypes.UserResourceType, p1ID), nil case systemTypes.RoleResourceType: - if res.RefResource != nil { - p1 := resource.FindRole(state.ParentResources, res.RefResource.Identifiers) + if res.RefRes != nil { + p1 := resource.FindRole(state.ParentResources, res.RefRes.Identifiers) if p1 == nil { - return resource.RoleErrUnresolved(res.RefResource.Identifiers) + return "", resource.RoleErrUnresolved(res.RefRes.Identifiers) } p1ID = p1.Handle } - r.res.Resource = fmt.Sprintf(systemTypes.RoleRbacResourceTpl(), systemTypes.RoleResourceType, p1ID) + return fmt.Sprintf(systemTypes.RoleRbacResourceTpl(), systemTypes.RoleResourceType, p1ID), nil case systemTypes.ApplicationResourceType: - if res.RefResource != nil { - p1 := resource.FindApplication(state.ParentResources, res.RefResource.Identifiers) + if res.RefRes != nil { + p1 := resource.FindApplication(state.ParentResources, res.RefRes.Identifiers) if p1 == nil { - return resource.ApplicationErrUnresolved(res.RefResource.Identifiers) + return "", resource.ApplicationErrUnresolved(res.RefRes.Identifiers) } p1ID = p1.Name } - r.res.Resource = fmt.Sprintf(systemTypes.ApplicationRbacResourceTpl(), systemTypes.ApplicationResourceType, p1ID) + return fmt.Sprintf(systemTypes.ApplicationRbacResourceTpl(), systemTypes.ApplicationResourceType, p1ID), nil - // // @todo - // case systemTypes.ApigwRouteResourceType: - // case systemTypes.ApigwFilterResourceType: - - default: - return fmt.Errorf("unsupported resource type '%s' for RBAC YAML encode", r.res.Resource) + // // @todo + // case systemTypes.ApigwRouteResourceType: + // case systemTypes.ApigwFilterResourceType: } -next: - doc.addRbacRule(r) + return "", fmt.Errorf("unsupported resource type '%s' for RBAC YAML encode", r.res.Resource) +} - return nil +func (n *rbacRule) handleComposeRecord(pp []resource.Interface) bool { + for _, p := range pp { + if p.ResourceType() == composeTypes.RecordResourceType && p.Identifiers().HasAny(n.refPathRes[1].Identifiers) { + return true + } + } + + return false +} + +func (n *rbacRule) handleComposeModuleField(pp []resource.Interface) bool { + for _, p := range pp { + if p.ResourceType() == composeTypes.ModuleResourceType && p.Identifiers().HasAny(n.refPathRes[1].Identifiers) { + return true + } + } + + return false } func (rr rbacRuleSet) MarshalYAML() (interface{}, error) { @@ -272,13 +318,8 @@ func (rr rbacRuleSet) MarshalYAML() (interface{}, error) { } } - rr := roleRules[0].relRole - rk := rr.Handle - if rk == "" { - rk = rr.Name - } roleNode, err = addMap(roleNode, - rk, resNode, + roleRules[0].getRoleKey(), resNode, ) if err != nil { return nil, err diff --git a/pkg/envoy/yaml/rbac_rules_unmarshal.go b/pkg/envoy/yaml/rbac_rules_unmarshal.go index fd8e9efea..03d124a29 100644 --- a/pkg/envoy/yaml/rbac_rules_unmarshal.go +++ b/pkg/envoy/yaml/rbac_rules_unmarshal.go @@ -6,6 +6,7 @@ import ( "github.com/cortezaproject/corteza-server/pkg/envoy/resource" "github.com/cortezaproject/corteza-server/pkg/rbac" "github.com/cortezaproject/corteza-server/pkg/y7s" + "github.com/cortezaproject/corteza-server/system/types" "gopkg.in/yaml.v3" ) @@ -46,7 +47,7 @@ func (rr rbacRuleSet) decodeRbac(a rbac.Access, rules *yaml.Node) (oo rbacRuleSe Access: a, Operation: op.Value, }, - refRole: roleRef, + refRole: resource.MakeRef(types.RoleResourceType, resource.MakeIdentifiers(roleRef)), } if res != "" { @@ -75,21 +76,13 @@ func (rr rbacRuleSet) decodeRbac(a rbac.Access, rules *yaml.Node) (oo rbacRuleSe } func (rr rbacRuleSet) bindResource(resI resource.Interface) rbacRuleSet { - ref := &resource.Ref{ - ResourceType: resI.ResourceType(), - Identifiers: resI.Identifiers(), - } - - var path []*resource.Ref - if resRbac, ok := resI.(resource.RBACInterface); ok { - path = resRbac.RBACPath() - } + res, ref, pp := resI.(resource.RBACInterface).RBACParts() rtr := make(rbacRuleSet, 0, len(rr)) for _, r := range rr { - _ = r.SetResource(ref.ResourceType) - r.refRes = ref - r.refPathRes = path + r.refRbacResource = res + r.refRbacRes = ref + r.refPathRes = pp rtr = append(rtr, r) } @@ -100,20 +93,19 @@ func (rr rbacRuleSet) MarshalEnvoy() ([]resource.Interface, error) { var nn = make([]resource.Interface, 0, len(rr)) for _, r := range rr { - nn = append(nn, resource.NewRbacRule(r.res, r.refRole, r.refRes, r.refPathRes...)) + nn = append(nn, resource.NewRbacRule(r.res, r.refRole.Identifiers.First(), r.refRbacRes, r.refRbacResource, r.refPathRes...)) } return nn, nil } func (r *rbacRule) SetResource(res string) error { - res, ref, pp, err := resource.ParseRule(res) + _, ref, pp, err := resource.ParseRule(res) if err != nil { return err } - if res != "" { - r.res.Resource = res - } - - return r.bindRefs(ref, pp, err) + r.refRbacResource = res + r.refRbacRes = ref + r.refPathRes = pp + return nil } diff --git a/pkg/envoy/yaml/resource_translation_marshal.go b/pkg/envoy/yaml/resource_translation_marshal.go index 61f9719af..cd78c0f52 100644 --- a/pkg/envoy/yaml/resource_translation_marshal.go +++ b/pkg/envoy/yaml/resource_translation_marshal.go @@ -155,7 +155,7 @@ func (r *resourceTranslation) makeResourceTranslationResource(state *envoy.Resou p1ID = p1.Handle // field - f := resource.FindComposeModuleField(p1, res.RefRes.Identifiers) + f := resource.FindComposeModuleField(state.ParentResources, res.RefPath[1].Identifiers, res.RefRes.Identifiers) if f == nil { return "", resource.ComposeModuleFieldErrUnresolved(res.RefRes.Identifiers) } diff --git a/pkg/envoy/yaml/resource_translation_unmarshal.go b/pkg/envoy/yaml/resource_translation_unmarshal.go index 25db8d410..118e0351c 100644 --- a/pkg/envoy/yaml/resource_translation_unmarshal.go +++ b/pkg/envoy/yaml/resource_translation_unmarshal.go @@ -86,7 +86,6 @@ func (rr resourceTranslationSet) bindResource(resI resource.Interface) resourceT rtr := make(resourceTranslationSet, 0, len(rr)) for _, r := range rr { - r.refResourceTranslation = res r.refLocaleRes = ref r.refPathRes = pp diff --git a/tests/envoy/testdata/base/rbac_rules.yaml b/tests/envoy/testdata/base/rbac_rules.yaml index d47f78f35..1e93ef1af 100644 --- a/tests/envoy/testdata/base/rbac_rules.yaml +++ b/tests/envoy/testdata/base/rbac_rules.yaml @@ -1,12 +1,88 @@ +# Setup so we don't need any boilerplate code +roles: + r1: + name: test role 1 + r2: + name: test role 2 + +namespaces: + ns1: + name: test namespace 1 + +modules: + mod1: + name: test module 1 + fields: + f1: + label: test module field 1 + +charts: + chr1: + name: test chart 1 + +# Access control allow: r1: - corteza::compose/: - - access + corteza::compose: + - allow;r1;compose;op1 + + corteza::compose:namespace/*: + - allow;r1;compose:namespace/*;op1 + corteza::compose:namespace/ns1: + - allow;r1;compose:namespace/ns1;op1 + + corteza::compose:module/*/*: + - allow;r1;compose:module/*/*;op1 + corteza::compose:module/ns1/*: + - allow;r1;compose:module/ns1/*;op1 + corteza::compose:module/ns1/mod1: + - allow;r1;compose:module/ns1/mod1;op1 + + corteza::compose:module-field/*/*/*: + - allow;r1;compose:module-field/*/*/*;op1 + corteza::compose:module-field/ns1/*/*: + - allow;r1;compose:module-field/ns1/*/*;op1 + corteza::compose:module-field/ns1/mod1/*: + - allow;r1;compose:module-field/ns1/mod1/*;op1 + corteza::compose:module-field/ns1/mod1/f1: + - allow;r1;compose:module-field/ns1/mod1/f1;op1 + + corteza::compose:chart/*/*: + - allow;r1;compose:chart/*/*;op1 + corteza::compose:chart/ns1/*: + - allow;r1;compose:chart/ns1/*;op1 + corteza::compose:chart/ns1/chr1: + - allow;r1;compose:chart/ns1/chr1;op1 deny: r1: + corteza::compose: + - deny;r1;compose;op1 + corteza::compose:namespace/*: - - access - # @todo see comment for rbac store marshal if I forget to mention this to you - corteza::system/: - - access + - deny;r1;compose:namespace/*;op1 + corteza::compose:namespace/ns1: + - deny;r1;compose:namespace/ns1;op1 + + corteza::compose:module/*/*: + - deny;r1;compose:module/*/*;op1 + corteza::compose:module/ns1/*: + - deny;r1;compose:module/ns1/*;op1 + corteza::compose:module/ns1/mod1: + - deny;r1;compose:module/ns1/mod1;op1 + + corteza::compose:module-field/*/*/*: + - deny;r1;compose:module-field/*/*/*;op1 + corteza::compose:module-field/ns1/*/*: + - deny;r1;compose:module-field/ns1/*/*;op1 + corteza::compose:module-field/ns1/mod1/*: + - deny;r1;compose:module-field/ns1/mod1/*;op1 + corteza::compose:module-field/ns1/mod1/f1: + - deny;r1;compose:module-field/ns1/mod1/f1;op1 + + corteza::compose:chart/*/*: + - deny;r1;compose:chart/*/*;op1 + corteza::compose:chart/ns1/*: + - deny;r1;compose:chart/ns1/*;op1 + corteza::compose:chart/ns1/chr1: + - deny;r1;compose:chart/ns1/chr1;op1 diff --git a/tests/envoy/testdata/provision/simple/0000_access_control.yaml b/tests/envoy/testdata/provision/simple/0000_access_control.yaml index 521c62cb4..0259e3192 100644 --- a/tests/envoy/testdata/provision/simple/0000_access_control.yaml +++ b/tests/envoy/testdata/provision/simple/0000_access_control.yaml @@ -3,9 +3,6 @@ allow: corteza::compose/: - op1 - corteza::system:apigw-route/*: - - op1 - corteza::compose:namespace/*: - op1 diff --git a/tests/envoy/yaml_store_base_test.go b/tests/envoy/yaml_store_base_test.go index fed3dbd95..6a7e19de4 100644 --- a/tests/envoy/yaml_store_base_test.go +++ b/tests/envoy/yaml_store_base_test.go @@ -405,22 +405,6 @@ func TestYamlStore_base(t *testing.T) { }, }, - { - name: "rbac rules", - file: "rbac_rules", - pre: func() (err error) { - return collect( - storeRole(ctx, s, 100, "r1"), - ) - }, - check: func(req *require.Assertions) { - rr, _, err := store.SearchRbacRules(ctx, s, rbac.RuleFilter{}) - req.NoError(err) - req.NotNil(rr) - req.Len(rr, 3) - }, - }, - { name: "records; no namespace", file: "records_no_ns", @@ -552,54 +536,6 @@ func TestYamlStore_base(t *testing.T) { }, }, - { - name: "base access control", - file: "access_control_base", - check: func(req *require.Assertions) { - role, err := store.LookupRoleByHandle(ctx, s, "authenticated") - req.NoError(err) - req.NotNil(role) - - rr, _, err := store.SearchRbacRules(ctx, s, rbac.RuleFilter{}) - req.NoError(err) - req.Len(rr, 16) - - // Check that the role is ok - for _, r := range rr { - req.Equal(role.ID, r.RoleID) - } - - resources := []string{ - "corteza::compose:namespace/11", - "corteza::compose:namespace/11", - "corteza::compose:module/11/12", - "corteza::compose:module/11/12", - "corteza::compose:page/11/13", - "corteza::compose:page/11/13", - "corteza::compose:chart/11/14", - "corteza::compose:chart/11/14", - "corteza::system:role/16", - "corteza::system:role/16", - "corteza::system:user/17", - "corteza::system:user/17", - "corteza::system:application/18", - "corteza::system:application/18", - "corteza::compose/", - "corteza::compose/", - } - - for i, res := range resources { - req.Equal(res, rr[i].Resource) - if i%2 == 0 { - req.Equal("op1", rr[i].Operation) - req.Equal(rbac.Allow, rr[i].Access) - } else { - req.Equal("op2", rr[i].Operation) - req.Equal(rbac.Deny, rr[i].Access) - } - } - }, - }, { name: "settings", file: "settings", diff --git a/tests/envoy/yaml_store_provision_test.go b/tests/envoy/yaml_store_provision_test.go index 49c2cab85..ed983091b 100644 --- a/tests/envoy/yaml_store_provision_test.go +++ b/tests/envoy/yaml_store_provision_test.go @@ -73,7 +73,7 @@ func TestYamlStore_provision(t *testing.T) { rrls, _, err = store.SearchRbacRules(ctx, s, rbac.RuleFilter{}) req.NoError(err) req.NotNil(rrls) - req.Len(rrls, 15) + req.Len(rrls, 14) }) t.Run("namespaces", func(t *testing.T) { diff --git a/tests/envoy/yaml_store_rbac_test.go b/tests/envoy/yaml_store_rbac_test.go new file mode 100644 index 000000000..2fe71d5d9 --- /dev/null +++ b/tests/envoy/yaml_store_rbac_test.go @@ -0,0 +1,228 @@ +package envoy + +import ( + "context" + "testing" + + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/auth" + "github.com/cortezaproject/corteza-server/pkg/envoy" + "github.com/cortezaproject/corteza-server/pkg/envoy/resource" + su "github.com/cortezaproject/corteza-server/pkg/envoy/store" + "github.com/cortezaproject/corteza-server/pkg/envoy/yaml" + "github.com/cortezaproject/corteza-server/pkg/rbac" + "github.com/cortezaproject/corteza-server/store" + systemTypes "github.com/cortezaproject/corteza-server/system/types" + "github.com/stretchr/testify/require" +) + +func TestYamlStore_rbac(t *testing.T) { + var ( + ctx = context.Background() + namespace = "base" + s = initServices(ctx, t) + f = "rbac_rules.yaml" + req = require.New(t) + ) + + ctx = auth.SetIdentityToContext(ctx, auth.ServiceUser()) + + ni := uint64(10) + su.NextID = func() uint64 { + ni++ + return ni + } + + truncateStore(ctx, s, t) + + // util to check rule's operation + checkPfx := func(req *require.Assertions, rr rbac.RuleSet, pfx string) { + for _, r := range rr { + req.Contains(r.Operation, pfx) + } + } + // util to check the current store state if it's as expected + checkStore := func() { + // Preload all the resource + ns, err := store.LookupComposeNamespaceBySlug(ctx, s, "ns1") + req.NoError(err) + + mod, err := store.LookupComposeModuleByNamespaceIDHandle(ctx, s, ns.ID, "mod1") + req.NoError(err) + + fld, err := store.LookupComposeModuleFieldByModuleIDName(ctx, s, mod.ID, "f1") + req.NoError(err) + + chr, err := store.LookupComposeChartByNamespaceIDHandle(ctx, s, ns.ID, "chr1") + req.NoError(err) + + // Preload RBAC rules + rr, _, err := store.SearchRbacRules(ctx, s, rbac.RuleFilter{}) + req.NoError(err) + + // Check + // - component + composeRR := rbacRuleFilterByResource(rr, types.ComponentRbacResource()) + req.Len(composeRR, 2) + req.Len(composeRR.FilterAccess(rbac.Allow), 1) + req.Len(composeRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, composeRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, composeRR.FilterAccess(rbac.Deny), "deny") + + // - namespace + nsRR := rbacRuleFilterByResource(rr, types.NamespaceRbacResource(0)) + req.Len(nsRR, 2) + req.Len(nsRR.FilterAccess(rbac.Allow), 1) + req.Len(nsRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, nsRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, nsRR.FilterAccess(rbac.Deny), "deny") + + nsRR = rbacRuleFilterByResource(rr, ns.RbacResource()) + req.Len(nsRR, 2) + req.Len(nsRR.FilterAccess(rbac.Allow), 1) + req.Len(nsRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, nsRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, nsRR.FilterAccess(rbac.Deny), "deny") + + // - module + modRR := rbacRuleFilterByResource(rr, types.ModuleRbacResource(0, 0)) + req.Len(modRR, 2) + req.Len(modRR.FilterAccess(rbac.Allow), 1) + req.Len(modRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, modRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, modRR.FilterAccess(rbac.Deny), "deny") + + modRR = rbacRuleFilterByResource(rr, types.ModuleRbacResource(ns.ID, 0)) + req.Len(modRR, 2) + req.Len(modRR.FilterAccess(rbac.Allow), 1) + req.Len(modRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, modRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, modRR.FilterAccess(rbac.Deny), "deny") + + modRR = rbacRuleFilterByResource(rr, types.ModuleRbacResource(ns.ID, mod.ID)) + req.Len(modRR, 2) + req.Len(modRR.FilterAccess(rbac.Allow), 1) + req.Len(modRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, modRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, modRR.FilterAccess(rbac.Deny), "deny") + + // - module field + mfRR := rbacRuleFilterByResource(rr, types.ModuleFieldRbacResource(0, 0, 0)) + req.Len(mfRR, 2) + req.Len(mfRR.FilterAccess(rbac.Allow), 1) + req.Len(mfRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, mfRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, mfRR.FilterAccess(rbac.Deny), "deny") + + mfRR = rbacRuleFilterByResource(rr, types.ModuleFieldRbacResource(ns.ID, 0, 0)) + req.Len(mfRR, 2) + req.Len(mfRR.FilterAccess(rbac.Allow), 1) + req.Len(mfRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, mfRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, mfRR.FilterAccess(rbac.Deny), "deny") + + mfRR = rbacRuleFilterByResource(rr, types.ModuleFieldRbacResource(ns.ID, mod.ID, 0)) + req.Len(mfRR, 2) + req.Len(mfRR.FilterAccess(rbac.Allow), 1) + req.Len(mfRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, mfRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, mfRR.FilterAccess(rbac.Deny), "deny") + + mfRR = rbacRuleFilterByResource(rr, types.ModuleFieldRbacResource(ns.ID, mod.ID, fld.ID)) + req.Len(mfRR, 2) + req.Len(mfRR.FilterAccess(rbac.Allow), 1) + req.Len(mfRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, mfRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, mfRR.FilterAccess(rbac.Deny), "deny") + + // - chart + chrRR := rbacRuleFilterByResource(rr, types.ChartRbacResource(0, 0)) + req.Len(chrRR, 2) + req.Len(chrRR.FilterAccess(rbac.Allow), 1) + req.Len(chrRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, chrRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, chrRR.FilterAccess(rbac.Deny), "deny") + + chrRR = rbacRuleFilterByResource(rr, types.ChartRbacResource(ns.ID, 0)) + req.Len(chrRR, 2) + req.Len(chrRR.FilterAccess(rbac.Allow), 1) + req.Len(chrRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, chrRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, chrRR.FilterAccess(rbac.Deny), "deny") + + chrRR = rbacRuleFilterByResource(rr, types.ChartRbacResource(ns.ID, chr.ID)) + req.Len(chrRR, 2) + req.Len(chrRR.FilterAccess(rbac.Allow), 1) + req.Len(chrRR.FilterAccess(rbac.Deny), 1) + checkPfx(req, chrRR.FilterAccess(rbac.Allow), "allow") + checkPfx(req, chrRR.FilterAccess(rbac.Deny), "deny") + } + + // YAML -> Store + nn, err := decodeYaml(ctx, namespace, f) + req.NoError(err) + err = encode(ctx, s, nn) + req.NoError(err) + + checkStore() + + // Store -> YAML -> Store + df := su.NewDecodeFilter(). + ComposeNamespace(&types.NamespaceFilter{ + Slug: "ns1", + }). + ComposeModule(&types.ModuleFilter{}). + ComposeChart(&types.ChartFilter{}). + Roles(&systemTypes.RoleFilter{Handle: "r1"}). + Rbac(&rbac.RuleFilter{}) + + // - decode store + sd := su.Decoder() + nn, err = sd.Decode(ctx, s, df) + req.NoError(err) + // + // - into yaml + ye := yaml.NewYamlEncoder(&yaml.EncoderConfig{}) + bld := envoy.NewBuilder(ye) + g, err := bld.Build(ctx, nn...) + req.NoError(err) + err = envoy.Encode(ctx, g, ye) + req.NoError(err) + ss := ye.Stream() + // + // - cleanup the store + truncateStore(ctx, s, t) + // + // - back into store + se := su.NewStoreEncoder(s, &su.EncoderConfig{}) + yd := yaml.Decoder() + nn = make([]resource.Interface, 0, len(nn)) + for _, s := range ss { + mm, err := yd.Decode(ctx, s.Source, nil) + req.NoError(err) + nn = append(nn, mm...) + } + bld = envoy.NewBuilder(se) + g, err = bld.Build(ctx, nn...) + req.NoError(err) + + err = envoy.Encode(ctx, g, se) + req.NoError(err) + // + // - check store state + checkStore() + + truncateStore(ctx, s, t) +} + +func rbacRuleFilterByResource(rr rbac.RuleSet, res string) rbac.RuleSet { + out := make(rbac.RuleSet, 0, len(rr)/2) + + for _, r := range rr { + if r.Resource == res { + out = append(out, r) + } + } + + return out +}