[#770] object: Introduce ape chain checker for object svc
* Introduce Request type converted from RequestInfo type to implement policy-engine's Request interface * Implement basic ape checker to check if a request is permitted to be performed * Make put handlers use APE checker instead EACL Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
5ec73fe8a0
commit
8e11ef46b8
6 changed files with 184 additions and 8 deletions
|
@ -426,6 +426,7 @@ func createACLServiceV2(c *cfg, splitSvc *objectService.TransportSplitter, irFet
|
||||||
c.cfgObject.eaclSource,
|
c.cfgObject.eaclSource,
|
||||||
eaclSDK.NewValidator(),
|
eaclSDK.NewValidator(),
|
||||||
ls),
|
ls),
|
||||||
|
acl.NewAPEChecker(c.log, c.cfgObject.apeChainSource),
|
||||||
c.cfgObject.cnrSource,
|
c.cfgObject.cnrSource,
|
||||||
v2.WithLogger(c.log),
|
v2.WithLogger(c.log),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apeChainSourceImpl struct {
|
type apeChainSourceImpl struct {
|
||||||
|
mtx sync.Mutex
|
||||||
localChainStorage map[cid.ID]policyengine.CachedChainStorage
|
localChainStorage map[cid.ID]policyengine.CachedChainStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +22,9 @@ func NewAPESource() container.AccessPolicyEngineChainSource {
|
||||||
var _ container.AccessPolicyEngineChainSource = (*apeChainSourceImpl)(nil)
|
var _ container.AccessPolicyEngineChainSource = (*apeChainSourceImpl)(nil)
|
||||||
|
|
||||||
func (c *apeChainSourceImpl) GetChainSource(cid cid.ID) (policyengine.CachedChainStorage, error) {
|
func (c *apeChainSourceImpl) GetChainSource(cid cid.ID) (policyengine.CachedChainStorage, error) {
|
||||||
|
c.mtx.Lock()
|
||||||
|
defer c.mtx.Unlock()
|
||||||
|
|
||||||
s, ok := c.localChainStorage[cid]
|
s, ok := c.localChainStorage[cid]
|
||||||
if ok {
|
if ok {
|
||||||
return s, nil
|
return s, nil
|
||||||
|
|
56
pkg/services/object/acl/ape.go
Normal file
56
pkg/services/object/acl/ape.go
Normal file
|
@ -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
|
||||||
|
}
|
105
pkg/services/object/acl/ape_request.go
Normal file
105
pkg/services/object/acl/ape_request.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -67,6 +67,10 @@ type cfg struct {
|
||||||
|
|
||||||
checker ACLChecker
|
checker ACLChecker
|
||||||
|
|
||||||
|
// TODO(aarifullin): apeCheck is temporarily the part of
|
||||||
|
// acl service and must be standalone.
|
||||||
|
apeChecker APEChainChecker
|
||||||
|
|
||||||
irFetcher InnerRingFetcher
|
irFetcher InnerRingFetcher
|
||||||
|
|
||||||
nm netmap.Source
|
nm netmap.Source
|
||||||
|
@ -79,6 +83,7 @@ func New(next object.ServiceServer,
|
||||||
nm netmap.Source,
|
nm netmap.Source,
|
||||||
irf InnerRingFetcher,
|
irf InnerRingFetcher,
|
||||||
acl ACLChecker,
|
acl ACLChecker,
|
||||||
|
apeChecker APEChainChecker,
|
||||||
cs container.Source,
|
cs container.Source,
|
||||||
opts ...Option,
|
opts ...Option,
|
||||||
) Service {
|
) Service {
|
||||||
|
@ -88,6 +93,7 @@ func New(next object.ServiceServer,
|
||||||
nm: nm,
|
nm: nm,
|
||||||
irFetcher: irf,
|
irFetcher: irf,
|
||||||
checker: acl,
|
checker: acl,
|
||||||
|
apeChecker: apeChecker,
|
||||||
containers: cs,
|
containers: cs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,10 +218,8 @@ func (b Service) Head(
|
||||||
|
|
||||||
reqInfo.obj = obj
|
reqInfo.obj = obj
|
||||||
|
|
||||||
if !b.checker.CheckBasicACL(reqInfo) {
|
if err := b.apeChecker.CheckIfRequestPermitted(reqInfo); err != nil {
|
||||||
return nil, basicACLErr(reqInfo)
|
return nil, err
|
||||||
} else if err := b.checker.CheckEACL(request, reqInfo); err != nil {
|
|
||||||
return nil, eACLErr(reqInfo, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := b.next.Head(ctx, request)
|
resp, err := b.next.Head(ctx, request)
|
||||||
|
@ -560,10 +564,8 @@ func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRe
|
||||||
|
|
||||||
reqInfo.obj = obj
|
reqInfo.obj = obj
|
||||||
|
|
||||||
if !p.source.checker.CheckBasicACL(reqInfo) || !p.source.checker.StickyBitCheck(reqInfo, idOwner) {
|
if err := p.source.apeChecker.CheckIfRequestPermitted(reqInfo); err != nil {
|
||||||
return basicACLErr(reqInfo)
|
return err
|
||||||
} else if err := p.source.checker.CheckEACL(request, reqInfo); err != nil {
|
|
||||||
return eACLErr(reqInfo, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,3 +26,9 @@ type InnerRingFetcher interface {
|
||||||
// the actual inner ring.
|
// the actual inner ring.
|
||||||
InnerRingKeys() ([][]byte, error)
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue