frostfs-node/pkg/util/ape/converter.go
Airat Arifullin e2cb0640f1 [#1501] util: Move eACL-to-APE converter to pkg/util
* `ConvertEACLToAPE` is useful method which couldn't be imported
  out of frostfs-node so far as it has been in `internal`
* Since `ConvertEACLToAPE` and related structures and unit-tests
  are placed in `pkg/util`

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-11-20 07:58:32 +00:00

280 lines
8.4 KiB
Go

package ape
import (
"encoding/hex"
"fmt"
v2acl "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
)
type ConvertEACLError struct {
nested error
}
func (e *ConvertEACLError) Error() string {
if e == nil {
return ""
}
return "failed to convert eACL table to policy engine chain: " + e.nested.Error()
}
func (e *ConvertEACLError) Unwrap() error {
if e == nil {
return nil
}
return e.nested
}
// ConvertEACLToAPE converts eacl.Table to apechain.Chain.
func ConvertEACLToAPE(eaclTable *eacl.Table) (*apechain.Chain, error) {
if eaclTable == nil {
return nil, nil
}
res := &apechain.Chain{
MatchType: apechain.MatchTypeFirstMatch,
}
resource := getResource(eaclTable)
for _, eaclRecord := range eaclTable.Records() {
if len(eaclRecord.Targets()) == 0 {
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
// and https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L36
// such record doesn't have any effect
continue
}
st, err := actionToStatus(eaclRecord.Action())
if err != nil {
return nil, err
}
act, err := operationToAction(eaclRecord.Operation())
if err != nil {
return nil, err
}
if len(eaclRecord.Filters()) == 0 {
res.Rules = appendTargetsOnly(res.Rules, st, act, resource, eaclRecord.Targets())
} else {
res.Rules, err = appendTargetsAndFilters(res.Rules, st, act, resource, eaclRecord.Targets(), eaclRecord.Filters())
if err != nil {
return nil, err
}
}
}
return res, nil
}
func apeRoleConds(role eacl.Role) (res []apechain.Condition) {
switch role {
case eacl.RoleSystem:
res = append(res,
apechain.Condition{
Op: apechain.CondStringEquals,
Kind: apechain.KindRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleContainer,
},
)
res = append(res,
apechain.Condition{
Op: apechain.CondStringEquals,
Kind: apechain.KindRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleIR,
},
)
case eacl.RoleOthers:
res = append(res,
apechain.Condition{
Op: apechain.CondStringEquals,
Kind: apechain.KindRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOthers,
},
)
case eacl.RoleUser:
res = append(res,
apechain.Condition{
Op: apechain.CondStringEquals,
Kind: apechain.KindRequest,
Key: nativeschema.PropertyKeyActorRole,
Value: nativeschema.PropertyValueContainerRoleOwner,
},
)
case eacl.RoleUnknown:
// such condition has no effect
default:
}
return
}
func appendTargetsOnly(source []apechain.Rule, st apechain.Status, act apechain.Actions, res apechain.Resources, targets []eacl.Target) []apechain.Rule {
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
// role OR public key must be equal
rule := apechain.Rule{
Status: st,
Actions: act,
Resources: res,
Any: true,
}
for _, target := range targets {
rule.Condition = append(rule.Condition, apeRoleConds(target.Role())...)
for _, binKey := range target.BinaryKeys() {
var pubKeyCondition apechain.Condition
pubKeyCondition.Kind = apechain.KindRequest
pubKeyCondition.Key = nativeschema.PropertyKeyActorPublicKey
pubKeyCondition.Value = hex.EncodeToString(binKey)
pubKeyCondition.Op = apechain.CondStringEquals
rule.Condition = append(rule.Condition, pubKeyCondition)
}
}
return append(source, rule)
}
func appendTargetsAndFilters(source []apechain.Rule, st apechain.Status, act apechain.Actions, res apechain.Resources,
targets []eacl.Target, filters []eacl.Filter,
) ([]apechain.Rule, error) {
// see https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/ab75edd70939564421936d207ef80d6c1398b51b/eacl/validator.go#L101
// role OR public key must be equal
// so filters are repeated for each role and public key
var err error
for _, target := range targets {
rule := apechain.Rule{
Status: st,
Actions: act,
Resources: res,
}
rule.Condition = append(rule.Condition, apeRoleConds(target.Role())...)
rule.Condition, err = appendFilters(rule.Condition, filters)
if err != nil {
return nil, err
}
source = append(source, rule)
for _, binKey := range target.BinaryKeys() {
rule := apechain.Rule{
Status: st,
Actions: act,
Resources: res,
}
var pubKeyCondition apechain.Condition
pubKeyCondition.Kind = apechain.KindRequest
pubKeyCondition.Key = nativeschema.PropertyKeyActorPublicKey
pubKeyCondition.Value = hex.EncodeToString(binKey)
pubKeyCondition.Op = apechain.CondStringEquals
rule.Condition = append(rule.Condition, pubKeyCondition)
rule.Condition, err = appendFilters(rule.Condition, filters)
if err != nil {
return nil, err
}
source = append(source, rule)
}
}
return source, nil
}
func appendFilters(source []apechain.Condition, filters []eacl.Filter) ([]apechain.Condition, error) {
for _, filter := range filters {
var cond apechain.Condition
var isObject bool
if filter.From() == eacl.HeaderFromObject {
cond.Kind = apechain.KindResource
isObject = true
} else if filter.From() == eacl.HeaderFromRequest {
cond.Kind = apechain.KindRequest
} else {
return nil, &ConvertEACLError{nested: fmt.Errorf("unknown filter from: %d", filter.From())}
}
if filter.Matcher() == eacl.MatchStringEqual {
cond.Op = apechain.CondStringEquals
} else if filter.Matcher() == eacl.MatchStringNotEqual {
cond.Op = apechain.CondStringNotEquals
} else {
return nil, &ConvertEACLError{nested: fmt.Errorf("unknown filter matcher: %d", filter.Matcher())}
}
cond.Key = eaclKeyToAPEKey(filter.Key(), isObject)
cond.Value = filter.Value()
source = append(source, cond)
}
return source, nil
}
func eaclKeyToAPEKey(key string, isObject bool) string {
if !isObject {
return key
}
switch key {
default:
return key
case v2acl.FilterObjectVersion:
return nativeschema.PropertyKeyObjectVersion
case v2acl.FilterObjectID:
return nativeschema.PropertyKeyObjectID
case v2acl.FilterObjectContainerID:
return nativeschema.PropertyKeyObjectContainerID
case v2acl.FilterObjectOwnerID:
return nativeschema.PropertyKeyObjectOwnerID
case v2acl.FilterObjectCreationEpoch:
return nativeschema.PropertyKeyObjectCreationEpoch
case v2acl.FilterObjectPayloadLength:
return nativeschema.PropertyKeyObjectPayloadLength
case v2acl.FilterObjectPayloadHash:
return nativeschema.PropertyKeyObjectPayloadHash
case v2acl.FilterObjectType:
return nativeschema.PropertyKeyObjectType
case v2acl.FilterObjectHomomorphicHash:
return nativeschema.PropertyKeyObjectHomomorphicHash
}
}
func getResource(eaclTable *eacl.Table) apechain.Resources {
cnrID, isSet := eaclTable.CID()
if isSet {
return apechain.Resources{
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, cnrID.EncodeToString())},
}
}
return apechain.Resources{
Names: []string{nativeschema.ResourceFormatRootObjects},
}
}
func actionToStatus(a eacl.Action) (apechain.Status, error) {
switch a {
case eacl.ActionAllow:
return apechain.Allow, nil
case eacl.ActionDeny:
return apechain.AccessDenied, nil
default:
return apechain.NoRuleFound, &ConvertEACLError{nested: fmt.Errorf("unknown action: %d", a)}
}
}
var eaclOperationToEngineAction = map[eacl.Operation]apechain.Actions{
eacl.OperationGet: {Names: []string{nativeschema.MethodGetObject}},
eacl.OperationHead: {Names: []string{nativeschema.MethodHeadObject}},
eacl.OperationPut: {Names: []string{nativeschema.MethodPutObject}},
eacl.OperationDelete: {Names: []string{nativeschema.MethodDeleteObject}},
eacl.OperationSearch: {Names: []string{nativeschema.MethodSearchObject}},
eacl.OperationRange: {Names: []string{nativeschema.MethodRangeObject}},
eacl.OperationRangeHash: {Names: []string{nativeschema.MethodHashObject}},
}
func operationToAction(op eacl.Operation) (apechain.Actions, error) {
if v, ok := eaclOperationToEngineAction[op]; ok {
return v, nil
}
return apechain.Actions{}, &ConvertEACLError{nested: fmt.Errorf("unknown operation: %d", op)}
}