From edf2e2d62f511af7d11920779dd73e0a5c4ec29e Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Thu, 10 Sep 2020 17:51:58 +0300 Subject: [PATCH] [#139] sdk/acl: Add eACL structures Signed-off-by: Alex Vanin --- pkg/acl/eacl/enums.go | 224 +++++++++++++++++++++++++++++++++++++ pkg/acl/eacl/filter.go | 55 +++++++++ pkg/acl/eacl/record.go | 127 +++++++++++++++++++++ pkg/acl/eacl/table.go | 113 +++++++++++++++++++ pkg/acl/eacl/table_test.go | 17 +++ pkg/acl/eacl/target.go | 56 ++++++++++ 6 files changed, 592 insertions(+) create mode 100644 pkg/acl/eacl/enums.go create mode 100644 pkg/acl/eacl/filter.go create mode 100644 pkg/acl/eacl/record.go create mode 100644 pkg/acl/eacl/table.go create mode 100644 pkg/acl/eacl/table_test.go create mode 100644 pkg/acl/eacl/target.go diff --git a/pkg/acl/eacl/enums.go b/pkg/acl/eacl/enums.go new file mode 100644 index 0000000..38ab97d --- /dev/null +++ b/pkg/acl/eacl/enums.go @@ -0,0 +1,224 @@ +package eacl + +import ( + v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" +) + +// Action taken if EACL record matched request. +type Action uint32 + +const ( + ActionUnknown Action = iota + ActionAllow + ActionDeny +) + +// Operation is a object service method to match request. +type Operation uint32 + +const ( + OperationUnknown Operation = iota + OperationGet + OperationHead + OperationPut + OperationDelete + OperationSearch + OperationRange + OperationRangeHash +) + +// Role is a group of request senders to match request. +type Role uint32 + +const ( + RoleUnknown Role = iota + // RoleUser is a group of senders that contains only key of container owner. + RoleUser + // RoleSystem is a group of senders that contains keys of container nodes and + // inner ring nodes. + RoleSystem + // RoleOthers is a group of senders that contains none of above keys. + RoleOthers +) + +// Match is binary operation on filer name and value to check if request is matched. +type Match uint32 + +const ( + MatchUnknown Match = iota + MatchStringEqual + MatchStringNotEqual +) + +// FilterHeaderType indicates source of headers to make matches. +type FilterHeaderType uint32 + +const ( + HeaderTypeUnknown FilterHeaderType = iota + HeaderFromRequest + HeaderFromObject +) + +const ( + // HdrObjSysNameID is a name of ID field in system header of object. + HdrObjSysNameID = "_ID" + + // HdrObjSysNameCID is a name of cid field in system header of object. + HdrObjSysNameCID = "_CID" + + // HdrObjSysNameOwnerID is a name of OwnerID field in system header of object. + HdrObjSysNameOwnerID = "_OWNER_ID" + + // HdrObjSysNameVersion is a name of version field in system header of object. + HdrObjSysNameVersion = "_VERSION" + + // HdrObjSysNamePayloadLength is a name of PayloadLength field in system header of object. + HdrObjSysNamePayloadLength = "_PAYLOAD_LENGTH" + + // HdrObjSysNameCreatedEpoch is a name of CreatedAt.Epoch field in system header of object. + HdrObjSysNameCreatedEpoch = "_CREATED_EPOCH" +) + +func (a Action) ToV2() v2acl.Action { + switch a { + case ActionAllow: + return v2acl.ActionAllow + case ActionDeny: + return v2acl.ActionDeny + default: + return v2acl.ActionUnknown + } +} + +func ActionFromV2(action v2acl.Action) (a Action) { + switch action { + case v2acl.ActionAllow: + a = ActionAllow + case v2acl.ActionDeny: + a = ActionDeny + default: + a = ActionUnknown + } + + return a +} + +func (o Operation) ToV2() v2acl.Operation { + switch o { + case OperationGet: + return v2acl.OperationGet + case OperationHead: + return v2acl.OperationHead + case OperationPut: + return v2acl.OperationPut + case OperationDelete: + return v2acl.OperationDelete + case OperationSearch: + return v2acl.OperationSearch + case OperationRange: + return v2acl.OperationRange + case OperationRangeHash: + return v2acl.OperationRangeHash + default: + return v2acl.OperationUnknown + } +} + +func OperationFromV2(operation v2acl.Operation) (o Operation) { + switch operation { + case v2acl.OperationGet: + o = OperationGet + case v2acl.OperationHead: + o = OperationHead + case v2acl.OperationPut: + o = OperationPut + case v2acl.OperationDelete: + o = OperationDelete + case v2acl.OperationSearch: + o = OperationSearch + case v2acl.OperationRange: + o = OperationRange + case v2acl.OperationRangeHash: + o = OperationRangeHash + default: + o = OperationUnknown + } + + return o +} + +func (r Role) ToV2() v2acl.Role { + switch r { + case RoleUser: + return v2acl.RoleUser + case RoleSystem: + return v2acl.RoleSystem + case RoleOthers: + return v2acl.RoleOthers + default: + return v2acl.RoleUnknown + } +} + +func RoleFromV2(role v2acl.Role) (r Role) { + switch role { + case v2acl.RoleUser: + r = RoleUser + case v2acl.RoleSystem: + r = RoleSystem + case v2acl.RoleOthers: + r = RoleOthers + default: + r = RoleUnknown + } + + return r +} + +func (m Match) ToV2() v2acl.MatchType { + switch m { + case MatchStringEqual: + return v2acl.MatchTypeStringEqual + case MatchStringNotEqual: + return v2acl.MatchTypeStringNotEqual + default: + return v2acl.MatchTypeUnknown + } +} + +func MatchFromV2(match v2acl.MatchType) (m Match) { + switch match { + case v2acl.MatchTypeStringEqual: + m = MatchStringEqual + case v2acl.MatchTypeStringNotEqual: + m = MatchStringNotEqual + default: + m = MatchUnknown + } + + return m +} + +func (h FilterHeaderType) ToV2() v2acl.HeaderType { + switch h { + case HeaderFromRequest: + return v2acl.HeaderTypeRequest + case HeaderFromObject: + return v2acl.HeaderTypeObject + default: + return v2acl.HeaderTypeUnknown + } +} + +func FilterHeaderTypeFromV2(header v2acl.HeaderType) (h FilterHeaderType) { + switch header { + case v2acl.HeaderTypeRequest: + h = HeaderFromRequest + case v2acl.HeaderTypeObject: + h = HeaderFromObject + default: + h = HeaderTypeUnknown + } + + return h +} diff --git a/pkg/acl/eacl/filter.go b/pkg/acl/eacl/filter.go new file mode 100644 index 0000000..8ea8537 --- /dev/null +++ b/pkg/acl/eacl/filter.go @@ -0,0 +1,55 @@ +package eacl + +import ( + v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" +) + +// Filter defines check conditions if request header is matched or not. Matched +// header means that request should be processed according to EACL action. +type Filter struct { + from FilterHeaderType + name string + matcher Match + value string +} + +func (a Filter) Value() string { + return a.value +} + +func (a Filter) Matcher() Match { + return a.matcher +} + +func (a Filter) Name() string { + return a.name +} + +func (a Filter) From() FilterHeaderType { + return a.from +} + +func (a *Filter) ToV2() *v2acl.HeaderFilter { + filter := new(v2acl.HeaderFilter) + filter.SetValue(a.value) + filter.SetName(a.name) + filter.SetMatchType(a.matcher.ToV2()) + filter.SetHeaderType(a.from.ToV2()) + + return filter +} + +func NewFilterFromV2(filter *v2acl.HeaderFilter) *Filter { + f := new(Filter) + + if filter == nil { + return f + } + + f.from = FilterHeaderTypeFromV2(filter.GetHeaderType()) + f.matcher = MatchFromV2(filter.GetMatchType()) + f.name = filter.GetName() + f.value = filter.GetValue() + + return f +} diff --git a/pkg/acl/eacl/record.go b/pkg/acl/eacl/record.go new file mode 100644 index 0000000..9c46d2f --- /dev/null +++ b/pkg/acl/eacl/record.go @@ -0,0 +1,127 @@ +package eacl + +import ( + "crypto/ecdsa" + + v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" +) + +type ( + // Record of the EACL rule, that defines EACL action, targets for this action, + // object service operation and filters for request headers. + Record struct { + action Action + operation Operation + filters []Filter + targets []Target + } +) + +func (r Record) Targets() []Target { + return r.targets +} + +func (r Record) Filters() []Filter { + return r.filters +} + +func (r Record) Operation() Operation { + return r.operation +} + +func (r *Record) SetOperation(operation Operation) { + r.operation = operation +} + +func (r Record) Action() Action { + return r.action +} + +func (r *Record) SetAction(action Action) { + r.action = action +} + +func (r *Record) AddTarget(role Role, keys ...ecdsa.PublicKey) { + t := Target{ + role: role, + keys: make([]ecdsa.PublicKey, 0, len(keys)), + } + + for i := range keys { + t.keys = append(t.keys, keys[i]) + } + + r.targets = append(r.targets, t) +} + +func (r *Record) AddFilter(from FilterHeaderType, matcher Match, name, value string) { + filter := Filter{ + from: from, + name: name, + matcher: matcher, + value: value, + } + + r.filters = append(r.filters, filter) +} + +func (r *Record) ToV2() *v2acl.Record { + targets := make([]*v2acl.Target, 0, len(r.targets)) + for _, target := range r.targets { + targets = append(targets, target.ToV2()) + } + + filters := make([]*v2acl.HeaderFilter, 0, len(r.filters)) + for _, filter := range r.filters { + filters = append(filters, filter.ToV2()) + } + + v2 := new(v2acl.Record) + + v2.SetAction(r.action.ToV2()) + v2.SetOperation(r.operation.ToV2()) + v2.SetTargets(targets) + v2.SetFilters(filters) + + return v2 +} + +func NewRecord() *Record { + return new(Record) +} + +func CreateRecord(action Action, operation Operation) *Record { + r := NewRecord() + r.action = action + r.operation = operation + r.targets = []Target{} + r.filters = []Filter{} + + return r +} + +func NewRecordFromV2(record *v2acl.Record) *Record { + r := NewRecord() + + if record == nil { + return r + } + + r.action = ActionFromV2(record.GetAction()) + r.operation = OperationFromV2(record.GetOperation()) + + v2targets := record.GetTargets() + v2filters := record.GetFilters() + + r.targets = make([]Target, 0, len(v2targets)) + for i := range v2targets { + r.targets = append(r.targets, *NewTargetFromV2(v2targets[i])) + } + + r.filters = make([]Filter, 0, len(v2filters)) + for i := range v2filters { + r.filters = append(r.filters, *NewFilterFromV2(v2filters[i])) + } + + return r +} diff --git a/pkg/acl/eacl/table.go b/pkg/acl/eacl/table.go new file mode 100644 index 0000000..a0fe857 --- /dev/null +++ b/pkg/acl/eacl/table.go @@ -0,0 +1,113 @@ +package eacl + +import ( + "crypto/sha256" + + "github.com/nspcc-dev/neofs-api-go/pkg" + "github.com/nspcc-dev/neofs-api-go/pkg/container" + v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" +) + +type ( + // Table is a group of EACL records for single container. + Table struct { + version pkg.Version + cid *container.ID + records []Record + } +) + +func (t Table) CID() *container.ID { + return t.cid +} + +func (t *Table) SetCID(cid *container.ID) { + t.cid = cid +} + +func (t Table) Version() pkg.Version { + return t.version +} + +func (t *Table) SetVersion(version pkg.Version) { + t.version = version +} + +func (t Table) Records() []Record { + return t.records +} + +func (t *Table) AddRecord(r *Record) { + if r != nil { + t.records = append(t.records, *r) + } +} + +func (t *Table) ToV2() *v2acl.Table { + v2 := new(v2acl.Table) + + if t.cid != nil { + v2.SetContainerID(t.cid.ToV2()) + } + + records := make([]*v2acl.Record, 0, len(t.records)) + for _, record := range t.records { + records = append(records, record.ToV2()) + } + + v2.SetVersion(t.version.ToV2()) + v2.SetRecords(records) + + return v2 +} + +func NewTable() *Table { + t := new(Table) + t.SetVersion(*pkg.SDKVersion()) + + return t +} + +func CreateTable(cid container.ID) *Table { + t := NewTable() + t.SetCID(&cid) + + return t +} + +func NewTableFromV2(table *v2acl.Table) *Table { + t := new(Table) + + if table == nil { + return t + } + + // set version + if v := table.GetVersion(); v != nil { + version := pkg.Version{} + version.SetMajor(v.GetMajor()) + version.SetMinor(v.GetMinor()) + + t.SetVersion(version) + } + + // set container id + if cid := table.GetContainerID(); cid != nil { + if t.cid == nil { + t.cid = new(container.ID) + } + + var h [sha256.Size]byte + copy(h[:], table.GetContainerID().GetValue()) + t.cid.SetSHA256(h) + } + + // set eacl records + v2records := table.GetRecords() + t.records = make([]Record, 0, len(v2records)) + for i := range v2records { + t.records = append(t.records, *NewRecordFromV2(v2records[i])) + } + + return t +} diff --git a/pkg/acl/eacl/table_test.go b/pkg/acl/eacl/table_test.go new file mode 100644 index 0000000..b4d52b5 --- /dev/null +++ b/pkg/acl/eacl/table_test.go @@ -0,0 +1,17 @@ +package eacl_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" + "github.com/nspcc-dev/neofs-crypto/test" +) + +func TextExample(t *testing.T) { + record := eacl.CreateRecord(eacl.ActionDeny, eacl.OperationPut) + record.AddFilter(eacl.HeaderFromObject, eacl.MatchStringEqual, "filename", "cat.jpg") + record.AddTarget(eacl.RoleOthers, test.DecodeKey(1).PublicKey, test.DecodeKey(2).PublicKey) + + table := eacl.NewTable() + table.AddRecord(record) +} diff --git a/pkg/acl/eacl/target.go b/pkg/acl/eacl/target.go new file mode 100644 index 0000000..09fdfa7 --- /dev/null +++ b/pkg/acl/eacl/target.go @@ -0,0 +1,56 @@ +package eacl + +import ( + "crypto/ecdsa" + + v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" + crypto "github.com/nspcc-dev/neofs-crypto" +) + +// Target is a group of request senders to match EACL. Defined by role enum +// and set of public keys. +type Target struct { + role Role + keys []ecdsa.PublicKey +} + +func (t Target) Keys() []ecdsa.PublicKey { + return t.keys +} + +func (t Target) Role() Role { + return t.role +} + +func (t *Target) ToV2() *v2acl.Target { + keys := make([][]byte, 0, len(t.keys)) + for i := range t.keys { + key := crypto.MarshalPublicKey(&t.keys[i]) + keys = append(keys, key) + } + + target := new(v2acl.Target) + + target.SetRole(t.role.ToV2()) + target.SetKeyList(keys) + + return target +} + +func NewTargetFromV2(target *v2acl.Target) *Target { + t := new(Target) + + if target == nil { + return t + } + + t.role = RoleFromV2(target.GetRole()) + v2keys := target.GetKeyList() + t.keys = make([]ecdsa.PublicKey, 0, len(v2keys)) + for i := range v2keys { + key := crypto.UnmarshalPublicKey(v2keys[i]) + t.keys = append(t.keys, *key) + } + + return t +}