From d076dbd70ffd88945ae3626db9684c7cb7434b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Mon, 19 Oct 2020 16:58:05 +0200 Subject: [PATCH] Define base Compose resources supported by envoy * ComposeNamespace, * ComposeModule (+ fields), * ComposeRecord (+ values), * RBAC permissions. --- compose/types/module_field_options.go | 16 +++- pkg/envoy/types/compose_module.go | 86 +++++++++++++++++ pkg/envoy/types/compose_module_node.go | 110 ++++++++++++++++++++++ pkg/envoy/types/compose_namespace.go | 48 ++++++++++ pkg/envoy/types/compose_namespace_node.go | 45 +++++++++ pkg/envoy/types/compose_record.go | 72 ++++++++++++++ pkg/envoy/types/compose_record_node.go | 76 +++++++++++++++ pkg/envoy/types/rbac.go | 9 ++ pkg/envoy/types/util.go | 24 +++++ 9 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 pkg/envoy/types/compose_module.go create mode 100644 pkg/envoy/types/compose_module_node.go create mode 100644 pkg/envoy/types/compose_namespace.go create mode 100644 pkg/envoy/types/compose_namespace_node.go create mode 100644 pkg/envoy/types/compose_record.go create mode 100644 pkg/envoy/types/compose_record_node.go create mode 100644 pkg/envoy/types/rbac.go create mode 100644 pkg/envoy/types/util.go diff --git a/compose/types/module_field_options.go b/compose/types/module_field_options.go index 2fcb85e2b..36b5b3185 100644 --- a/compose/types/module_field_options.go +++ b/compose/types/module_field_options.go @@ -4,8 +4,9 @@ import ( "database/sql/driver" "encoding/json" "fmt" - "github.com/pkg/errors" "strconv" + + "github.com/pkg/errors" ) type ( @@ -91,6 +92,19 @@ func (opt ModuleFieldOptions) Strings(key string) []string { return nil } +// String returns option value for key as single string +// +// Invalid, non-existing are returned as empty string ("") +func (opt ModuleFieldOptions) String(key string) string { + if _, has := opt[key]; has { + if v, ok := opt[key].(string); ok { + return v + } + } + + return "" +} + // IsUnique - should value in this field be unique across records? func (opt ModuleFieldOptions) IsUnique() bool { return opt.Bool(moduleFieldOptionIsUnique) diff --git a/pkg/envoy/types/compose_module.go b/pkg/envoy/types/compose_module.go new file mode 100644 index 000000000..e9a19e5dc --- /dev/null +++ b/pkg/envoy/types/compose_module.go @@ -0,0 +1,86 @@ +package types + +import ( + compTypes "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/envoy/util" + "gopkg.in/yaml.v3" +) + +type ( + ComposeModuleField struct { + compTypes.ModuleField `yaml:",inline"` + } + ComposeModuleFieldSet []*ComposeModuleField + + ComposeModule struct { + compTypes.Module `yaml:",inline"` + Fields ComposeModuleFieldSet `yaml:"fields"` + Rbac `yaml:",inline"` + } + ComposeModuleSet []*ComposeModule +) + +func (mm *ComposeModuleSet) UnmarshalYAML(n *yaml.Node) error { + cms := ComposeModuleSet{} + + err := util.YamlIterator(n, func(n, m *yaml.Node) error { + handle := "" + if n != nil { + handle = n.Value + } + + mod := &ComposeModule{} + err := m.Decode(mod) + if err != nil { + return err + } + + if mod.Handle == "" { + mod.Handle = handle + } + if mod.Name == "" { + mod.Name = handle + } + cms = append(cms, mod) + return nil + }) + + if err != nil { + return err + } + + *mm = cms + return nil +} + +func (ff *ComposeModuleFieldSet) UnmarshalYAML(n *yaml.Node) error { + ffs := ComposeModuleFieldSet{} + + err := util.YamlIterator(n, func(n, m *yaml.Node) error { + name := "" + if n != nil { + name = n.Value + } + f := &ComposeModuleField{} + + err := m.Decode(f) + if err != nil { + return err + } + if f.Name == "" { + f.Name = name + } + if f.Label == "" { + f.Label = name + } + ffs = append(ffs, f) + return nil + }) + + if err != nil { + return err + } + + *ff = ffs + return nil +} diff --git a/pkg/envoy/types/compose_module_node.go b/pkg/envoy/types/compose_module_node.go new file mode 100644 index 000000000..568d7273b --- /dev/null +++ b/pkg/envoy/types/compose_module_node.go @@ -0,0 +1,110 @@ +package types + +import ( + "strconv" + + "github.com/cortezaproject/corteza-server/compose/types" +) + +type ( + // ComposeModuleNode represents a ComposeModule + ComposeModuleNode struct { + Mod *ComposeModule + + // Related namespace + Ns *ComposeNamespace + } +) + +func (n *ComposeModuleNode) Identifiers() NodeIdentifiers { + ii := make(NodeIdentifiers, 0) + + if n.Mod.Handle != "" { + ii = ii.Add(n.Mod.Handle) + } + + if n.Mod.Name != "" { + ii = ii.Add(n.Mod.Name) + } + + if n.Mod.ID > 0 { + ii = ii.Add(strconv.FormatUint(n.Mod.ID, 10)) + } + + return ii +} + +func (n *ComposeModuleNode) Matches(resource string, identifiers ...string) bool { + if resource != n.Resource() { + return false + } + + return n.Identifiers().HasAny(identifiers...) +} + +func (n *ComposeModuleNode) Resource() string { + return types.ModuleRBACResource.String() +} + +func (n *ComposeModuleNode) Relations() NodeRelationships { + rel := make(NodeRelationships) + + // Related namespace + nsr := types.NamespaceRBACResource.String() + if n.Ns.Slug != "" { + rel.Add(nsr, n.Ns.Slug) + } + if n.Ns.Name != "" { + rel.Add(nsr, n.Ns.Name) + } + if n.Ns.ID > 0 { + rel.Add(nsr, strconv.FormatUint(n.Ns.ID, 10)) + } + + // Related modules via Record module fields + mdr := types.ModuleRBACResource.String() + for _, f := range n.Mod.Fields { + // @todo should a missing module property raise an error? + if f.Kind == "Record" && f.Options.String("module") != "" { + rel.Add(mdr, f.Options.String("module")) + } + } + + return rel +} + +func (n *ComposeModuleNode) Update(mm ...Node) { + for _, m := range mm { + n.updateNamespace(m) + n.updateRecFields(m) + } +} + +func (n *ComposeModuleNode) updateNamespace(m Node) { + if m.Resource() != types.NamespaceRBACResource.String() { + return + } + + mn := m.(*ComposeNamespaceNode) + n.Ns = mn.Ns +} + +func (n *ComposeModuleNode) updateRecFields(m Node) { + if m.Resource() != types.ModuleRBACResource.String() { + return + } + mn := m.(*ComposeModuleNode) + + // Check what record module field we can link this to + for _, f := range n.Mod.Fields { + if f.Kind != "Record" { + continue + } + + if mn.Identifiers().HasAny(f.Options.String("module")) { + f.Options["module"] = strconv.FormatUint(mn.Mod.ID, 10) + } + } + + n.Ns = mn.Ns +} diff --git a/pkg/envoy/types/compose_namespace.go b/pkg/envoy/types/compose_namespace.go new file mode 100644 index 000000000..1211a2265 --- /dev/null +++ b/pkg/envoy/types/compose_namespace.go @@ -0,0 +1,48 @@ +package types + +import ( + compTypes "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/envoy/util" + "gopkg.in/yaml.v3" +) + +type ( + ComposeNamespace struct { + compTypes.Namespace `yaml:",inline"` + Modules ComposeModuleSet + Rbac `yaml:",inline"` + } + ComposeNamespaceSet []*ComposeNamespace +) + +func (ss *ComposeNamespaceSet) UnmarshalYAML(n *yaml.Node) error { + nss := ComposeNamespaceSet{} + + err := util.YamlIterator(n, func(n, m *yaml.Node) error { + slug := "" + if n != nil { + slug = n.Value + } + + ns := &ComposeNamespace{} + err := m.Decode(ns) + if err != nil { + return err + } + if ns.Slug == "" { + ns.Slug = slug + } + if ns.Name == "" { + ns.Name = slug + } + nss = append(nss, ns) + return nil + }) + + if err != nil { + return err + } + + *ss = nss + return nil +} diff --git a/pkg/envoy/types/compose_namespace_node.go b/pkg/envoy/types/compose_namespace_node.go new file mode 100644 index 000000000..b99a0d614 --- /dev/null +++ b/pkg/envoy/types/compose_namespace_node.go @@ -0,0 +1,45 @@ +package types + +import ( + "strconv" + + "github.com/cortezaproject/corteza-server/compose/types" +) + +type ( + ComposeNamespaceNode struct { + Ns *ComposeNamespace + } +) + +func (n *ComposeNamespaceNode) Identifiers() NodeIdentifiers { + ii := make(NodeIdentifiers, 0) + + if n.Ns.Slug != "" { + ii = ii.Add(n.Ns.Slug) + } + if n.Ns.Name != "" { + ii = ii.Add(n.Ns.Name) + } + if n.Ns.ID > 0 { + ii = ii.Add(strconv.FormatUint(n.Ns.ID, 10)) + } + + return ii +} + +func (n *ComposeNamespaceNode) Matches(resource string, identifiers ...string) bool { + if resource != n.Resource() { + return false + } + + return n.Identifiers().HasAny(identifiers...) +} + +func (n *ComposeNamespaceNode) Resource() string { + return types.NamespaceRBACResource.String() +} + +func (n *ComposeNamespaceNode) Relations() NodeRelationships { + return nil +} diff --git a/pkg/envoy/types/compose_record.go b/pkg/envoy/types/compose_record.go new file mode 100644 index 000000000..391547c7c --- /dev/null +++ b/pkg/envoy/types/compose_record.go @@ -0,0 +1,72 @@ +package types + +import ( + "errors" + + compTypes "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/envoy/util" + "gopkg.in/yaml.v3" +) + +type ( + ComposeRecordValue struct { + compTypes.RecordValue `yaml:",inline"` + } + ComposeRecordValueSet []*ComposeRecordValue + + ComposeRecord struct { + compTypes.Record `yaml:",inline"` + Values ComposeRecordValueSet `yaml:"values"` + } + ComposeRecordSet []*ComposeRecord +) + +var ( + ErrRecordDecoderMapNotSupported = errors.New("record decoder: maps not supported") +) + +func (rr *ComposeRecordSet) UnmarshalYAML(n *yaml.Node) error { + crs := ComposeRecordSet{} + if n.Kind != yaml.SequenceNode { + return ErrRecordDecoderMapNotSupported + } + + err := util.YamlIterator(n, func(n, m *yaml.Node) error { + var r *ComposeRecord + err := m.Decode(&r) + if err != nil { + return err + } + + crs = append(crs, r) + return nil + }) + + if err != nil { + return err + } + + *rr = crs + return nil +} + +func (vv *ComposeRecordValueSet) UnmarshalYAML(n *yaml.Node) error { + vvs := ComposeRecordValueSet{} + + err := util.YamlIterator(n, func(n, m *yaml.Node) error { + v := &ComposeRecordValue{} + + v.Name = n.Value + v.Value = m.Value + v.Updated = true + vvs = append(vvs, v) + return nil + }) + + if err != nil { + return err + } + + *vv = vvs + return nil +} diff --git a/pkg/envoy/types/compose_record_node.go b/pkg/envoy/types/compose_record_node.go new file mode 100644 index 000000000..5fe72ecea --- /dev/null +++ b/pkg/envoy/types/compose_record_node.go @@ -0,0 +1,76 @@ +package types + +import ( + "github.com/cortezaproject/corteza-server/compose/types" +) + +type ( + RecordIterator func(func(*ComposeRecord) error) error + + ComposeRecordNode struct { + Walk RecordIterator + + // Metafields for relationship management + Mod *ComposeModule + } +) + +func (n *ComposeRecordNode) Identifiers() NodeIdentifiers { + return identifiersForModule(n.Mod) +} + +func (n *ComposeRecordNode) Resource() string { + return "compose:record:" +} + +func (n *ComposeRecordNode) Matches(resource string, identifiers ...string) bool { + if resource != n.Resource() { + return false + } + + return n.Identifiers().HasAny(identifiers...) +} + +func (n *ComposeRecordNode) Relations() NodeRelationships { + // This omits the namespace rel. as it's transitively implied via the modules + + rel := make(NodeRelationships) + mdr := types.ModuleRBACResource.String() + rrr := "compose:record:" + + if n.Mod == nil { + return rel + } + + // Original module + mIdentifiers := identifiersForModule(n.Mod) + rel.Add(mdr, mIdentifiers...) + + // Field relationships + for _, f := range n.Mod.Fields { + if f.Kind == "Record" { + modID := f.Options.String("module") + // For the module + rel.Add(mdr, modID) + + // For the records. + // Since this record depends on another module, it also depends on those records. + rel.Add(rrr, modID) + } + } + + return rel +} + +func (n *ComposeRecordNode) Update(mm ...Node) { + for _, m := range mm { + switch m.Resource() { + case types.ModuleRBACResource.String(): + mn, _ := m.(*ComposeModuleNode) + // Direct module dependency + if n.Matches(n.Resource(), identifiersForModule(mn.Mod)...) { + n.Mod = mn.Mod + } + } + } +} diff --git a/pkg/envoy/types/rbac.go b/pkg/envoy/types/rbac.go new file mode 100644 index 000000000..29208a1f2 --- /dev/null +++ b/pkg/envoy/types/rbac.go @@ -0,0 +1,9 @@ +package types + +type ( + RbacRules map[string][]string + Rbac struct { + Allow RbacRules + Deny RbacRules + } +) diff --git a/pkg/envoy/types/util.go b/pkg/envoy/types/util.go new file mode 100644 index 000000000..2faa4e57d --- /dev/null +++ b/pkg/envoy/types/util.go @@ -0,0 +1,24 @@ +package types + +import "strconv" + +// A little helper to extract identifiers for a given module +func identifiersForModule(m *ComposeModule) NodeIdentifiers { + ii := make(NodeIdentifiers, 0) + + if m == nil { + return ii + } + + if m.Handle != "" { + ii = ii.Add(m.Handle) + } + if m.Name != "" { + ii = ii.Add(m.Name) + } + if m.ID > 0 { + ii = ii.Add(strconv.FormatUint(m.ID, 10)) + } + + return ii +}