diff --git a/cmd/frostfs-node/object.go b/cmd/frostfs-node/object.go index bbaec01ed..79f492190 100644 --- a/cmd/frostfs-node/object.go +++ b/cmd/frostfs-node/object.go @@ -426,6 +426,7 @@ func createACLServiceV2(c *cfg, splitSvc *objectService.TransportSplitter, irFet c.cfgObject.eaclSource, eaclSDK.NewValidator(), ls), + acl.NewAPEChecker(c.log, c.cfgObject.apeChainSource), c.cfgObject.cnrSource, v2.WithLogger(c.log), ) diff --git a/cmd/frostfs-node/policy_engine.go b/cmd/frostfs-node/policy_engine.go index 039124a6b..f0bd78629 100644 --- a/cmd/frostfs-node/policy_engine.go +++ b/cmd/frostfs-node/policy_engine.go @@ -1,12 +1,15 @@ package main import ( + "sync" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" policyengine "git.frostfs.info/TrueCloudLab/policy-engine" ) type apeChainSourceImpl struct { + mtx sync.Mutex localChainStorage map[cid.ID]policyengine.CachedChainStorage } @@ -19,6 +22,9 @@ func NewAPESource() container.AccessPolicyEngineChainSource { var _ container.AccessPolicyEngineChainSource = (*apeChainSourceImpl)(nil) func (c *apeChainSourceImpl) GetChainSource(cid cid.ID) (policyengine.CachedChainStorage, error) { + c.mtx.Lock() + defer c.mtx.Unlock() + s, ok := c.localChainStorage[cid] if ok { return s, nil diff --git a/pkg/services/object/acl/ape.go b/pkg/services/object/acl/ape.go new file mode 100644 index 000000000..b064b1eec --- /dev/null +++ b/pkg/services/object/acl/ape.go @@ -0,0 +1,56 @@ +package acl + +import ( + "errors" + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" + v2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/v2" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" + apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" + policyengine "git.frostfs.info/TrueCloudLab/policy-engine" +) + +var ( + errAPEChainNoSource = errors.New("could not get ape chain source for the container") +) + +type apeCheckerImpl struct { + log *logger.Logger + apeSrc container.AccessPolicyEngineChainSource +} + +func NewAPEChecker(log *logger.Logger, apeSrc container.AccessPolicyEngineChainSource) v2.APEChainChecker { + return &apeCheckerImpl{ + log: log, + apeSrc: apeSrc, + } +} + +func (c *apeCheckerImpl) CheckIfRequestPermitted(reqInfo v2.RequestInfo) error { + cnr := reqInfo.ContainerID() + + chainCache, err := c.apeSrc.GetChainSource(cnr) + if err != nil { + return errAPEChainNoSource + } + + request := new(Request) + request.FromRequestInfo(reqInfo) + + status, ruleFound := chainCache.IsAllowed(policyengine.Ingress, "", request) + + if !ruleFound || status == policyengine.Allow { + return nil + } + + return apeErr(reqInfo, status) +} + +const accessDeniedAPEReasonFmt = "access to operation %s is denied by access policy engine: %s" + +func apeErr(req v2.RequestInfo, status policyengine.Status) error { + errAccessDenied := &apistatus.ObjectAccessDenied{} + errAccessDenied.WriteReason(fmt.Sprintf(accessDeniedAPEReasonFmt, req.Operation(), status.String())) + return errAccessDenied +} diff --git a/pkg/services/object/acl/ape_request.go b/pkg/services/object/acl/ape_request.go new file mode 100644 index 000000000..3e8bb173d --- /dev/null +++ b/pkg/services/object/acl/ape_request.go @@ -0,0 +1,105 @@ +package acl + +import ( + "fmt" + + v2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/v2" + aclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" + policyengine "git.frostfs.info/TrueCloudLab/policy-engine" +) + +type Request struct { + operation string + resource *resource + properties map[string]string +} + +var _ policyengine.Request = (*Request)(nil) + +type resource struct { + name string + properties map[string]string +} + +var _ policyengine.Resource = (*resource)(nil) + +func (r *resource) Name() string { + return r.name +} + +func (r *resource) Property(key string) string { + return r.properties[key] +} + +// TODO (aarifullin): these stringified verbs, properties and namespaces +// should be non-implementation-specific. +func getResource(reqInfo v2.RequestInfo) *resource { + cid := reqInfo.ContainerID() + oid := "*" + if reqOID := reqInfo.ObjectID(); reqOID != nil { + oid = reqOID.EncodeToString() + } + name := fmt.Sprintf("native:::object/%s/%s", + cid, + oid) + + return &resource{ + name: name, + properties: make(map[string]string), + } +} + +func getProperties(_ v2.RequestInfo) map[string]string { + return map[string]string{ + "Actor": "", + } +} + +// TODO (aarifullin): these stringified verbs, properties and namespaces +// should be non-implementation-specific. +func getOperation(reqInfo v2.RequestInfo) string { + var verb string + switch op := reqInfo.Operation(); op { + case aclSDK.OpObjectGet: + verb = "GetObject" + case aclSDK.OpObjectHead: + verb = "HeadObject" + case aclSDK.OpObjectPut: + verb = "PutObject" + case aclSDK.OpObjectDelete: + verb = "DeleteObject" + case aclSDK.OpObjectSearch: + verb = "SearchObject" + case aclSDK.OpObjectRange: + verb = "RangeObject" + case aclSDK.OpObjectHash: + verb = "HashObject" + } + + return "native:" + verb +} + +func NewRequest() *Request { + return &Request{ + resource: new(resource), + properties: map[string]string{}, + } +} + +func (r *Request) FromRequestInfo(ri v2.RequestInfo) { + r.operation = getOperation(ri) + r.resource = getResource(ri) + r.properties = getProperties(ri) +} + +func (r *Request) Operation() string { + return r.operation +} + +func (r *Request) Property(key string) string { + return r.properties[key] +} + +func (r *Request) Resource() policyengine.Resource { + return r.resource +} diff --git a/pkg/services/object/acl/v2/service.go b/pkg/services/object/acl/v2/service.go index b5bd3d4f4..ba9d1dfa5 100644 --- a/pkg/services/object/acl/v2/service.go +++ b/pkg/services/object/acl/v2/service.go @@ -67,6 +67,10 @@ type cfg struct { checker ACLChecker + // TODO(aarifullin): apeCheck is temporarily the part of + // acl service and must be standalone. + apeChecker APEChainChecker + irFetcher InnerRingFetcher nm netmap.Source @@ -79,6 +83,7 @@ func New(next object.ServiceServer, nm netmap.Source, irf InnerRingFetcher, acl ACLChecker, + apeChecker APEChainChecker, cs container.Source, opts ...Option, ) Service { @@ -88,6 +93,7 @@ func New(next object.ServiceServer, nm: nm, irFetcher: irf, checker: acl, + apeChecker: apeChecker, containers: cs, } @@ -212,10 +218,8 @@ func (b Service) Head( reqInfo.obj = obj - if !b.checker.CheckBasicACL(reqInfo) { - return nil, basicACLErr(reqInfo) - } else if err := b.checker.CheckEACL(request, reqInfo); err != nil { - return nil, eACLErr(reqInfo, err) + if err := b.apeChecker.CheckIfRequestPermitted(reqInfo); err != nil { + return nil, err } resp, err := b.next.Head(ctx, request) @@ -560,10 +564,8 @@ func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRe reqInfo.obj = obj - if !p.source.checker.CheckBasicACL(reqInfo) || !p.source.checker.StickyBitCheck(reqInfo, idOwner) { - return basicACLErr(reqInfo) - } else if err := p.source.checker.CheckEACL(request, reqInfo); err != nil { - return eACLErr(reqInfo, err) + if err := p.source.apeChecker.CheckIfRequestPermitted(reqInfo); err != nil { + return err } } diff --git a/pkg/services/object/acl/v2/types.go b/pkg/services/object/acl/v2/types.go index 061cd26b6..a113c4693 100644 --- a/pkg/services/object/acl/v2/types.go +++ b/pkg/services/object/acl/v2/types.go @@ -26,3 +26,9 @@ type InnerRingFetcher interface { // the actual inner ring. InnerRingKeys() ([][]byte, error) } + +// APEChainChecker is the interface that provides methods to +// check if the access policy engine permits to perform the request. +type APEChainChecker interface { + CheckIfRequestPermitted(RequestInfo) error +}